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
113 changes: 113 additions & 0 deletions site/app/docs/[kind]/page.tsx
Original file line number Diff line number Diff line change
@@ -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<Metadata> {
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 (
<DocsShell href={kindHref(kind)}>
<DocHead crumb="Diagram kinds" title={label} lede={page.lede} />
<P>
<Inline text={page.use} />
</P>

<H2 id="example">An example</H2>
<P muted>
The skill’s own example, <C>{key}.json</C>, laid out by stackmap.
</P>
<LinkedFigure
split={page.split}
still={stillOf(`skill/${key}`)}
url={`file:///…/.stackmap/${key.split('.')[0]}/diagram.html`}
code={<JsonCode file={`${key}.json`} code={code} owners={jsonOwners(code)} />}
/>

<H2 id="parts">Its parts</H2>
<Parts kind={kind} page={page} />

<H2 id="rules">Rules</H2>
<P muted>
From the{' '}
<a href={LINKS.authoring.href} target="_blank" rel="noreferrer">
authoring contract
</a>{' '}
the skill gives your agent. <C>stackmap validate</C> enforces the hard ones and names the fix.
</P>
<ul className="d-ul">
{page.rules.map((r) => (
<li key={r} className="d-li">
<Inline text={r} />
</li>
))}
</ul>
{page.types && (
<div className="kind-types">
<H3>{page.types.title}</H3>
<Table
head={['Type', 'Use it for']}
rows={Object.entries(page.types.uses).map(([t, use]) => [<TypeChip key={t} type={t as NodeType} small />, use])}
/>
</div>
)}

<H2 id="ask">Ask for one</H2>
<Ask text={page.ask} className="kind-ask" />
<P muted>Your agent picks the kind that answers the question; naming it is the surest way to get it.</P>

<H2 id="examples">Examples</H2>
<ul className="g-grid kind-grid">
{EXAMPLES.filter((e) => e.kind === kind).map((e) => (
<li key={id(e)} className="ex-li">
<ExampleCard entry={e} />
</li>
))}
</ul>
<P muted>
<Link className="g-doc" href={`/examples?kind=${kind}`}>
<span>See {label.toLowerCase()} examples in the gallery</span>
<ArrowRight size={14} strokeWidth={1.75} aria-hidden="true" />
</Link>
</P>
</DocsShell>
);
}
54 changes: 54 additions & 0 deletions site/app/docs/brands/page.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<DocsShell href="/docs/brands">
<DocHead
crumb="Reference"
title="Brand slugs"
lede={
<>
The values <C>nodes[].card.brand</C> accepts, all {BRAND_SLUGS.length} from Simple Icons (CC0). Anything else is a warning, and the card shows its type icon
instead.
</>
}
/>
<P muted>The logo only sits in the icon tile; the node’s type still sets the colour. Click a slug to copy it.</P>
<BrandGrid total={BRAND_SLUGS.length}>
{BRAND_SLUGS.map((slug) => {
const b = BRANDS[slug]!;
return (
<li key={slug} id={slug} className="br" data-slug={slug} data-hay={`${slug} ${b.title.toLowerCase()}`}>
<button type="button" className="br-b" aria-label={`Copy ${slug} (${b.title})`}>
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path d={b.path} />
</svg>
<span className="br-t">
<span className="mono">{slug}</span>
<span className="br-n2">{b.title}</span>
</span>
<span className="br-ok" aria-hidden="true">
<Check size={12} strokeWidth={2.5} />
</span>
</button>
</li>
);
})}
</BrandGrid>
</DocsShell>
);
}
186 changes: 186 additions & 0 deletions site/app/docs/cli/page.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<p className="syn mono">
<span aria-hidden="true">$</span> stackmap <b>{c.name}</b> {c.usage}
</p>
);
}

