Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,19 @@ jobs:
path: .stack-cli
persist-credentials: false

- name: Read pinned example corpus contract
id: corpus
run: |
node --input-type=module -e 'import { exampleCorpusSource } from "./scripts/example-corpus.config.mjs"; console.log(`revision=${exampleCorpusSource.revision}`)' >> "$GITHUB_OUTPUT"

- name: Check out pinned Stack example corpus
uses: actions/checkout@v7
with:
repository: stack-sh/specification
ref: ${{ steps.corpus.outputs.revision }}
path: .stack-specification
persist-credentials: false

- name: Install minimum supported Rust
run: rustup toolchain install 1.85.0 --profile minimal

Expand All @@ -55,6 +68,11 @@ jobs:
- name: Run tests
run: npm test

- name: Verify pinned example snapshots
env:
STACK_SPECIFICATION_ROOT: ${{ github.workspace }}/.stack-specification
run: npm run examples:check:source

- name: Smoke test documentation CLI examples
env:
STACK_CLI_BIN: ${{ github.workspace }}/.stack-cli/target/debug/stack
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ docs/.vitepress/cache
.wrangler
.dev.vars*
.stack-cli
.stack-specification

# Editor directories and files
.vscode/*
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ npm run dev
npm run docs:dev
npm run docs:check
npm run docs:test
npm run examples:check
npm run format
npm run format:check
npm run lint
Expand All @@ -46,6 +47,8 @@ Formatting and linting use Oxfmt and Oxlint. The production build compiles the R

CI additionally builds the Stack CLI revision pinned in [`scripts/docs-validation.config.mjs`](./scripts/docs-validation.config.mjs) with its minimum supported Rust version, then runs `npm run docs:smoke` through `STACK_CLI_BIN`. This checks the documented version, help surface, and safe executable examples without adding Rust to normal Web builds. Intentional locale or execution differences must be declared in the same configuration with a non-empty reason; the validator rejects stale exceptions.

The example gallery is generated from the public specification commit pinned in [`scripts/example-corpus.config.mjs`](./scripts/example-corpus.config.mjs). [`example-corpus`](./example-corpus) is a hermetic snapshot for the Playground and documentation build; CI checks every catalog, schema, and `.stack` source byte against the pinned provider commit. `npm run examples:check` checks and renders all examples, resolves every namespaced icon against the published provider catalog, and detects stale SVG thumbnails. To intentionally advance the corpus, check out the new pinned specification revision, run `STACK_SPECIFICATION_ROOT=/path/to/specification npm run examples:sync`, and then run `npm run examples:generate`.

## Brand mark

[`public/favicon.svg`](./public/favicon.svg) is the canonical Stack logo mark for Web surfaces. The Playground uses it as its favicon and header mark; VitePress uses the identical [`docs/public/favicon.svg`](./docs/public/favicon.svg) copy for its favicon, navigation, and localized home pages. The documentation validator rejects the build if the two public-root copies differ.
Expand Down
6 changes: 6 additions & 0 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ type Labels = {
guide: string
introduction: string
gettingStarted: string
examples: string
playground: string
providerIcons: string
language: string
Expand Down Expand Up @@ -107,6 +108,7 @@ function localeTheme(prefix: string, labels: Labels): DefaultTheme.Config {
return {
nav: [
{ text: labels.guide, link: `${prefix}/guide/getting-started` },
{ text: labels.examples, link: `${prefix}/examples/` },
{ text: labels.reference, link: `${prefix}/reference/diagnostics-and-limits` },
{ text: "Playground", link: "https://stack-diagram.com/", target: "_self" },
],
Expand All @@ -131,6 +133,7 @@ const en: Labels = {
guide: "Guide",
introduction: "What is Stack?",
gettingStarted: "Getting started",
examples: "Examples",
playground: "Using the Playground",
providerIcons: "Provider icons",
language: "Language",
Expand Down Expand Up @@ -159,6 +162,7 @@ const ja: Labels = {
guide: "ガイド",
introduction: "Stackとは",
gettingStarted: "はじめる",
examples: "Example gallery",
playground: "Playgroundの使い方",
providerIcons: "Provider icon",
language: "言語",
Expand Down Expand Up @@ -187,6 +191,7 @@ const zh: Labels = {
guide: "指南",
introduction: "什么是 Stack?",
gettingStarted: "快速开始",
examples: "示例库",
playground: "使用 Playground",
providerIcons: "云服务商图标",
language: "语言",
Expand Down Expand Up @@ -215,6 +220,7 @@ const ko: Labels = {
guide: "가이드",
introduction: "Stack이란?",
gettingStarted: "시작하기",
examples: "예제 갤러리",
playground: "Playground 사용법",
providerIcons: "클라우드 제공자 아이콘",
language: "언어",
Expand Down
136 changes: 136 additions & 0 deletions docs/.vitepress/theme/components/ExampleGallery.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
<script setup lang="ts">
import corpus from "../../../../example-corpus/catalog.json"
import { exampleCorpusSource } from "../../../../scripts/example-corpus.config.mjs"

type Locale = "en" | "ja" | "zh" | "ko"
type LearningStage = "starter" | "intermediate" | "advanced"

defineProps<{ locale: Locale }>()

const stages: LearningStage[] = ["starter", "intermediate", "advanced"]
const labels = {
en: {
stage: { starter: "Start small", intermediate: "Build fluency", advanced: "Model systems" },
packs: "Icon packs",
core: "Built-in icons",
expected: "Expected output",
structure: { nodes: "nodes", groups: "groups", edges: "edges" },
features: "Syntax features",
source: "View canonical .stack source",
contract: "Pinned source",
},
ja: {
stage: { starter: "小さく始める", intermediate: "表現を広げる", advanced: "Systemを描く" },
packs: "Icon pack",
core: "組み込みicon",
expected: "期待する出力",
structure: { nodes: "node", groups: "group", edges: "edge" },
features: "Syntax feature",
source: "Canonical .stack sourceを見る",
contract: "Pin済みsource",
},
zh: {
stage: { starter: "从小处开始", intermediate: "扩展表达", advanced: "描述系统" },
packs: "图标包",
core: "内置图标",
expected: "预期输出",
structure: { nodes: "节点", groups: "分组", edges: "连线" },
features: "语法特性",
source: "查看规范 .stack 源码",
contract: "固定源版本",
},
ko: {
stage: { starter: "작게 시작하기", intermediate: "표현 넓히기", advanced: "시스템 모델링" },
packs: "아이콘 팩",
core: "기본 아이콘",
expected: "예상 출력",
structure: { nodes: "노드", groups: "그룹", edges: "엣지" },
features: "문법 기능",
source: "Canonical .stack 소스 보기",
contract: "고정된 소스",
},
} as const

function examplesFor(stage: LearningStage) {
return corpus.examples.filter((example) => example.learningStage === stage)
}

function sourceUrl(source: string) {
return `https://raw.githubusercontent.com/${exampleCorpusSource.repository}/${exampleCorpusSource.revision}/examples/${source}`
}

function featureLabel(feature: string) {
return feature.replaceAll("-", " ")
}
</script>

<template>
<section class="stack-example-gallery">
<p class="stack-example-gallery__contract">
{{ labels[locale].contract }}:
<a
:href="`https://github.com/${exampleCorpusSource.repository}/tree/${exampleCorpusSource.revision}/examples`"
>
<code>{{ exampleCorpusSource.revision.slice(0, 8) }}</code>
</a>
</p>

<section v-for="stage in stages" :key="stage" class="stack-example-stage">
<h2 :id="`examples-${stage}`">{{ labels[locale].stage[stage] }}</h2>
<div class="stack-example-list">
<article
v-for="example in examplesFor(stage)"
:key="example.id"
class="stack-example-card"
:aria-labelledby="`example-${example.id}`"
>
<a class="stack-example-card__preview" :href="sourceUrl(example.source)">
<img
:src="`/docs/examples/${example.id}.svg`"
:alt="example.thumbnail.alt"
loading="lazy"
/>
</a>
<div class="stack-example-card__body">
<div class="stack-example-card__heading">
<h3 :id="`example-${example.id}`">{{ example.title }}</h3>
<span class="stack-example-card__stage">{{ example.learningStage }}</span>
</div>
<p>{{ example.summary }}</p>

<div class="stack-example-card__meta">
<div>
<strong>{{ labels[locale].packs }}</strong>
<span>{{ example.providers.join(", ") || labels[locale].core }}</span>
</div>
<div>
<strong>{{ labels[locale].expected }}</strong>
<span>{{ example.expected.description }}</span>
</div>
</div>

<dl class="stack-example-card__counts">
<div v-for="field in ['nodes', 'groups', 'edges'] as const" :key="field">
<dt>{{ labels[locale].structure[field] }}</dt>
<dd>{{ example.expected[field] }}</dd>
</div>
</dl>

<details class="stack-example-card__features">
<summary>{{ labels[locale].features }}</summary>
<ul>
<li v-for="feature in example.features" :key="feature">
{{ featureLabel(feature) }}
</li>
</ul>
</details>

<a class="stack-example-card__source" :href="sourceUrl(example.source)">
{{ labels[locale].source }}
</a>
</div>
</article>
</div>
</section>
</section>
</template>
2 changes: 2 additions & 0 deletions docs/.vitepress/theme/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ import DefaultTheme from "vitepress/theme"
import "@fontsource-variable/ibm-plex-sans"
import "@fontsource/ibm-plex-mono/400.css"

import ExampleGallery from "./components/ExampleGallery.vue"
import IconCatalog from "./components/IconCatalog.vue"
import ProviderCatalog from "./components/ProviderCatalog.vue"
import "./style.css"

export default {
extends: DefaultTheme,
enhanceApp({ app }) {
app.component("ExampleGallery", ExampleGallery)
app.component("IconCatalog", IconCatalog)
app.component("ProviderCatalog", ProviderCatalog)
},
Expand Down
Loading