From c2b47582f392e7e9f611d77b5890f8d3dd593f3a Mon Sep 17 00:00:00 2001 From: Joseph Dale Banares Date: Thu, 1 Oct 2026 13:42:05 +0800 Subject: [PATCH 1/4] docs(site): rebuild the docs and examples on the v2 canvas: a docs sidebar with search, pages for the viewer, CLI, brands and each kind, and a page per example --- site/app/docs/[kind]/page.tsx | 113 ++ site/app/docs/brands/page.tsx | 54 + site/app/docs/cli/page.tsx | 186 +++ site/app/docs/page.tsx | 246 ++- site/app/docs/schema/page.tsx | 266 +-- site/app/docs/viewer/page.tsx | 87 + site/app/examples/[key]/page.tsx | 142 ++ site/app/examples/page.tsx | 54 +- site/app/kinds/[kind]/page.tsx | 72 - site/app/layout.tsx | 5 +- site/app/search.json/route.ts | 8 + site/app/site.css | 51 + site/components/docs/BrandGrid.tsx | 58 + site/components/docs/DocsNav.tsx | 207 +++ site/components/docs/DocsShell.tsx | 145 +- site/components/docs/LinkedFigure.tsx | 66 + site/components/docs/Search.tsx | 190 +++ site/components/docs/Tabs.tsx | 44 + site/components/docs/Toc.tsx | 47 - site/components/docs/ViewerTry.tsx | 120 ++ site/components/docs/docs.css | 1714 ++++++++++++++++++++ site/components/docs/prose.tsx | 130 +- site/components/examples/ExampleCard.tsx | 42 + site/components/examples/ExampleGrid.tsx | 106 ++ site/components/examples/ExampleViewer.tsx | 34 + site/components/examples/Gallery.tsx | 129 -- site/components/examples/examples.css | 570 +++++++ site/components/examples/gallery-props.tsx | 15 - site/components/examples/gallery.css | 181 --- site/components/kinds/Parts.tsx | 32 +- site/components/landing/HeroDemo.tsx | 43 +- site/components/landing/Kinds.tsx | 3 +- site/components/site/Footer.tsx | 32 +- site/components/site/FooterFor.tsx | 9 + site/components/site/Nav.tsx | 2 +- site/components/site/links.ts | 5 +- site/components/ui/Code.tsx | 113 ++ site/components/ui/CodeBlock.tsx | 60 - site/components/ui/code.css | 155 ++ site/components/viewer/EmbeddedViewer.tsx | 99 +- site/components/viewer/useTry.ts | 48 + site/components/viewer/viewer.css | 33 +- site/e2e/smoke.spec.ts | 80 +- site/lib/catalog.ts | 13 +- site/lib/data/cli-samples.ts | 62 + site/lib/data/codes.ts | 29 + site/lib/data/diagrams.ts | 6 +- site/lib/docs-nav.ts | 103 ++ site/lib/json-lines.test.ts | 43 + site/lib/json-lines.ts | 52 + site/lib/kinds.ts | 79 +- site/lib/reference.ts | 48 + site/lib/schema-docs.test.ts | 22 + site/lib/schema-docs.ts | 131 ++ site/lib/search-index.ts | 98 ++ site/lib/search.test.ts | 33 + site/lib/search.ts | 44 + site/lib/seo.test.ts | 6 +- site/lib/seo.ts | 6 +- site/lib/site-data.ts | 4 +- site/next.config.ts | 2 + site/scripts/build-data.ts | 6 +- site/tests/catalog.test.ts | 8 +- site/tests/docs.test.ts | 45 + site/tests/links.test.ts | 6 +- 65 files changed, 5562 insertions(+), 1080 deletions(-) create mode 100644 site/app/docs/[kind]/page.tsx create mode 100644 site/app/docs/brands/page.tsx create mode 100644 site/app/docs/cli/page.tsx create mode 100644 site/app/docs/viewer/page.tsx create mode 100644 site/app/examples/[key]/page.tsx delete mode 100644 site/app/kinds/[kind]/page.tsx create mode 100644 site/app/search.json/route.ts create mode 100644 site/components/docs/BrandGrid.tsx create mode 100644 site/components/docs/DocsNav.tsx create mode 100644 site/components/docs/LinkedFigure.tsx create mode 100644 site/components/docs/Search.tsx create mode 100644 site/components/docs/Tabs.tsx delete mode 100644 site/components/docs/Toc.tsx create mode 100644 site/components/docs/ViewerTry.tsx create mode 100644 site/components/docs/docs.css create mode 100644 site/components/examples/ExampleCard.tsx create mode 100644 site/components/examples/ExampleGrid.tsx create mode 100644 site/components/examples/ExampleViewer.tsx delete mode 100644 site/components/examples/Gallery.tsx create mode 100644 site/components/examples/examples.css delete mode 100644 site/components/examples/gallery-props.tsx delete mode 100644 site/components/examples/gallery.css create mode 100644 site/components/site/FooterFor.tsx create mode 100644 site/components/ui/Code.tsx delete mode 100644 site/components/ui/CodeBlock.tsx create mode 100644 site/components/ui/code.css create mode 100644 site/components/viewer/useTry.ts create mode 100644 site/lib/data/cli-samples.ts create mode 100644 site/lib/data/codes.ts create mode 100644 site/lib/docs-nav.ts create mode 100644 site/lib/json-lines.test.ts create mode 100644 site/lib/json-lines.ts create mode 100644 site/lib/reference.ts create mode 100644 site/lib/schema-docs.test.ts create mode 100644 site/lib/schema-docs.ts create mode 100644 site/lib/search-index.ts create mode 100644 site/lib/search.test.ts create mode 100644 site/lib/search.ts create mode 100644 site/tests/docs.test.ts diff --git a/site/app/docs/[kind]/page.tsx b/site/app/docs/[kind]/page.tsx new file mode 100644 index 0000000..561999e --- /dev/null +++ b/site/app/docs/[kind]/page.tsx @@ -0,0 +1,113 @@ +import type { Metadata } from 'next'; +import Link from 'next/link'; +import { notFound } from 'next/navigation'; +import { ArrowRight } from 'lucide-react'; +import { DIAGRAM_KINDS, KIND_LABELS, type DiagramKind, type NodeType } from '@stackmap/core'; +import { DocsShell } from '@/components/docs/DocsShell'; +import { LinkedFigure } from '@/components/docs/LinkedFigure'; +import { Ask, C, DocHead, H2, H3, Inline, P, Table } from '@/components/docs/prose'; +import { ExampleCard } from '@/components/examples/ExampleCard'; +import { Parts } from '@/components/kinds/Parts'; +import { LINKS } from '@/components/site/links'; +import { JsonCode } from '@/components/ui/Code'; +import { TypeChip } from '@/components/ui/TypeChip'; +import { EXAMPLES, id, SKILL_EXAMPLE } from '@/lib/catalog'; +import { SOURCES, stillOf } from '@/lib/diagrams'; +import { kindHref } from '@/lib/docs-nav'; +import { jsonOwners } from '@/lib/json-lines'; +import { KIND_PAGES } from '@/lib/kinds'; +import { OPEN_GRAPH } from '@/lib/seo'; + +export const dynamicParams = false; + +export function generateStaticParams() { + return DIAGRAM_KINDS.map((kind) => ({ kind })); +} + +const isKind = (k: string): k is DiagramKind => (DIAGRAM_KINDS as readonly string[]).includes(k); + +export async function generateMetadata({ params }: PageProps<'/docs/[kind]'>): Promise { + const { kind } = await params; + if (!isKind(kind)) return {}; + return { + title: `${KIND_LABELS[kind]} diagrams`, + description: KIND_PAGES[kind].lede, + alternates: { canonical: kindHref(kind) }, + openGraph: { ...OPEN_GRAPH, url: kindHref(kind) }, + }; +} + +export default async function KindPage({ params }: PageProps<'/docs/[kind]'>) { + const { kind } = await params; + if (!isKind(kind)) notFound(); + const page = KIND_PAGES[kind]; + const key = SKILL_EXAMPLE[kind]; + const code = SOURCES.skill[key]!.trimEnd(); + const label = KIND_LABELS[kind]; + return ( + + +

+ +

+ +

An example

+

+ The skill’s own example, {key}.json, laid out by stackmap. +

+ } + /> + +

Its parts

+ + +

Rules

+

+ From the{' '} + + authoring contract + {' '} + the skill gives your agent. stackmap validate enforces the hard ones and names the fix. +

+
    + {page.rules.map((r) => ( +
  • + +
  • + ))} +
+ {page.types && ( +
+

{page.types.title}

+ [, use])} + /> + + )} + +