const FAMILY_TEXT: Record<Family, string> = {
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 (
<DocsShell href="/docs/cli">
<DocHead
crumb="Reference"
title="The CLI"
lede="The skill runs the CLI for you, and it works on its own too: give it a diagram.json and it checks it, lays it out and writes the viewer."
/>

<H2 id="run">Run it</H2>
<P>With Node 22.12 or later, nothing to install first:</P>
<div className="d-cmd">
<Cmd command={SITE.install.cli} label="Copy the command" />
</div>
<P muted>
The commands below call it <C>stackmap</C>; with npx it is <C>{npx}</C>.
</P>

<H2 id="validate">validate</H2>
<Synopsis name="validate" />
<P>
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.
</P>
<div className="d-fig">
<Terminal title="stackmap validate" lines={[`$ stackmap validate ${DEMO}`, ...refs]} />
</div>
<H3>--json</H3>
<P muted>The same diagnostics, for a program to read: each one has its code, severity, subject (a JSON pointer), message, evidence and allowed fixes.</P>
<div className="d-fig">
<JsonCode file="stackmap validate diagram.json --json" code={SITE.cli.json} label="stdout" />
</div>

<H2 id="deliver">deliver</H2>
<Synopsis name="deliver" />
<P>
Validates, lays out and writes one offline HTML file, next to the input unless you pass <C>-o</C>. 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.
</P>
<div className="d-fig">
<Terminal title="stackmap deliver" lines={[`$ stackmap deliver ${DEMO}`, `delivered ${file} · sha256 ${sha256} · ${bytes} bytes`]} />
</div>
<P muted>The receipt is the only thing on stdout. Warnings go to stderr; with errors, nothing is written and the command exits 1.</P>

<H2 id="serve">serve</H2>
<Synopsis name="serve" />
<P>
A live viewer that reloads whenever the file changes, for editing a diagram by hand or watching an agent work. It binds <C>127.0.0.1</C> only and takes the next
free port when <C>4400</C> is taken. When a save is invalid it keeps the last good version on screen, and it keeps your camera and selection across reloads.
</P>
<div className="d-fig">
<Terminal
title="stackmap serve"
lines={[
'$ stackmap serve diagram.json',
'serving http://127.0.0.1:4400 · watching diagram.json · Ctrl-C to stop',
'✓ diagram.json ready',
'✗ diagram.json: 1 error; keeping the last good version',
'✓ diagram.json reloaded',
]}
/>
</div>

<H2 id="options">Options</H2>
<Table head={['Option', 'For', 'Does']} rows={CLI_OPTIONS.map((o) => [<C key="f">{o.flag}</C>, o.for, o.does])} />

<H2 id="diagnostics">Diagnostics</H2>
<P>Every diagnostic has the same shape, so an agent can act on it without guessing:</P>
<div className="anat" role="group" aria-label="The parts of a diagnostic">
<div className="anat-c">
<div className="anat-l mono">
<span className="a-c">error</span> <span className="a-c">refs/unknown-node</span> <span className="a-p">/edges/6/to</span>
</div>
<div className="anat-l mono">
{' '}
<span className="a-m">Unknown node &quot;orders-db&quot;</span>
</div>
<div className="anat-l mono">
{' '}
<span className="a-m">fix:</span> use &quot;orders&quot;
</div>
<div className="anat-l mono">
{' '}
<span className="a-m">fix:</span> add node &quot;orders-db&quot; or remove the edge
</div>
</div>
<dl className="anat-k">
<div>
<dt>Severity</dt>
<dd>
<C>error</C> blocks delivery; <C>warning</C> doesn’t.
</dd>
</div>
<div>
<dt>Code</dt>
<dd>
<C>&lt;family&gt;/&lt;rule&gt;</C>: schema, refs, semantics or card-fit.
</dd>
</div>
<div>
<dt>Subject</dt>
<dd>A JSON pointer to the value at fault.</dd>
</div>
<div>
<dt>Fixes</dt>
<dd>The repairs it allows. The agent applies one, and only what is named.</dd>
</div>
</dl>
</div>
<Tabs
label="Diagnostic families"
mono
tabs={FAMILIES.map((f) => ({
id: f,
label: f,
panel: (
<>
<P muted>{FAMILY_TEXT[f]}</P>
<Terminal title="stackmap validate diagram.json" lines={['$ stackmap validate diagram.json', ...SITE.cli.families[f]]} />
</>
),
}))}
/>

<H2 id="codes">Diagnostic codes</H2>
<P muted>
Every code <C>validate</C> can report. Schema codes are named after the check that failed.
</P>
<div className="codes-w">
{FAMILIES.map((f) => (
<div key={f} className="codes-g">
<H3>{f}</H3>
<ul className="codes">
{SITE.codes[f].map((code) => (
<li key={code}>
<C>{code}</C>
</li>
))}
</ul>
</div>
))}
</div>

<H2 id="exit">Exit codes</H2>
<Table head={['Code', 'Means']} rows={EXIT_CODES.map((e) => [<C key="c">{String(e.code)}</C>, e.means])} />
</DocsShell>
);
}
Loading
Loading