Ask for one

+ +

Your agent picks the kind that answers the question; naming it is the surest way to get it.

+ +

Examples

+
    + {EXAMPLES.filter((e) => e.kind === kind).map((e) => ( +
  • + +
  • + ))} +
+

+ + See {label.toLowerCase()} examples in the gallery +

+ + ); +} diff --git a/site/app/docs/brands/page.tsx b/site/app/docs/brands/page.tsx new file mode 100644 index 0000000..6854a4e --- /dev/null +++ b/site/app/docs/brands/page.tsx @@ -0,0 +1,54 @@ +import type { Metadata } from 'next'; +import { Check } from 'lucide-react'; +import { BRAND_SLUGS } from '@stackmap/core'; +import { BRANDS } from '@stackmap/viewer/src/icons/brands.gen'; +import { BrandGrid } from '@/components/docs/BrandGrid'; +import { DocsShell } from '@/components/docs/DocsShell'; +import { C, DocHead, P } from '@/components/docs/prose'; +import { OPEN_GRAPH } from '@/lib/seo'; + +export const metadata: Metadata = { + title: 'Brand slugs', + description: 'Every logo a stackmap card can show: the Simple Icons slugs nodes[].card.brand accepts.', + alternates: { canonical: '/docs/brands' }, + openGraph: { ...OPEN_GRAPH, url: '/docs/brands' }, +}; + +export default function BrandsPage() { + return ( + + + The values nodes[].card.brand accepts, all {BRAND_SLUGS.length} from Simple Icons (CC0). Anything else is a warning, and the card shows its type icon + instead. + + } + /> +

The logo only sits in the icon tile; the node’s type still sets the colour. Click a slug to copy it.

+ + {BRAND_SLUGS.map((slug) => { + const b = BRANDS[slug]!; + return ( +
  • + +
  • + ); + })} +
    +
    + ); +} diff --git a/site/app/docs/cli/page.tsx b/site/app/docs/cli/page.tsx new file mode 100644 index 0000000..2045e36 --- /dev/null +++ b/site/app/docs/cli/page.tsx @@ -0,0 +1,186 @@ +import type { Metadata } from 'next'; +import { DocsShell } from '@/components/docs/DocsShell'; +import { C, DocHead, H2, H3, P, Table } from '@/components/docs/prose'; +import { Tabs } from '@/components/docs/Tabs'; +import { Cmd } from '@/components/ui/Cmd'; +import { JsonCode, Terminal } from '@/components/ui/Code'; +import { CLI_COMMANDS, CLI_OPTIONS, EXIT_CODES, FAMILIES, type Family } from '@/lib/reference'; +import { OPEN_GRAPH } from '@/lib/seo'; +import { SITE } from '@/lib/site-data'; + +export const metadata: Metadata = { + title: 'The CLI', + description: 'stackmap validate, deliver and serve: what each command does, its options, every diagnostic code and the exit codes.', + alternates: { canonical: '/docs/cli' }, + openGraph: { ...OPEN_GRAPH, url: '/docs/cli' }, +}; + +const DEMO = '.stackmap/commerce-api/diagram.json'; +const npx = `npx @hyamero/stackmap@${SITE.version}`; + +function Synopsis({ name }: { name: (typeof CLI_COMMANDS)[number]['name'] }) { + const c = CLI_COMMANDS.find((x) => x.name === name)!; + return ( +

    + stackmap {c.name} {c.usage} +

    + ); +} + +const FAMILY_TEXT: Record = { + schema: 'The JSON doesn’t match the schema: a wrong type, a value outside an enum, an unknown key. The code is the schema check that failed.', + refs: 'An id points at nothing: a connection to a node that doesn’t exist, a group, lane, view or phase member that isn’t there. The fix names the closest id when there is one.', + semantics: 'Valid, but not what the kind expects or a reader can use: an orphan node, an empty view, lanes on an architecture. Most are warnings.', + 'card-fit': 'Text that won’t fit its slot, measured with the real font. Text never wraps, so this is an error, with the exact number of characters that fit.', +}; + +export default function CliPage() { + const { file, sha256, bytes } = SITE.receipt; + const refs = SITE.cli.families.refs; + return ( + + + +

    Run it

    +

    With Node 22.12 or later, nothing to install first:

    +
    + +
    +

    + The commands below call it stackmap; with npx it is {npx}. +

    + +

    validate

    + +

    + Checks the diagram and lists every problem with a code, where it is and the fixes it allows, including card text that won’t fit. It exits 1 when the diagram has + errors; warnings alone exit 0. +

    +
    + +
    +

    --json

    +

    The same diagnostics, for a program to read: each one has its code, severity, subject (a JSON pointer), message, evidence and allowed fixes.

    +
    + +
    + +

    deliver

    + +

    + Validates, lays out and writes one offline HTML file, next to the input unless you pass -o. It refuses to overwrite its input. Delivery is deterministic: + the same JSON always gives the same file, byte for byte, so the receipt’s hash is worth keeping. +

    +
    + +
    +

    The receipt is the only thing on stdout. Warnings go to stderr; with errors, nothing is written and the command exits 1.

    + +

    serve

    + +

    + A live viewer that reloads whenever the file changes, for editing a diagram by hand or watching an agent work. It binds 127.0.0.1 only and takes the next + free port when 4400 is taken. When a save is invalid it keeps the last good version on screen, and it keeps your camera and selection across reloads. +

    +
    + +
    + +

    Options

    +
    [{o.flag}, o.for, o.does])} /> + +

    Diagnostics

    +

    Every diagnostic has the same shape, so an agent can act on it without guessing:

    +
    +
    +
    + error refs/unknown-node /edges/6/to +
    +
    + {' '} + Unknown node "orders-db" +
    +
    + {' '} + fix: use "orders" +
    +
    + {' '} + fix: add node "orders-db" or remove the edge +
    +
    +
    +
    +
    Severity
    +
    + error blocks delivery; warning doesn’t. +
    +
    +
    +
    Code
    +
    + <family>/<rule>: schema, refs, semantics or card-fit. +
    +
    +
    +
    Subject
    +
    A JSON pointer to the value at fault.
    +
    +
    +
    Fixes
    +
    The repairs it allows. The agent applies one, and only what is named.
    +
    +
    +
    + ({ + id: f, + label: f, + panel: ( + <> +

    {FAMILY_TEXT[f]}

    + + + ), + }))} + /> + +

    Diagnostic codes

    +

    + Every code validate can report. Schema codes are named after the check that failed. +

    +
    + {FAMILIES.map((f) => ( +
    +

    {f}

    +
      + {SITE.codes[f].map((code) => ( +
    • + {code} +
    • + ))} +
    +
    + ))} +
    + +

    Exit codes

    +
    [{String(e.code)}, e.means])} /> + + ); +} diff --git a/site/app/docs/page.tsx b/site/app/docs/page.tsx index 3937096..53fb902 100644 --- a/site/app/docs/page.tsx +++ b/site/app/docs/page.tsx @@ -1,14 +1,20 @@ import type { Metadata } from 'next'; -import { BRAND_SLUGS, INFRA_TYPES, STATE_TYPES } from '@stackmap/core'; -import { FitDiagram } from '@/components/diagram/FitDiagram'; -import { DocsShell, NextLink } from '@/components/docs/DocsShell'; -import { Ask, Code, docs } from '@/components/docs/prose'; -import { CodeBlock, Command } from '@/components/ui/CodeBlock'; +import Link from 'next/link'; +import { BookOpen, Braces, LayoutGrid, Shapes } from 'lucide-react'; +import { BRAND_SLUGS, DIAGRAM_KINDS, INFRA_TYPES, STATE_TYPES } from '@stackmap/core'; +import { DocsShell } from '@/components/docs/DocsShell'; +import { LinkedFigure } from '@/components/docs/LinkedFigure'; +import { Ask, C, DocHead, H2, P } from '@/components/docs/prose'; +import { Cmd } from '@/components/ui/Cmd'; +import { JsonCode, Terminal } from '@/components/ui/Code'; import { TypeChip } from '@/components/ui/TypeChip'; -import { spell } from '@/lib/words'; +import { EXAMPLES } from '@/lib/catalog'; import { SOURCES, stillOf } from '@/lib/diagrams'; +import { kindHref } from '@/lib/docs-nav'; +import { jsonOwners } from '@/lib/json-lines'; import { OPEN_GRAPH } from '@/lib/seo'; import { SITE } from '@/lib/site-data'; +import { spell } from '@/lib/words'; export const metadata: Metadata = { title: 'Quick start', @@ -17,171 +23,141 @@ export const metadata: Metadata = { openGraph: { ...OPEN_GRAPH, url: '/docs' }, }; -const TOC = [ - { id: 'quick-start', label: 'Quick start' }, - { id: 'json', label: 'What the agent writes' }, - { id: 'cli', label: 'The CLI' }, -]; - -const CLI = [ - { usage: 'validate diagram.json', flags: '[--json]', does: 'Lists every problem with a code, its evidence and the allowed fixes, including card text that won’t fit.' }, - { usage: 'deliver diagram.json', flags: '[-o out.html]', does: 'Validates, lays out and writes one offline HTML file. The same JSON always gives the same file, byte for byte.' }, - { - usage: 'serve diagram.json', - flags: '[--port 4400]', - does: 'A live viewer that reloads on every save. It binds 127.0.0.1 only, keeps the last good version when a save is invalid, and keeps your camera and selection across reloads.', - }, -]; - -const EXIT_CODES = [ - { code: 0, meaning: 'OK. Warnings are allowed.' }, - { code: 1, meaning: 'The diagram has errors.' }, - { code: 2, meaning: 'A usage, file or internal error. Never a stack trace.' }, -]; - +const ASK = 'Make an architecture diagram of this repository, backed by evidence from the code.'; // The agent's file pins the schema to the installed CLI, so the example does too. -const json = SOURCES.quickStart.replace('{\n', `{\n "$schema": "https://unpkg.com/@hyamero/stackmap@${SITE.version}/dist/stackmap.schema.json",\n`); +const JSON_TEXT = SOURCES.quickStart.replace('{\n', `{\n "$schema": "https://unpkg.com/@hyamero/stackmap@${SITE.version}/dist/stackmap.schema.json",\n`); +const FILE = '.stackmap/commerce-api/diagram.json'; -function Step({ n, title, children }: { n: number; title: string; children: React.ReactNode }) { +function Step({ n, id, title, children }: { n: number; id: string; title: string; children: React.ReactNode }) { return ( -
  • - {n} -

    {title}

    - {children} +
  • + +
    +

    + Step {n}: + {title} +

    + {children} +
  • ); } -function Transcript() { +function Repair() { const { broken, clean } = SITE.repair; const { file, sha256, bytes } = SITE.receipt; - const prompt = (cmd: string) => ( - - stackmap · {cmd} .stackmap/commerce-api/diagram.json - - ); - const line = (l: string, i: number) => { - const tone = l.startsWith('error') || l.startsWith('✗') ? 'text-[#f0806e]' : l.startsWith('✓') ? 'text-[#a6d97a]' : ''; - return ( - - {l} - - ); - }; return ( -
    -
    A repair round, as the agent sees it
    -
    -        {prompt('validate')}
    -        {broken.map(line)}
    -         
    -        {prompt('validate')}
    -        {clean.map(line)}
    -         
    -        {prompt('deliver')}
    -        
    -          delivered {file} · sha256 {sha256.slice(0, 12)}… · {bytes} bytes
    -        
    -      
    +
    +
    ); } -export default function DocsPage() { +const agents = EXAMPLES.filter((e) => e.prompt).length; + +const NEXT = [ + { href: '/docs/viewer', icon: BookOpen, title: 'The viewer', text: 'Select, trace, route, find, present and share what the file shows.' }, + { href: kindHref('architecture'), icon: Shapes, title: 'Diagram kinds', text: `${spell(DIAGRAM_KINDS.length, true)} kinds, each with its own layout. Pick the one that answers the question.` }, + { href: '/docs/schema', icon: Braces, title: 'Schema reference', text: 'Every field of diagram.json, with a real example beside each part.' }, + { href: '/examples', icon: LayoutGrid, title: 'Examples', text: `${spell(EXAMPLES.length, true)} diagrams, ${spell(agents)} of them written by coding agents.` }, +]; + +export default function QuickStartPage() { return ( - -

    Docs › Get started

    -

    - Quick start. -

    -

    Install the skill, ask your coding agent for a diagram, and open the file it delivers.

    -
      - - -

      It installs into Claude Code, Cursor, Codex and the other agents that skills supports.

      + + +
        + + +

        + It installs into Claude Code, Cursor, Codex and the{' '} + + other agents that skills supports + + . +

        - - Make an architecture diagram of this repository, backed by evidence from the code. -

        - The agent writes .stackmap/<name>/diagram.json, validates it, repairs what the diagnostics name, and delivers{' '} - .stackmap/<name>/diagram.html. + + +

        + The agent writes .stackmap/<name>/diagram.json, validates it, repairs what the diagnostics name, and delivers{' '} + .stackmap/<name>/diagram.html.

        - -

        - Open diagram.html in any browser. There is no server, no account and nothing to install. To change the diagram, ask again: the agent edits - the JSON and delivers it again. The viewer itself is read-only. + +

        + Open diagram.html in any browser. There is no server, no account and nothing to install. To change the diagram, ask again: the agent edits the JSON + and delivers it again. The viewer itself is read-only.

      -

      - What the agent writes. -

      -

      - One small typed JSON file. The agent names the nodes, connections, groups and views; stackmap validates it and lays it out. There are no coordinates to write. -

      - -
      -
      - {[0, 1, 2].map((i) => ( -
    - {h} -
    {f.key}{f.type}{f.required ? 'yes' : 'no'} - -