base/_viewer/index.html
<!doctype html>
<html lang="en" class="antialiased">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>iii — tech spec</title>
<meta
name="description"
content="an iii tech spec, rendered — the full markdown, mermaid diagrams live."
/>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Chivo+Mono:wght@400;500;600&display=swap"
/>
<script>
try {
const t = localStorage.getItem('iii-theme');
document.documentElement.dataset.theme =
t === 'dark' || t === 'light' ? t : 'light';
} catch (_) {
document.documentElement.dataset.theme = 'light';
}
</script>
</head>
<body class="bg-bg text-ink font-sans">
<div id="root"></div>
<script type="module" src="./src/main.tsx"></script>
</body>
</html>
base/_viewer/src/main.tsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import '@lib/index.css'
import { ViewerApp } from './ViewerApp'
createRoot(document.getElementById('root')!).render(
<StrictMode>
<ViewerApp />
</StrictMode>,
)
base/_viewer/src/ViewerApp.tsx
import { Sheet } from '@lib/components/schematic/Sheet'
import { TopNav } from '@lib/components/TopNav'
import { useHashRoute } from '@lib/hooks/useHashRoute'
import { SpecPage } from '@lib/pages/SpecPage'
import { useEffect, useState } from 'react'
/**
* The generic markdown spec viewer — the page a spec gets at
* /roadmap/<slug>/ before (or without) an interactive deck. build.mjs
* copies this app per md-only spec and drops a spec.json beside it; the raw
* `.md` files sit in the same directory, so everything is fetched relative.
*/
interface SpecManifest {
slug: string
title: string
tagline: string
docs: { file: string; label: string }[]
}
type LoadState =
| { kind: 'loading' }
| { kind: 'error' }
| { kind: 'ready'; manifest: SpecManifest; docs: Record<string, string> }
export function ViewerApp() {
const route = useHashRoute()
const [state, setState] = useState<LoadState>({ kind: 'loading' })
useEffect(() => {
let cancelled = false
const load = async () => {
try {
const res = await fetch('./spec.json')
if (!res.ok) throw new Error(String(res.status))
const manifest = (await res.json()) as SpecManifest
const entries = await Promise.all(
manifest.docs.map(async ({ file }) => {
const md = await fetch(`./${file}`)
if (!md.ok) throw new Error(`${file}: ${md.status}`)
return [file, await md.text()] as const
}),
)
if (cancelled) return
document.title = `iii — ${manifest.title}`
setState({ kind: 'ready', manifest, docs: Object.fromEntries(entries) })
} catch {
if (!cancelled) setState({ kind: 'error' })
}
}
void load()
return () => {
cancelled = true
}
}, [])
return (
<div className="@container min-h-screen">
<Sheet>
<TopNav
route={route}
meta={{
wordmarkLabel: state.kind === 'ready' ? state.manifest.title : 'iii / tech-specs',
}}
nav={[]}
specHref={null}
/>
{state.kind === 'loading' ? (
<main className="px-4 py-24 @3xl:px-9">
<p className="font-mono text-[14px] lowercase text-ink-faint">loading spec…</p>
</main>
) : state.kind === 'error' ? (
<main className="px-4 py-24 @3xl:px-9">
<p className="font-mono text-[14px] lowercase text-ink-faint">
this spec could not be loaded.{' '}
<a href="../" className="text-ink hover:text-accent">
back to all tech specs
</a>
</p>
</main>
) : (
<>
<section className="border-b border-rule px-4 py-10 @3xl:px-9 @3xl:py-12">
<h1 className="font-mono text-[26px] @3xl:text-[36px] font-semibold lowercase leading-[1.1] tracking-[-0.02em] text-ink max-w-[26ch]">
{state.manifest.title}
</h1>
{state.manifest.tagline ? (
<p className="mt-4 font-mono text-[13.5px] leading-[1.7] text-ink-faint max-w-[70ch]">
{state.manifest.tagline}
</p>
) : null}
<p className="mt-4 font-mono text-[11px] uppercase tracking-[0.08em] text-ink-ghost">
spec — markdown edition ·{' '}
<a href="../" className="normal-case lowercase text-ink-faint hover:text-ink">
all tech specs →
</a>
</p>
</section>
<SpecPage docs={state.docs} />
</>
)}
</Sheet>
</div>
)
}
base/.gitignore
node_modules/
dist/
.vite/
*.tsbuildinfo
.DS_Store
base/build.mjs
#!/usr/bin/env node
/**
* build.mjs — assembles the whole tech-specs site into dist/.
*
* Output layout (deployed to iii.dev under /roadmap/ by
* .github/workflows/deploy-website.yml):
*
* dist/index.html the gallery (index of every spec)
* dist/index.json machine-readable spec list (landing-page timeline)
* dist/<slug>/index.html one page per spec: the interactive deck when
* website/roadmap/<slug>/ exists, otherwise
* the generic markdown spec viewer
* dist/<slug>/<file>.md raw spec markdown, directly linkable
*
* Contracts enforced here (fail fast, named paths):
* - every deck dir pairs with a tech-specs/<slug>/ spec dir (no orphans)
* - deck index.html entries are relative (./src/main.tsx) so the single dev
* server and the /roadmap/ prefix both work
* - every shared src component has a COMPONENTS.md entry (warn; hard fail
* with --strict-registry)
*
* Usage:
* node build.mjs build everything
* node build.mjs --only=<slug> gallery + one spec (fast rebuild)
* node build.mjs --strict-registry registry parity warnings become errors
*/
import {
cpSync,
existsSync,
mkdirSync,
readdirSync,
readFileSync,
rmSync,
statSync,
writeFileSync,
} from 'node:fs'
import { join } from 'node:path'
import { build } from 'vite'
import { listSpecDocs, readSpecs, ROOT, SPECS_DIR } from './scripts/manifest.mjs'
const DIST = join(ROOT, 'dist')
const VIEWER_TMP = join(DIST, '_viewer-tmp')
// top-level dirs of this project that are never a deck
const NOT_A_DECK = new Set(['src', 'scripts', 'dist', 'node_modules', '_viewer'])
const args = process.argv.slice(2)
const onlyEq = args.find((a) => a.startsWith('--only='))?.slice('--only='.length)
const onlyPos = args.includes('--only') ? args[args.indexOf('--only') + 1] : undefined
const ONLY = onlyEq ?? (onlyPos && !onlyPos.startsWith('--') ? onlyPos : undefined)
const STRICT_REGISTRY = args.includes('--strict-registry')
/** @param {string} root @param {string} outDir @param {string} label */
async function buildApp(root, outDir, label) {
console.log(`\n▸ ${label}`)
await build({
configFile: join(ROOT, 'vite.config.ts'),
root,
logLevel: 'warn',
build: { outDir, emptyOutDir: false },
})
}
/** deck dirs present in this project (top-level, contain an index.html) */
function discoverDeckDirs() {
return readdirSync(ROOT, { withFileTypes: true })
.filter((e) => e.isDirectory() && !NOT_A_DECK.has(e.name) && !e.name.startsWith('.'))
.map((e) => e.name)
.filter((name) => existsSync(join(ROOT, name, 'index.html')))
.sort()
}
/** every shared src file must have a `### <basename>` entry in COMPONENTS.md */
function checkRegistry() {
const registryPath = join(ROOT, 'COMPONENTS.md')
const registry = existsSync(registryPath) ? readFileSync(registryPath, 'utf8') : ''
const entries = new Set([...registry.matchAll(/^### (\S+)$/gm)].map((m) => m[1]))
const skip = new Set(['main.tsx', 'App.tsx', 'vite-env.d.ts', 'index.css'])
/** @type {string[]} */
const problems = []
const files = []
const walk = (/** @type {string} */ dir) => {
for (const entry of readdirSync(dir, { withFileTypes: true })) {
const p = join(dir, entry.name)
if (entry.isDirectory()) walk(p)
else if (/\.(tsx?|mts)$/.test(entry.name) && !skip.has(entry.name)) files.push(p)
}
}
for (const sub of ['components', 'hooks', 'content', 'pages', 'lib', 'gallery']) {
const dir = join(ROOT, 'src', sub)
if (existsSync(dir)) walk(dir)
}
const names = files.map((f) => f.split('/').pop()?.replace(/\.(tsx?|mts)$/, '') ?? '')
for (let i = 0; i < names.length; i++) {
if (!entries.has(names[i])) {
problems.push(`unregistered component: ${files[i]} — add a COMPONENTS.md entry`)
}
}
for (const entry of entries) {
if (!names.includes(entry)) problems.push(`stale registry entry: ${entry} — no matching file`)
}
return problems
}
async function main() {
const started = Date.now()
// 1. discover + validate specs (throws on hard frontmatter violations)
const { specs, warnings } = readSpecs()
for (const w of warnings) console.warn(`⚠ ${w}`)
if (ONLY && !specs.some((s) => s.slug === ONLY)) {
throw new Error(`--only=${ONLY}: no such spec in ${SPECS_DIR}`)
}
// 2. pairing + entry contracts
const deckDirs = discoverDeckDirs()
for (const deck of deckDirs) {
if (!existsSync(join(SPECS_DIR, deck))) {
throw new Error(
`orphan deck: ${join(ROOT, deck)} has no spec at ${join(SPECS_DIR, deck)} — ` +
'the deck dir must be named after its spec dir',
)
}
const html = readFileSync(join(ROOT, deck, 'index.html'), 'utf8')
if (!html.includes('./src/main.tsx')) {
throw new Error(
`${deck}/index.html must reference its entry as "./src/main.tsx" (relative) — ` +
'a root-relative "/src/..." breaks the shared dev server and subpath hosting',
)
}
}
// 3. registry parity
const registryProblems = checkRegistry()
for (const p of registryProblems) console.warn(`⚠ ${p}`)
if (STRICT_REGISTRY && registryProblems.length > 0) {
throw new Error(`--strict-registry: ${registryProblems.length} registry problem(s)`)
}
// 4. build the gallery (project-root index.html → dist/)
if (!ONLY) rmSync(DIST, { recursive: true, force: true })
mkdirSync(DIST, { recursive: true })
await buildApp(ROOT, DIST, 'gallery (site root)')
// 5. build the generic viewer once, if any target spec needs it
const targets = specs.filter((s) => !ONLY || s.slug === ONLY)
const needsViewer = targets.some((s) => !s.hasDeck)
if (needsViewer) {
await buildApp(join(ROOT, '_viewer'), VIEWER_TMP, 'spec viewer (md-only specs)')
}
// 6. one page per spec + raw markdown
for (const spec of targets) {
const out = join(DIST, spec.slug)
if (ONLY) rmSync(out, { recursive: true, force: true })
if (spec.hasDeck) {
await buildApp(join(ROOT, spec.slug), out, `deck: ${spec.slug} → /${spec.slug}/`)
} else {
console.log(`\n▸ viewer: ${spec.slug} → /${spec.slug}/`)
cpSync(VIEWER_TMP, out, { recursive: true })
writeFileSync(
join(out, 'spec.json'),
`${JSON.stringify(
{ slug: spec.slug, title: spec.title, tagline: spec.tagline, docs: listSpecDocs(spec.slug) },
null,
2,
)}\n`,
)
}
for (const f of readdirSync(join(SPECS_DIR, spec.slug))) {
if (f.endsWith('.md')) cpSync(join(SPECS_DIR, spec.slug, f), join(out, f))
}
}
rmSync(VIEWER_TMP, { recursive: true, force: true })
// 7. dist/index.json — the landing-page feed. Drafts are deliberately
// excluded here (and from the sitemap); they still build and show muted in
// the gallery.
const feed = {
generatedAt: new Date().toISOString(),
specs: specs
.filter((s) => s.status !== 'draft')
.map(({ slug, title, tagline, date, month, dayLabel, tags, status, hasDeck }) => ({
slug,
title,
tagline,
date,
month,
dayLabel,
tags,
status,
hasDeck,
url: `/roadmap/${slug}/`,
})),
}
writeFileSync(join(DIST, 'index.json'), `${JSON.stringify(feed, null, 2)}\n`)
// 8. post-build contract checks
if (!statSync(join(DIST, 'index.html')).size) throw new Error('gallery emitted empty index.html')
for (const spec of specs) {
const page = join(DIST, spec.slug, 'index.html')
if (!ONLY || spec.slug === ONLY) {
if (!existsSync(page)) throw new Error(`missing ${page} after build`)
}
}
JSON.parse(readFileSync(join(DIST, 'index.json'), 'utf8'))
const secs = ((Date.now() - started) / 1000).toFixed(1)
const built = targets.map((s) => `${s.slug}${s.hasDeck ? '' : ' (viewer)'}`).join(', ')
console.log(`\n✓ dist/ in ${secs}s — gallery + ${targets.length} spec(s): ${built || '(none)'}`)
}
main().catch((err) => {
console.error(`\n✗ build failed: ${err instanceof Error ? err.message : err}`)
process.exit(1)
})
base/COMPONENTS.md
# component registry
every shared file under `src/` has exactly one entry here (heading = file
basename). before building any visual for a deck, read this file — **reuse
first**. to add a component, meet the checklist in the presentation skill's
`reference/component-standards.md`, then append an entry in alphabetical order
within its kind. `node build.mjs` warns when a src file has no entry (or an
entry has no file); `--strict-registry` turns the warning into a failure.
kinds, in section order: **layout · primitive · archetype · hook · util · gallery**
## layout
### Footer
- kind: layout
- import: `import { Footer } from '@lib/components/Footer'`
- purpose: deck footer — eyebrow, big closing line, command chip, attribution bar
- props: `{ footer: FooterSpec }` (from the deck's `content/deck.ts`)
- use when: every deck; wired once in App.tsx
- used by: all decks
### PageShell
- kind: layout
- import: `import { PageShell } from '@lib/components/PageShell'`
- purpose: deep-dive page wrapper — eyebrow, title, description, prose column
- props: `{ eyebrow: string; title: string; description: ReactNode; children }`
- use when: any `#/<slug>` deep-dive page
- used by: 2026-06-29-codegen, 2026-06-22-rbac-proxy-worker
### PlayerControls
- kind: layout
- import: `import { PlayerControls } from '@lib/components/PlayerControls'`
- purpose: stepper transport bar (prev/next/play) under steppable diagrams
- props: `{ stepper: Stepper; total: number; label?: string; className?: string }`
- use when: any diagram driven by `useStepper`
- used by: all decks (via archetypes)
### Section
- kind: layout
- import: `import { Section } from '@lib/components/Section'`
- purpose: numbered home-page section shell with reveal-on-scroll
- props: `{ id: string; index: string; eyebrow: string; title: ReactNode; lede?: ReactNode; children }`
- use when: every home-page slide; `id` must match a NAV entry
- used by: all decks
### SpecSheet
- kind: layout
- import: `import { SpecSheet, SpecRow } from '@lib/components/SpecSheet'`
- purpose: closed-by-default `<details>` depth layer — execs skim, engineers drill
- props: `{ title: ReactNode; meta?: ReactNode; defaultOpen?: boolean; children }`; `SpecRow { name: string; children }`
- use when: schema/field/config detail behind a slide's single claim
- used by: all decks
### TopNav
- kind: layout
- import: `import { TopNav } from '@lib/components/TopNav'`
- purpose: sticky top nav — wordmark, scroll-spy section links, spec link, theme toggle
- props: `{ route: Route; meta: DeckMeta; nav: NavItem[]; specHref?: string | null }`
- use when: every deck; wired once in App.tsx (viewer passes `specHref={null}`)
- used by: all decks, _viewer
## primitive
### Button
- kind: primitive
- import: `import { Button } from '@lib/components/schematic/Button'`
- purpose: border-driven button, no rounded corners; primary = the one filled accent
- props: `{ variant?: ButtonVariant; size?: ButtonSize }` + anchor/button attrs
- use when: CTAs; ration primary to one per screen
- used by: all decks
### Caret
- kind: primitive
- import: `import { Caret } from '@lib/components/schematic/Caret'`
- purpose: blinking terminal cursor
- props: `{ className?: string }`
- use when: implying a live prompt in terminal chrome
- used by: all decks
### Cell
- kind: primitive
- import: `import { Cell } from '@lib/components/schematic/Cell'`
- purpose: bordered grid cell for `gap-px bg-rule` mosaics
- props: `{ title?: ReactNode; children; className?; bodyClassName? }`
- use when: problem grids, capability mosaics, failure cards
- used by: all decks
### CodeBlock
- kind: primitive
- import: `import { CodeBlock, K, S, C, M } from '@lib/components/schematic/CodeBlock'`
- purpose: titled code block; inline `<K>/<S>/<C>/<M>` tags color keywords/strings/comments/muted
- props: `{ title?: ReactNode; children }`
- use when: config/pseudocode walkthroughs authored as JSX
- used by: all decks
### FnChip
- kind: primitive
- import: `import { FnChip } from '@lib/components/schematic/FnChip'`
- purpose: monospace identifier chip (function ids keep their casing)
- props: `{ tone?: ChipTone; children }`
- use when: naming `worker::function` ids inline or in grids
- used by: all decks
### ModeToggle
- kind: primitive
- import: `import { ModeToggle } from '@lib/components/schematic/ModeToggle'`
- purpose: bordered segmented toggle (theme, policy modes, language tracks)
- props: `{ value: T; onChange: (next: T) => void; options: ModeToggleOption<T>[]; className? }`
- use when: switching between 2–4 named modes; active = accent border + text
- used by: all decks, gallery
### Prompt
- kind: primitive
- import: `import { Prompt } from '@lib/components/schematic/Prompt'`
- purpose: terminal prompt symbol prefix (`$`, `//`)
- props: `{ symbol?: string; className?: string; children? }`
- use when: eyebrows and command lines
- used by: all decks, gallery
### Sheet
- kind: primitive
- import: `import { Sheet } from '@lib/components/schematic/Sheet'`
- purpose: the centered max-w-[1200px] drafting sheet with left/right rules
- props: `{ children; className? }`
- use when: the root shell of every page; use container queries inside, not viewport
- used by: all decks, gallery, _viewer
### StatusDot
- kind: primitive
- import: `import { StatusDot } from '@lib/components/schematic/StatusDot'`
- purpose: status dot, optional pulse animation
- props: `{ tone?: DotTone; pulse?: boolean }`
- use when: live/running/draft indicators
- used by: all decks, gallery
### StatusPanel
- kind: primitive
- import: `import { StatusPanel } from '@lib/components/schematic/StatusPanel'`
- purpose: bordered status callout (alert/accent tones) with headline + detail
- props: `{ variant?: StatusVariant; icon?: ReactNode; headline: ReactNode; detail?: ReactNode; className? }`
- use when: failure modes, guarantees, fail-closed statements
- used by: all decks
### Terminal
- kind: primitive
- import: `import { Terminal, TerminalRow } from '@lib/components/schematic/Terminal'`
- purpose: terminal window with staggered `fade-rise` output lines
- props: `{ title?: ReactNode; children; className? }`; `TerminalRow { command: ReactNode; output?: ReactNode; showCaret?: boolean }`
- use when: install/run sequences; prefer CliPlayground for multi-track playback
- used by: all decks
### Wordmark
- kind: primitive
- import: `import { Wordmark } from '@lib/components/schematic/Wordmark'`
- purpose: the iii brand mark
- props: `{ className?: string }`
- use when: nav + footer chrome only
- used by: all decks, gallery, _viewer
### WorkerCard
- kind: primitive
- import: `import { WorkerCard } from '@lib/components/schematic/WorkerCard'`
- purpose: one worker as a product card — name, version, description, install command, kind
- props: `{ name: string; version?: string; description: ReactNode; command: ReactNode; kind: string; focused?: boolean; className? }`
- use when: cataloguing installable workers/components
- used by: (available — promoted from the agentic deck's system)
## archetype
### CliPlayground
- kind: archetype
- import: `import { CliPlayground } from '@lib/components/diagrams/CliPlayground'`
- purpose: A3 — terminal playback with switchable tracks, staggered fade-rise lines
- props: `{ tracks: CliTrack[]; title?: string; intervalMs?: number; className? }`
- use when: the spec's win is a command-line flow with variants (langs, modes)
- used by: 2026-06-29-codegen, 2026-06-22-rbac-proxy-worker
### DurabilityTimeline
- kind: archetype
- import: `import { DurabilityTimeline } from '@lib/components/diagrams/DurabilityTimeline'`
- purpose: A16 — steppable lifecycle timeline where each stage narrates an evolving state record
- props: `{ stages: TimelineStage[]; heading: string; headingNote?: string; recordHeading: string; intervalMs?; className? }`
- use when: a durable thing survives crashes/waits and you can show its record at each stage
- not when: state just accumulates with no record panel (use StepReveal)
- used by: 2026-06-08-agentic
### EventFanOut
- kind: archetype
- import: `import { EventFanOut } from '@lib/components/diagrams/EventFanOut'`
- purpose: A17 — one concrete write → trigger type → N named subscribers, ambient animation
- props: `{ heading; source; sourceSub?; trigger; handlers: FanOutHandler[]; edges: FanOutEdge[]; footnote?; ariaLabel; … }`
- use when: narrating a specific reactive write with named handlers, always-on
- not when: you need an abstract many-to-one/one-to-many with steppable ripples (use FanOut)
- used by: 2026-06-08-agentic
### FanOut
- kind: archetype
- import: `import { FanOut } from '@lib/components/diagrams/FanOut'`
- purpose: A7 — one write fans to many handlers, ripple + marching edges
- props: `{ source: {label, sub?}; trigger: string; handlers: FanHandler[]; … }`
- use when: the reactive surface is the claim (subscribe once, everything updates)
- used by: 2026-06-29-codegen, 2026-06-22-rbac-proxy-worker
### Funnel
- kind: archetype
- import: `import { Funnel } from '@lib/components/diagrams/Funnel'`
- purpose: A8 — many paths converge on one target (optional rejected path)
- props: `{ title?: string; paths: FunnelPath[]; target: {label, sub?} }`
- use when: consolidation claims — N ways in, one enforced way through
- used by: 2026-06-29-codegen, 2026-06-22-rbac-proxy-worker
### SequencePlayer
- kind: archetype
- import: `import { SequencePlayer } from '@lib/components/diagrams/SequencePlayer'`
- purpose: A5 — step-through sequence diagram: lifelines per lane, one arrow per step, narration
- props: `{ title: string; lanes: SeqLane[]; steps: SeqStep[]; width?; intervalMs?; className? }`
- use when: a temporal protocol, turn loop, handshake, numbered steps
- used by: 2026-06-08-agentic, 2026-06-29-codegen, 2026-06-22-rbac-proxy-worker
### SpawnTree
- kind: archetype
- import: `import { SpawnTree } from '@lib/components/diagrams/SpawnTree'`
- purpose: A18 — parent spawns N parallel children, joins their results; steppable narrative states
- props: `{ heading; chips?; parentTitle; parentLabels; parentCallLine; nodes: SpawnTreeChild[]; states: SpawnTreeState[]; ariaLabel; … }`
- use when: fan-out/join concurrency with a parked parent is the claim
- used by: 2026-06-08-agentic
### StepReveal
- kind: archetype
- import: `import { StepReveal } from '@lib/components/diagrams/StepReveal'`
- purpose: A6 — lifecycle stepper with an evolving state panel
- props: `{ title: string; stages: RevealStage[]; intervalMs?; className? }`
- use when: a linear lifecycle where each stage adds/changes state
- not when: you need the record-beside-narration density of DurabilityTimeline
- used by: 2026-06-29-codegen, 2026-06-22-rbac-proxy-worker
### SystemMap
- kind: archetype
- import: `import { SystemMap, MapDatasheet } from '@lib/components/diagrams/SystemMap'`
- purpose: A4 — interactive node/edge architecture map; click a node → datasheet
- props: `{ nodes: MapNode[]; edges: MapEdge[]; selected: string; onSelect: (id) => void; width?; }`; `MapDatasheet { info: MapNodeInfo }`
- use when: the architecture overview slide (nearly every deck's opener)
- used by: 2026-06-29-codegen, 2026-06-22-rbac-proxy-worker (agentic ships a deck-local fork)
## hook
### useHashRoute
- kind: hook
- import: `import { useHashRoute, type Route } from '@lib/hooks/useHashRoute'`
- purpose: hash routing — `#/` home with scroll anchors, `#/<slug>[/rest]` pages
- props: returns `Route = { kind: 'home' } | { kind: 'page'; slug: string; rest: string[] }`
- use when: every deck App.tsx; SpecPage reads `rest[0]` for `#/spec/<file>`
- used by: all decks, _viewer
### useStepper
- kind: hook
- import: `import { useStepper, type Stepper } from '@lib/hooks/useStepper'`
- purpose: autoplaying step state for diagram archetypes (goTo/next/prev/pause)
- props: `useStepper(total: number, intervalMs: number)`
- use when: any steppable diagram; pair with PlayerControls
- used by: all decks (via archetypes)
### useTheme
- kind: hook
- import: `import { useTheme } from '@lib/hooks/useTheme'`
- purpose: light/dark theme state persisted to localStorage, sets `data-theme`
- props: returns `[theme, setTheme]`
- use when: chrome with a theme toggle (TopNav/SiteHeader already wire it)
- used by: all decks, gallery, _viewer
## util
### deck-types
- kind: util
- import: `import type { DeckMeta, FooterSpec, NavItem } from '@lib/lib/deck-types'`
- purpose: the shapes a deck's `content/deck.ts` feeds the shared chrome
- props: types only
- use when: typing a deck's DECK_META / NAV / FOOTER
- used by: all decks
### highlight
- kind: util
- import: `import { Highlight, HighlightStyles, type HlLang } from '@lib/content/highlight'`
- purpose: syntax highlighter (ts/js/rust/python/yaml; monochrome fallback)
- props: `Highlight { code: string; lang: HlLang }`; mount `HighlightStyles` once per page
- use when: highlighted code from string data (CodeBlock covers JSX-authored code)
- used by: all decks (markdown), 2026-06-29-codegen
### markdown
- kind: util
- import: `import { Markdown } from '@lib/content/markdown'`
- purpose: trusted spec markdown → React in the drafting-sheet system; strips leading frontmatter; ```mermaid fences render live
- props: `{ source: string }`
- use when: rendering spec md (SpecPage/viewer do this for you)
- used by: SpecPage, _viewer
### mermaid
- kind: util
- import: `import { Mermaid } from '@lib/content/mermaid'`
- purpose: theme-aware live mermaid diagram (lazy-loads mermaid on first render)
- props: `{ chart: string }`
- use when: mermaid fences in spec md (wired via markdown)
- used by: markdown
### SpecPage
- kind: util
- import: `import { SpecPage } from '@lib/pages/SpecPage'`
- purpose: A15 — the `#/spec` page: file sidebar + rendered spec markdown
- props: `{ docs: Record<string, string> }` — pass the deck's `spec-docs.ts` glob
- use when: every deck (`spec` entry in PAGES); the md-only viewer reuses it
- used by: all decks, _viewer
### utils
- kind: util
- import: `import { cn } from '@lib/lib/utils'`
- purpose: `cn()` — clsx + tailwind-merge class combiner
- props: `cn(...inputs)`
- use when: any conditional className
- used by: everything
## gallery
### Gallery
- kind: gallery
- import: `import { Gallery } from '@lib/gallery/Gallery'` (gallery app only)
- purpose: the roadmap timeline — one column, newest first, month markers on a vertical rule, rendered from `virtual:spec-manifest`
- props: none (reads SPECS)
- use when: gallery app only
- used by: gallery
### PresentationCard
- kind: gallery
- import: `import { PresentationCard } from '@lib/gallery/PresentationCard'`
- purpose: one spec card — number, deck/spec badge, title, tagline, tags, open → (the date lives in the timeline gutter)
- props: `{ spec: SpecEntry; index: number }`
- use when: gallery app only
- used by: gallery
### site
- kind: gallery
- import: `import { SITE } from '@lib/gallery/site'`
- purpose: the gallery's repo identity (wordmark label, hero copy, attribution) — set once
- props: data only
- use when: gallery chrome; never per-spec data
- used by: gallery
### SiteFooter
- kind: gallery
- import: `import { SiteFooter } from '@lib/gallery/SiteFooter'`
- purpose: gallery footer bar (attribution + source of truth)
- props: none
- use when: gallery app only
- used by: gallery
### SiteHeader
- kind: gallery
- import: `import { SiteHeader } from '@lib/gallery/SiteHeader'`
- purpose: gallery sticky header — wordmark, label, theme toggle
- props: none
- use when: gallery app only
- used by: gallery
base/index.html
<!doctype html>
<html lang="en" class="antialiased">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>__GALLERY_TITLE__</title>
<meta
name="description"
content="__GALLERY_DESCRIPTION__"
/>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Chivo+Mono:wght@400;500;600&display=swap"
/>
<script>
try {
const t = localStorage.getItem('iii-theme');
document.documentElement.dataset.theme =
t === 'dark' || t === 'light' ? t : 'light';
} catch (_) {
document.documentElement.dataset.theme = 'light';
}
</script>
</head>
<body class="bg-bg text-ink font-sans">
<div id="root"></div>
<script type="module" src="./src/gallery/main.tsx"></script>
</body>
</html>
base/package.json
{
"name": "__REPO__-presentations",
"private": true,
"version": "0.1.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "node build.mjs",
"build:only": "node build.mjs --only",
"preview": "vite preview --outDir dist",
"type-check": "tsc -b --noEmit"
},
"dependencies": {
"clsx": "^2.1.1",
"marked": "^18.0.5",
"mermaid": "^11.16.0",
"react": "^19.2.6",
"react-dom": "^19.2.6",
"tailwind-merge": "^3.6.0"
},
"devDependencies": {
"@tailwindcss/vite": "^4.3.0",
"@types/node": "^25.8.0",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.2",
"tailwindcss": "^4.3.0",
"typescript": "^6.0.3",
"vite": "^8.0.13"
}
}
base/README.md
# tech-spec presentations — one site
One Vite project that builds every tech spec into **__SITE_HOST__/roadmap/** — a
roadmap at `/` (a one-column timeline, newest spec first, grouped by month),
one page per spec at `/<slug>/` (an interactive deck when one exists, a
rendered markdown viewer when only the spec does), the raw `.md` linkable
beside it, and `index.json` feeding the __SITE_HOST__ landing timeline.
## the two trees
```
tech-specs/<slug>/*.md the spec — MARKDOWN ONLY, frontmatter in README.md
website/roadmap/<slug>/ the deck's content layer (optional, pairs by dirname)
website/roadmap/src/ the shared design system + component library
```
The slug is the directory basename, used identically in three places: the spec
dir, the deck dir, and the URL `/roadmap/<slug>/`. Never prettify it; never
rename after publishing.
## the conflict contract (read this first)
```
SHARED / HOT — never edited when adding a spec or deck:
src/** build.mjs vite.config.ts tsconfig* package.json index.html _viewer/**
(COMPONENTS.md: append-only, component promotions only)
PER-SPEC — the only things a spec/deck PR touches:
tech-specs/<slug>/** website/roadmap/<slug>/**
```
Corollary: two spec PRs never conflict. A component promotion is the deliberate
exception (procedure c).
## a. add a tech spec
Via `/tech-spec`, or by hand:
1. `mkdir tech-specs/YYYY-MM-DD-<slug>/` — the dirname IS the url slug; the
day prefix is what orders and labels the roadmap timeline.
2. Write `README.md` (the index/overview) + one `.md` per domain topic. Markdown only.
3. Top of `README.md`, the frontmatter block:
```yaml
---
title: the developer experience overhaul # fallback: first H1
tagline: one file, one command, zero zombies. # fallback: first paragraph
date: 2026-06-21 # YYYY-MM-DD; fallback: dirname
# prefix. day precision drives
# the roadmap order + labels
tags: [dx, cli] # ≤ 4
status: live # or draft (muted card, kept
# out of index.json + sitemap)
featured: false # pin in the landing feed
# (index.json); the roadmap
# itself stays chronological
---
```
`slug` is never a frontmatter field — the build hard-errors if present.
4. Merge. The site now serves `/roadmap/<slug>/` as a rendered spec viewer,
the gallery gets a card, and the landing timeline picks it up. A deck can
come later — or never.
## b. add a presentation to a spec
Run `/presentation tech-specs/<slug>` — it proposes a narrative outline (gate 1:
your approval), scaffolds `website/roadmap/<slug>/` (content layer only:
`index.html` + `src/{App,sections,pages,content,spec-docs}` — no package.json,
no config), reuses the shared library via `@lib`, updates the spec's
frontmatter, and verifies (gate 2: typecheck + build + browser pass).
It may touch ONLY `website/roadmap/<slug>/**`, the spec README's
frontmatter block, and (rarely) an additive component promotion per (c).
The one fragile cross-tree coupling: `src/spec-docs.ts` bundles the spec md via
`import.meta.glob('../../../../tech-specs/<slug>/*.md', …)` — the depth encodes
the two-tree layout.
## c. add or promote a shared component
Default is **deck-local**: `website/roadmap/<slug>/src/diagrams/<Name>.tsx`.
Promote into `src/components/` only when all three hold: props-driven with zero
spec data inside; maps to a recurring spec shape (a lifecycle, a tree, a
timeline, a fan-out…); passes the standards checklist (design tokens only,
reduced-motion gate, keyboard operable, aria-label, container queries,
overflow-x-auto + min-w, no shadows/gradients, documented props). A promotion
MUST add its [COMPONENTS.md](./COMPONENTS.md) entry in the same change —
`node build.mjs` warns on unregistered files, `--strict-registry` fails.
Never fork a shared component into a deck to tweak it — extend it with additive
props, or build a genuinely different deck-local one. Changing an EXISTING
shared component re-renders every deck: that is a design-system PR (full
`node build.mjs` verify), not a spec PR.
## d. local dev
```
pnpm install # repo root (workspace)
pnpm dev # ONE server: gallery at /, decks at /<slug>/
pnpm type-check # strict, shared lib + every deck
node build.mjs --only=<slug> # fast: gallery + one spec
pnpm build && pnpm preview # the full site at :4173
```
## e. ship
Open a PR — expected surface is the two per-spec paths only (reviewers should
bounce anything else). On merge to main, `your static-site CI`
builds this project and syncs `dist/` to S3 under `/roadmap/` + invalidates
CloudFront. No Vercel, no manual deploy, no per-deck pipeline.
URLs: `__SITE_HOST__/roadmap/` (gallery) · `__SITE_HOST__/roadmap/<slug>/` (deck or
viewer) · `…/<slug>/#/spec` (a deck's reading mode) · `…/<slug>/<file>.md`
(raw markdown) · `__SITE_HOST__/roadmap/index.json` (machine-readable list).
## f. troubleshooting
- card 404s → deck dirname ≠ spec dirname (the pairing contract)
- frontmatter warnings → schema table in (a); the build prints the exact rule
- `unregistered component` → procedure (c)
- orphan deck error → the spec dir moved/renamed underneath its deck
base/scripts/manifest.mjs
/**
* manifest.mjs — spec discovery + frontmatter parsing.
*
* The single source of truth for "which tech specs exist and what are they
* called". Consumed by:
* - build.mjs (dist/index.json, viewer emission, contract checks)
* - vite.config.ts (the `virtual:spec-manifest` module the gallery renders)
*
* Specs live at <repo>/tech-specs/<slug>/ and are MARKDOWN ONLY. Metadata is
* YAML frontmatter at the top of each spec's README.md (title, tagline, date,
* tags, status, featured) with derived fallbacks: date ← the dirname's
* YYYY-MM-DD (or legacy YYYY-MM) prefix, title ← the README's first H1.
* `slug` is NEVER a frontmatter field — the directory name is the identity
* (folder = URL = deck dir), which kills the old manifest-vs-dirname drift
* bug class by construction.
*
* Dates carry day precision so the gallery can render the roadmap timeline:
* `month` groups entries ("2026 · june"), `dayLabel` marks each one ("jun 29",
* null when a spec only has a month).
*
* Dependency-free by design (same approach as website/scripts/blog-posts.ts):
* a minimal parser sized to our six fields instead of a YAML library.
*/
import { existsSync, readdirSync, readFileSync } from 'node:fs'
import { dirname, join, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
/** the roadmap project root (this file lives in <root>/scripts/) */
export const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
/** the markdown-only spec tree, a sibling of website/ at the repo root */
export const SPECS_DIR = resolve(ROOT, '..', '..', 'tech-specs')
const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---/
const KNOWN_KEYS = new Set(['title', 'tagline', 'date', 'tags', 'status', 'featured'])
const MONTH_NAMES = [
'january',
'february',
'march',
'april',
'may',
'june',
'july',
'august',
'september',
'october',
'november',
'december',
]
/** @param {string} raw */
export function parseFrontmatter(raw) {
const match = raw.match(FRONTMATTER_RE)
/** @type {Record<string, string | string[] | boolean>} */
const fields = {}
/** @type {string[]} */
const unknown = []
if (!match) return { fields, unknown, present: false }
for (const line of match[1].split(/\r?\n/)) {
const m = line.match(/^([A-Za-z_][\w-]*)\s*:\s*(.*?)\s*$/)
if (!m) continue
const key = m[1]
let value = m[2]
if (!KNOWN_KEYS.has(key)) {
unknown.push(key)
continue
}
if (key === 'tags') {
const inner = value.replace(/^\[/, '').replace(/\]$/, '')
fields.tags = inner
.split(',')
.map((t) => t.trim().replace(/^['"]|['"]$/g, ''))
.filter(Boolean)
continue
}
if (key === 'featured') {
fields.featured = value === 'true'
continue
}
if (
(value.startsWith("'") && value.endsWith("'")) ||
(value.startsWith('"') && value.endsWith('"'))
) {
value = value.slice(1, -1)
}
fields[key] = value
}
// `slug` in frontmatter is the drift bug the dirname-identity rule exists to
// prevent — surface it loudly rather than silently ignoring it.
if (/^slug\s*:/m.test(match[1])) {
throw new Error(
'frontmatter declares `slug` — the directory name IS the slug; remove the field',
)
}
return { fields, unknown, present: true }
}
/** @param {string} md */
export function stripFrontmatter(md) {
return md.replace(FRONTMATTER_RE, '').replace(/^\s+/, '')
}
/** @param {string} md */
export function firstHeading(md) {
const m = stripFrontmatter(md).match(/^#\s+(.+)$/m)
return m ? m[1].trim() : null
}
/** @param {string} md */
function firstParagraph(md) {
for (const block of stripFrontmatter(md).split(/\r?\n\r?\n/)) {
const line = block.trim().replace(/\s+/g, ' ')
if (!line || line.startsWith('#') || line.startsWith('```') || line.startsWith('|')) continue
return line
}
return null
}
/** "2026-06-29" or "2026-06" → "2026 · june" (the timeline's month group) */
export function monthLabel(date) {
const m = date?.match(/^(\d{4})-(\d{2})(?:-\d{2})?$/)
if (!m) return date ?? ''
const name = MONTH_NAMES[Number(m[2]) - 1]
return name ? `${m[1]} · ${name}` : date
}
/** "2026-06-29" → "jun 29"; null when the date has no day component */
export function dayLabel(date) {
const m = date?.match(/^\d{4}-(\d{2})-(\d{2})$/)
if (!m) return null
const name = MONTH_NAMES[Number(m[1]) - 1]
return name ? `${name.slice(0, 3)} ${Number(m[2])}` : null
}
/**
* Non-README markdown files of a spec, viewer/spec-page ordering: README
* first, then alphabetical; review notes (`*-review-*`) and underscore-prefixed
* scratch files excluded — same filter the deck SpecPage applies.
* @param {string} slug
*/
export function listSpecDocs(slug) {
const dir = join(SPECS_DIR, slug)
const files = readdirSync(dir)
.filter((f) => f.endsWith('.md'))
.filter((f) => !/-review-/.test(f) && !f.startsWith('_'))
.sort((a, b) => (a === 'README.md' ? -1 : b === 'README.md' ? 1 : a.localeCompare(b)))
return files.map((file) => {
const raw = readFileSync(join(dir, file), 'utf8')
return { file, label: firstHeading(raw) ?? file.replace(/\.md$/, '') }
})
}
/**
* Read every spec in tech-specs/: parse + validate frontmatter, apply
* fallbacks, detect decks, and sort gallery-style (featured first, then date
* desc, then title). Returns { specs, warnings }; throws on hard contract
* violations (a `slug` frontmatter field).
*/
export function readSpecs() {
/** @type {string[]} */
const warnings = []
const specs = []
if (!existsSync(SPECS_DIR)) return { specs, warnings: [`no spec tree at ${SPECS_DIR}`] }
for (const entry of readdirSync(SPECS_DIR, { withFileTypes: true })) {
if (!entry.isDirectory() || entry.name.startsWith('.') || entry.name.startsWith('_')) continue
const slug = entry.name
const readmePath = join(SPECS_DIR, slug, 'README.md')
if (!existsSync(readmePath)) {
warnings.push(`${slug}: no README.md — spec not listed`)
continue
}
const raw = readFileSync(readmePath, 'utf8')
let parsed
try {
parsed = parseFrontmatter(raw)
} catch (err) {
throw new Error(`${slug}/README.md: ${err instanceof Error ? err.message : err}`)
}
const { fields, unknown } = parsed
for (const key of unknown) warnings.push(`${slug}: unknown frontmatter key \`${key}\``)
if (!/^\d{4}-\d{2}-\d{2}-[a-z][a-z0-9-]*$/.test(slug)) {
warnings.push(`${slug}: dirname does not match YYYY-MM-DD-<slug> convention`)
}
const title = typeof fields.title === 'string' ? fields.title : firstHeading(raw)
if (!title) warnings.push(`${slug}: no title frontmatter and no H1 — falling back to slug`)
const tagline = typeof fields.tagline === 'string' ? fields.tagline : firstParagraph(raw)
if (typeof fields.tagline !== 'string') {
warnings.push(`${slug}: no tagline frontmatter — using the README's first paragraph`)
}
const dirDate = slug.match(/^(\d{4}-\d{2}(?:-\d{2})?)/)?.[1] ?? null
let date = typeof fields.date === 'string' ? fields.date : null
if (date && !/^\d{4}-(0[1-9]|1[0-2])(-(0[1-9]|[12][0-9]|3[01]))?$/.test(date)) {
warnings.push(`${slug}: invalid date \`${date}\` — falling back to dirname prefix`)
date = null
}
if (date && dirDate && date !== dirDate) {
warnings.push(`${slug}: frontmatter date ${date} ≠ dirname prefix ${dirDate}`)
}
date = date ?? dirDate
if (!date) {
warnings.push(`${slug}: no date derivable — spec not listed`)
continue
}
let status = typeof fields.status === 'string' ? fields.status : 'live'
if (status !== 'live' && status !== 'draft') {
warnings.push(`${slug}: invalid status \`${status}\` — treating as live`)
status = 'live'
}
let tags = Array.isArray(fields.tags) ? fields.tags : []
if (tags.length > 4) {
warnings.push(`${slug}: ${tags.length} tags — keeping the first 4`)
tags = tags.slice(0, 4)
}
specs.push({
slug,
title: title ?? slug,
tagline: tagline ?? '',
date,
month: monthLabel(date),
dayLabel: dayLabel(date),
tags,
status,
featured: fields.featured === true,
hasDeck: existsSync(join(ROOT, slug, 'index.html')),
})
}
specs.sort(
(a, b) =>
Number(b.featured) - Number(a.featured) ||
b.date.localeCompare(a.date) ||
a.title.localeCompare(b.title),
)
return { specs, warnings }
}
/**
* Vite plugin resolving `virtual:spec-manifest` to the live spec list — the
* gallery's data source. No generated file in the tree, so nothing to commit
* and nothing to conflict on; dev picks up frontmatter edits via the watcher.
*/
export function specManifestPlugin() {
const VIRTUAL_ID = 'virtual:spec-manifest'
const RESOLVED_ID = `\0${VIRTUAL_ID}`
return {
name: 'spec-manifest',
resolveId(/** @type {string} */ id) {
return id === VIRTUAL_ID ? RESOLVED_ID : undefined
},
load(/** @type {string} */ id) {
if (id !== RESOLVED_ID) return undefined
const { specs, warnings } = readSpecs()
for (const w of warnings) this.warn(`[spec-manifest] ${w}`)
return `export const SPECS = ${JSON.stringify(specs, null, 2)}\n`
},
configureServer(/** @type {import('vite').ViteDevServer} */ server) {
server.watcher.add(SPECS_DIR)
const invalidate = (/** @type {string} */ file) => {
if (!file.startsWith(SPECS_DIR)) return
const mod = server.moduleGraph.getModuleById(RESOLVED_ID)
if (mod) server.moduleGraph.invalidateModule(mod)
server.ws.send({ type: 'full-reload' })
}
server.watcher.on('change', invalidate)
server.watcher.on('add', invalidate)
server.watcher.on('unlink', invalidate)
},
}
}
base/src/components/diagrams/CliPlayground.tsx
import { PlayerControls } from '@lib/components/PlayerControls'
import { FnChip } from '@lib/components/schematic/FnChip'
import { ModeToggle } from '@lib/components/schematic/ModeToggle'
import { Prompt } from '@lib/components/schematic/Prompt'
import { useStepper } from '@lib/hooks/useStepper'
import { cn } from '@lib/lib/utils'
import { useEffect, useMemo, useState } from 'react'
/**
* archetype A3 — interactive terminal step-player.
*
* use for a command sequence: a golden-path install, a day-2 ops demo, the
* "show it, don't tell it" proof slide where the transcript IS the argument.
* the stepper reveals one line at a time; output lines stream in with a typed
* feel, tinted by leading glyph (✓ accent, ✗ alert, ! warn, else faint).
* pass >1 track to get a ModeToggle that switches between transcripts.
*/
export interface CliLine {
cmd?: string // a command, rendered after a $ prompt
out?: string[] // output lines; tinted by leading glyph (see lineClass)
fn?: string // optional backing function id, shown as a faint FnChip
exit?: number // optional exit code, shown subtly under the output
}
export interface CliTrack {
id: string
label: string
lines: CliLine[]
}
interface CliPlaygroundProps {
tracks: CliTrack[]
title?: string
intervalMs?: number
className?: string
}
/** tint one output line by its leading glyph — accent stays rationed to ✓ / results */
function lineClass(line: string): string {
const t = line.trimStart()
if (t.startsWith('✓')) return 'text-accent'
if (t.startsWith('✗')) return 'text-alert'
if (t.startsWith('!')) return 'text-warn'
if (t.startsWith('{') || t.startsWith('"')) return 'text-ink'
if (t.startsWith('→')) return 'text-ink-faint'
return 'text-ink-faint'
}
function CommandBlock({ line, active, blinkKey }: { line: CliLine; active: boolean; blinkKey: string }) {
return (
<div className="pb-3">
{line.cmd ? (
<div className="flex flex-wrap items-baseline gap-x-2 gap-y-1">
<Prompt symbol="$" className="text-[13px] shrink-0 select-none" />
<span className="font-mono text-[13px] text-ink break-all">{line.cmd}</span>
{line.fn ? (
<FnChip tone="faint" className="ml-1">
{line.fn}
</FnChip>
) : null}
</div>
) : null}
{/* output streams in, one line per row, staggered for a typed feel */}
{line.out && line.out.length ? (
<div key={blinkKey} className={cn('flex flex-col gap-y-0.5', line.cmd && 'mt-2 ml-4')}>
{line.out.map((text, i) => (
<pre
key={i}
className={cn(
'font-mono text-[12.5px] leading-[1.5] whitespace-pre-wrap break-words',
lineClass(text),
active && 'fade-rise',
)}
style={active ? { animationDelay: `${Math.min(i * 70, 700)}ms` } : undefined}
>
{text || ' '}
</pre>
))}
</div>
) : null}
{typeof line.exit === 'number' ? (
<pre
className={cn(
'mt-1 font-mono text-[11px] uppercase tracking-[0.08em] tabular-nums',
line.cmd && 'ml-4',
line.exit === 0 ? 'text-ink-ghost' : 'text-alert',
)}
>
exit {line.exit}
</pre>
) : null}
</div>
)
}
export function CliPlayground({
tracks,
title = 'simulated terminal',
intervalMs = 2600,
className,
}: CliPlaygroundProps) {
const [trackId, setTrackId] = useState(tracks[0].id)
const track = useMemo(() => tracks.find((t) => t.id === trackId) ?? tracks[0], [tracks, trackId])
const stepper = useStepper(track.lines.length, intervalMs)
// reset the transcript whenever the active track changes
useEffect(() => {
stepper.goTo(0)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [trackId])
const revealed = track.lines.slice(0, stepper.step + 1)
const multi = tracks.length > 1
return (
<div className={cn('border border-rule bg-bg', className)}>
{/* panel header: title + a track toggle when there is more than one */}
<div className="flex flex-wrap items-center justify-between gap-2 bg-panel px-3.5 py-2 border-b border-rule">
<span className="font-mono text-[11px] font-medium uppercase tracking-[0.06em] text-ink-faint">{title}</span>
{multi ? (
<ModeToggle
value={trackId}
onChange={setTrackId}
options={tracks.map((t) => ({ value: t.id, label: t.label }))}
/>
) : null}
</div>
{/* transcript — height bounded, scrolls when the track runs long */}
<div className="px-4 py-4 min-h-[280px] max-h-[460px] overflow-y-auto [scrollbar-gutter:stable]">
{revealed.map((line, i) => (
<CommandBlock
key={`${track.id}-${i}`}
line={line}
active={i === stepper.step}
blinkKey={`${track.id}-${i}`}
/>
))}
{stepper.atEnd ? (
<div className="flex items-center gap-x-2 pt-1">
<Prompt symbol="$" className="text-[13px]" />
<span aria-hidden className="blink inline-block w-[6px] h-[13px] bg-ink align-middle" />
</div>
) : null}
</div>
<PlayerControls stepper={stepper} total={track.lines.length} label={multi ? track.label : undefined} />
</div>
)
}
base/src/components/diagrams/DurabilityTimeline.tsx
import { PlayerControls } from '@lib/components/PlayerControls'
import { FnChip } from '@lib/components/schematic/FnChip'
import { useStepper } from '@lib/hooks/useStepper'
import { cn } from '@lib/lib/utils'
/**
* A16 - a lifecycle told as a steppable timeline of stages, each stage pairing
* a narration with an evolving state record beside it. Built for "a durable
* thing survives crashes and waits" stories; generic over any staged record.
* All content arrives via props — stage data lives in the deck's content/.
*/
export type TimelineTone = 'ink' | 'alert' | 'warn' | 'accent'
export interface TimelineStage {
id: string
label: string
sub: string
tone: TimelineTone
title: string
desc: string
record: { status: string; step: string; calls: string; note: string }
/** render the connector after this stage dashed (a pause / long wait) */
gapAfter?: boolean
}
const toneClasses: Record<TimelineTone, { box: string; active: string; label: string }> = {
ink: { box: 'border-rule', active: 'border-ink bg-panel', label: 'text-ink' },
alert: { box: 'border-alert/50', active: 'border-alert bg-panel', label: 'text-alert' },
warn: { box: 'border-warn/50', active: 'border-warn bg-panel', label: 'text-warn' },
accent: { box: 'border-rule', active: 'border-accent bg-panel', label: 'text-accent' },
}
export function DurabilityTimeline({
stages,
heading,
headingNote,
recordHeading,
intervalMs = 3000,
className,
}: {
stages: TimelineStage[]
/** panel header, e.g. "one turn vs. a crash and a long wait" */
heading: string
/** right-hand header note, e.g. "turn t_001" */
headingNote?: string
/** caption above the record panel, e.g. "the turn record — state, not a process" */
recordHeading: string
intervalMs?: number
className?: string
}) {
const stepper = useStepper(stages.length, intervalMs)
const active = stages[stepper.step]
return (
<div className={cn('border border-rule bg-bg', className)}>
<div className="flex items-center justify-between bg-panel px-3.5 py-2 border-b border-rule">
<span className="font-mono text-[11px] font-medium uppercase tracking-[0.06em] text-ink-faint">{heading}</span>
{headingNote ? (
<span className="font-mono text-[11px] uppercase tracking-[0.06em] text-ink-ghost tabular-nums">
{headingNote}
</span>
) : null}
</div>
{/* the track */}
<div className="overflow-x-auto">
<div className="flex items-stretch gap-0 px-4 py-6 min-w-[860px]">
{stages.map((stage, i) => {
const reached = i <= stepper.step
const isActive = i === stepper.step
const tone = toneClasses[stage.tone]
return (
<div key={stage.id} className="flex items-center flex-1 min-w-0">
<button
type="button"
onClick={() => stepper.goTo(i)}
className={cn(
'border bg-bg px-2.5 py-2 w-full min-w-0 text-left transition-all duration-200 cursor-pointer',
reached ? tone.box : 'border-rule-2 opacity-45',
isActive && tone.active,
isActive && stage.tone === 'ink' && 'border-accent',
)}
>
<div
className={cn(
'font-mono text-[10.5px] font-semibold leading-[1.35] truncate',
reached ? tone.label : 'text-ink-ghost',
isActive && stage.tone === 'ink' && 'text-ink',
)}
>
{stage.tone === 'alert' ? '⚡ ' : stage.tone === 'warn' ? '⏸ ' : ''}
{stage.label}
</div>
<div className="mt-0.5 font-mono text-[9px] tracking-[0.03em] text-ink-ghost truncate">
{stage.sub}
</div>
</button>
{i < stages.length - 1 ? (
<span
aria-hidden
className={cn(
'h-px w-4 shrink-0',
i < stepper.step ? 'bg-ink-faint' : 'bg-rule',
stage.gapAfter && 'w-6 border-t border-dashed border-rule bg-transparent',
)}
/>
) : null}
</div>
)
})}
</div>
</div>
{/* the evolving record beside the narration */}
<div className="grid grid-cols-1 @3xl:grid-cols-[minmax(0,7fr)_minmax(0,5fr)] border-t border-rule">
<div className="px-4 py-3.5 min-h-[108px]">
<div className="flex flex-wrap items-center gap-x-3 gap-y-1.5">
<span className="font-mono text-[11px] text-ink-ghost tabular-nums">
{String(stepper.step + 1).padStart(2, '0')}
</span>
<span className="font-mono text-[14px] font-semibold lowercase text-ink">{active.title}</span>
</div>
<p className="mt-1.5 font-mono text-[12.5px] leading-[1.65] text-ink-faint lowercase max-w-[78ch]">
{active.desc}
</p>
</div>
<div className="border-t @3xl:border-t-0 @3xl:border-l border-rule-2 bg-paper-2 px-4 py-3.5">
<div className="font-mono text-[10px] uppercase tracking-[0.14em] text-ink-faint mb-2">{recordHeading}</div>
<dl className="font-mono text-[11.5px] leading-[1.8]">
<div className="flex gap-x-2">
<dt className="text-ink-ghost w-14 shrink-0">status</dt>
<dd className="text-ink">{active.record.status}</dd>
</div>
<div className="flex gap-x-2">
<dt className="text-ink-ghost w-14 shrink-0">step</dt>
<dd className="text-ink tabular-nums">{active.record.step}</dd>
</div>
<div className="flex gap-x-2">
<dt className="text-ink-ghost w-14 shrink-0">calls</dt>
<dd className="text-ink">{active.record.calls}</dd>
</div>
</dl>
<div className="mt-2">
<FnChip tone="faint" className="whitespace-normal">
{active.record.note}
</FnChip>
</div>
</div>
</div>
<PlayerControls stepper={stepper} total={stages.length} />
</div>
)
}
base/src/components/diagrams/EventFanOut.tsx
import { cn } from '@lib/lib/utils'
import { useMemo } from 'react'
/**
* A17 - one write fans out to every bound handler: an ambient, always-on
* visual for a reactive surface (source → trigger type → N subscribers).
* Denser and more literal than FanOut (A7), which animates ripples on demand;
* this one narrates a concrete write with named handlers. All content arrives
* via props — handler/edge data lives in the deck's content/.
*/
export interface FanOutHandler {
id: string
sub: string
/** y coordinate of the handler box (viewBox is 980×262; boxes are 228×46) */
y: number
}
export interface FanOutEdge {
/** svg path from the trigger's ripple point to a handler box */
d: string
/** seconds for the traveling-dot animation */
dur: number
}
export function EventFanOut({
heading,
headingNote,
source,
sourceSub,
trigger,
handlers,
edges,
footnote,
ariaLabel,
className,
}: {
/** panel header, e.g. "one write, every surface" */
heading: string
/** right-hand header note, e.g. "ambient — always on" */
headingNote?: string
/** the write, e.g. "session::update-message" */
source: string
/** line under the write, e.g. "revision 7 — one write, no publish step" */
sourceSub?: string
/** the trigger type the write emits, e.g. "session::message-updated" */
trigger: string
handlers: readonly FanOutHandler[]
edges: readonly FanOutEdge[]
/** caption under the handler column */
footnote?: string
ariaLabel: string
className?: string
}) {
const reducedMotion = useMemo(
() => typeof window !== 'undefined' && window.matchMedia('(prefers-reduced-motion: reduce)').matches,
[],
)
return (
<div className={cn('border border-rule bg-bg', className)}>
<div className="flex items-center justify-between bg-panel px-3.5 py-2 border-b border-rule">
<span className="font-mono text-[11px] font-medium uppercase tracking-[0.06em] text-ink-faint">{heading}</span>
{headingNote ? (
<span className="font-mono text-[11px] uppercase tracking-[0.06em] text-ink-ghost">{headingNote}</span>
) : null}
</div>
<div className="overflow-x-auto">
<svg
viewBox="0 0 980 262"
className="w-full h-auto min-w-[680px] font-mono select-none"
role="img"
aria-label={ariaLabel}
>
<defs>
<marker
id="fan-arr"
viewBox="0 0 8 8"
refX="7"
refY="4"
markerWidth="6.5"
markerHeight="6.5"
orient="auto-start-reverse"
>
<path d="M 0 0 L 8 4 L 0 8 z" className="fill-accent" />
</marker>
</defs>
{/* the write */}
<rect x={24} y={100} width={230} height={64} strokeWidth={1.25} className="fill-bg stroke-ink" />
<text x={139} y={127} textAnchor="middle" fontSize="12.5" fontWeight={600} className="fill-ink">
{source}
</text>
{sourceSub ? (
<text x={139} y={146} textAnchor="middle" fontSize="9.5" letterSpacing="0.05em" className="fill-ink-ghost">
{sourceSub}
</text>
) : null}
{/* write → trigger type */}
<path
d="M 254 132 L 364 132"
fill="none"
strokeWidth={1}
className="stroke-ink-ghost"
markerEnd="url(#fan-arr)"
/>
<text
x={309}
y={122}
textAnchor="middle"
fontSize="9"
letterSpacing="0.04em"
className="fill-ink-ghost"
style={{ paintOrder: 'stroke', stroke: 'var(--color-bg)', strokeWidth: 3.5 }}
>
emits
</text>
{/* the trigger type */}
<rect x={368} y={106} width={244} height={52} strokeWidth={1.4} className="fill-panel stroke-accent" />
<text x={490} y={129} textAnchor="middle" fontSize="12" fontWeight={600} className="fill-ink">
{trigger}
</text>
<text
x={490}
y={146}
textAnchor="middle"
fontSize="9"
letterSpacing="0.06em"
className="fill-ink-faint uppercase"
>
trigger type
</text>
{!reducedMotion ? (
<>
<circle cx={612} cy={132} className="fill-none stroke-accent ripple-ring" strokeWidth={1} />
<circle
cx={612}
cy={132}
className="fill-none stroke-accent ripple-ring"
strokeWidth={1}
style={{ animationDelay: '0.8s' }}
/>
</>
) : null}
{/* fan-out edges */}
{edges.map((edge, i) => (
<g key={edge.d}>
<path d={edge.d} fill="none" strokeWidth={1.1} className="stroke-accent" markerEnd="url(#fan-arr)" />
{!reducedMotion ? (
<circle r="2.6" className="fill-accent">
<animateMotion dur={`${edge.dur}s`} begin={`${i * 0.25}s`} repeatCount="indefinite" path={edge.d} />
</circle>
) : null}
</g>
))}
{/* bound handlers */}
{handlers.map((handler) => (
<g key={handler.id}>
<rect x={732} y={handler.y} width={228} height={46} strokeWidth={1} className="fill-bg stroke-rule" />
<text
x={846}
y={handler.y + 20}
textAnchor="middle"
fontSize="11.5"
fontWeight={600}
className="fill-ink"
>
{handler.id}
</text>
<text
x={846}
y={handler.y + 36}
textAnchor="middle"
fontSize="9"
letterSpacing="0.04em"
className="fill-ink-faint"
>
{handler.sub}
</text>
</g>
))}
{footnote ? (
<text
x={846}
y={250}
textAnchor="middle"
fontSize="9"
letterSpacing="0.06em"
className="fill-ink-ghost uppercase"
>
{footnote}
</text>
) : null}
</svg>
</div>
</div>
)
}
base/src/components/diagrams/FanOut.tsx
import { cn } from '@lib/lib/utils'
import { useMemo } from 'react'
/**
* archetype A7 — one event fans out to many handlers.
* use when the spec has a reactive/event model: one write/emit triggers
* N bound handlers. ambient (always-on), no stepper: a source box emits a
* trigger that ripples out and fans to every bound handler at once.
*/
export interface FanHandler {
id: string
label: string
desc?: string
}
interface FanOutProps {
source: { label: string; sub?: string } // the write/source box on the left
trigger: string // the event/trigger type emitted (keeps its casing)
handlers: FanHandler[] // 2-5 handlers it fans to, stacked on the right
title?: string
className?: string
}
// fixed horizontal geometry; vertical positions are derived from handler count
const SOURCE_X = 24
const SOURCE_W = 230
const SOURCE_H = 64
const TRIGGER_X = 368
const TRIGGER_W = 212
const TRIGGER_H = 52
const HANDLER_X = 732
const HANDLER_W = 228
const HANDLER_H = 46
const ROW_GAP = 26 // vertical gap between handler boxes
const TOP = 30 // top padding above the first handler
export function FanOut({ source, trigger, handlers, title = 'one write, every handler', className }: FanOutProps) {
const reducedMotion = useMemo(
() => typeof window !== 'undefined' && window.matchMedia('(prefers-reduced-motion: reduce)').matches,
[],
)
// total drawing height grows with the handler count; the source + trigger
// nodes center on the vertical midpoint of the stacked handlers.
const stackH = handlers.length * HANDLER_H + (handlers.length - 1) * ROW_GAP
const height = TOP + stackH + TOP
const midY = TOP + stackH / 2
// emit point: the right edge of the trigger node, on the centerline
const emitX = TRIGGER_X + TRIGGER_W
const emitY = midY
return (
<div className={cn('border border-rule bg-bg', className)}>
<div className="flex items-center justify-between bg-panel px-3.5 py-2 border-b border-rule">
<span className="font-mono text-[11px] font-medium uppercase tracking-[0.06em] text-ink-faint">{title}</span>
<span className="font-mono text-[11px] uppercase tracking-[0.06em] text-ink-ghost tabular-nums">
fans to {handlers.length}
</span>
</div>
<div className="overflow-x-auto">
<svg
viewBox={`0 0 980 ${height}`}
className="w-full h-auto min-w-[720px] font-mono select-none"
role="group"
aria-label={`${source.label} emits ${trigger}, fanning out to ${handlers.length} bound handlers`}
>
<defs>
<marker
id="fan-arr"
viewBox="0 0 8 8"
refX="7"
refY="4"
markerWidth="6.5"
markerHeight="6.5"
orient="auto-start-reverse"
>
<path d="M 0 0 L 8 4 L 0 8 z" className="fill-accent" />
</marker>
<marker
id="fan-arr-ink"
viewBox="0 0 8 8"
refX="7"
refY="4"
markerWidth="6.5"
markerHeight="6.5"
orient="auto-start-reverse"
>
<path d="M 0 0 L 8 4 L 0 8 z" className="fill-ink-ghost" />
</marker>
</defs>
{/* the write / source */}
<rect
x={SOURCE_X}
y={midY - SOURCE_H / 2}
width={SOURCE_W}
height={SOURCE_H}
strokeWidth={1.25}
className="fill-bg stroke-ink"
/>
<text
x={SOURCE_X + SOURCE_W / 2}
y={source.sub ? midY - 5 : midY + 4}
textAnchor="middle"
fontSize="12.5"
fontWeight={600}
className="fill-ink"
>
{source.label}
</text>
{source.sub ? (
<text
x={SOURCE_X + SOURCE_W / 2}
y={midY + 14}
textAnchor="middle"
fontSize="9.5"
letterSpacing="0.05em"
className="fill-ink-ghost"
>
{source.sub}
</text>
) : null}
{/* source → trigger node */}
<path
d={`M ${SOURCE_X + SOURCE_W} ${midY} L ${TRIGGER_X - 4} ${midY}`}
fill="none"
strokeWidth={1}
className="stroke-ink-ghost"
markerEnd="url(#fan-arr-ink)"
/>
<text
x={(SOURCE_X + SOURCE_W + TRIGGER_X) / 2}
y={midY - 10}
textAnchor="middle"
fontSize="9"
letterSpacing="0.04em"
className="fill-ink-ghost"
style={{ paintOrder: 'stroke', stroke: 'var(--color-bg)', strokeWidth: 3.5 }}
>
emits
</text>
{/* the trigger type node */}
<rect
x={TRIGGER_X}
y={midY - TRIGGER_H / 2}
width={TRIGGER_W}
height={TRIGGER_H}
strokeWidth={1.4}
className="fill-panel stroke-accent"
/>
<text
x={TRIGGER_X + TRIGGER_W / 2}
y={midY - 3}
textAnchor="middle"
fontSize="12"
fontWeight={600}
className="fill-ink"
>
{trigger}
</text>
<text
x={TRIGGER_X + TRIGGER_W / 2}
y={midY + 14}
textAnchor="middle"
fontSize="9"
letterSpacing="0.06em"
className="fill-ink-faint uppercase"
>
trigger type
</text>
{/* ambient ripple emanating from the emit point */}
{!reducedMotion ? (
<>
<circle cx={emitX} cy={emitY} className="fill-none stroke-accent ripple-ring" strokeWidth={1} />
<circle
cx={emitX}
cy={emitY}
className="fill-none stroke-accent ripple-ring"
strokeWidth={1}
style={{ animationDelay: '0.8s' }}
/>
</>
) : null}
{/* fan-out edges + bound handlers */}
{handlers.map((handler, i) => {
const hy = TOP + i * (HANDLER_H + ROW_GAP) // top of this handler box
const targetY = hy + HANDLER_H / 2 // centerline of this handler
// smooth cubic from the emit point to the handler's left edge
const cx1 = emitX + 56
const cx2 = HANDLER_X - 56
const d = `M ${emitX} ${emitY} C ${cx1} ${emitY}, ${cx2} ${targetY}, ${HANDLER_X} ${targetY}`
// stagger pulse timing + travel duration so edges don't beat in sync
const dur = 1.5 + (i % 3) * 0.3
return (
<g key={handler.id}>
<path
d={d}
fill="none"
strokeWidth={1.1}
className="stroke-accent flow-dash"
markerEnd="url(#fan-arr)"
/>
{!reducedMotion ? (
<circle r="2.6" className="fill-accent">
<animateMotion dur={`${dur}s`} begin={`${i * 0.25}s`} repeatCount="indefinite" path={d} />
</circle>
) : null}
<rect
x={HANDLER_X}
y={hy}
width={HANDLER_W}
height={HANDLER_H}
strokeWidth={1}
className="fill-bg stroke-rule"
/>
<text
x={HANDLER_X + HANDLER_W / 2}
y={handler.desc ? hy + 20 : hy + HANDLER_H / 2 + 4}
textAnchor="middle"
fontSize="11.5"
fontWeight={600}
className="fill-ink"
>
{handler.label}
</text>
{handler.desc ? (
<text
x={HANDLER_X + HANDLER_W / 2}
y={hy + 36}
textAnchor="middle"
fontSize="9"
letterSpacing="0.04em"
className="fill-ink-faint"
>
{handler.desc}
</text>
) : null}
</g>
)
})}
{/* caption under the handler stack */}
<text
x={HANDLER_X + HANDLER_W / 2}
y={height - 10}
textAnchor="middle"
fontSize="9"
letterSpacing="0.06em"
className="fill-ink-ghost uppercase"
>
bound handlers — subscribe once, run on every emit
</text>
</svg>
</div>
</div>
)
}
base/src/components/diagrams/Funnel.tsx
import { cn } from '@lib/lib/utils'
import { useMemo } from 'react'
/**
* archetype A8 — the "many paths converge to one" funnel.
*
* use when the spec collapses many cases into a single guaranteed
* mechanism: many spawn paths -> one owned-spawn, many inputs -> one
* reaper. the input boxes sit across the top and every arrow converges
* down into one accent target box. an optional `reject` draws a dashed
* alert path off to the side, marked removed/forbidden with an x.
*
* mostly static; a subtle travelling pulse rides the convergence edges,
* gated behind prefers-reduced-motion.
*/
export interface FunnelPath {
id: string
label: string
desc?: string
}
interface FunnelProps {
title?: string
/** 2-5 input boxes across the top */
paths: FunnelPath[]
/** the single convergence box at the bottom */
target: { label: string; sub?: string }
/** optional dashed alert path that is eliminated */
reject?: { label: string; desc?: string }
className?: string
}
// drawing geometry, in viewBox units
const W = 980
const PATH_Y = 22
const PATH_H = 64
const PATH_W = 188
const PATH_GAP = 18
const TARGET_H = 86
const TARGET_W = 360
const TARGET_Y = 232
const REJECT_W = 210
const REJECT_H = 64
const REJECT_Y = 243
export function Funnel({ title = 'many paths converge to one', paths, target, reject, className }: FunnelProps) {
const reducedMotion = useMemo(
() => typeof window !== 'undefined' && window.matchMedia('(prefers-reduced-motion: reduce)').matches,
[],
)
// the input boxes are centered as a block within the left region so the
// convergence stays visually balanced even when a reject column is shown.
const n = paths.length
const blockW = n * PATH_W + (n - 1) * PATH_GAP
// when there is a reject path, hold the left region narrower to leave room
const regionRight = reject ? W - REJECT_W - 56 : W
const blockX = Math.max(20, (regionRight - blockW) / 2)
const pathX = (i: number) => blockX + i * (PATH_W + PATH_GAP)
const targetX = (regionRight - TARGET_W) / 2 < 20 ? 20 : (regionRight - TARGET_W) / 2
const targetCx = targetX + TARGET_W / 2
const targetTop = TARGET_Y
const rejectX = W - REJECT_W - 20
const rejectCx = rejectX + REJECT_W / 2
const height = 340
return (
<div className={cn('border border-rule bg-bg', className)}>
<div className="flex items-center justify-between bg-panel px-3.5 py-2 border-b border-rule">
<span className="font-mono text-[11px] font-medium uppercase tracking-[0.06em] text-ink-faint">{title}</span>
<span className="font-mono text-[11px] uppercase tracking-[0.06em] text-ink-ghost tabular-nums">
{n} paths {'→'} 1
</span>
</div>
<div className="overflow-x-auto p-3">
<svg
viewBox={`0 0 ${W} ${height}`}
className="w-full h-auto min-w-[680px] font-mono select-none"
role="img"
aria-label={`${n} input paths converging into one mechanism: ${target.label}${reject ? `, with the eliminated path ${reject.label}` : ''}`}
>
<defs>
<marker
id="fun-arr"
viewBox="0 0 8 8"
refX="7"
refY="4"
markerWidth="6.5"
markerHeight="6.5"
orient="auto-start-reverse"
>
<path d="M 0 0 L 8 4 L 0 8 z" className="fill-ink-faint" />
</marker>
<marker
id="fun-arr-accent"
viewBox="0 0 8 8"
refX="7"
refY="4"
markerWidth="6.5"
markerHeight="6.5"
orient="auto-start-reverse"
>
<path d="M 0 0 L 8 4 L 0 8 z" className="fill-accent" />
</marker>
<marker
id="fun-arr-alert"
viewBox="0 0 8 8"
refX="7"
refY="4"
markerWidth="6.5"
markerHeight="6.5"
orient="auto-start-reverse"
>
<path d="M 0 0 L 8 4 L 0 8 z" className="fill-alert" />
</marker>
</defs>
{/* converging edges: each input drops, curves toward the target top */}
{paths.map((p, i) => {
const startX = pathX(i) + PATH_W / 2
const startY = PATH_Y + PATH_H
const endY = targetTop
const d = `M ${startX} ${startY} C ${startX} ${startY + 56}, ${targetCx} ${endY - 56}, ${targetCx} ${endY}`
return (
<g key={`edge-${p.id}`}>
<path d={d} fill="none" strokeWidth={1.2} markerEnd="url(#fun-arr-accent)" className="stroke-accent" />
{!reducedMotion ? (
<circle r="2.4" className="fill-accent">
<animateMotion dur="2s" begin={`${i * 0.32}s`} repeatCount="indefinite" path={d} />
</circle>
) : null}
</g>
)
})}
{/* rejected edge: dashed, alert, struck out with an x */}
{reject
? (() => {
const startX = rejectCx
const startY = PATH_Y + PATH_H
const endY = REJECT_Y
const d = `M ${startX} ${startY} L ${startX} ${endY}`
const midY = (startY + endY) / 2
return (
<g>
<path
d={d}
fill="none"
strokeWidth={1.2}
strokeDasharray="5 4"
markerEnd="url(#fun-arr-alert)"
className="stroke-alert"
/>
{/* x marking the path as removed */}
<g className="stroke-alert" strokeWidth={1.6} strokeLinecap="round">
<line x1={startX - 7} y1={midY - 7} x2={startX + 7} y2={midY + 7} />
<line x1={startX - 7} y1={midY + 7} x2={startX + 7} y2={midY - 7} />
</g>
<rect
x={startX - 11}
y={midY - 11}
width={22}
height={22}
rx={2}
className="fill-bg stroke-alert"
strokeWidth={1}
style={{ fillOpacity: 0.0 }}
/>
</g>
)
})()
: null}
{/* input path boxes across the top */}
{paths.map((p, i) => {
const x = pathX(i)
return (
<g key={`box-${p.id}`}>
<rect x={x} y={PATH_Y} width={PATH_W} height={PATH_H} className="fill-bg stroke-rule" strokeWidth={1} />
<text x={x + 13} y={PATH_Y + 26} fontSize="12" fontWeight={600} className="fill-ink">
{p.label}
</text>
{p.desc ? (
<text x={x + 13} y={PATH_Y + 45} fontSize="9.5" className="fill-ink-ghost">
{p.desc}
</text>
) : null}
</g>
)
})}
{/* the single convergence target — accent border + accent spine */}
<rect
x={targetX}
y={targetTop}
width={TARGET_W}
height={TARGET_H}
className="fill-panel stroke-accent"
strokeWidth={1.5}
/>
<rect x={targetX} y={targetTop} width={3} height={TARGET_H} className="fill-accent" />
<text
x={targetCx}
y={targetTop + (target.sub ? 36 : 49)}
textAnchor="middle"
fontSize="15"
fontWeight={600}
className="fill-ink"
>
{target.label}
</text>
{target.sub ? (
<text
x={targetCx}
y={targetTop + 58}
textAnchor="middle"
fontSize="10"
letterSpacing="0.03em"
className="fill-ink-faint"
>
{target.sub}
</text>
) : null}
{/* the eliminated target box, off to one side */}
{reject ? (
<g>
<rect
x={rejectX}
y={REJECT_Y}
width={REJECT_W}
height={REJECT_H}
className="fill-bg stroke-alert"
strokeWidth={1}
strokeDasharray="5 4"
/>
<text x={rejectX + 13} y={REJECT_Y + 24} fontSize="12" fontWeight={600} className="fill-alert">
{reject.label}
</text>
{reject.desc ? (
<text x={rejectX + 13} y={REJECT_Y + 43} fontSize="9.5" className="fill-ink-faint">
{reject.desc}
</text>
) : null}
<text
x={rejectX + REJECT_W - 11}
y={REJECT_Y + 24}
textAnchor="end"
fontSize="11"
letterSpacing="0.06em"
className="fill-alert uppercase"
>
{'✗ removed'}
</text>
</g>
) : null}
</svg>
</div>
</div>
)
}
base/src/components/diagrams/SequencePlayer.tsx
import { PlayerControls } from '@lib/components/PlayerControls'
import { FnChip } from '@lib/components/schematic/FnChip'
import { useStepper } from '@lib/hooks/useStepper'
import { cn } from '@lib/lib/utils'
import { useMemo } from 'react'
export interface SeqLane {
id: string
label: string
x: number
}
export interface SeqStep {
from: string
to: string // same as from => self loop
label: string
title: string
desc: string
event?: string // trigger type this step emits
}
interface SequencePlayerProps {
title: string
lanes: SeqLane[]
steps: SeqStep[]
width?: number
intervalMs?: number
className?: string
}
const ROW_H = 46
const TOP = 64
/**
* a step-through sequence diagram: lifelines per lane, one arrow per step,
* past steps in ink, the active step in accent with a travelling pulse.
*/
export function SequencePlayer({
title,
lanes,
steps,
width = 980,
intervalMs = 2800,
className,
}: SequencePlayerProps) {
const stepper = useStepper(steps.length, intervalMs)
const height = TOP + steps.length * ROW_H + 18
const laneById = useMemo(() => Object.fromEntries(lanes.map((l) => [l.id, l])), [lanes])
const reducedMotion = useMemo(
() => typeof window !== 'undefined' && window.matchMedia('(prefers-reduced-motion: reduce)').matches,
[],
)
const active = steps[stepper.step]
const activeLanes = new Set([active.from, active.to])
return (
<div className={cn('border border-rule bg-bg', className)}>
<div className="flex items-center justify-between bg-panel px-3.5 py-2 border-b border-rule">
<span className="font-mono text-[11px] font-medium uppercase tracking-[0.06em] text-ink-faint">{title}</span>
<span className="font-mono text-[11px] uppercase tracking-[0.06em] text-ink-ghost tabular-nums">
{steps.length} steps
</span>
</div>
<div className="overflow-x-auto">
<svg
viewBox={`0 0 ${width} ${height}`}
className="w-full h-auto min-w-[720px] font-mono select-none"
role="img"
aria-label={`sequence diagram: ${title}`}
>
<defs>
<marker
id="seq-arr-ink"
viewBox="0 0 8 8"
refX="7"
refY="4"
markerWidth="6.5"
markerHeight="6.5"
orient="auto-start-reverse"
>
<path d="M 0 0 L 8 4 L 0 8 z" className="fill-ink-faint" />
</marker>
<marker
id="seq-arr-accent"
viewBox="0 0 8 8"
refX="7"
refY="4"
markerWidth="6.5"
markerHeight="6.5"
orient="auto-start-reverse"
>
<path d="M 0 0 L 8 4 L 0 8 z" className="fill-accent" />
</marker>
</defs>
{/* lifelines + lane headers */}
{lanes.map((lane) => {
const hot = activeLanes.has(lane.id)
return (
<g key={lane.id}>
<line
x1={lane.x}
y1={46}
x2={lane.x}
y2={height - 8}
strokeDasharray="2 5"
className={cn('transition-[stroke]', hot ? 'stroke-ink-faint' : 'stroke-rule')}
strokeWidth={1}
/>
<rect
x={lane.x - 62}
y={14}
width={124}
height={26}
strokeWidth={hot ? 1.4 : 1}
className={cn('transition-all', hot ? 'fill-panel stroke-accent' : 'fill-bg stroke-rule')}
/>
<text
x={lane.x}
y={31}
textAnchor="middle"
fontSize="11"
fontWeight={600}
className={cn('transition-[fill]', hot ? 'fill-ink' : 'fill-ink-faint')}
>
{lane.label}
</text>
</g>
)
})}
{/* one row per revealed step */}
{steps.map((step, i) => {
if (i > stepper.step) return null
const isActive = i === stepper.step
const y = TOP + i * ROW_H
const from = laneById[step.from]
const to = laneById[step.to]
const self = step.from === step.to
const d = self
? `M ${from.x} ${y} C ${from.x + 64} ${y - 4}, ${from.x + 64} ${y + 22}, ${from.x + 6} ${y + 20}`
: `M ${from.x} ${y} L ${to.x} ${y}`
const midX = self ? from.x + 70 : (from.x + to.x) / 2
return (
<g key={`${step.label}-${i}`} className={cn(!isActive && 'opacity-75')}>
<path
d={d}
fill="none"
strokeWidth={isActive ? 1.4 : 1}
markerEnd={`url(#${isActive ? 'seq-arr-accent' : 'seq-arr-ink'})`}
className={isActive ? 'stroke-accent' : 'stroke-ink-ghost'}
/>
{isActive && !reducedMotion && !self ? (
<circle r="2.6" className="fill-accent">
<animateMotion dur="1.4s" repeatCount="indefinite" path={d} />
</circle>
) : null}
<text
x={self ? from.x + 78 : midX}
y={y - 7}
textAnchor={self ? 'start' : 'middle'}
fontSize="10.5"
className={isActive ? 'fill-ink' : 'fill-ink-faint'}
style={{ paintOrder: 'stroke', stroke: 'var(--color-bg)', strokeWidth: 4 }}
>
{step.label}
</text>
{step.event ? (
<g>
<circle
cx={self ? from.x + 70 : midX - (step.event.length * 5.4) / 2 - 10}
cy={y + 12}
r={2.4}
className={isActive ? 'fill-accent' : 'fill-ink-ghost'}
/>
<text
x={self ? from.x + 78 : midX}
y={y + 16}
textAnchor={self ? 'start' : 'middle'}
fontSize="9"
letterSpacing="0.04em"
className="fill-ink-ghost"
style={{ paintOrder: 'stroke', stroke: 'var(--color-bg)', strokeWidth: 3.5 }}
>
fires {step.event}
</text>
</g>
) : null}
</g>
)
})}
</svg>
</div>
{/* active step narration */}
<div className="border-t border-rule px-4 py-3.5 min-h-[92px]">
<div className="flex flex-wrap items-center gap-x-3 gap-y-1.5">
<span className="font-mono text-[11px] text-ink-ghost tabular-nums">
{String(stepper.step + 1).padStart(2, '0')}
</span>
<span className="font-mono text-[14px] font-semibold lowercase text-ink">{active.title}</span>
<FnChip tone="accent">{active.label}</FnChip>
</div>
<p className="mt-1.5 font-mono text-[12.5px] leading-[1.65] text-ink-faint lowercase max-w-[88ch]">
{active.desc}
</p>
</div>
<PlayerControls stepper={stepper} total={steps.length} />
</div>
)
}
base/src/components/diagrams/SpawnTree.tsx
import { PlayerControls } from '@lib/components/PlayerControls'
import { FnChip } from '@lib/components/schematic/FnChip'
import { useStepper } from '@lib/hooks/useStepper'
import { cn } from '@lib/lib/utils'
import { useMemo } from 'react'
/**
* A18 - a parent spawns N parallel children and joins their results: fan out,
* work concurrently, join back, one answer. Steppable narrative states drive
* which children exist, run, and have delivered. All content arrives via
* props — child/state data lives in the deck's content/.
*/
export type SpawnChildState = 'hidden' | 'queued' | 'running' | 'done'
export type SpawnParentState = 'running' | 'parked' | 'resumed' | 'completed'
export interface SpawnTreeChild {
id: string
/** title line inside the child box, e.g. "sub-agent a — s_9c1" */
label: string
/** second line, e.g. "task: survey the provider landscape" */
task: string
/** third line, e.g. "policy: parent ∩ request · output: json + schema" */
meta?: string
/** x coordinate of the child box (viewBox is 980×320; boxes are 240×92) */
x: number
}
export interface SpawnTreeState {
title: string
desc: string
parent: SpawnParentState
children: SpawnChildState[]
/** per child: has its result edge been delivered back to the parent */
joins: boolean[]
}
const CHILD_Y = 196
const CHILD_W = 240
const CHILD_H = 92
const PARENT = { x: 360, y: 36, w: 260, h: 78 }
export function SpawnTree({
heading,
chips = [],
parentTitle,
parentLabels,
parentCallLine,
nodes,
states,
ariaLabel,
intervalMs = 3200,
className,
}: {
/** panel header, e.g. "harness::spawn — fan out, join back" */
heading: string
/** ghost chips in the header, e.g. ["depth ≤ 3", "fan-out ≤ 5"] */
chips?: string[]
/** title inside the parent box, e.g. "parent turn — s_7a1 / t_004" */
parentTitle: string
/** status line per parent state */
parentLabels: Record<SpawnParentState, string>
/** bottom line of the parent box, by step: e.g. first "model emits: spawn ×3", later "calls: 3 × spawn" */
parentCallLine: { first: string; rest: string }
nodes: SpawnTreeChild[]
states: SpawnTreeState[]
ariaLabel: string
intervalMs?: number
className?: string
}) {
const stepper = useStepper(states.length, intervalMs)
const state = states[stepper.step]
const reducedMotion = useMemo(
() => typeof window !== 'undefined' && window.matchMedia('(prefers-reduced-motion: reduce)').matches,
[],
)
return (
<div className={cn('border border-rule bg-bg', className)}>
<div className="flex flex-wrap items-center justify-between gap-2 bg-panel px-3.5 py-2 border-b border-rule">
<span className="font-mono text-[11px] font-medium uppercase tracking-[0.06em] text-ink-faint">{heading}</span>
{chips.length > 0 ? (
<span className="flex gap-x-2">
{chips.map((chip) => (
<FnChip key={chip} tone="ghost">
{chip}
</FnChip>
))}
</span>
) : null}
</div>
<div className="overflow-x-auto">
<svg
viewBox="0 0 980 320"
className="w-full h-auto min-w-[760px] font-mono select-none"
role="img"
aria-label={ariaLabel}
>
<defs>
<marker
id="tree-arr"
viewBox="0 0 8 8"
refX="7"
refY="4"
markerWidth="6.5"
markerHeight="6.5"
orient="auto-start-reverse"
>
<path d="M 0 0 L 8 4 L 0 8 z" className="fill-ink-ghost" />
</marker>
<marker
id="tree-arr-accent"
viewBox="0 0 8 8"
refX="7"
refY="4"
markerWidth="6.5"
markerHeight="6.5"
orient="auto-start-reverse"
>
<path d="M 0 0 L 8 4 L 0 8 z" className="fill-accent" />
</marker>
</defs>
{/* parent node */}
<rect
x={PARENT.x}
y={PARENT.y}
width={PARENT.w}
height={PARENT.h}
strokeWidth={1.4}
className={cn(
'transition-all duration-300',
state.parent === 'parked' ? 'fill-paper-2 stroke-warn' : 'fill-panel stroke-accent',
)}
/>
<text
x={PARENT.x + PARENT.w / 2}
y={PARENT.y + 26}
textAnchor="middle"
fontSize="13"
fontWeight={600}
className="fill-ink"
>
{parentTitle}
</text>
<text
x={PARENT.x + PARENT.w / 2}
y={PARENT.y + 45}
textAnchor="middle"
fontSize="9.5"
letterSpacing="0.03em"
className={cn(state.parent === 'parked' ? 'fill-warn' : 'fill-ink-faint')}
>
{parentLabels[state.parent]}
</text>
<text
x={PARENT.x + PARENT.w / 2}
y={PARENT.y + 62}
textAnchor="middle"
fontSize="9"
className="fill-ink-ghost"
>
{stepper.step === 0 ? parentCallLine.first : parentCallLine.rest}
</text>
{/* spawn + join edges, per child */}
{nodes.map((child, i) => {
const cs = state.children[i]
if (!cs || cs === 'hidden') return null
const cx = child.x + CHILD_W / 2
const px = PARENT.x + PARENT.w / 2 + (i - (nodes.length - 1) / 2) * 70
const spawnD = `M ${px} ${PARENT.y + PARENT.h} C ${px} ${PARENT.y + PARENT.h + 40}, ${cx} ${CHILD_Y - 40}, ${cx} ${CHILD_Y}`
const joinD = `M ${cx + 24} ${CHILD_Y} C ${cx + 24} ${CHILD_Y - 44}, ${px + 24} ${PARENT.y + PARENT.h + 36}, ${px + 24} ${PARENT.y + PARENT.h}`
const joined = state.joins[i]
return (
<g key={child.id}>
<path
d={spawnD}
fill="none"
strokeWidth={1}
className={cn('transition-[stroke]', cs === 'queued' ? 'stroke-rule' : 'stroke-ink-ghost')}
markerEnd="url(#tree-arr)"
/>
{joined ? (
<>
<path
d={joinD}
fill="none"
strokeWidth={1.2}
strokeDasharray="5 4"
className="stroke-accent"
markerEnd="url(#tree-arr-accent)"
/>
{!reducedMotion && stepper.step < states.length - 1 ? (
<circle r="2.4" className="fill-accent">
<animateMotion dur="1.6s" repeatCount="indefinite" path={joinD} />
</circle>
) : null}
</>
) : null}
</g>
)
})}
{/* children */}
{nodes.map((child, i) => {
const cs = state.children[i]
if (!cs || cs === 'hidden') return null
return (
<g key={child.id} className="transition-opacity duration-300">
<rect
x={child.x}
y={CHILD_Y}
width={CHILD_W}
height={CHILD_H}
strokeWidth={cs === 'running' ? 1.4 : 1}
className={cn(
'transition-all duration-300',
cs === 'queued' && 'fill-bg stroke-rule',
cs === 'running' && 'fill-bg stroke-ink',
cs === 'done' && 'fill-paper-2 stroke-ink-faint',
)}
/>
<circle
cx={child.x + 16}
cy={CHILD_Y + 19}
r={3}
className={cn(
cs === 'queued' && 'fill-ink-ghost',
cs === 'running' && 'fill-accent',
cs === 'done' && 'fill-ink-faint',
)}
>
{cs === 'running' && !reducedMotion ? (
<animate attributeName="opacity" values="1;0.25;1" dur="1.2s" repeatCount="indefinite" />
) : null}
</circle>
<text x={child.x + 28} y={CHILD_Y + 23} fontSize="11.5" fontWeight={600} className="fill-ink">
{child.label}
</text>
<text x={child.x + 16} y={CHILD_Y + 44} fontSize="9.5" className="fill-ink-faint">
{child.task}
</text>
{child.meta ? (
<text x={child.x + 16} y={CHILD_Y + 62} fontSize="9" className="fill-ink-ghost">
{child.meta}
</text>
) : null}
<text
x={child.x + 16}
y={CHILD_Y + 78}
fontSize="9"
letterSpacing="0.05em"
className={cn('uppercase', cs === 'running' ? 'fill-accent' : 'fill-ink-ghost')}
>
{cs === 'queued' ? 'queued' : cs === 'running' ? 'turn running' : 'completed ✓'}
</text>
</g>
)
})}
</svg>
</div>
<div className="border-t border-rule px-4 py-3.5 min-h-[96px]">
<div className="flex flex-wrap items-center gap-x-3 gap-y-1.5">
<span className="font-mono text-[11px] text-ink-ghost tabular-nums">
{String(stepper.step + 1).padStart(2, '0')}
</span>
<span className="font-mono text-[14px] font-semibold lowercase text-ink">{state.title}</span>
</div>
<p className="mt-1.5 font-mono text-[12.5px] leading-[1.65] text-ink-faint lowercase max-w-[88ch]">
{state.desc}
</p>
</div>
<PlayerControls stepper={stepper} total={states.length} />
</div>
)
}
base/src/components/diagrams/StepReveal.tsx
import { PlayerControls } from '@lib/components/PlayerControls'
import { FnChip } from '@lib/components/schematic/FnChip'
import { useStepper } from '@lib/hooks/useStepper'
import { cn } from '@lib/lib/utils'
import { useMemo } from 'react'
export interface RevealStage {
label: string // short stage name, e.g. "crash"
tone?: 'ink' | 'accent' | 'alert' | 'warn' // accent for this stage box
caption?: string // one-line description shown when active
rows?: { k: string; v: string }[] // a small record that evolves per stage
note?: string // optional extra faint line for the active stage
}
export interface StepRevealProps {
title: string
stages: RevealStage[]
intervalMs?: number
className?: string
}
type StageTone = NonNullable<RevealStage['tone']>
const toneClasses: Record<StageTone, { box: string; active: string; label: string }> = {
ink: { box: 'border-rule', active: 'border-accent bg-panel', label: 'text-ink' },
accent: { box: 'border-rule', active: 'border-accent bg-panel', label: 'text-accent' },
alert: { box: 'border-alert/50', active: 'border-alert bg-panel', label: 'text-alert' },
warn: { box: 'border-warn/50', active: 'border-warn bg-panel', label: 'text-warn' },
}
// a glyph hint per tone, mirroring the reference timeline's stage markers
const toneGlyph: Record<StageTone, string> = {
ink: '',
accent: '',
alert: '⚡ ',
warn: '⏸ ',
}
/**
* archetype A6 — a step-through LIFECYCLE / TIMELINE walker.
*
* use when the spec describes a process whose STATE evolves across ordered
* stages: a boot sequence, a durability timeline, a readiness bring-up, a
* crash-and-resume story. a horizontal strip of numbered stage boxes is
* connected left-to-right; the stepper walks them — past stages dim, the
* active stage lights up per its tone — while a panel below shows that
* stage's caption, its evolving k/v record, and an optional note.
*/
export function StepReveal({ title, stages, intervalMs = 2400, className }: StepRevealProps) {
const stepper = useStepper(stages.length, intervalMs)
const reducedMotion = useMemo(
() => typeof window !== 'undefined' && window.matchMedia('(prefers-reduced-motion: reduce)').matches,
[],
)
const active = stages[stepper.step]
const activeTone: StageTone = active.tone ?? 'ink'
// narrow strips scroll-x; give each box room to breathe past ~7 stages
const minWidth = Math.max(640, stages.length * 132)
return (
<div className={cn('border border-rule bg-bg', className)}>
<div className="flex items-center justify-between bg-panel px-3.5 py-2 border-b border-rule">
<span className="font-mono text-[11px] font-medium uppercase tracking-[0.06em] text-ink-faint">{title}</span>
<span className="font-mono text-[11px] uppercase tracking-[0.06em] text-ink-ghost tabular-nums">
{stages.length} stages
</span>
</div>
{/* the strip — numbered stage boxes connected left-to-right */}
<div className="overflow-x-auto">
<div className="flex items-stretch gap-0 px-4 py-6" style={{ minWidth: `${minWidth}px` }}>
{stages.map((stage, i) => {
const reached = i <= stepper.step
const isActive = i === stepper.step
const tone = toneClasses[stage.tone ?? 'ink']
return (
<div key={`${stage.label}-${i}`} className="flex items-center flex-1 min-w-0">
<button
type="button"
onClick={() => stepper.goTo(i)}
aria-current={isActive ? 'step' : undefined}
className={cn(
'border bg-bg px-2.5 py-2 w-full min-w-0 text-left transition-all duration-200 cursor-pointer',
reached ? tone.box : 'border-rule-2 opacity-45',
isActive && tone.active,
isActive && !reducedMotion && 'fade-rise',
)}
>
<div className="flex items-baseline gap-1.5">
<span className="font-mono text-[9px] tabular-nums shrink-0 text-ink-ghost">
{String(i + 1).padStart(2, '0')}
</span>
<span
className={cn(
'font-mono text-[10.5px] font-semibold leading-[1.35] truncate',
reached ? tone.label : 'text-ink-ghost',
)}
>
{toneGlyph[stage.tone ?? 'ink']}
{stage.label}
</span>
</div>
</button>
{i < stages.length - 1 ? (
<span
aria-hidden
className={cn('h-px w-4 shrink-0', i < stepper.step ? 'bg-ink-faint' : 'bg-rule')}
/>
) : null}
</div>
)
})}
</div>
</div>
{/* active stage panel — caption + the evolving record + note */}
<div className="border-t border-rule px-4 py-3.5 min-h-[120px]">
<div className="flex flex-wrap items-center gap-x-3 gap-y-1.5">
<span className="font-mono text-[11px] text-ink-ghost tabular-nums">
{String(stepper.step + 1).padStart(2, '0')}
</span>
<span
className={cn(
'font-mono text-[14px] font-semibold lowercase',
activeTone === 'alert'
? 'text-alert'
: activeTone === 'warn'
? 'text-warn'
: activeTone === 'accent'
? 'text-accent'
: 'text-ink',
)}
>
{active.label}
</span>
</div>
{active.caption ? (
<p className="mt-1.5 font-mono text-[12.5px] leading-[1.65] text-ink-faint lowercase max-w-[88ch]">
{active.caption}
</p>
) : null}
{active.rows && active.rows.length > 0 ? (
<dl className="mt-2.5 font-mono text-[11.5px] leading-[1.8]">
{active.rows.map((row) => (
<div key={row.k} className="flex gap-x-2">
<dt className="text-ink-ghost w-20 shrink-0 truncate">{row.k}</dt>
<dd className="text-ink tabular-nums">{row.v}</dd>
</div>
))}
</dl>
) : null}
{active.note ? (
<div className="mt-2.5">
<FnChip tone="faint" className="whitespace-normal">
{active.note}
</FnChip>
</div>
) : null}
</div>
<PlayerControls stepper={stepper} total={stages.length} />
</div>
)
}
base/src/components/diagrams/SystemMap.tsx
import { FnChip } from '@lib/components/schematic/FnChip'
import { Prompt } from '@lib/components/schematic/Prompt'
import { StatusDot } from '@lib/components/schematic/StatusDot'
import { cn } from '@lib/lib/utils'
import { type ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react'
/**
* A1 / A4 — the system map archetype.
*
* A data-driven SVG topology: rectangles for nodes, hand-authored Bezier paths
* for edges. Selecting a node highlights the edges that touch it (accent +
* travelling pulse) and surfaces its datasheet. Everything is props — supply a
* `nodes` + `edges` description and a record of `MapNodeInfo` and the component
* does the rest. Hand-author the edge `d` paths against the same coordinate
* space as the node x/y/w/h (default 1030×600 viewBox).
*/
export type MapNodeKind = 'primary' | 'secondary' | 'optional' | 'external'
export interface MapNode {
id: string
x: number
y: number
w: number
h: number
title: string
sub?: string
kind: MapNodeKind
/** override the small uppercase corner tag; defaults from kind */
tag?: string
}
export interface MapEdge {
id: string
from: string
to: string
/** SVG path in the same coordinate space as the nodes */
d: string
label?: string
/** label anchor point */
lx?: number
ly?: number
anchor?: 'start' | 'middle' | 'end'
dashed?: boolean
/** seconds for the travelling pulse on the active edge */
dur?: number
}
const KIND_TAG: Record<MapNodeKind, string> = {
primary: 'core',
secondary: 'worker',
optional: 'optional',
external: 'external',
}
interface SystemMapProps {
nodes: MapNode[]
edges: MapEdge[]
selected: string
onSelect: (id: string) => void
/** SVG coordinate space; author node + edge coordinates to match */
width?: number
height?: number
className?: string
}
export function SystemMap({ nodes, edges, selected, onSelect, width = 1030, height = 600, className }: SystemMapProps) {
const reducedMotion = useMemo(
() => typeof window !== 'undefined' && window.matchMedia('(prefers-reduced-motion: reduce)').matches,
[],
)
const activeEdges = useMemo(
() => new Set(edges.filter((e) => e.from === selected || e.to === selected).map((e) => e.id)),
[edges, selected],
)
const connected = useMemo(() => {
const set = new Set<string>([selected])
for (const e of edges) {
if (e.from === selected) set.add(e.to)
if (e.to === selected) set.add(e.from)
}
return set
}, [edges, selected])
return (
<svg
viewBox={`0 0 ${width} ${height}`}
role="group"
aria-label="system map"
className={cn('w-full h-auto font-mono select-none', className)}
>
<defs>
<marker
id="map-arr-faint"
viewBox="0 0 8 8"
refX="7"
refY="4"
markerWidth="7"
markerHeight="7"
orient="auto-start-reverse"
>
<path d="M 0 0 L 8 4 L 0 8 z" className="fill-ink-ghost" />
</marker>
<marker
id="map-arr-accent"
viewBox="0 0 8 8"
refX="7"
refY="4"
markerWidth="7"
markerHeight="7"
orient="auto-start-reverse"
>
<path d="M 0 0 L 8 4 L 0 8 z" className="fill-accent" />
</marker>
</defs>
{/* edges under nodes */}
{edges.map((edge) => {
const active = activeEdges.has(edge.id)
return (
<g key={edge.id}>
<path
d={edge.d}
fill="none"
strokeWidth={active ? 1.4 : 1}
strokeDasharray={edge.dashed ? '5 4' : undefined}
markerEnd={`url(#${active ? 'map-arr-accent' : 'map-arr-faint'})`}
className={cn('transition-[stroke] duration-200', active ? 'stroke-accent' : 'stroke-rule')}
/>
{active && !reducedMotion ? (
<circle r="2.6" className="fill-accent">
<animateMotion dur={`${edge.dur ?? 2}s`} repeatCount="indefinite" path={edge.d} />
</circle>
) : null}
{edge.label ? (
<text
x={edge.lx ?? 0}
y={edge.ly ?? 0}
textAnchor={edge.anchor ?? 'middle'}
fontSize="9.5"
letterSpacing="0.04em"
className={cn(active ? 'fill-ink' : 'fill-ink-ghost', 'transition-[fill] duration-200')}
style={{
paintOrder: 'stroke',
stroke: 'var(--color-bg)',
strokeWidth: 4,
}}
>
{edge.label}
</text>
) : null}
</g>
)
})}
{/* nodes */}
{nodes.map((node) => {
const isSelected = node.id === selected
const isConnected = connected.has(node.id)
const strong = node.kind === 'primary'
return (
<g
key={node.id}
role="button"
tabIndex={0}
aria-pressed={isSelected}
aria-label={`select ${node.title}`}
onClick={() => onSelect(node.id)}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault()
onSelect(node.id)
}
}}
className="cursor-pointer focus:outline-none group"
>
<rect
x={node.x}
y={node.y}
width={node.w}
height={node.h}
strokeWidth={isSelected ? 1.5 : strong ? 1.25 : 1}
strokeDasharray={node.kind === 'optional' ? '5 4' : undefined}
className={cn(
'transition-all duration-200',
isSelected ? 'fill-panel stroke-accent' : 'fill-bg group-hover:fill-panel',
!isSelected &&
(strong
? isConnected
? 'stroke-ink'
: 'stroke-ink-ghost'
: isConnected
? 'stroke-ink-faint'
: 'stroke-rule'),
)}
/>
{isSelected ? <rect x={node.x} y={node.y} width={3} height={node.h} className="fill-accent" /> : null}
<text
x={node.x + node.w / 2}
y={node.y + node.h / 2 + (node.sub ? -4 : 4)}
textAnchor="middle"
fontSize="14"
fontWeight={600}
className={cn(isSelected || isConnected || strong ? 'fill-ink' : 'fill-ink-faint')}
>
{node.title}
</text>
{node.sub ? (
<text
x={node.x + node.w / 2}
y={node.y + node.h / 2 + 13}
textAnchor="middle"
fontSize="9.5"
letterSpacing="0.05em"
className="fill-ink-ghost"
>
{node.sub}
</text>
) : null}
<text
x={node.x + node.w - 7}
y={node.y + 13}
textAnchor="end"
fontSize="8"
letterSpacing="0.08em"
className={cn('uppercase', isSelected ? 'fill-accent' : 'fill-ink-ghost')}
>
{node.tag ?? KIND_TAG[node.kind]}
</text>
</g>
)
})}
</svg>
)
}
/* ---- datasheet ---- */
export interface InfoItem {
name: string
desc?: string
}
export interface InfoSection {
/** small caps heading, e.g. "surface" or "emits" */
heading: string
items: InfoItem[]
/** render items with a leading status dot (good for events) */
dotted?: boolean
}
export interface MapNodeInfo {
id: string
/** small caps tag in the header, e.g. "core worker" */
kindLabel: string
/** one-line role summary at the top */
role: string
sections?: InfoSection[]
/** closing faint paragraph */
note?: string
/** left-border caveat lines */
bullets?: string[]
/** bottom command chip */
install?: string
}
function ScrollFadePanel({
children,
contentKey,
layoutKey,
}: {
children: ReactNode
contentKey: string
layoutKey?: string | number
}) {
const scrollRef = useRef<HTMLDivElement>(null)
const contentRef = useRef<HTMLDivElement>(null)
const [canScrollUp, setCanScrollUp] = useState(false)
const [canScrollDown, setCanScrollDown] = useState(false)
const updateScrollState = useCallback(() => {
const el = scrollRef.current
if (!el) return
const { scrollTop, scrollHeight, clientHeight } = el
const overflow = scrollHeight - clientHeight > 8
setCanScrollUp(overflow && scrollTop > 4)
setCanScrollDown(overflow && scrollTop + clientHeight < scrollHeight - 4)
}, [])
useEffect(() => {
const scrollEl = scrollRef.current
const contentEl = contentRef.current
if (!scrollEl || !contentEl) return
scrollEl.scrollTop = 0
const sync = () => {
updateScrollState()
}
sync()
requestAnimationFrame(sync)
scrollEl.addEventListener('scroll', sync, { passive: true })
const observer = new ResizeObserver(sync)
observer.observe(scrollEl)
observer.observe(contentEl)
return () => {
scrollEl.removeEventListener('scroll', sync)
observer.disconnect()
}
}, [contentKey, layoutKey, updateScrollState])
return (
<div className="relative min-h-0 flex-1 overflow-hidden">
<div ref={scrollRef} className="h-full overflow-y-auto overscroll-contain [scrollbar-gutter:stable]">
<div ref={contentRef}>{children}</div>
</div>
{canScrollUp ? (
<div
aria-hidden
className="pointer-events-none absolute inset-x-0 top-0 z-10 h-8 border-b border-rule bg-gradient-to-b from-bg via-bg/95 to-transparent"
/>
) : null}
{canScrollDown ? (
<>
<div
aria-hidden
className="pointer-events-none absolute inset-x-0 bottom-0 z-10 h-12 bg-gradient-to-t from-bg via-bg/95 to-transparent"
/>
<div
aria-hidden
className="pointer-events-none absolute inset-x-0 bottom-0 z-10 flex items-end justify-center gap-x-1.5 pb-2"
>
<span className="font-mono text-[9px] uppercase tracking-[0.14em] text-accent">scroll</span>
<span className="font-mono text-[10px] leading-none text-accent">↓</span>
</div>
</>
) : null}
</div>
)
}
/** the side datasheet for the selected map node */
export function MapDatasheet({
info,
className,
layoutKey,
}: {
info: MapNodeInfo
className?: string
layoutKey?: string | number
}) {
return (
<aside className={cn('border border-rule bg-bg flex flex-col min-w-0 min-h-0 overflow-hidden', className)}>
<header className="shrink-0 flex items-center justify-between gap-x-3 bg-panel px-4 py-3 border-b border-rule">
<span className="font-mono text-[16px] font-semibold text-ink">{info.id}</span>
<span className="font-mono text-[10px] uppercase tracking-[0.08em] text-ink-faint whitespace-nowrap">
{info.kindLabel}
</span>
</header>
<ScrollFadePanel contentKey={info.id} layoutKey={layoutKey}>
<div className="px-4 py-3.5 font-mono text-[13px] leading-[1.7] text-ink lowercase border-b border-rule-2">
{info.role}
</div>
{(info.sections ?? []).map((section) => (
<div key={section.heading} className="px-4 py-3.5 border-b border-rule-2">
<div className="font-mono text-[10px] uppercase tracking-[0.14em] text-ink-faint mb-2.5">
{section.heading}
</div>
<ul className="flex flex-col gap-y-2.5">
{section.items.map((item) => (
<li key={item.name} className="min-w-0">
{section.dotted ? (
<span className="flex items-baseline gap-x-2 min-w-0">
<StatusDot tone="accent" className="translate-y-[-1px]" />
<span className="min-w-0">
<span className="font-mono text-[11.5px] text-ink">{item.name}</span>
{item.desc ? (
<span className="font-mono text-[11.5px] text-ink-faint lowercase"> — {item.desc}</span>
) : null}
</span>
</span>
) : (
<>
<FnChip tone="ink" className="max-w-full overflow-hidden">
{item.name}
</FnChip>
{item.desc ? (
<div className="mt-1 font-mono text-[11.5px] leading-[1.6] text-ink-faint lowercase">
{item.desc}
</div>
) : null}
</>
)}
</li>
))}
</ul>
</div>
))}
{info.note ? (
<div className="px-4 py-3.5 font-mono text-[12px] leading-[1.7] text-ink-faint lowercase">{info.note}</div>
) : null}
{info.bullets && info.bullets.length > 0 ? (
<div className="px-4 pb-3.5 flex flex-col gap-y-1.5">
{info.bullets.map((note) => (
<div
key={note}
className="border-l-2 border-rule pl-2.5 font-mono text-[11.5px] leading-[1.6] text-ink-faint lowercase"
>
{note}
</div>
))}
</div>
) : null}
</ScrollFadePanel>
{info.install ? (
<div className="shrink-0 bg-panel border-t border-rule px-4 py-2.5 font-mono text-[12.5px]">
<Prompt symbol="$">{info.install}</Prompt>
</div>
) : null}
</aside>
)
}
base/src/components/Footer.tsx
import { Prompt } from '@lib/components/schematic/Prompt'
import { Wordmark } from '@lib/components/schematic/Wordmark'
import type { FooterSpec } from '@lib/lib/deck-types'
export function Footer({ footer }: { footer: FooterSpec }) {
return (
<footer className="border-t border-rule">
<div className="px-4 py-16 @3xl:px-9 @3xl:py-20">
<div className="font-mono text-[11px] uppercase tracking-[0.06em] text-ink-faint mb-5">
<Prompt symbol="$">{footer.eyebrow}</Prompt>
</div>
<div className="font-mono text-[34px] @3xl:text-[48px] font-semibold lowercase leading-[1.05] tracking-[-0.03em] text-ink max-w-[24ch]">
{footer.headline}
</div>
<div className="mt-8 inline-flex items-center gap-x-2 border border-rule bg-bg px-4 py-3 font-mono text-[13px]">
<Prompt symbol="$" />
<span className="text-ink">{footer.command}</span>
</div>
</div>
<div className="flex flex-wrap items-center justify-between gap-y-3 border-t border-rule px-4 py-4 @3xl:px-9">
<span className="flex items-center gap-x-2.5">
<Wordmark className="h-[14px]" />
<span className="font-mono text-[11px] uppercase tracking-[0.06em] text-ink-faint">{footer.attribution}</span>
</span>
<span className="font-mono text-[11px] uppercase tracking-[0.06em] text-ink-ghost">{footer.source}</span>
</div>
</footer>
)
}
base/src/components/PageShell.tsx
import { Prompt } from '@lib/components/schematic/Prompt'
import type * as React from 'react'
export interface RelatedLink {
/** page slug, used to build the `#/<slug>` href */
slug: string
label: string
}
interface PageShellProps {
/** small caps label above the title, e.g. "deep dive" or "use case" */
eyebrow: string
title: string
description: React.ReactNode
children: React.ReactNode
/** other deep-dive pages to cross-link in the footer */
related?: RelatedLink[]
}
/**
* wrapper for a deep-dive page (`#/<slug>` route): a titled header, a stacked
* content column, and a footer that cross-links sibling pages plus a back link
* to the overview.
*/
export function PageShell({ eyebrow, title, description, children, related = [] }: PageShellProps) {
return (
<main>
<div className="px-4 py-12 @3xl:px-9 @3xl:py-16 border-b border-rule">
<div className="font-mono text-[11px] uppercase tracking-[0.06em] text-ink-faint mb-4">
<Prompt symbol="$">{eyebrow}</Prompt>
</div>
<h1 className="font-mono text-[30px] @3xl:text-[40px] font-medium leading-[1.15] tracking-[-0.02em] text-ink lowercase max-w-[26ch]">
{title}
</h1>
<p className="mt-4 font-mono text-[14px] leading-[1.7] text-ink-faint lowercase max-w-[64ch]">{description}</p>
</div>
<div className="px-4 py-12 @3xl:px-9 flex flex-col gap-10">{children}</div>
<div className="border-t border-rule px-4 py-8 @3xl:px-9 flex flex-wrap items-center gap-x-6 gap-y-3">
{related.length > 0 ? (
<span className="font-mono text-[11px] uppercase tracking-[0.14em] text-ink-faint">more walkthroughs</span>
) : null}
{related.map((other) => (
<a
key={other.slug}
href={`#/${other.slug}`}
className="font-mono text-[13px] lowercase text-ink-faint hover:text-ink transition-colors"
>
{other.label} →
</a>
))}
<a href="#/" className="ml-auto font-mono text-[13px] lowercase text-ink hover:text-accent transition-colors">
← back to the overview
</a>
</div>
</main>
)
}
base/src/components/PlayerControls.tsx
import { Button } from '@lib/components/schematic/Button'
import type { Stepper } from '@lib/hooks/useStepper'
import { cn } from '@lib/lib/utils'
interface PlayerControlsProps {
stepper: Stepper
total: number
label?: string
className?: string
}
/** play / step / reset strip shared by every diagram player */
export function PlayerControls({ stepper, total, label, className }: PlayerControlsProps) {
return (
<div
className={cn(
'flex flex-wrap items-center gap-x-2 gap-y-2 border-t border-rule bg-panel px-3.5 py-2.5',
className,
)}
>
<Button variant="pill" size="sm" onClick={stepper.toggle}>
{stepper.playing ? 'pause' : stepper.atEnd ? 'replay' : 'play'}
</Button>
<Button variant="icon" size="icon" aria-label="previous step" onClick={stepper.prev}>
←
</Button>
<Button variant="icon" size="icon" aria-label="next step" onClick={stepper.next}>
→
</Button>
<Button variant="ghost" size="sm" onClick={stepper.reset}>
reset
</Button>
<span className="ml-auto font-mono text-[11px] uppercase tracking-[0.06em] text-ink-faint tabular-nums">
{label ? `${label} — ` : ''}step {stepper.step + 1}/{total}
</span>
</div>
)
}
base/src/components/schematic/Button.tsx
import { cn } from '@lib/lib/utils'
import * as React from 'react'
type ButtonVariant = 'primary' | 'ghost' | 'pill' | 'icon'
type ButtonSize = 'sm' | 'md' | 'icon'
const variantClasses: Record<ButtonVariant, string> = {
primary: 'bg-ink text-bg border border-ink hover:bg-bg hover:text-ink',
ghost: 'bg-transparent text-ink border border-transparent hover:bg-ink hover:text-bg',
pill: 'bg-bg text-ink border border-ink hover:bg-ink hover:text-bg',
icon: 'bg-bg text-ink-faint border border-rule hover:text-ink',
}
const sizeClasses: Record<ButtonSize, string> = {
sm: 'h-8 px-3 text-[13px]',
md: 'h-9 px-5 text-[13px]',
icon: 'size-[30px] p-0',
}
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: ButtonVariant
size?: ButtonSize
}
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant = 'primary', size = 'md', children, ...props }, ref) => {
return (
<button
ref={ref}
className={cn(
'inline-flex items-center justify-center gap-x-2 whitespace-nowrap font-mono lowercase rounded-none transition-[background-color,color,border-color] duration-150 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-accent disabled:pointer-events-none disabled:opacity-40 select-none cursor-pointer',
variantClasses[variant],
sizeClasses[size],
className,
)}
{...props}
>
{children}
</button>
)
},
)
Button.displayName = 'Button'
base/src/components/schematic/Caret.tsx
import { cn } from '@lib/lib/utils'
interface CaretProps {
className?: string
}
export function Caret({ className }: CaretProps) {
return <span aria-hidden className={cn('blink inline-block w-[6px] h-[13px] bg-ink align-middle', className)} />
}
base/src/components/schematic/Cell.tsx
import { cn } from '@lib/lib/utils'
import type * as React from 'react'
interface CellProps {
title?: React.ReactNode
children: React.ReactNode
className?: string
bodyClassName?: string
}
export function Cell({ title, children, className, bodyClassName }: CellProps) {
return (
<div className={cn('border border-rule bg-bg p-7', className)}>
{title ? (
<div className="font-mono text-[16px] font-semibold tracking-[-0.01em] text-ink mb-3 lowercase">{title}</div>
) : null}
<div className={cn('font-mono text-[13px] leading-[1.7] text-ink-faint', bodyClassName)}>{children}</div>
</div>
)
}
base/src/components/schematic/CodeBlock.tsx
import { cn } from '@lib/lib/utils'
import type * as React from 'react'
interface CodeBlockProps extends Omit<React.HTMLAttributes<HTMLPreElement>, 'title'> {
title?: React.ReactNode
children: React.ReactNode
}
/**
* `bg` fill, 1px rule border, 12.5px mono. Optional `panel` head strip with a
* label-caps title. Light syntax tinting is applied by the caller via
* <K> / <S> / <C> helpers below — accent stays rationed to string literals
* and the active return value.
*/
export function CodeBlock({ title, className, children, ...props }: CodeBlockProps) {
return (
<div className={cn('border border-rule bg-bg min-w-0', className)}>
{title ? (
<div className="bg-panel px-3.5 py-2 border-b border-rule font-mono text-[11px] font-medium uppercase tracking-[0.06em] text-ink-faint">
{title}
</div>
) : null}
<pre className="overflow-x-auto px-5 py-4 font-mono text-[12.5px] leading-[1.55] text-ink" {...props}>
<code>{children}</code>
</pre>
</div>
)
}
/** keyword — bold ink */
export function K({ children }: { children: React.ReactNode }) {
return <span className="font-semibold text-ink">{children}</span>
}
/** string literal — the accent moment of a code block */
export function S({ children }: { children: React.ReactNode }) {
return <span className="text-accent">{children}</span>
}
/** comment — italic ghost */
export function C({ children }: { children: React.ReactNode }) {
return <span className="italic text-ink-ghost">{children}</span>
}
/** muted punctuation / secondary tokens */
export function M({ children }: { children: React.ReactNode }) {
return <span className="text-ink-faint">{children}</span>
}
base/src/components/schematic/FnChip.tsx
import { cn } from '@lib/lib/utils'
import type * as React from 'react'
type ChipTone = 'ink' | 'faint' | 'ghost' | 'accent' | 'alert'
const toneClasses: Record<ChipTone, string> = {
ink: 'border-rule text-ink',
faint: 'border-rule text-ink-faint',
ghost: 'border-rule-2 text-ink-ghost',
accent: 'border-accent text-accent',
alert: 'border-alert text-alert',
}
interface FnChipProps extends React.HTMLAttributes<HTMLSpanElement> {
tone?: ChipTone
children: React.ReactNode
}
/**
* a function-id / trigger-type chip: 1px border, code-sm mono, no fill.
* identifiers keep their original casing — the lowercase rule does not
* apply to ids.
*/
export function FnChip({ tone = 'faint', className, children, ...props }: FnChipProps) {
return (
<span
className={cn(
'inline-flex items-center border bg-bg px-1.5 py-0.5 font-mono text-[11.5px] leading-[1.4] whitespace-nowrap transition-colors',
toneClasses[tone],
className,
)}
{...props}
>
{children}
</span>
)
}
base/src/components/schematic/ModeToggle.tsx
import { cn } from '@lib/lib/utils'
import type * as React from 'react'
interface ModeToggleOption<T extends string> {
value: T
label: React.ReactNode
}
interface ModeToggleProps<T extends string> {
value: T
onChange: (next: T) => void
options: ModeToggleOption<T>[]
className?: string
}
export function ModeToggle<T extends string>({ value, onChange, options, className }: ModeToggleProps<T>) {
return (
<div role="tablist" className={cn('inline-flex border border-rule p-[2px]', className)}>
{options.map((opt) => {
const active = opt.value === value
return (
<button
key={opt.value}
type="button"
role="tab"
aria-pressed={active}
onClick={() => onChange(opt.value)}
className={cn(
'font-mono text-[13px] px-3 py-1 transition-colors lowercase cursor-pointer',
active ? 'bg-ink text-bg' : 'bg-transparent text-ink-faint hover:text-ink',
)}
>
{opt.label}
</button>
)
})}
</div>
)
}
base/src/components/schematic/Prompt.tsx
import { cn } from '@lib/lib/utils'
import type * as React from 'react'
interface PromptProps {
symbol?: string
className?: string
children?: React.ReactNode
}
export function Prompt({ symbol = '$', className, children }: PromptProps) {
return (
<span className={cn('font-mono text-accent', className)}>
{symbol}
{children !== undefined ? <span className="text-ink ml-2">{children}</span> : null}
</span>
)
}
base/src/components/schematic/Sheet.tsx
import { cn } from '@lib/lib/utils'
import type * as React from 'react'
interface SheetProps {
children: React.ReactNode
className?: string
}
export function Sheet({ children, className }: SheetProps) {
return (
<div className={cn('mx-auto w-full max-w-[1200px] border-x border-rule min-h-screen bg-bg', className)}>
{children}
</div>
)
}
base/src/components/schematic/StatusDot.tsx
import { cn } from '@lib/lib/utils'
import type * as React from 'react'
type DotTone = 'accent' | 'alert' | 'warn' | 'ink' | 'ghost'
const dotTone: Record<DotTone, string> = {
accent: 'bg-accent',
alert: 'bg-alert',
warn: 'bg-warn',
ink: 'bg-ink',
ghost: 'bg-ink-ghost',
}
interface StatusDotProps extends React.HTMLAttributes<HTMLSpanElement> {
tone?: DotTone
pulse?: boolean
}
export function StatusDot({ tone = 'accent', pulse, className, ...props }: StatusDotProps) {
return (
<span
aria-hidden
className={cn('inline-block size-1.5 rounded-full shrink-0', dotTone[tone], pulse && 'pulse-dot', className)}
{...props}
/>
)
}
base/src/components/schematic/StatusPanel.tsx
import { cn } from '@lib/lib/utils'
import type * as React from 'react'
export type StatusVariant = 'info' | 'success' | 'warn' | 'alert'
const variantTone: Record<StatusVariant, { border: string; icon: string; headline: string }> = {
info: {
border: 'border-rule',
icon: 'text-ink',
headline: 'text-ink',
},
success: {
border: 'border-accent',
icon: 'text-accent',
headline: 'text-accent',
},
warn: {
border: 'border-warn',
icon: 'text-warn',
headline: 'text-warn',
},
alert: {
border: 'border-alert',
icon: 'text-alert',
headline: 'text-alert',
},
}
interface StatusPanelProps {
variant?: StatusVariant
icon?: React.ReactNode
headline: React.ReactNode
detail?: React.ReactNode
className?: string
}
export function StatusPanel({ variant = 'info', icon, headline, detail, className }: StatusPanelProps) {
const tone = variantTone[variant]
return (
<div className={cn('flex items-start gap-x-3 border bg-bg px-3.5 py-3', tone.border, className)}>
{icon ? (
<span aria-hidden className={cn('size-[18px] shrink-0', tone.icon)}>
{icon}
</span>
) : null}
<div className="min-w-0 flex flex-col gap-y-0.5">
<div className={cn('font-mono text-[13px] font-semibold lowercase', tone.headline)}>{headline}</div>
{detail ? <div className="font-mono text-[12px] text-ink-faint lowercase">{detail}</div> : null}
</div>
</div>
)
}
base/src/components/schematic/Terminal.tsx
import { cn } from '@lib/lib/utils'
import type * as React from 'react'
import { Caret } from './Caret'
import { Prompt } from './Prompt'
interface TerminalProps {
title?: React.ReactNode
children: React.ReactNode
className?: string
}
export function Terminal({ title, children, className }: TerminalProps) {
return (
<div className={cn('border border-rule bg-bg', className)}>
{title ? (
<div className="bg-panel px-3.5 py-2 border-b border-rule font-mono text-[11px] font-medium uppercase tracking-[0.06em] text-ink-faint">
{title}
</div>
) : null}
<div className="p-4 font-mono text-[13px] text-ink">{children}</div>
</div>
)
}
interface TerminalRowProps {
command: React.ReactNode
output?: React.ReactNode
showCaret?: boolean
className?: string
}
export function TerminalRow({ command, output, showCaret, className }: TerminalRowProps) {
return (
<div className={cn('flex flex-col gap-y-1', className)}>
<div className="flex items-center gap-x-2">
<Prompt symbol="$" />
<span className="text-ink">{command}</span>
{showCaret ? <Caret /> : null}
</div>
{output ? <div className="pl-4 text-[12.5px] text-ink-faint">{output}</div> : null}
</div>
)
}
base/src/components/schematic/Wordmark.tsx
import { cn } from '@lib/lib/utils'
/**
* the iii wordmark: three "i"s, each a stem + a tittle, six rectangles total,
* all sharing the same square unit, all in ink. Gaps are half a unit; stems are
* two and a half units tall, so the mark is a square.
*/
export function Wordmark({ className }: { className?: string }) {
const unit = 4
const stems = [0, 6, 12]
return (
<svg aria-label="iii" role="img" viewBox="0 0 16 16" className={cn('h-[18px] w-auto', className)}>
{stems.map((x) => (
<g key={x} className="fill-ink">
<rect x={x} y={0} width={unit} height={unit} />
<rect x={x} y={6} width={unit} height={10} />
</g>
))}
</svg>
)
}
base/src/components/schematic/WorkerCard.tsx
import { cn } from '@lib/lib/utils'
import type * as React from 'react'
interface WorkerCardProps {
name: string
version?: string
description: React.ReactNode
command: React.ReactNode
kind: string
focused?: boolean
className?: string
}
export function WorkerCard({ name, version, description, command, kind, focused, className }: WorkerCardProps) {
return (
<article
className={cn(
'border border-rule transition-colors',
focused ? 'bg-panel border-l-2 border-l-accent' : 'bg-bg',
className,
)}
>
<header className="flex items-center justify-between px-4 py-3 border-b border-rule-2">
<div className="font-mono text-[16px] font-semibold lowercase text-ink">{name}</div>
{version ? (
<div className="font-mono text-[11px] uppercase tracking-[0.06em] text-ink-ghost tabular-nums">
v{version}
</div>
) : null}
</header>
<div className="px-4 py-3 font-mono text-[13px] leading-[1.7] text-ink-faint">{description}</div>
<div className="bg-panel font-mono text-[12.5px] text-ink px-4 py-2 border-t border-rule-2">{command}</div>
<footer className="flex items-center justify-between px-4 py-2 border-t border-rule-2">
<span className="font-mono text-[11px] uppercase tracking-[0.06em] text-ink-faint">{kind}</span>
<span aria-hidden className="text-accent">
✓
</span>
</footer>
</article>
)
}
base/src/components/Section.tsx
import { cn } from '@lib/lib/utils'
import type * as React from 'react'
import { useEffect, useRef, useState } from 'react'
interface SectionProps {
id: string
index: string // drafting-sheet section number, e.g. "01"
eyebrow: string
title: React.ReactNode
lede?: React.ReactNode
children: React.ReactNode
className?: string
}
/**
* a numbered drafting-sheet section: full-width top rule, label-caps eyebrow
* with the sheet number on the right, headline-section title, ink-faint lede.
* content reveals on first scroll into view.
*/
export function Section({ id, index, eyebrow, title, lede, children, className }: SectionProps) {
const ref = useRef<HTMLElement>(null)
const [shown, setShown] = useState(false)
useEffect(() => {
const el = ref.current
if (!el) return
const observer = new IntersectionObserver(
(entries) => {
for (const entry of entries) {
if (entry.isIntersecting) {
setShown(true)
observer.disconnect()
}
}
},
{ rootMargin: '0px 0px -10% 0px' },
)
observer.observe(el)
return () => observer.disconnect()
}, [])
return (
<section id={id} ref={ref} className={cn('border-t border-rule scroll-mt-[64px]', className)}>
<div className="flex items-center justify-between px-4 py-2.5 border-b border-rule-2 @3xl:px-9">
<span className="font-mono text-[12px] font-medium uppercase tracking-[0.18em] text-ink-faint">{eyebrow}</span>
<span className="font-mono text-[12px] font-medium uppercase tracking-[0.14em] text-ink-ghost tabular-nums">
{index}
</span>
</div>
<div className={cn('px-4 py-12 @3xl:px-9 @3xl:py-16 reveal', shown && 'reveal-in')}>
<h2 className="font-mono text-[28px] font-medium leading-[1.25] tracking-[-0.01em] text-ink lowercase max-w-[28ch]">
{title}
</h2>
{lede ? (
<p className="mt-4 font-mono text-[14px] leading-[1.7] text-ink-faint lowercase max-w-[62ch]">{lede}</p>
) : null}
<div className="mt-10">{children}</div>
</div>
</section>
)
}
base/src/components/SpecSheet.tsx
import { cn } from '@lib/lib/utils'
import type * as React from 'react'
interface SpecSheetProps {
title: React.ReactNode
meta?: React.ReactNode
children: React.ReactNode
defaultOpen?: boolean
className?: string
}
/**
* the expandable technical layer: a native <details> datasheet with a
* panel-tinted head strip. closed by default so the page stays a story;
* the spec is one click away.
*/
export function SpecSheet({ title, meta, children, defaultOpen, className }: SpecSheetProps) {
return (
<details className={cn('group border border-rule bg-bg', className)} open={defaultOpen}>
<summary className="flex cursor-pointer select-none items-center justify-between gap-x-3 bg-panel px-3.5 py-2.5 border-b border-transparent group-open:border-rule list-none [&::-webkit-details-marker]:hidden">
<span className="flex items-center gap-x-2.5 min-w-0">
<span aria-hidden className="font-mono text-[13px] text-accent w-3 shrink-0">
<span className="group-open:hidden">+</span>
<span className="hidden group-open:inline">−</span>
</span>
<span className="font-mono text-[12px] font-medium uppercase tracking-[0.14em] text-ink-faint truncate">
{title}
</span>
</span>
{meta ? (
<span className="font-mono text-[11px] uppercase tracking-[0.06em] text-ink-ghost shrink-0">{meta}</span>
) : null}
</summary>
<div className="p-5">{children}</div>
</details>
)
}
interface SpecRowProps {
name: string
type?: string
children?: React.ReactNode
required?: boolean
indent?: boolean
}
/** one row of a type-table: `name type required-marker` + faint description */
export function SpecRow({ name, type, children, required, indent }: SpecRowProps) {
return (
<div className={cn('py-1.5 border-b border-rule-2 last:border-b-0', indent && 'pl-4 border-l border-rule-2')}>
<div className="flex flex-wrap items-baseline gap-x-3">
<span className="font-mono text-[12.5px] text-ink">
{name}
{required ? <span className="text-accent">*</span> : null}
</span>
{type ? <span className="font-mono text-[11.5px] text-ink-ghost">{type}</span> : null}
</div>
{children ? (
<div className="mt-0.5 font-mono text-[12px] leading-[1.6] text-ink-faint lowercase">{children}</div>
) : null}
</div>
)
}
base/src/components/TopNav.tsx
import { ModeToggle } from '@lib/components/schematic/ModeToggle'
import { Wordmark } from '@lib/components/schematic/Wordmark'
import type { Route } from '@lib/hooks/useHashRoute'
import { useTheme } from '@lib/hooks/useTheme'
import type { DeckMeta, NavItem } from '@lib/lib/deck-types'
import { cn } from '@lib/lib/utils'
import { useEffect, useState } from 'react'
export function TopNav({
route,
meta,
nav,
specHref = '#/spec',
}: {
route: Route
meta: DeckMeta
nav: NavItem[]
/** set to null to hide the spec link (e.g. in the md-only viewer) */
specHref?: string | null
}) {
const [theme, setTheme] = useTheme()
const [active, setActive] = useState<string | null>(null)
// scroll-spy: highlight the section currently in view (home page only)
useEffect(() => {
if (route.kind !== 'home') return
const sections = nav.map((l) => document.getElementById(l.id)).filter((el): el is HTMLElement => el !== null)
if (sections.length === 0) return
const observer = new IntersectionObserver(
(entries) => {
for (const entry of entries) {
if (entry.isIntersecting) setActive(entry.target.id)
}
},
{ rootMargin: '-20% 0px -70% 0px' },
)
for (const el of sections) observer.observe(el)
return () => observer.disconnect()
}, [route, nav])
return (
<header className="sticky top-0 z-50 border-b border-rule bg-bg">
<div className="flex items-center gap-x-5 px-4 py-2.5 @3xl:px-9">
<a href="#/" className="flex items-center gap-x-2.5 shrink-0" aria-label="iii — back to overview">
<Wordmark />
<span className="font-mono text-[13px] font-semibold lowercase text-ink hidden @lg:inline">
{meta.wordmarkLabel}
</span>
</a>
{route.kind === 'home' ? (
<nav className="hidden @3xl:flex items-center gap-x-4 min-w-0 overflow-x-auto">
{nav.map((link) => (
<a
key={link.id}
href={`#${link.id}`}
className={cn(
'font-mono text-[12px] lowercase py-1.5 transition-colors whitespace-nowrap',
active === link.id ? 'text-accent' : 'text-ink-faint hover:text-ink',
)}
>
{link.label}
</a>
))}
</nav>
) : (
<a href="#/" className="font-mono text-[12px] lowercase text-ink-faint hover:text-ink transition-colors">
← back to the overview
</a>
)}
<div className="ml-auto flex items-center gap-x-4 shrink-0">
{specHref ? (
<a
href={specHref}
className={cn(
'font-mono text-[12px] lowercase whitespace-nowrap transition-colors',
route.kind === 'page' && route.slug === 'spec' ? 'text-accent' : 'text-ink-faint hover:text-ink',
)}
>
spec
</a>
) : null}
<ModeToggle
value={theme}
onChange={setTheme}
options={[
{ value: 'light', label: 'light' },
{ value: 'dark', label: 'dark' },
]}
/>
</div>
</div>
</header>
)
}
base/src/content/highlight.tsx
import { type CSSProperties, Fragment, type ReactNode } from 'react'
/**
* A dependency-free, multi-color ("rainbow") syntax highlighter for the four
* languages codegen emits. It classifies tokens into keyword / string / comment
* / type / function / number / operator / plain and colors each from a
* dedicated syntax palette (see HighlightStyles). The palette is theme-aware: a
* darker saturated set on the light sheet, a brighter One-Dark-style set under
* [data-theme="dark"]. This intentionally overrides the deck's rationed-accent
* rule for code blocks.
*/
export type HlLang = 'typescript' | 'javascript' | 'rust' | 'python' | 'yaml'
const KEYWORDS: Record<HlLang, Set<string>> = {
typescript: new Set([
'export',
'import',
'from',
'interface',
'type',
'async',
'function',
'return',
'const',
'let',
'await',
'new',
'class',
'extends',
'implements',
'as',
'void',
'in',
'of',
'typeof',
'keyof',
'string',
'number',
'boolean',
]),
javascript: new Set([
'export',
'import',
'from',
'async',
'function',
'return',
'const',
'let',
'await',
'new',
'class',
'extends',
'as',
'in',
'of',
'typeof',
]),
rust: new Set([
'use',
'pub',
'type',
'struct',
'fn',
'async',
'let',
'await',
'crate',
'mut',
'match',
'impl',
'mod',
'as',
'return',
'self',
'Self',
'where',
'dyn',
'move',
'enum',
'trait',
'const',
'static',
'ref',
]),
python: new Set([
'from',
'import',
'class',
'def',
'return',
'None',
'True',
'False',
'and',
'or',
'not',
'in',
'is',
'if',
'else',
'elif',
'for',
'while',
'await',
'async',
'with',
'as',
'pass',
'lambda',
'yield',
'try',
'except',
'raise',
'str',
'int',
'float',
'bool',
]),
yaml: new Set(['true', 'false', 'null', 'yes', 'no', 'on', 'off']),
}
type TokKind = 'ws' | 'comment' | 'string' | 'number' | 'keyword' | 'type' | 'function' | 'punct' | 'plain'
interface Tok {
kind: TokKind
value: string
}
const isSpace = (c: string) => c === ' ' || c === '\t' || c === '\n' || c === '\r'
const isWordStart = (c: string) => /[A-Za-z_$]/.test(c)
const isWord = (c: string) => /[A-Za-z0-9_$]/.test(c)
const isDigit = (c: string) => c >= '0' && c <= '9'
function tokenize(code: string, lang: HlLang): Tok[] {
const kw = KEYWORDS[lang]
const hashComments = lang === 'python'
const slashComments = lang !== 'python'
const toks: Tok[] = []
const n = code.length
let i = 0
while (i < n) {
const c = code[i]
if (isSpace(c)) {
let j = i + 1
while (j < n && isSpace(code[j])) j++
toks.push({ kind: 'ws', value: code.slice(i, j) })
i = j
continue
}
if (hashComments && c === '#') {
let j = i + 1
while (j < n && code[j] !== '\n') j++
toks.push({ kind: 'comment', value: code.slice(i, j) })
i = j
continue
}
if (slashComments && c === '/' && code[i + 1] === '/') {
let j = i + 2
while (j < n && code[j] !== '\n') j++
toks.push({ kind: 'comment', value: code.slice(i, j) })
i = j
continue
}
if (slashComments && c === '/' && code[i + 1] === '*') {
let j = i + 2
while (j < n && !(code[j] === '*' && code[j + 1] === '/')) j++
j = Math.min(n, j + 2)
toks.push({ kind: 'comment', value: code.slice(i, j) })
i = j
continue
}
if (c === '"' || c === "'") {
let j = i + 1
while (j < n && code[j] !== c) {
if (code[j] === '\\') j++
j++
}
j = Math.min(n, j + 1)
toks.push({ kind: 'string', value: code.slice(i, j) })
i = j
continue
}
if (isDigit(c)) {
let j = i + 1
while (j < n && (isWord(code[j]) || code[j] === '.')) j++
toks.push({ kind: 'number', value: code.slice(i, j) })
i = j
continue
}
if (isWordStart(c)) {
let j = i + 1
while (j < n && isWord(code[j])) j++
const word = code.slice(i, j)
const next = code[j]
let kind: TokKind
if (kw.has(word)) kind = 'keyword'
else if (next === '(' || next === '<') kind = 'function'
else if (/^[A-Z]/.test(word)) kind = 'type'
else kind = 'plain'
toks.push({ kind, value: word })
i = j
continue
}
// a run of operator / punctuation chars, stopping before tokens a later
// pass should own
let j = i + 1
while (
j < n &&
!isSpace(code[j]) &&
!isWordStart(code[j]) &&
!isDigit(code[j]) &&
code[j] !== '"' &&
code[j] !== "'" &&
code[j] !== '/'
) {
j++
}
toks.push({ kind: 'punct', value: code.slice(i, j) })
i = j
}
return toks
}
function tokenizeYamlValue(text: string, toks: Tok[]) {
const consts = KEYWORDS.yaml
const n = text.length
let i = 0
while (i < n) {
const c = text[i]
if (isSpace(c)) {
let j = i + 1
while (j < n && isSpace(text[j])) j++
toks.push({ kind: 'ws', value: text.slice(i, j) })
i = j
continue
}
if (c === '#') {
toks.push({ kind: 'comment', value: text.slice(i) })
i = n
continue
}
if (c === '"' || c === "'") {
let j = i + 1
while (j < n && text[j] !== c) {
if (text[j] === '\\') j++
j++
}
j = Math.min(n, j + 1)
toks.push({ kind: 'string', value: text.slice(i, j) })
i = j
continue
}
if (isDigit(c)) {
let j = i + 1
while (j < n && (isWord(text[j]) || text[j] === '.')) j++
toks.push({ kind: 'number', value: text.slice(i, j) })
i = j
continue
}
if (isWordStart(c)) {
let j = i + 1
while (j < n && isWord(text[j])) j++
const w = text.slice(i, j)
toks.push({ kind: consts.has(w) ? 'keyword' : 'plain', value: w })
i = j
continue
}
let j = i + 1
while (
j < n &&
!isSpace(text[j]) &&
!isWordStart(text[j]) &&
!isDigit(text[j]) &&
text[j] !== '"' &&
text[j] !== "'" &&
text[j] !== '#'
) {
j++
}
toks.push({ kind: 'punct', value: text.slice(i, j) })
i = j
}
}
function tokenizeYaml(code: string): Tok[] {
const toks: Tok[] = []
const lines = code.split('\n')
lines.forEach((line, idx) => {
if (idx > 0) toks.push({ kind: 'ws', value: '\n' })
const indent = (line.match(/^\s*/) as RegExpMatchArray)[0]
if (indent) toks.push({ kind: 'ws', value: indent })
let rest = line.slice(indent.length)
if (rest === '') return
if (rest[0] === '#') {
toks.push({ kind: 'comment', value: rest })
return
}
if (rest[0] === '-' && (rest[1] === ' ' || rest.length === 1)) {
toks.push({ kind: 'punct', value: '-' })
rest = rest.slice(1)
}
const keyMatch = rest.match(/^([\w./@-]+)(:)(?=\s|$)/)
if (keyMatch) {
toks.push({ kind: 'keyword', value: keyMatch[1] })
toks.push({ kind: 'punct', value: ':' })
rest = rest.slice(keyMatch[0].length)
}
tokenizeYamlValue(rest, toks)
})
return toks
}
const COLOR: Partial<Record<TokKind, string>> = {
comment: 'var(--cg-comment)',
string: 'var(--cg-string)',
number: 'var(--cg-number)',
keyword: 'var(--cg-keyword)',
type: 'var(--cg-type)',
function: 'var(--cg-function)',
punct: 'var(--cg-punct)',
}
/** The syntax palette. Render once per page. */
export function HighlightStyles() {
const css = `
.cg-hl {
--cg-plain: #0a0a0a;
--cg-comment: #9a948c;
--cg-keyword: #9333ea;
--cg-string: #15803d;
--cg-number: #b45309;
--cg-type: #0e7490;
--cg-function: #1d4ed8;
--cg-punct: #6b6865;
color: var(--cg-plain);
}
[data-theme="dark"] .cg-hl {
--cg-plain: #e6e6e6;
--cg-comment: #7f848e;
--cg-keyword: #c678dd;
--cg-string: #98c379;
--cg-number: #d19a66;
--cg-type: #e5c07b;
--cg-function: #61afef;
--cg-punct: #abb2bf;
}`
return <style>{css}</style>
}
export function Highlight({ code, lang }: { code: string; lang: HlLang }): ReactNode {
const toks = lang === 'yaml' ? tokenizeYaml(code) : tokenize(code, lang)
return (
<span className="cg-hl">
{toks.map((tok, idx) => {
const color = COLOR[tok.kind]
if (!color) return <Fragment key={idx}>{tok.value}</Fragment>
const style: CSSProperties = { color }
if (tok.kind === 'comment') style.fontStyle = 'italic'
return (
<span key={idx} style={style}>
{tok.value}
</span>
)
})}
</span>
)
}
base/src/content/markdown.tsx
import { CodeBlock } from '@lib/components/schematic/CodeBlock'
import { Highlight, type HlLang } from '@lib/content/highlight'
import { Mermaid } from '@lib/content/mermaid'
import { cn } from '@lib/lib/utils'
import { marked } from 'marked'
import { Fragment, type MouseEvent, type ReactNode } from 'react'
/**
* Renders a markdown string as React, styled in the deck's drafting-sheet
* system. Fenced code reuses the deck's syntax highlighter where the language
* is supported (monochrome otherwise); ```mermaid fences render as live,
* themed diagrams. Spec content keeps its original casing (the deck's lowercase
* rule applies to chrome copy, not rendered docs).
*
* Input is locally-authored, trusted spec markdown (not user input).
*/
const LANG_MAP: Record<string, HlLang> = {
ts: 'typescript',
typescript: 'typescript',
tsx: 'typescript',
js: 'javascript',
jsx: 'javascript',
javascript: 'javascript',
mjs: 'javascript',
rs: 'rust',
rust: 'rust',
py: 'python',
python: 'python',
yml: 'yaml',
yaml: 'yaml',
}
/** GitHub-style heading slug (matches the anchors the specs link to). */
export function slugify(text: string): string {
return text
.trim()
.toLowerCase()
.replace(/[^\w\s-]/g, '')
.replace(/\s+/g, '-')
}
/** Rewrite a markdown href: sibling `*.md` links point into the spec viewer. */
function rewriteHref(href: string): string {
if (!href) return '#'
if (href.startsWith('#')) return href
const m = href.match(/^\.?\/?([\w./-]+?)\.md(?:#.*)?$/)
if (m) {
const base = m[1].split('/').pop() ?? m[1]
return `#/spec/${base.toLowerCase()}`
}
return href
}
// marked's Token union is broad; this renderer reads a known subset of fields.
type Tok = { type: string; [k: string]: unknown }
function renderInline(tokens: Tok[] = [], keyPrefix = 'i'): ReactNode[] {
return tokens.map((t, i) => {
const key = `${keyPrefix}-${i}`
const sub = (t.tokens as Tok[] | undefined) ?? []
switch (t.type) {
case 'text':
return sub.length ? (
<Fragment key={key}>{renderInline(sub, key)}</Fragment>
) : (
<Fragment key={key}>{t.text as string}</Fragment>
)
case 'strong':
return (
<strong key={key} className="font-semibold text-ink">
{renderInline(sub, key)}
</strong>
)
case 'em':
return (
<em key={key} className="italic">
{renderInline(sub, key)}
</em>
)
case 'del':
return (
<del key={key} className="text-ink-ghost line-through">
{renderInline(sub, key)}
</del>
)
case 'codespan':
return (
<code key={key} className="font-mono text-[0.88em] text-ink bg-panel px-1 py-0.5 border border-rule">
{t.text as string}
</code>
)
case 'link': {
const href = rewriteHref(t.href as string)
const external = /^https?:/.test(href)
// a same-page anchor (`#section`) must scroll in place, not change the
// hash to a bare `#…` (which the router would read as "go home")
const anchor = href.startsWith('#') && !href.startsWith('#/')
return (
<a
key={key}
href={href}
className="text-accent hover:underline underline-offset-2"
{...(external ? { target: '_blank', rel: 'noreferrer' } : {})}
{...(anchor
? {
onClick: (e: MouseEvent) => {
e.preventDefault()
document.getElementById(decodeURIComponent(href.slice(1)))?.scrollIntoView({ behavior: 'smooth' })
},
}
: {})}
>
{renderInline(sub, key)}
</a>
)
}
case 'br':
return <br key={key} />
case 'escape':
return <Fragment key={key}>{t.text as string}</Fragment>
default:
return <Fragment key={key}>{(t.text as string) ?? ''}</Fragment>
}
})
}
function renderTokens(tokens: Tok[] = [], keyPrefix = 'b'): ReactNode[] {
return tokens.map((t, i) => {
const key = `${keyPrefix}-${i}`
const sub = (t.tokens as Tok[] | undefined) ?? []
switch (t.type) {
case 'heading': {
const id = slugify(t.text as string)
const inner = renderInline(sub, key)
const depth = t.depth as number
if (depth <= 1)
return (
<h1
key={key}
id={id}
className="font-mono font-semibold text-ink text-[24px] @3xl:text-[28px] leading-tight mt-0 mb-4 scroll-mt-20"
>
{inner}
</h1>
)
if (depth === 2)
return (
<h2
key={key}
id={id}
className="font-mono font-medium text-ink text-[18px] leading-snug mt-10 mb-3 pt-5 border-t border-rule scroll-mt-20"
>
{inner}
</h2>
)
if (depth === 3)
return (
<h3 key={key} id={id} className="font-mono font-semibold text-ink text-[14px] mt-6 mb-2 scroll-mt-20">
{inner}
</h3>
)
return (
<h4
key={key}
id={id}
className="font-mono font-semibold text-ink-faint text-[11px] uppercase tracking-[0.08em] mt-5 mb-2 scroll-mt-20"
>
{inner}
</h4>
)
}
case 'paragraph':
return (
<p key={key} className="font-mono text-[13px] @3xl:text-[14px] leading-[1.7] text-ink-faint my-3">
{renderInline(sub, key)}
</p>
)
case 'text':
return sub.length ? (
<Fragment key={key}>{renderInline(sub, key)}</Fragment>
) : (
<Fragment key={key}>{t.text as string}</Fragment>
)
case 'code': {
const code = (t.text as string) ?? ''
const lang = ((t.lang as string) ?? '').trim().toLowerCase().split(/\s+/)[0]
if (lang === 'mermaid') return <Mermaid key={key} chart={code} />
const hl = LANG_MAP[lang]
return (
<div key={key} className="my-4">
<CodeBlock title={lang || undefined}>{hl ? <Highlight code={code} lang={hl} /> : code}</CodeBlock>
</div>
)
}
case 'blockquote':
return (
<blockquote key={key} className="border-l-2 border-rule pl-4 my-4 [&>p]:text-ink-faint [&>p]:my-2">
{renderTokens(sub, key)}
</blockquote>
)
case 'list': {
const ordered = Boolean(t.ordered)
const items = (t.items as Tok[]) ?? []
const className = cn(
'my-3 pl-5 font-mono text-[13px] @3xl:text-[14px] leading-[1.7] text-ink-faint space-y-1.5 marker:text-ink-ghost',
ordered ? 'list-decimal' : 'list-disc',
)
const lis = items.map((it, j) => (
<li key={`${key}-${j}`} className="pl-1">
{renderTokens((it.tokens as Tok[]) ?? [], `${key}-${j}`)}
</li>
))
return ordered ? (
<ol key={key} className={className}>
{lis}
</ol>
) : (
<ul key={key} className={className}>
{lis}
</ul>
)
}
case 'table': {
const header = (t.header as Tok[]) ?? []
const rows = (t.rows as Tok[][]) ?? []
return (
<div key={key} className="my-4 overflow-x-auto">
<table className="w-full border-collapse text-[13px] min-w-[480px]">
<thead>
<tr>
{header.map((cell, c) => (
<th
key={c}
className="border border-rule bg-panel px-3 py-2 text-left font-mono text-[10px] uppercase tracking-[0.06em] text-ink-faint"
>
{renderInline((cell.tokens as Tok[]) ?? [], `${key}-h-${c}`)}
</th>
))}
</tr>
</thead>
<tbody>
{rows.map((row, r) => (
<tr key={r}>
{row.map((cell, c) => (
<td
key={c}
className="border border-rule px-3 py-2 align-top font-mono text-ink-faint leading-[1.6]"
>
{renderInline((cell.tokens as Tok[]) ?? [], `${key}-${r}-${c}`)}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
)
}
case 'hr':
return <hr key={key} className="my-8 border-0 border-t border-rule" />
case 'space':
return null
case 'html':
return (
<div
key={key}
className="contents"
// biome-ignore lint/security/noDangerouslySetInnerHtml: trusted local spec markdown
dangerouslySetInnerHTML={{ __html: t.text as string }}
/>
)
default:
return null
}
})
}
// spec READMEs open with a YAML frontmatter block (site metadata, parsed by
// scripts/manifest.mjs) — never render it as prose
const FRONTMATTER_RE = /^---\r?\n[\s\S]*?\r?\n---\r?\n?/
export function Markdown({ source }: { source: string }) {
const tokens = marked.lexer(source.replace(FRONTMATTER_RE, '')) as unknown as Tok[]
return <div className="min-w-0">{renderTokens(tokens)}</div>
}
base/src/content/mermaid.tsx
import { CodeBlock } from '@lib/components/schematic/CodeBlock'
import { useEffect, useId, useState } from 'react'
/**
* A mermaid diagram themed to the drafting sheet (mono font, ink borders, panel
* fills, light/dark aware). Renders the SVG client-side and re-renders when the
* deck's data-theme flips. Falls back to the diagram source while rendering or
* if mermaid throws.
*/
function themeVariables(dark: boolean) {
return dark
? {
background: '#111110',
mainBkg: '#1a1916',
primaryColor: '#1a1916',
primaryBorderColor: '#f2f0ed',
primaryTextColor: '#f2f0ed',
secondaryColor: '#1f1e1c',
tertiaryColor: '#1f1e1c',
lineColor: '#9c9893',
textColor: '#f2f0ed',
nodeBorder: '#f2f0ed',
clusterBkg: '#1f1e1c',
clusterBorder: '#2a2926',
titleColor: '#f2f0ed',
edgeLabelBackground: '#111110',
actorBkg: '#1a1916',
actorBorder: '#f2f0ed',
actorTextColor: '#f2f0ed',
signalColor: '#9c9893',
signalTextColor: '#f2f0ed',
labelBoxBkgColor: '#1a1916',
labelBoxBorderColor: '#2a2926',
}
: {
background: '#f2f0ed',
mainBkg: '#e9e6e2',
primaryColor: '#e9e6e2',
primaryBorderColor: '#0a0a0a',
primaryTextColor: '#0a0a0a',
secondaryColor: '#ebe8e3',
tertiaryColor: '#f2f0ed',
lineColor: '#6b6865',
textColor: '#0a0a0a',
nodeBorder: '#0a0a0a',
clusterBkg: '#ebe8e3',
clusterBorder: '#d8d5d0',
titleColor: '#0a0a0a',
edgeLabelBackground: '#f2f0ed',
actorBkg: '#e9e6e2',
actorBorder: '#0a0a0a',
actorTextColor: '#0a0a0a',
signalColor: '#6b6865',
signalTextColor: '#0a0a0a',
labelBoxBkgColor: '#e9e6e2',
labelBoxBorderColor: '#d8d5d0',
}
}
export function Mermaid({ chart }: { chart: string }) {
const rawId = useId()
const id = `mmd-${rawId.replace(/[^a-zA-Z0-9]/g, '')}`
const [svg, setSvg] = useState<string | null>(null)
useEffect(() => {
let cancelled = false
const render = async () => {
// lazy-load mermaid (~1.5MB) so decks without mermaid fences never ship it
const { default: mermaid } = await import('mermaid')
const dark = document.documentElement.dataset.theme === 'dark'
mermaid.initialize({
startOnLoad: false,
securityLevel: 'strict',
theme: 'base',
fontFamily: '"Chivo Mono", ui-monospace, monospace',
themeVariables: themeVariables(dark),
})
try {
const { svg } = await mermaid.render(id, chart)
if (!cancelled) setSvg(svg)
} catch {
if (!cancelled) setSvg(null)
}
}
void render()
const observer = new MutationObserver(() => void render())
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ['data-theme'],
})
return () => {
cancelled = true
observer.disconnect()
}
}, [chart, id])
if (!svg) {
return <CodeBlock title="mermaid">{chart}</CodeBlock>
}
return (
<div className="my-5 overflow-x-auto border border-rule bg-bg p-4">
<div
className="flex justify-center [&_svg]:max-w-full [&_svg]:h-auto"
// biome-ignore lint/security/noDangerouslySetInnerHtml: trusted, locally-authored spec diagram rendered by mermaid
dangerouslySetInnerHTML={{ __html: svg }}
/>
</div>
)
}
base/src/gallery/App.tsx
import { SPECS } from 'virtual:spec-manifest'
import { Prompt } from '@lib/components/schematic/Prompt'
import { Sheet } from '@lib/components/schematic/Sheet'
import { Gallery } from './Gallery'
import { SiteFooter } from './SiteFooter'
import { SiteHeader } from './SiteHeader'
import { SITE } from './site'
function Hero() {
const live = SPECS.filter((s) => s.status !== 'draft')
const decks = SPECS.filter((s) => s.hasDeck).length
const draft = SPECS.length - live.length
// the newest shipped spec — the "where the roadmap is right now" stat
const latest = [...live].sort((a, b) => b.date.localeCompare(a.date))[0]
const stats = [
{ value: String(SPECS.length), label: SPECS.length === 1 ? 'spec' : 'specs' },
{ value: String(decks), label: 'interactive' },
...(latest ? [{ value: latest.dayLabel ?? latest.month, label: 'latest' }] : []),
...(draft > 0 ? [{ value: String(draft), label: 'in draft' }] : []),
]
return (
<section className="border-b border-rule px-4 py-16 @3xl:px-9 @3xl:py-20">
<div className="font-mono text-[11px] uppercase tracking-[0.18em] text-ink-faint mb-6">
<Prompt symbol="//">{SITE.heroEyebrow}</Prompt>
</div>
<h1 className="font-mono text-[34px] @3xl:text-[52px] font-semibold lowercase leading-[1.05] tracking-[-0.03em] text-ink max-w-[20ch]">
{SITE.heroTitle}
</h1>
<p className="mt-6 font-mono text-[14px] @3xl:text-[15px] leading-[1.7] text-ink-faint max-w-[64ch]">
{SITE.heroLead}
</p>
<div className="mt-10 flex flex-wrap items-stretch border border-rule w-fit">
{stats.map((s, i) => (
<div key={s.label} className={`px-5 py-4 ${i > 0 ? 'border-l border-rule' : ''}`}>
<div className="font-mono text-[24px] @3xl:text-[28px] font-semibold leading-none text-ink">{s.value}</div>
<div className="mt-1.5 font-mono text-[11px] uppercase tracking-[0.06em] text-ink-faint">{s.label}</div>
</div>
))}
</div>
</section>
)
}
export default function App() {
return (
<div className="@container min-h-screen">
<Sheet>
<SiteHeader />
<main>
<Hero />
<section id="roadmap" className="px-4 py-12 @3xl:px-9 @3xl:py-16">
<div className="font-mono text-[11px] uppercase tracking-[0.14em] text-ink-ghost mb-8">the roadmap</div>
<Gallery />
</section>
</main>
<SiteFooter />
</Sheet>
</div>
)
}
base/src/gallery/Gallery.tsx
import { SPECS } from 'virtual:spec-manifest'
import type { SpecEntry } from 'virtual:spec-manifest'
import { PresentationCard } from './PresentationCard'
interface TimelineItem {
spec: SpecEntry
index: number
}
interface MonthGroup {
month: string
items: TimelineItem[]
}
/**
* the roadmap: one column, strictly newest first, grouped under month
* markers on a vertical rule. the manifest pre-sorts featured-first for the
* landing feed (dist/index.json); a timeline is chronological by definition,
* so it re-sorts by date here and ignores pinning.
*/
export function Gallery() {
if (SPECS.length === 0) {
return (
<div className="border border-rule bg-bg px-6 py-16 text-center font-mono text-[13px] lowercase text-ink-faint">
no specs yet. add <span className="text-ink">tech-specs/<yyyy-mm-dd-slug>/README.md</span> with a
frontmatter block to list the first one.
</div>
)
}
const ordered = [...SPECS].sort((a, b) => b.date.localeCompare(a.date))
const groups: MonthGroup[] = []
ordered.forEach((spec, index) => {
const last = groups[groups.length - 1]
const item = { spec, index }
if (last && last.month === spec.month) last.items.push(item)
else groups.push({ month: spec.month, items: [item] })
})
return (
<ol className="relative max-w-[860px] border-l border-rule">
{groups.map((group, g) => (
<li key={group.month} className={g > 0 ? 'pt-12' : ''}>
<div className="relative pl-6 @3xl:pl-9">
<span
aria-hidden
className="absolute left-0 top-[3px] size-[9px] -translate-x-1/2 border border-rule bg-panel"
/>
<h3 className="font-mono text-[11px] uppercase tracking-[0.18em] leading-none text-ink-ghost">
{group.month}
</h3>
</div>
<ol>
{group.items.map(({ spec, index }) => (
<li key={spec.slug} className="relative mt-7 pl-6 @3xl:pl-9">
<span
aria-hidden
className={`absolute left-0 top-[4px] size-[7px] -translate-x-1/2 rounded-full ${
index === 0 ? 'bg-accent pulse-dot' : 'bg-ink-ghost'
}`}
/>
<div className="font-mono text-[11px] uppercase tracking-[0.14em] leading-none text-ink-faint">
{spec.dayLabel ?? spec.month}
{index === 0 ? <span className="text-accent"> · latest</span> : null}
</div>
<div className="mt-3">
<PresentationCard spec={spec} index={index} />
</div>
</li>
))}
</ol>
</li>
))}
</ol>
)
}
base/src/gallery/main.tsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import '@lib/index.css'
import App from './App'
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
)
base/src/gallery/PresentationCard.tsx
import type { SpecEntry } from 'virtual:spec-manifest'
import { StatusDot } from '@lib/components/schematic/StatusDot'
/**
* one spec on the roadmap. a plain anchor to `<slug>/` (a real navigation
* into the built deck — or the markdown viewer when no deck exists). the
* timeline gutter owns the date, so the card carries only sequence number and
* status; the single accent appears only on hover — border, title, arrow.
*/
export function PresentationCard({ spec, index }: { spec: SpecEntry; index: number }) {
const { slug, title, tagline, tags, status, hasDeck } = spec
const isDraft = status === 'draft'
const num = String(index + 1).padStart(2, '0')
return (
<a
href={`${slug}/`}
aria-label={`${title}. ${tagline}${isDraft ? ' (draft)' : ''}`}
style={{ animationDelay: `${Math.min(index, 8) * 60}ms` }}
className="group flex flex-col border border-rule bg-bg p-6 @3xl:p-7 transition-colors hover:border-accent card-rise"
>
<div className="flex items-center justify-between gap-x-3">
<span className="font-mono text-[11px] uppercase tracking-[0.14em] text-ink-ghost">{num}</span>
<span className="flex items-center gap-x-3 font-mono text-[11px] uppercase tracking-[0.06em] text-ink-faint">
{isDraft ? (
<span className="flex items-center gap-x-1.5 text-warn">
<StatusDot tone="warn" />
draft
</span>
) : null}
<span>{hasDeck ? 'interactive deck' : 'spec'}</span>
</span>
</div>
<h3 className="mt-4 font-mono text-[18px] @3xl:text-[20px] font-semibold lowercase tracking-[-0.01em] leading-[1.25] text-ink transition-colors group-hover:text-accent">
{title}
</h3>
<p className="mt-2.5 font-mono text-[13px] leading-[1.6] text-ink-faint">{tagline}</p>
<div className="mt-auto pt-6 flex items-end justify-between gap-x-3">
<span className="flex flex-wrap gap-1.5">
{tags.slice(0, 4).map((t) => (
<span
key={t}
className="border border-rule px-1.5 py-0.5 font-mono text-[10px] uppercase tracking-[0.08em] text-ink-faint"
>
{t}
</span>
))}
</span>
<span
aria-hidden
className="font-mono text-[12px] lowercase whitespace-nowrap text-ink-faint transition-colors group-hover:text-accent"
>
open →
</span>
</div>
<div className="mt-4 border-t border-rule-2 pt-3 font-mono text-[10px] lowercase text-ink-ghost truncate">
tech-specs/{slug}
</div>
</a>
)
}
base/src/gallery/site.ts
/**
* site.ts — the gallery's repo identity, set once. Everything listed on the
* page itself comes from `virtual:spec-manifest` (each spec README's
* frontmatter); nothing per-spec ever lands in this file. `heroLead` is the
* one hand-curated line: it speaks in roadmap voice (what's next, what
* landed) without naming specs, so it never drifts as entries are added.
*/
export const SITE = {
/** text next to the wordmark in the header */
wordmarkLabel: '__WORDMARK_LABEL__',
/** small-caps eyebrow above the hero title */
heroEyebrow: 'tech-specs / roadmap',
/** the big hero line — the roadmap framing, e.g. "what we're working on" */
heroTitle: '__HERO_TITLE__',
heroLead: '__HERO_LEAD__',
/** left attribution in the footer bar */
attribution: '__ATTRIBUTION__',
/** right "source of truth" line in the footer bar */
source: 'source of truth: __REPO__/tech-specs',
}
base/src/gallery/SiteFooter.tsx
import { Wordmark } from '@lib/components/schematic/Wordmark'
import { SITE } from './site'
export function SiteFooter() {
return (
<footer className="border-t border-rule">
<div className="flex flex-wrap items-center justify-between gap-y-3 px-4 py-4 @3xl:px-9">
<span className="flex items-center gap-x-2.5">
<Wordmark className="h-[14px]" />
<span className="font-mono text-[11px] uppercase tracking-[0.06em] text-ink-faint">{SITE.attribution}</span>
</span>
<span className="font-mono text-[11px] uppercase tracking-[0.06em] text-ink-ghost">{SITE.source}</span>
</div>
</footer>
)
}
base/src/gallery/SiteHeader.tsx
import { ModeToggle } from '@lib/components/schematic/ModeToggle'
import { Wordmark } from '@lib/components/schematic/Wordmark'
import { useTheme } from '@lib/hooks/useTheme'
import { SITE } from './site'
export function SiteHeader() {
const [theme, setTheme] = useTheme()
return (
<header className="sticky top-0 z-50 border-b border-rule bg-bg">
<div className="flex items-center gap-x-5 px-4 py-2.5 @3xl:px-9">
<a href="./" className="flex items-center gap-x-2.5 shrink-0" aria-label="back to the index">
<Wordmark />
<span className="font-mono text-[13px] font-semibold lowercase text-ink hidden @lg:inline">
{SITE.wordmarkLabel}
</span>
</a>
<span className="hidden @3xl:inline font-mono text-[12px] lowercase text-ink-ghost">
the roadmap, spec by spec
</span>
<div className="ml-auto shrink-0">
<ModeToggle
value={theme}
onChange={setTheme}
options={[
{ value: 'light', label: 'light' },
{ value: 'dark', label: 'dark' },
]}
/>
</div>
</div>
</header>
)
}
base/src/hooks/useHashRoute.ts
import { useEffect, useState } from 'react'
export type Route = { kind: 'home' } | { kind: 'page'; slug: string; rest: string[] }
function parse(hash: string): Route {
// `#/<slug>` (and `#/<slug>/<sub>/...`) are page routes; the slug is the
// FIRST segment, matched against the PAGES registry in App.tsx, and any
// further segments are exposed as `rest` (e.g. the spec viewer's file).
const m = hash.match(/^#\/(.+)$/)
if (m) {
const segments = m[1].split('/').filter(Boolean)
const [slug, ...rest] = segments
if (slug) return { kind: 'page', slug, rest }
}
return { kind: 'home' }
}
/**
* hash routing with two namespaces: `#/...` paths are routes (deep-dive
* pages); bare `#section-id` hashes stay native anchor scrolls on the home
* page. unknown page slugs fall back to home (App renders a not-found note).
*/
export function useHashRoute(): Route {
const [route, setRoute] = useState<Route>(() => parse(window.location.hash))
useEffect(() => {
const onChange = () => setRoute(parse(window.location.hash))
window.addEventListener('hashchange', onChange)
return () => window.removeEventListener('hashchange', onChange)
}, [])
useEffect(() => {
// deep-dive pages and the explicit "#/" home link start at the top;
// bare "#section" hashes keep native anchor behaviour.
if (route.kind === 'page' || window.location.hash === '#/') {
window.scrollTo({ top: 0, behavior: 'instant' })
}
}, [route])
return route
}
base/src/hooks/useStepper.ts
import { useCallback, useEffect, useRef, useState } from 'react'
export interface Stepper {
step: number
playing: boolean
atEnd: boolean
play: () => void
pause: () => void
toggle: () => void
next: () => void
prev: () => void
reset: () => void
goTo: (step: number) => void
}
/**
* shared driver for the step-through diagrams: `step` walks 0..total-1,
* auto-advancing every `intervalMs` while playing; pauses at the end.
*/
export function useStepper(total: number, intervalMs = 2400, autoPlay = false): Stepper {
const [step, setStep] = useState(0)
const [playing, setPlaying] = useState(autoPlay)
const timer = useRef<ReturnType<typeof setInterval> | null>(null)
const atEnd = step >= total - 1
useEffect(() => {
if (!playing) return
timer.current = setInterval(() => {
setStep((s) => {
if (s >= total - 1) {
setPlaying(false)
return s
}
return s + 1
})
}, intervalMs)
return () => {
if (timer.current) clearInterval(timer.current)
}
}, [playing, intervalMs, total])
const play = useCallback(() => {
setStep((s) => (s >= total - 1 ? 0 : s))
setPlaying(true)
}, [total])
const pause = useCallback(() => setPlaying(false), [])
const toggle = useCallback(() => {
setPlaying((p) => {
if (!p) setStep((s) => (s >= total - 1 ? 0 : s))
return !p
})
}, [total])
const next = useCallback(() => {
setPlaying(false)
setStep((s) => Math.min(total - 1, s + 1))
}, [total])
const prev = useCallback(() => {
setPlaying(false)
setStep((s) => Math.max(0, s - 1))
}, [])
const reset = useCallback(() => {
setPlaying(false)
setStep(0)
}, [])
const goTo = useCallback(
(s: number) => {
setPlaying(false)
setStep(Math.max(0, Math.min(total - 1, s)))
},
[total],
)
return { step, playing, atEnd, play, pause, toggle, next, prev, reset, goTo }
}
base/src/hooks/useTheme.ts
import { useCallback, useEffect, useState } from 'react'
export type Theme = 'light' | 'dark'
function readTheme(): Theme {
try {
const t = localStorage.getItem('iii-theme')
return t === 'dark' ? 'dark' : 'light'
} catch {
return 'light'
}
}
export function useTheme(): [Theme, (next: Theme) => void] {
const [theme, setTheme] = useState<Theme>(readTheme)
useEffect(() => {
document.documentElement.dataset.theme = theme
}, [theme])
const set = useCallback((next: Theme) => {
setTheme(next)
try {
localStorage.setItem('iii-theme', next)
} catch {
/* private mode — theme just won't persist */
}
}, [])
return [theme, set]
}
base/src/index.css
/* scan the whole presentations project (shared src + every deck + the viewer)
for utility classes, regardless of which app is the vite root per build */
@import "tailwindcss" source("../");
@theme {
--font-sans: "Chivo Mono", ui-monospace, SFMono-Regular, Menlo, Monaco,
Consolas, "Liberation Mono", "Courier New", monospace;
--font-mono: "Chivo Mono", ui-monospace, SFMono-Regular, Menlo, Monaco,
Consolas, "Liberation Mono", "Courier New", monospace;
/* paper / surface ramp (3-step) */
--color-bg: #f2f0ed;
--color-panel: #e9e6e2;
--color-paper-2: #ebe8e3;
/* ink ramp (3-step) */
--color-ink: #0a0a0a;
--color-ink-faint: #6b6865;
--color-ink-ghost: #a3a09c;
/* structural lines */
--color-rule: #d8d5d0;
--color-rule-2: #e6e3df;
/* accent (single hero — hot orange) */
--color-accent: #ff5a1f;
--color-accent-fg: #f2f0ed;
/* status */
--color-alert: #c43e1c;
--color-warn: #a87a00;
/* radii — only two are allowed */
--radius-none: 0px;
--radius-full: 9999px;
/* spacing scale */
--spacing-gutter: 24px;
--spacing-section-x: 36px;
--spacing-section-y: 80px;
--spacing-sheet-max: 1200px;
--spacing-content-max: 1216px;
}
/* dark theme: invert paper/ink and swap accent to electric blue */
[data-theme="dark"] {
--color-bg: #111110;
--color-panel: #1a1916;
--color-paper-2: #1f1e1c;
--color-ink: #f2f0ed;
--color-ink-faint: #9c9893;
--color-ink-ghost: #5d5a55;
--color-rule: #2a2926;
--color-rule-2: #1f1e1c;
--color-accent: #3ea8ff;
--color-accent-fg: #111110;
}
@layer base {
html,
body,
#root {
height: 100%;
}
html,
body {
scrollbar-gutter: stable;
}
html {
overflow-y: scroll;
scroll-behavior: smooth;
}
body {
background-color: var(--color-bg);
color: var(--color-ink);
/* explicitly disable decorative ligatures — the schematic feel relies on
monospace columns, not typographic flourishes */
font-feature-settings: "liga" 0, "clig" 0, "calt" 0, "dlig" 0;
}
::selection {
background-color: var(--color-accent);
color: var(--color-accent-fg);
}
::-webkit-scrollbar {
width: 10px;
height: 10px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: var(--color-rule);
}
::-webkit-scrollbar-thumb:hover {
background: var(--color-ink-ghost);
}
}
@keyframes pulse-dot {
0% {
box-shadow: 0 0 0 0 var(--color-accent);
}
100% {
box-shadow: 0 0 0 8px transparent;
}
}
@utility pulse-dot {
animation: pulse-dot 1.6s ease-out infinite;
}
@keyframes blink {
0%, 49% { opacity: 1; }
50%, 100% { opacity: 0; }
}
@utility blink {
animation: blink 1s steps(1) infinite;
}
@keyframes wiggle {
0%, 90%, 100% { transform: rotate(0deg); }
93% { transform: rotate(-3deg); }
96% { transform: rotate(3deg); }
}
@utility wiggle {
animation: wiggle 3s ease-in-out infinite;
}
/* the one sanctioned shadow stack — used only for stacked "deal" cards */
@utility deal-shadow {
box-shadow: -2px 0 0 var(--color-rule),
-16px 4px 36px -10px rgba(0, 0, 0, 0.22);
}
/* ---- schematic diagram utilities (same 1px-grid spirit) ---- */
/* marching dashes along an SVG edge — signal in motion on a wire */
@keyframes flow-dash {
to {
stroke-dashoffset: -16;
}
}
@utility flow-dash {
stroke-dasharray: 6 10;
animation: flow-dash 0.9s linear infinite;
}
/* a slower, fainter version for ambient wires */
@utility flow-dash-slow {
stroke-dasharray: 2 10;
animation: flow-dash 2.4s linear infinite;
}
/* event ripple — an expanding 1px ring, the fan-out cue */
@keyframes ripple-ring {
0% {
opacity: 0.9;
r: 4;
}
100% {
opacity: 0;
r: 26;
}
}
@utility ripple-ring {
animation: ripple-ring 1.6s ease-out infinite;
}
/* terminal output: each line fades + rises in, staggered by animation-delay */
@keyframes fade-rise {
from {
opacity: 0;
transform: translateY(3px);
}
to {
opacity: 1;
transform: none;
}
}
@utility fade-rise {
animation: fade-rise 0.4s ease-out both;
}
/* scroll-reveal: sections rise 8px and fade in once observed */
@utility reveal {
opacity: 0;
transform: translateY(8px);
transition: opacity 0.55s ease-out, transform 0.55s ease-out;
}
@utility reveal-in {
opacity: 1;
transform: translateY(0);
}
/* gallery cards stagger in on load; fill-mode `both` holds opacity:0 through
the stagger delay so cards appear in order */
@keyframes card-rise {
from {
opacity: 0;
transform: translateY(8px);
}
to {
opacity: 1;
transform: none;
}
}
@utility card-rise {
animation: card-rise 0.5s ease-out both;
}
@media (prefers-reduced-motion: reduce) {
html {
scroll-behavior: auto;
}
.flow-dash,
.flow-dash-slow,
.ripple-ring,
.pulse-dot,
.wiggle,
.card-rise,
.fade-rise {
animation: none;
}
.reveal {
opacity: 1;
transform: none;
transition: none;
}
}
base/src/lib/deck-types.ts
/**
* deck-types.ts — the shapes a deck's content layer feeds into the shared
* chrome (TopNav, Footer). Each deck defines its values in its own
* `src/content/deck.ts` and passes them as props from its App.tsx; the shared
* components never reach into deck content.
*/
export interface NavItem {
/** must match a section's DOM id so scroll-spy + anchor links work */
id: string
label: string
}
export interface FooterSpec {
/** small caps eyebrow above the closing line, e.g. "get started" */
eyebrow: string
/** the big closing line — the one-command payoff */
headline: string
/** the command shown in the bordered chip */
command: string
/** left attribution line in the bottom bar */
attribution: string
/** right "source of truth" line in the bottom bar */
source: string
}
export interface DeckMeta {
/** wordmark text next to the logo in the top nav */
wordmarkLabel: string
}
base/src/lib/utils.ts
import { type ClassValue, clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
base/src/pages/SpecPage.tsx
import { HighlightStyles } from '@lib/content/highlight'
import { Markdown } from '@lib/content/markdown'
import { useHashRoute } from '@lib/hooks/useHashRoute'
import { cn } from '@lib/lib/utils'
import { useMemo } from 'react'
/**
* A15 - the spec viewer. A separate hash-route page (`#/spec/<file>`) that
* renders every markdown file of the tech-spec, with a sidebar listing the
* files and the rendered doc beside it.
*
* The markdown arrives via the `docs` prop: each deck ships a tiny
* `src/spec-docs.ts` whose `import.meta.glob` bundles its own spec's md at
* build time (the glob must live deck-side — it resolves relative to the
* importing file). Frontmatter in README.md is stripped by <Markdown>.
*/
interface Doc {
slug: string
file: string
label: string
source: string
}
function firstHeading(src: string): string | null {
const m = src.match(/^#\s+(.+?)\s*$/m)
return m ? m[1].replace(/`/g, '').trim() : null
}
function toDocs(raw: Record<string, string>): Doc[] {
return Object.entries(raw)
.map(([path, source]) => {
const file = path.split('/').pop() ?? path
const base = file.replace(/\.md$/, '')
return { slug: base.toLowerCase(), file, label: firstHeading(source) ?? base, source }
})
.filter((d) => !/-review-|^_/.test(d.file))
.sort((a, b) => {
if (a.slug === 'readme') return -1
if (b.slug === 'readme') return 1
return a.file.localeCompare(b.file)
})
}
export function SpecPage({ docs }: { docs: Record<string, string> }) {
const route = useHashRoute()
const items = useMemo(() => toDocs(docs), [docs])
const wanted = route.kind === 'page' ? route.rest[0] : undefined
const active = items.find((d) => d.slug === wanted) ?? items[0]
if (!active) {
return (
<main className="px-4 py-24 @3xl:px-9">
<p className="font-mono text-[14px] lowercase text-ink-faint">no spec markdown found beside this deck.</p>
</main>
)
}
return (
<main className="px-4 @3xl:px-9 py-8 @3xl:py-10">
<HighlightStyles />
<div className="grid grid-cols-1 @3xl:grid-cols-[210px_minmax(0,1fr)] gap-6 @3xl:gap-10">
<nav
aria-label="spec files"
className="@3xl:sticky @3xl:top-20 @3xl:self-start min-w-0 border-b border-rule pb-3 @3xl:border-b-0 @3xl:pb-0 @3xl:border-r @3xl:border-rule @3xl:pr-6"
>
<div className="font-mono text-[10px] uppercase tracking-[0.14em] text-ink-ghost mb-3">spec</div>
<ul className="flex @3xl:flex-col gap-x-1 gap-y-0.5 overflow-x-auto @3xl:overflow-visible">
{items.map((d) => {
const on = d.slug === active.slug
return (
<li key={d.slug} className="shrink-0 @3xl:shrink">
<a
href={`#/spec/${d.slug}`}
aria-current={on ? 'page' : undefined}
className={cn(
'block whitespace-nowrap @3xl:whitespace-normal font-mono text-[12px] leading-[1.45] py-1.5 px-3 @3xl:pl-3 @3xl:-ml-px @3xl:border-l-2 transition-colors',
on ? 'text-ink @3xl:border-accent' : 'text-ink-faint hover:text-ink @3xl:border-transparent',
)}
>
{d.label}
</a>
</li>
)
})}
</ul>
</nav>
<article className="min-w-0 max-w-[80ch]">
<Markdown source={active.source} />
</article>
</div>
</main>
)
}
base/src/vite-env.d.ts
/// <reference types="vite/client" />
declare module 'virtual:spec-manifest' {
export interface SpecEntry {
slug: string
title: string
tagline: string
/** "YYYY-MM-DD" (legacy specs may carry month-only "YYYY-MM") */
date: string
/** the timeline's month group, e.g. "2026 · june" */
month: string
/** e.g. "jun 29" — null when the date has no day component */
dayLabel: string | null
tags: string[]
status: 'live' | 'draft'
featured: boolean
hasDeck: boolean
}
export const SPECS: SpecEntry[]
}
base/tsconfig.app.json
{
"compilerOptions": {
"target": "ES2022",
"useDefineForClassFields": true,
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true,
"paths": {
"@lib/*": ["./src/*"]
}
},
"include": ["src", "*/src", "_viewer/src"],
"exclude": ["dist", "node_modules"]
}
base/tsconfig.json
{
"files": [],
"references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }]
}
base/tsconfig.node.json
{
"compilerOptions": {
"target": "ES2023",
"lib": ["ES2023"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"allowJs": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"types": ["node"],
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["vite.config.ts", "build.mjs", "scripts"]
}
base/vite.config.ts
import { fileURLToPath, URL } from 'node:url'
import tailwindcss from '@tailwindcss/vite'
import react from '@vitejs/plugin-react'
import { defineConfig } from 'vite'
import { specManifestPlugin } from './scripts/manifest.mjs'
// One config for every app in this project. `vite` (dev) serves the gallery at
// `/` and every deck at `/<slug>/` from a single server; `build.mjs` reuses
// this file programmatically with a per-deck `root`/`outDir`, so each deck
// ships as a self-contained, relative-path static site that works under any
// URL prefix (deployed at iii.dev/roadmap/<slug>/).
export default defineConfig({
base: './',
plugins: [react(), tailwindcss(), specManifestPlugin()],
resolve: {
alias: {
'@lib': fileURLToPath(new URL('./src', import.meta.url)),
},
},
// Deck spec pages bundle `../../../../tech-specs/<slug>/*.md`, which sits
// outside the per-deck root — allow the dev server to read from the repo root.
server: { fs: { allow: [fileURLToPath(new URL('../..', import.meta.url))] } },
})
README.md
# /presentation
Turn a tech-spec directory into an interactive, marketing-grade web
presentation — the kind at [iii.dev/roadmap/](https://iii.dev/roadmap/).
The deck is built to: help engineers **understand** the design (the
architecture is a navigable map, not prose), be **interactive** (steppable
diagrams, a selectable system map, live toggles), read like **marketing**
(it argues the *why*), and be **build-in-public ready** (each deck builds to a
portable static `dist/<slug>/`).
## Use it
From a Claude Code session in this workspace:
```
/presentation tech-specs/2026-06-devexp
```
It reads the spec, reads the repo's component registry, proposes a
slide-by-slide narrative outline for your approval, scaffolds the deck's
**content layer** at `<base>/<slug>/` (in iii: `website/roadmap/<slug>/`),
generates the content against the shared `@lib` component library, writes the
spec's registration frontmatter, and verifies with a typecheck, a build, and a
browser pass.
## Hosting — one Vite project per repo, no per-deck deploys
Specs are markdown-only at `tech-specs/<slug>/`; decks are content layers
inside the repo's single presentations site (the "base"). The base owns the
one `package.json`, the shared component library + design tokens, the gallery,
a markdown viewer for specs without decks, and `build.mjs`. In iii, merging to
main deploys everything to `iii.dev/roadmap/` via the website workflow —
no Vercel, no manual step. See `reference/hosting.md`.
## What's in here
- `SKILL.md` — the operating instructions (the brain).
- `reference/` — the design system, the interactive archetype library, the
component standards + registry format, the narrative framework, the quality
bar, and the hosting/layout rules. The skill loads these first.
- `template/` — one deck's content-layer starter (~13 files: App, deck.ts,
spec-docs glob, three worked-example sections, a deep-dive page). No
package.json, no config — everything visual imports from `@lib`.
- `base/` — the full per-repo presentations site scaffold: shared `src/`
(primitives, diagram archetypes, hooks, markdown machinery, gallery,
viewer), `build.mjs`, `vite.config.ts`, tsconfigs, the `COMPONENTS.md`
registry seed, and the SOP README seed. Copied once per repo, identity
tokens filled, then only ever extended by component promotions.
## Extending the design
The live visual system for iii is `website/roadmap/src/` (tokens in
`src/index.css`, components under `src/components/`); every promoted component
gets a `COMPONENTS.md` entry per `reference/component-standards.md`.
**Maintenance note:** `base/` is a snapshot of that live `src/` — re-seed it
from `website/roadmap/` periodically (copy `src/`, `scripts/manifest.mjs`,
`vite.config.ts`, tsconfigs, `_viewer/`, `COMPONENTS.md`; take `build.mjs`
but restore the standalone `DIST = join(ROOT, 'dist')` — iii's copy emits
into `website/dist/roadmap/`; keep base's own `package.json`, since iii folds
those deps into the `iii-website` package; re-tokenize the `package.json`
name, `src/gallery/site.ts`, `index.html`, `README.md`) so fresh repos start
with the newest archetypes. The template imports only the
core `@lib` surface (Section, TopNav, Footer, Sheet, SpecSheet, PageShell,
hooks, SpecPage), which every base version guarantees.
reference/archetypes.md
# Archetype library
The deck is assembled from a fixed catalog of proven slide archetypes. For
every slide: **match the spec content to an archetype, then supply data.** Do
not invent a new diagram when one fits. Hand-build a bespoke SVG only when no
archetype matches and the concept is load-bearing — and then follow
`SequencePlayer.tsx`'s conventions.
Two kinds:
- **Diagram archetypes** — shared, props-driven components in
`@lib/components/diagrams/` (the base project's library). You import them
and pass data from the deck's `content/`.
- **Compositional archetypes** — patterns you build inline in a section from
primitives (`Cell`, `SpecSheet`, `CodeBlock`, `ModeToggle`, `Button`, …).
The starter `Hero.tsx` and `PayoffSection.tsx` are worked examples.
**The repo registry wins.** This catalog documents the bundled baseline; the
live inventory is `<base>/COMPONENTS.md`, which grows as decks promote new
archetypes (per `reference/component-standards.md`). Read it in Phase 1b —
when it and this file disagree, the registry is the truth.
## Selection heuristics
| The spec has… | Use |
|---|---|
| a thesis / one-line promise | A1 Hero |
| a "today's pain" enumeration | A2 Problem + A11 |
| a command / install / golden path | A3 CLI Playground |
| a component/responsibility map, planes, "owns / never does" | A4 System Map |
| a temporal protocol, a "turn", a handshake, numbered steps | A5 Sequence |
| a lifecycle whose state evolves (boot, durability, readiness) | A6 Step Reveal |
| one event → many handlers; reactive/triggers | A7 Fan-Out |
| many cases collapse to one mechanism (or one forks) | A8 Funnel |
| a mode/policy that changes what's allowed | A9 Toggle Explorer |
| a human-in-the-loop / approval / branching outcome | A10 Decision Flow |
| before/after, removed, renamed, metrics | A11 Scorecard + Solves |
| any field-level schema (the depth layer of any slide) | A12 Spec Datasheet |
| a config file, pseudocode, an API skeleton | A13 Code Walkthrough |
| a distinct consumer/scenario worth a full page | A14 Deep-dive Page |
Every interactive archetype is keyboard-operable, disables controls at bounds,
respects `prefers-reduced-motion`, and degrades to a readable static state.
---
## Diagram archetypes (bundled)
### A4 — System Map · `diagrams/SystemMap.tsx`
The signature visual: the whole architecture in one interactive diagram. Click
a node → its edges highlight and its datasheet updates. Author node `x/y/w/h`
and edge `d` paths in the same coordinate space (default `1030×600`).
```ts
type MapNodeKind = 'primary' | 'secondary' | 'optional' | 'external'
interface MapNode { id; x; y; w; h; title; sub?; kind; tag? }
interface MapEdge { id; from; to; d; label?; lx?; ly?; anchor?; dashed?; dur? }
interface MapNodeInfo { id; kindLabel; role; sections?; note?; bullets?; install? }
interface InfoSection { heading; items: { name; desc? }[]; dotted? }
```
Use in a section with the paired-layout pattern from
`sections/SystemMapSection.tsx` (ResizeObserver sticks the datasheet beside the
map ≥1024px, stacks below it narrower). `MapDatasheet` takes a resolved
`info` object — look it up from a `Record<string, MapNodeInfo>` by `selected`.
Maps from: the spec's architecture/component-responsibility section. One node
per component; `sections` from each component's surface/emits; `dotted: true`
for event lists.
### A5 — Sequence · `diagrams/SequencePlayer.tsx`
A step-through sequence diagram: lifelines per lane, one arrow per step, the
active step in accent with a travelling pulse and a narration line.
```ts
interface SeqLane { id; label; x }
interface SeqStep { from; to; label; title; desc; event? } // from===to => self-loop
<SequencePlayer title lanes={SEQ_LANES} steps={SEQ_STEPS} />
```
Maps from: any protocol / turn loop / handshake. One lane per participant; one
step per message. Set `event` when a step emits a trigger.
### A6 — Step Reveal · `diagrams/StepReveal.tsx`
A lifecycle/timeline walker: a strip of ordered stages; the stepper walks them;
a panel shows the active stage's evolving record.
```ts
interface RevealStage { label; tone?: 'ink'|'accent'|'alert'|'warn'; caption?; rows?: {k;v}[]; note? }
<StepReveal title stages={STAGES} />
```
Maps from: boot sequences, durability/crash-resume timelines, readiness
bring-up. Use `tone: 'alert'` for a crash/failure stage, `'accent'` for the
resolved one. `rows` is the state object that changes per stage.
### A3 — CLI Playground · `diagrams/CliPlayground.tsx`
An interactive terminal: lines reveal one at a time (staggered `fade-rise`);
switch tracks (golden path vs day-2) with a toggle.
```ts
interface CliLine { cmd?; out?: string[]; fn?; exit? } // out tinted by ✓/✗/! glyph
interface CliTrack { id; label; lines: CliLine[] }
<CliPlayground tracks={TRACKS} title="run it" />
```
Maps from: install/quickstart, the golden command path, day-2 ops. Put verbatim
commands + output in. `fn` shows the backing function id; prefix output lines
with `✓`/`✗`/`!` to tint them.
### A7 — Fan-Out · `diagrams/FanOut.tsx`
One write/emit fans out to many bound handlers — ambient ripple + marching
edges. No stepper; it just breathes.
```ts
interface FanHandler { id; label; desc? }
<FanOut source={{ label, sub? }} trigger="store::written" handlers={HANDLERS} />
```
Maps from: a reactive/event model. The `trigger` keeps its casing; handlers are
the functions bound to it.
### A8 — Funnel · `diagrams/Funnel.tsx`
Many paths converge into one guaranteed mechanism, with an optional dashed
rejected path.
```ts
interface FunnelPath { id; label; desc? }
<Funnel paths={PATHS} target={{ label, sub? }} reject={{ label, desc? }} />
```
Maps from: "many spawn paths → one owned spawn", "many inputs → one reaper".
Use `reject` to show the eliminated/forbidden path (rendered alert + dashed).
---
## Compositional archetypes (build inline from primitives)
### A1 — Hero (`sections/Hero.tsx` is the template)
States the **win** in one line + a three-value subhead — never the mechanism.
A stat strip quantifies it; a small `<Terminal>` makes it concrete; two CTAs
jump to the map and the payoff.
### A2 — Problem / Tangle
A grid of `<Cell>` cards naming today's failures, optionally `alert`-tinted, or
a `<StatusPanel variant="alert">` per failure. Make the reader feel the tangle
before the solution. Pull from the spec's pain points / motivation.
### A9 — Toggle Explorer
`useState` for the mode + `<ModeToggle>` + a grid of `<FnChip>`. On change,
re-filter the chips (opacity/strikethrough for blocked) and update a counter.
For policy/allow-list/capability slides.
### A10 — Decision Flow
`useState<'pending'|'approved'|'denied'>` + `<Button>` approve/deny. Borders,
`<StatusDot>`, and the output block change per state; a replay resets. For
human-in-the-loop / governance.
### A11 — Before/After Scorecard + Solves table (`sections/PayoffSection.tsx`)
A `gap-px bg-rule` metric grid (`before → after`, accent on the after) plus a
two-column problem (alert) / answer (accent) table. The closing argument.
### A12 — Spec Datasheet (`components/SpecSheet.tsx`)
`<SpecSheet>` + `<SpecRow>` — a `<details>` closed by default. This is the
**depth layer of every slide**: keep the narrative skimmable, put field-level
detail one click away.
### A13 — Code Walkthrough (`components/schematic/CodeBlock.tsx`)
`<CodeBlock>` with `<K>` keyword / `<S>` string / `<C>` comment / `<M>` muted.
The accent stays rationed to string literals. For config files, pseudocode,
API skeletons.
### A14 — Deep-dive Page (`components/PageShell.tsx`)
A full `#/<slug>` page via `<PageShell>` (cross-links siblings with `related`).
Register it in `App.tsx`'s `PAGES` map. Give one to each distinct
consumer/scenario worth the full walkthrough.
### A15 — Spec Viewer (`@lib/pages/SpecPage.tsx`, built-in)
The source-of-truth reading mode: a separate `#/spec/<file>` page that renders
every markdown file of the paired `tech-specs/<slug>/` directory with a file
**sidebar**. The shared `SpecPage` takes `{ docs }`; the deck's tiny
`src/spec-docs.ts` supplies them (an `import.meta.glob` whose literal the
skill substitutes at scaffold — see `reference/hosting.md`). The template
wires it (`spec: Spec` in `PAGES`; the shared `TopNav` renders the `spec`
link). You do not generate or edit it beyond that wiring.
- Markdown → React via `@lib/content/markdown.tsx`, styled in the
drafting-sheet system; the leading frontmatter block is stripped; fenced
code reuses `Highlight` where the language is supported
(`ts/js/rust/python/yaml`), monochrome otherwise.
- ` ```mermaid ` fences render as **live, theme-aware diagrams** via
`@lib/content/mermaid.tsx` (lazy-loaded; mono font, ink borders, light/dark).
- Sidebar labels come from each file's first H1; README sorts first. Sibling
`*.md` links rewrite into the viewer; in-page `#anchors` scroll.
It complements the narrative deck: the deck argues the *why* and makes the
architecture interactive; the spec viewer is the *complete text* one click
away. (Specs with no deck get the same reading mode from the base's
`_viewer/` app — every spec URL works either way.)
### A16 — Durability Timeline (`diagrams/DurabilityTimeline.tsx`)
A lifecycle told as a steppable timeline of stages, each pairing a narration
with an **evolving state record** beside it (status / step / calls / note).
Built for "a durable thing survives crashes and waits" stories.
`{ stages, heading, headingNote?, recordHeading }` — a stage can mark
`gapAfter` to render a dashed pause connector.
**vs A6 Step Reveal:** reach for A16 when the record-beside-narration density
is the point; A6 when state simply accumulates.
### A17 — Event Fan-Out (`diagrams/EventFanOut.tsx`)
One **concrete, named write** → trigger type → N named subscribers, with
ambient always-on motion (ripples + traveling dots). Denser and more literal
than A7. `{ heading, source, sourceSub?, trigger, handlers, edges, footnote?,
ariaLabel }`.
**vs A7 Fan-Out:** A17 narrates one specific write with real handler names;
A7 is the abstract reactive-surface shape.
### A18 — Spawn Tree (`diagrams/SpawnTree.tsx`)
A parent spawns N parallel children and joins their results: fan out, run
concurrently, join back, one answer — with steppable narrative states driving
which children exist/run/deliver, and a parked-parent visual state.
`{ heading, chips?, parentTitle, parentLabels, parentCallLine, nodes, states,
ariaLabel }`. Use for sub-agent / fan-out-join concurrency claims.
### Primitive: WorkerCard (`schematic/WorkerCard.tsx`)
One worker as a product card — name, version, description, install command,
kind badge. Use when cataloguing installable workers/components in a grid.
reference/component-standards.md
# component standards — deck-local vs promoted, and the registry
The shared library (`<base>/src/`) is the single copy of every reusable
visual. Decks never duplicate it and never reach into each other. This file
defines when a new component may join it, what it must satisfy, and how the
registry stays truthful.
## The decision rule
**Default: deck-local.** A new visual lives at
`<base>/<slug>/src/diagrams/<Name>.tsx`, imported relatively. It can bake in
its spec's copy and topology — it ships with one deck only.
**Promote to `<base>/src/components/{diagrams,schematic}/` only when ALL
three hold:**
1. **Generic over its data** — nothing spec-specific inside; every label,
node, stage, and copy string arrives via typed props (the deck keeps the
data in its `content/`).
2. **Recurring shape** — it maps to a spec shape future decks will plausibly
need (a lifecycle, a tree, a timeline, a fan-out, a funnel…), not this
spec's particular topology.
3. **Passes the checklist below** without deck-specific hacks.
A second deck needing a deck-local diagram is the natural promotion trigger:
promote it then (props-decouple first), never copy it.
**Never fork.** Don't copy a shared component into a deck to tweak it —
extend it with additive, non-breaking props, or build a genuinely different
deck-local one. **Changing an existing shared component's behavior or API
requires explicit user approval and a full `node build.mjs`** — it re-renders
every deck on the site.
## The promotion checklist
1. Props-driven; zero content/data inside the component.
2. Design tokens only (`bg-bg`, `text-ink-faint`, `stroke-rule`, …) — never
hardcoded hex; light/dark comes free.
3. Any auto/infinite animation gated behind a runtime
`prefers-reduced-motion` check; the static fallback reads complete.
4. Keyboard operable; controls disable at bounds; visible active state.
5. `aria-label` on the interactive figure; sensible roles.
6. Container queries (`@3xl:` …), never viewport breakpoints.
7. Wide content wrapped in `overflow-x-auto` with an inner `min-w-[…]`; the
page body never scrolls horizontally.
8. No shadows, no gradients (`deal-shadow` is the sole exception); radii 0.
9. Exported prop types + a one-line JSDoc header stating purpose (the source
of the registry entry's `purpose` line).
10. Its `COMPONENTS.md` entry lands **in the same change**.
## The registry (`<base>/COMPONENTS.md`)
One entry per file; the `### <heading>` equals the file's basename — that 1:1
rule is what makes the parity check trivial (`node build.mjs` warns on files
without entries and entries without files; `--strict-registry` fails).
Six sections in fixed order — **layout · primitive · archetype · hook · util ·
gallery** — alphabetical within each. Append at the right position; never
reword other entries in a deck PR; delete an entry only when deleting its file.
Entry template:
```markdown
### SequencePlayer
- kind: archetype
- import: `import { SequencePlayer } from '@lib/components/diagrams/SequencePlayer'`
- purpose: step-through sequence diagram — lifelines per lane, one arrow per step, narrated
- props: `{ title: string; lanes: SeqLane[]; steps: SeqStep[] }`
- use when: the spec has a temporal protocol, a turn loop, a handshake, numbered steps
- not when: state evolves in place with no message passing (use StepReveal)
- used by: 2026-06-08-agentic, 2026-06-29-codegen
```
`props` is a compact sketch (the file is the type truth); `use when` speaks
the archetype-selection voice; `not when` disambiguates near-siblings
(DurabilityTimeline vs StepReveal, EventFanOut vs FanOut); `used by` is
informative, maintained by the skill when it wires a deck.
## Worked precedents
- **WorkerCard** — already props-driven when promoted from the agentic deck:
the model promotion (zero refactor).
- **DurabilityTimeline / EventFanOut / SpawnTree** — promoted from the agentic
deck by moving their baked STAGES/HANDLERS/STATES arrays into the deck's
`content/{durability,fanout,spawn}.ts` and threading them as props.
- **agentic's SystemMap fork** — NOT promoted: 483 lines diverged from the
shared SystemMap with deck content baked in. It stays at
`2026-06-08-agentic/src/diagrams/SystemMap.tsx`, the canonical props-driven
`@lib` SystemMap serves everyone else.
reference/design-system.md
# Design system — the law
This is the locked visual system. It lives in the base project's
`src/index.css` (in iii: `website/roadmap/src/index.css`; seeded from
this skill's `base/`) as Tailwind v4 `@theme` tokens. **Do not change the
tokens in a deck run.** This file is the reference for *using* them correctly
in generated content.
The feel: a monospace drafting sheet. Emphasis comes from 1px borders and a
single rationed accent — never from gradients, shadows, or saturation. It
should read like a system you trust.
## Color tokens
Used as Tailwind utilities: `bg-bg`, `text-ink`, `border-rule`, `fill-accent`,
`stroke-ink-faint`, etc. Dark mode swaps automatically via
`[data-theme="dark"]`; never hardcode hex in components.
| Token | Light | Dark | Use for |
|---|---|---|---|
| `bg` | `#f2f0ed` | `#111110` | page / card background |
| `panel` | `#e9e6e2` | `#1a1916` | header strips, toggles |
| `paper-2` | `#ebe8e3` | `#1f1e1c` | a second, subtler surface |
| `ink` | `#0a0a0a` | `#f2f0ed` | primary text, strong borders |
| `ink-faint` | `#6b6865` | `#9c9893` | body copy, secondary text |
| `ink-ghost` | `#a3a09c` | `#5d5a55` | tertiary text, section numbers |
| `rule` | `#d8d5d0` | `#2a2926` | structural 1px lines |
| `rule-2` | `#e6e3df` | `#1f1e1c` | softer inner dividers |
| `accent` | `#ff5a1f` | `#3ea8ff` | success / active / CTA only |
| `accent-fg` | `#f2f0ed` | `#111110` | text on an accent fill |
| `alert` | `#c43e1c` | `#c43e1c` | errors, removed/rejected paths |
| `warn` | `#a87a00` | `#a87a00` | warnings, held states |
**Accent discipline.** The accent is the most powerful tool here precisely
because it is rare: ✓ success, the active nav link / map edge / step, a primary
CTA, a string literal in code. If it is on more than ~5% of the screen, it has
lost its meaning. Active states use an accent *border + text*, not a fill — the
only fill is the primary button.
## Radii
Only two exist: `rounded-none` (0) and `rounded-full` (pills/dots). Nothing in
between. No `rounded-lg`, no `rounded-2xl`.
## Typography
- **Font:** Chivo Mono (400/500/600), loaded in `index.html`. Set as both
`--font-sans` and `--font-mono`. Everything is monospace.
- **Ligatures are hard-disabled** (`font-feature-settings: 'liga' 0 …`). The
schematic look depends on monospace column alignment — do not re-enable them.
- **Scale:** hero h1 44/64px semibold, tracking `-0.02em`; section h2 28px
medium; label-caps 11–12px uppercase, tracking `0.06–0.18em`; body/code
12–14px, leading 1.6–1.7.
- **Case:** all copy is **lowercase** by default. The *only* exceptions are
identifiers, function ids, type names, and code — they keep their original
casing (use `<FnChip>` / `<CodeBlock>`). No Title Case headlines.
## Layout
- The whole deck sits in one centered `<Sheet>`: `max-w-[1200px]` with
`border-x border-rule`.
- Responsive via **container queries** (`@2xl @3xl @4xl @5xl`), because the
sheet — not the viewport — is the layout context. Use `@3xl:` etc., not `md:`.
- Gutters `px-4` → `@3xl:px-9`. Section verticals `py-12` → `@3xl:py-16`.
- **Visual grids are built with `gap-px bg-rule`**: lay cells in a grid with a
1px gap over a `rule`-colored background so the rule shows through as
hairlines. Do not draw a border on every cell.
- Wide content (SVG diagrams, tables, code) must scroll inside its own
`overflow-x-auto` container with a `min-w-[...]`. The page body must never
scroll horizontally.
## Motion
All keyframes live in `index.css` and are **disabled under
`prefers-reduced-motion`** centrally. Any *infinite/auto* animation a component
starts must also gate behind a runtime check:
```ts
const reducedMotion = useMemo(
() => typeof window !== 'undefined' &&
window.matchMedia('(prefers-reduced-motion: reduce)').matches,
[],
)
```
| Utility | What | Where |
|---|---|---|
| `reveal` / `reveal-in` | fade + 8px rise on scroll-in | every `<Section>` |
| `pulse-dot` | expanding ring on a dot | active status indicators |
| `flow-dash` / `flow-dash-slow` | marching dashes on an edge | active wires |
| `ripple-ring` | expanding 1px ring | event fan-out source |
| `fade-rise` | line fades + rises | staggered terminal output |
| `blink` | cursor | terminal caret |
| SVG `<animateMotion>` | a 2.6px circle travels a path | the active edge/arrow |
Stagger terminal lines with inline `animationDelay: i * 70ms` (cap ~700ms).
Motion should feel calm and purposeful — one thing moves to draw the eye, never
a carousel.
## The one shadow
`deal-shadow` exists for stacked "card deck" effects only. No other shadows.
No glows. No gradients except the tiny scroll-fade masks already in
`SystemMap`'s datasheet.
## Anti-slop guardrails
The drafting-sheet system exists to prevent generic AI-site slop. Treat any
urge toward these as a bug: hero gradients; emoji as iconography; everything
centered; three identical rounded-2xl drop-shadow feature cards; purple→blue
gradients; "Empower / Seamless / Unlock" copy; an accent on every element.
reference/hosting.md
# hosting — the two-tree layout, one site
Every repo hosts its spec presentations as **one Vite project** (the "base") —
no per-deck projects, no per-deck installs, no separate deploys. This file is
the law for that layer — do not re-derive it.
## The two trees
```
<repo>/tech-specs/<slug>/*.md the spec — MARKDOWN ONLY
(frontmatter in README.md = registration)
<base>/<slug>/ the deck's content layer (optional)
<base>/src/ the shared component library + tokens
```
In iii, `<base>` = `website/roadmap/`. The pointer file
`tech-specs/README.md` names the base dir — that is how Phase 0 finds it in
any repo.
**The pairing contract.** `slug` = the spec directory's basename, used
identically in three places: `tech-specs/<slug>/` (the md), `<base>/<slug>/`
(the deck), and the URL `/roadmap/<slug>/`. The build fails on an orphan
deck dir (no matching spec) and never needs a manifest — the folder is the
identity, so the old "manifest slug ≠ dirname → 404" bug class cannot exist.
## Registration = frontmatter (no central manifest)
The top of `tech-specs/<slug>/README.md`:
```yaml
---
title: the developer experience overhaul # fallback: the first H1
tagline: one file, one command, zero zombies. # fallback: first paragraph
date: 2026-06-21 # YYYY-MM-DD; fallback: dirname
# prefix. day precision drives
# the roadmap order + labels
tags: [dx, cli] # ≤ 4
status: live # or draft — muted card, kept
# out of index.json + sitemap
featured: false # pin in the landing feed
# (index.json); the roadmap
# itself stays chronological
---
```
- `slug` is NEVER a field — the build hard-errors if present.
- Deck presence is derived (`<base>/<slug>/index.html` exists), never declared.
- A spec with no frontmatter still lists (the fallbacks apply); the build
warns per derived field.
- A new spec touches only its own folder → two spec PRs can never conflict.
## What the build produces
`node build.mjs` (at `<base>`; `--only=<slug>` for a fast partial):
```
dist/index.html the roadmap (a one-column timeline, newest spec
first, month-grouped; built from
virtual:spec-manifest)
dist/index.json machine-readable spec list (feeds the iii.dev
landing timeline; drafts excluded)
dist/<slug>/index.html the deck — or the generic md viewer (_viewer/,
built once + copied) when the spec has no deck
dist/<slug>/<file>.md the raw spec markdown, directly linkable
dist/<slug>/spec.json (viewer pages only) the file list the viewer fetches
```
Every deck builds with `base: './'` and its own hashed assets, so
`dist/<slug>/` stays individually portable — any CDN, any prefix, or straight
from disk.
## The spec-docs glob (the one fragile coupling)
Each deck's `src/spec-docs.ts` bundles its spec markdown at build time:
```ts
export const SPEC_DOCS = import.meta.glob('../../../../tech-specs/<slug>/*.md', {
query: '?raw', import: 'default', eager: true,
}) as Record<string, string>
```
`import.meta.glob` resolves relative to the importing file, so the literal
encodes the depth from `<base>/<slug>/src/` to the spec tree. The skill
substitutes it at scaffold time (`__SPEC_MD_GLOB__`); if the base ever moves,
every deck's glob moves with it — `pnpm type-check`/`build` catch it.
## Dev, verify, ship
```
pnpm dev ONE server: gallery at /, every deck at /<slug>/
pnpm type-check strict, shared lib + gallery + viewer + all decks
node build.mjs --only=<slug> gallery + one spec (fast)
node build.mjs --strict-registry registry parity as a hard failure (CI)
pnpm build && pnpm preview the full site at :4173
```
**iii runs an integrated shape:** the decks build as Astro routes of the
`iii-website` package — pages at `website/src/pages/roadmap/` mount each
deck's `src/App.tsx` as a React island (the base's `src/DeckHost.tsx`,
code-split per deck), contract checks live in
`website/scripts/validate-roadmap.ts`, and the output lands at
`website/dist/roadmap/` in the same shape as the standalone build (hashed
assets under the site-level `/_astro/`). The base dir keeps no per-deck
`index.html`, no `build.mjs`, and no package.json of its own.
**Deploy (iii):** merging to main runs `.github/workflows/deploy-website.yml`,
which builds the whole site (`pnpm --filter iii-website build`) and syncs
`website/dist/` to S3 (immutable hashed assets; must-revalidate
html/xml/json/md/txt), then invalidates CloudFront. The CloudFront
viewer-request function rewrites `/roadmap/…/` directory URLs to
`…/index.html` and 301s extensionless forms to the trailing-slash canonical —
same mechanism as `/blog/`. No Vercel, no manual deploy, no per-deck pipeline.
**Deploy (other repos):** `dist/` is fully static with relative asset paths —
publish it under any prefix with whatever CI the repo uses. The skill never
creates deploy config.
## Porting a legacy layout
A repo on the old model (standalone Vite project per deck +
`tech-specs/build.mjs` + `_gallery/`) ports one deck at a time:
1. Move the spec md to `tech-specs/<slug>/` (md only) and add the frontmatter
block (values from the old `_gallery/src/content/presentations.ts` entry).
2. Move the deck's content layer (`index.html` → entry `./src/main.tsx`,
`src/{App,sections,pages,content}`) to `<base>/<slug>/`.
3. Delete its duplicated machinery: package.json, lockfile, vite/tsconfigs,
`src/{components,hooks,lib}`, `index.css`, markdown utils, SpecPage.
4. Rewrite imports (shared → `@lib/…`; deck-local → relative), thread
`meta`/`nav`/`footer` props from `content/deck.ts`, add `src/spec-docs.ts`
+ `PAGES.spec`.
5. Truly spec-specific diagrams stay deck-local under `<slug>/src/diagrams/`;
generic ones promote per `reference/component-standards.md`.
6. `pnpm type-check && node build.mjs --only=<slug>` + `/browse`; delete the
old per-deck project and, when the last deck is ported, the old
`_gallery/` + root glue.
reference/narrative-framework.md
# Narrative framework
A tech spec is organized for completeness. A presentation is organized for
**persuasion**. Do not mirror the spec's table of contents — re-sequence its
material into a story that makes a reader *want* the work to happen. This is
what satisfies requirements 1 (understanding) and 3 (marketing) at once.
## The arc (the default 8–12 slide spine)
1. **Hero — the promise.** Compress the thesis into one line + a three-value
subhead (simplicity / structure / correctness). Stat strip. Two CTAs. State
the *win*, never the mechanism. This is the hook. (A1)
2. **Why — the pain.** Name today's failures as concrete, cited cards. Make the
reader feel the tangle before you offer the fix. (A2)
3. **Show it — the demo.** An interactive proof, early: a CLI playground or a
sequence. "Run it, step through it" beats "read about it" and converts
skimmers fast. (A3 / A5)
4. **The map — what exists.** The whole architecture in one navigable diagram.
Orient the reader before any detail. (A4)
5..N. **The mechanisms — how it works.** One slide per design pillar, each in
the archetype that fits: sequence for protocols, step-reveal for lifecycles,
fan-out for reactivity, funnel for "many → one", toggle for policy, decision
flow for governance. One claim per slide; depth behind a `<SpecSheet>`.
N+1. **The transition — adoption.** Before/after, removed/renamed, a phased
roadmap. Lower the perceived risk of doing the work. (A11)
Last. **The payoff — why it holds.** A quantified scorecard + a problem→answer
table. Close the persuasion loop. (A11)
Deep-dive pages (A14) hang off the arc for distinct consumers/scenarios that
deserve a full walkthrough.
## Techniques (apply throughout)
- **Three-value hero framing.** Simplicity + structure + correctness in one
breath. "one file. one command. zero zombies."
- **Problem → solution pairing.** Never assert a benefit without first naming
the failure it cures.
- **Before/after wherever available.** Two columns, today vs target. Contrast
is the most legible form of argument.
- **Ration the accent.** It lands on success / active / CTA only. Overuse kills
persuasion; scarcity gives each accent moment weight.
- **Progressive disclosure.** Big claim (headline) → supporting cards → deep
spec (`<SpecSheet>`, closed). One page serves both the exec who skims and the
engineer who drills.
- **Quantify the win.** Pull the spec's numbers verbatim (files, commands,
workers, % reductions). If you compute one, say so. Numbers convince.
- **Stay honest.** Ground every claim in the spec. Keep one trade-offs /
open-questions beat — admitted limits read as credibility, which persuades
more than a flawless pitch.
- **Let the reader set the tempo.** Scroll-driven reveals, not an autoplay
carousel. They advance; the deck responds.
## The planning step (Phase 2)
Before writing any code, produce the outline and **show it to the user**:
```
slide | archetype | the one claim | source | data to pull
-------+-----------+---------------------------------------+---------------+----------------------------
hero | A1 | one file, one command, zero zombies | README §1 | thesis, 4 stats, 2 CTAs
why | A2 | today: 6 ways processes leak | daemon §2 | 6 failure cards + citations
run-it | A3 | bring the whole stack up in one step | onboarding §1 | golden-path commands
map | A4 | four planes, three meeting points | README §3 | nodes, edges, per-node info
... | ... | ... | ... | ...
payoff | A11 | smaller surface, stronger guarantees | README §7 | 5 metrics, problem/answer rows
```
This table is where a dry spec becomes a story, and it is far cheaper to revise
here than after scaffolding. Get sign-off, then build.
reference/quality-bar.md
# Quality bar
Run this before declaring a deck done. It maps directly to the four
requirements. Anything red gets fixed, not noted.
## Build integrity
- [ ] `pnpm type-check` passes at the base (strict, zero errors — shared lib +
every deck in one program).
- [ ] `node build.mjs --only=<slug>` succeeds with **zero frontmatter or
registry warnings**; `dist/<slug>/` uses relative paths (`base: './'`).
- [ ] Dev server boots; **zero console errors/warnings** in `/browse`.
- [ ] The write surface held: nothing outside `<base>/<slug>/**` and the
spec README's frontmatter block was modified — except a registered
component promotion (new file under `<base>/src/components/` + its
`COMPONENTS.md` entry in the same change).
- [ ] If anything under `<base>/src/` changed, the **full** `node build.mjs`
passes (a shared change must not break sibling decks).
- [ ] The frontmatter block validates (title/tagline/date/status; no `slug`
key; date is `YYYY-MM-DD`) and the spec lands on the roadmap timeline
in date order, its card rendered from the frontmatter.
- [ ] `#/spec` renders every spec md file with the frontmatter stripped.
- [ ] `src/content/example.ts` and the starter example sections/pages were
deleted or replaced — no placeholder content ships.
## Req 1 — engineers understand the spec
- [ ] Every major design pillar in the spec has a slide; nothing load-bearing
is dropped.
- [ ] Each slide makes exactly one claim, and its visual proves it.
- [ ] The architecture is one navigable map (A4), not prose.
- [ ] Field-level detail exists for skeptics, behind a `<SpecSheet>`, closed.
- [ ] Every claim is grounded in the spec; one honest trade-offs beat is kept.
## Req 2 — interactive
- [ ] At least three genuinely interactive archetypes are present (e.g. a
stepper, the selectable map, a toggle or decision flow).
- [ ] Every control is keyboard-operable, disables at its bounds, and shows a
visible active state.
- [ ] All motion respects `prefers-reduced-motion`; the static fallback is
fully readable.
- [ ] Scroll-spy nav + scroll-reveal work; deep-dive hash routes load and the
back link returns home.
## Req 3 — looks like marketing
- [ ] The hero states the *win* in one line + three values — not the mechanism.
- [ ] A problem→solution arc is present, with at least one before/after and one
quantified scorecard.
- [ ] The accent is rationed (success / active / CTA only); no gradient or
shadow drift (only `deal-shadow`).
- [ ] Light and dark are both correct; the accent swaps orange↔blue; there is
no theme flash on load.
- [ ] Typography law holds: Chivo Mono, ligatures off, lowercase copy,
identifiers keep their casing, caps tracking on labels.
- [ ] Voice: no em-dashes; no "robust / comprehensive / crucial / seamless /
unlock / delve".
## Req 4 — build-in-public ready
- [ ] Responsive: no horizontal body scroll at 375px; wide diagrams scroll
inside their own `overflow-x-auto` container.
- [ ] `dist/<slug>/` is individually portable (relative asset paths — any CDN,
any prefix); the raw spec `.md` files sit beside its `index.html`.
- [ ] Title + meta description set (`index.html` tokens substituted); the tab
wordmark is coherent.
- [ ] No secrets, no internal-only URLs, no broken links. Safe to share.
## Anti-slop check
None of these slipped in: a gradient hero; emoji as iconography; everything
centered; three identical rounded-2xl drop-shadow feature cards; purple→blue
gradients; "Empower / Seamless / Unlock" copy; the accent on every element.
SKILL.md
---
name: presentation
description: >-
Turn a tech-spec directory into an interactive, marketing-grade web
presentation — built so engineers understand the design, the reader is
convinced of the why, and the result is shareable in public. Use when someone
wants a spec turned into a deck.
---
# Presentation
Turn a technical specification into an interactive, persuasive web deck —
the kind at iii.dev/roadmap/. The output is a **content layer** inside the
repo's roadmap base (the shared component library, gallery, and markdown
spec viewer that build every deck into one static site — Astro routes of the
site package in iii, a standalone Vite project in other repos):
1. helps engineers **understand** the spec — the architecture is a navigable
map, not prose;
2. is **interactive** — steppable diagrams, a selectable system map, live
toggles; interactivity is what makes it stick;
3. reads like **marketing** — it argues the *why*. if no one is convinced the
work should happen, the spec has not done its job;
4. is **build-in-public ready** — each deck ships as a static page at
`/roadmap/<slug>/`, safe to share.
## Comparable to
A product launch microsite generated from an RFC. Stripe-doc clarity meets a
keynote narrative, in a monospace drafting-sheet style.
## Activation
### Use For
- generating an interactive deck from a tech-spec directory
- refreshing or extending a presentation already generated by this skill
### Do Not Use For
- writing the spec itself — use `/tech-spec`
- static slide exports (pdf / keynote) — use a slide tool
- general UI work unrelated to a spec — use `/design`
## Load First
Read these before building (they are the law — do not re-derive them):
- `reference/design-system.md` — the locked tokens, type, motion, layout
- `reference/archetypes.md` — the interactive slide library + how to pick one
- `reference/component-standards.md` — deck-local vs promoted components, the
promotion checklist, the registry format
- `reference/narrative-framework.md` — the persuasive arc + outline rules
- `reference/quality-bar.md` — the checklist to self-verify before finishing
- `reference/hosting.md` — the two-tree layout, the pairing contract,
frontmatter registration, and deploy
- **per repo:** `<base>/COMPONENTS.md` — the live registry of that repo's
shared components. It may exceed the bundled catalog; when it and
`reference/archetypes.md` disagree, **the repo registry wins**.
The skill bundles two scaffolds:
- `template/` — one deck's **content layer** (App, sections, pages, content
data, the spec-docs glob). Copy it per spec; everything visual comes from the
base's shared `src/` via the `@lib` alias. You generate only content.
- `base/` — the whole per-repo presentations site: the shared component
library + design tokens, the gallery, the md-only spec viewer, and the build
glue (`build.mjs`, `vite.config.ts`, one `package.json`). Copy once per repo
(in iii it already lives at `website/roadmap/`); per-deck runs never
modify it except **additive component promotion** per
`reference/component-standards.md`.
## Progress Updates
Emit one short line before each phase: `ingesting spec` → `reading the
component registry` → `proposing outline` → `scaffolding` → `generating slides
(k/N)` → `registering spec frontmatter` → `verifying`.
## Workflow
Phases are gated. Do not skip Phase 2's approval or Phase 5's verification.
### 0. Resolve inputs
- The argument is a tech-spec directory: `<repo>/tech-specs/<slug>/` —
**markdown only** (README.md + domain docs; frontmatter in README.md). If
given a path elsewhere, resolve into the spec tree or ask.
- The **slug is the spec directory's basename** (e.g. `2026-06-21-devexp` —
`YYYY-MM-DD-<name>`; the day prefix orders the roadmap timeline). It is
the deck directory name AND the URL segment — the pairing contract in
`reference/hosting.md`. Fix it now and use it everywhere; never prettify it.
- Resolve the **base project**: read `<repo>/tech-specs/README.md` — the
pointer names the base dir (in iii: `website/roadmap/`). Fallback:
search for a dir containing both `COMPONENTS.md` and a shared `src/`.
Detect its shape:
- **integrated base** (shared `src/` + `scripts/manifest.mjs`, no
package.json or build.mjs of its own — iii's shape: the site's Astro
pages at `website/src/pages/roadmap/` render each deck's `src/App.tsx`
as a React island via the base's `src/DeckHost.tsx`; deps live in the
`iii-website` package) → use it, and scaffold content layers only;
- **standalone base** (`build.mjs` + own `package.json`, one `index.html`
per deck — the `base/` snapshot's shape) → use it;
- **absent** → first run in this repo: pick the location with the user
(default `website/roadmap/` when `website/` exists, else
`roadmap/` at the repo root) and scaffold it in Phase 3;
- **legacy layout** (`tech-specs/build.mjs` + `_gallery/` — per-deck
standalone projects) → stop and offer the port procedure in
`reference/hosting.md` before generating anything new.
- Output location is `<base>/<slug>/`. If it exists and is non-empty, ask:
overwrite, update in place, or abort. **Never write a non-markdown file
under `tech-specs/`.**
- Detect the install mode: workspace (repo `pnpm-workspace.yaml` lists the
base) vs standalone (`pnpm install --ignore-workspace` inside the base).
### 1. Deep ingest (read, do not skim)
- Read the spec `README.md` in full first: thesis, architecture, principles,
cross-cutting contracts, migration overview. Note whether it already has a
frontmatter block (title/tagline/date/tags/status).
- Read every domain doc. For each, capture: the one load-bearing phrase, the
pain it removes, the mechanism, any schema/fields, any sequence/lifecycle,
any numbers, any honest trade-off.
- Build a **content inventory** (architecture, protocol/wire contract,
lifecycle, state model, config schema, security, migration, …). This is the
raw material for archetype matching.
**1b. Component awareness (before planning).** Read `<base>/COMPONENTS.md` end
to end and list `<base>/src/components/{schematic,diagrams}/` + `src/hooks/`.
The registry is the live catalog for this repo and supersedes the bundled
`reference/archetypes.md` where they disagree. **Reuse-first mandate:** a
slide may get a bespoke visual only after the catalog demonstrably has no fit
for its content shape. Name any planned new component in the Phase 2 outline,
marked `local` or `promote` (see `reference/component-standards.md`), so the
user approves it at the same gate.
### 2. Narrative plan — THE GATE
- Apply the arc in `reference/narrative-framework.md`. Produce a deck outline:
an ordered slide list, each with `{ title, archetype (or reused registry
component), the single claim, source section(s), the concrete data it pulls,
interactivity, new component: <Name> (local|promote) — only when nothing
fits }`. Include candidate deep-dive pages.
- Derive the hero line + three-value subhead + stat strip. Choose the wordmark
label.
- **Present the outline to the user for approval/edits before scaffolding.**
This is the cheapest place to turn a dry spec into a story. Skip only if the
user explicitly says "just build it".
### 3. Scaffold (mechanical)
**The deck:**
- `mkdir -p <base>/<slug>/` and copy `template/` into it — in an
**integrated base (iii)** copy `template/src/` only and skip `index.html`
and `src/main.tsx` (the site's `[slug]/index.astro` route provides the
document shell and mounts `src/App.tsx`; the page title/description come
from the spec frontmatter).
- Substitute the `__SPEC_MD_GLOB__` literal in `src/spec-docs.ts` with the
computed relative path from `<base>/<slug>/src/` to
`<specs-dir>/<slug>/*.md` (in iii: `../../../../tech-specs/<slug>/*.md`);
in a standalone base also `__TITLE__` / `__DESCRIPTION__` in `index.html`.
- **No per-deck install, no per-deck config, no lockfile.** Ensure deps once:
workspace mode → `pnpm install` at the repo root (only if the base's deps
are missing); standalone mode → `pnpm install --ignore-workspace` in
`<base>` (commit the generated lockfile).
**Registration:** write or update the YAML frontmatter block at the top of
`tech-specs/<slug>/README.md` (schema in `reference/hosting.md`): title +
tagline from the approved hero, `date: YYYY-MM-DD` (day precision — the
roadmap timeline orders and labels by it), 0–4 tags, `status: draft`.
There is no central manifest — the build aggregates every spec's frontmatter,
so this run touches nothing shared. If frontmatter already exists, update only
the fields this run owns (tagline polish, status).
**The base project** (first run in a repo only): copy `base/` into the chosen
dir (never its `node_modules`/`dist`). Fill the identity once: `__REPO__` in
`package.json`; the `__GALLERY_*__` / `__WORDMARK_LABEL__` / `__HERO_*__` /
`__ATTRIBUTION__` / `__SITE_HOST__` tokens in `index.html`,
`src/gallery/site.ts`, and `README.md`; write the `tech-specs/README.md`
pointer. The gallery page is a **roadmap**: hero copy in roadmap voice
(`__HERO_TITLE__` ≈ "what we're working on"; `__HERO_LEAD__` hints at the
current priority and what already landed, without naming specs), and the spec
list renders as a one-column timeline, newest first, grouped by month. In a workspace repo, add the base to `pnpm-workspace.yaml` **with
user confirmation** (a repo-level file). Never touch `build.mjs`,
`vite.config.ts`, tsconfigs, or `src/` beyond this copy.
### 4. Generate the content layer
Edit only these — the write surface is `<base>/<slug>/**` plus the spec's
frontmatter block (and an approved promotion):
- `src/content/deck.ts` — `DECK_META.wordmarkLabel`, `NAV`, `FOOTER`.
- `src/content/<topic>.ts` — the typed data arrays each archetype consumes
(map nodes/edges/info, sequence lanes/steps, reveal stages, cli tracks,
metrics, rows). Keep data here, out of components.
- `src/sections/<Name>.tsx` — one thin section per slide: import the matching
archetype from `@lib`, feed it data, wrap it in `<Section>`. Replace the
example sections; delete `src/content/example.ts` and `pages/ExamplePage.tsx`.
- `src/pages/<Name>.tsx` — deep dives via `@lib` `<PageShell>`.
- `src/App.tsx` — wire the ordered `SECTIONS` array and the `PAGES` map.
**The component protocol** (when a load-bearing concept has no fit in
`COMPONENTS.md`):
1. **Default: build it deck-local** in `<base>/<slug>/src/diagrams/<Name>.tsx`,
following `@lib/components/diagrams/SequencePlayer.tsx` conventions.
2. **Promote into `<base>/src/components/` only when all three hold:** (a) it
is generic over its data — nothing spec-specific inside, everything arrives
via typed props; (b) it maps to a recurring spec shape (a lifecycle, a
tree, a timeline, a fan-out…) future decks will plausibly need; (c) it
passes the checklist in `reference/component-standards.md` without
deck-specific hacks.
3. A promotion = the component file **plus its `COMPONENTS.md` entry in the
same change**. An unregistered shared component is a defect (the base's
registry check warns — `scripts/validate-roadmap.ts` in iii, `build.mjs`
standalone; strict mode makes it fatal).
4. Never fork a shared component into the deck to tweak it — extend it via
additive, non-breaking props, or build a genuinely different deck-local
one. **Modifying an existing shared component requires explicit user
approval** (it re-renders every other deck).
**Built-in spec viewer — do not delete.** Every deck ships the `#/spec` page:
the template wires `spec-docs.ts` (the compile-time glob over the paired
spec's markdown) into `@lib/pages/SpecPage` via `PAGES.spec`, and the shared
`TopNav` renders the `spec` link. The shared markdown renderer strips the
frontmatter block. It needs no per-deck content — leave the wiring in place.
### 5. Verify — THE SECOND GATE
All commands run from `<base>`'s package (iii: `pnpm --filter iii-website
<script>` from the repo root; standalone: inside `<base>`):
- `pnpm type-check` — the whole roadmap project (shared src + gallery + every
deck) must pass strict; fix every error.
- The build must succeed with zero frontmatter-validation or registry-parity
warnings — iii: `pnpm build` (the site build runs the roadmap contract
checks and emits `dist/roadmap/<slug>/`); standalone: `node build.mjs
--only=<slug>`.
- `pnpm dev` in the background, then dogfood with the **`/browse` skill**
(never `mcp__claude-in-chrome__*`): load
`http://localhost:4321/roadmap/<slug>/` (standalone: `:5173/<slug>/`),
click the map, run a stepper, toggle the theme, open a deep-dive, open
`#/spec` and confirm every markdown file renders (mermaid fences live, no
raw frontmatter). Zero console errors; no horizontal body scroll at 375px.
- `pnpm build && pnpm preview`, then `/browse
http://localhost:4321/roadmap/` (standalone: `:4173/`): the spec appears on
the roadmap timeline in date order under its month, its card shows the
frontmatter title/tagline/tags with the day marker in the timeline gutter,
and clicking it lands on `/roadmap/<slug>/`. If the spec previously served
the md-only viewer, confirm the deck replaced it at the same URL.
- **If anything under `<base>/src/` was touched (a promotion): run the full
site build** — a shared change must not break sibling decks.
- Run `reference/quality-bar.md` end to end; fix anything red.
### 6. Hand off
Report: the deck path (`<base>/<slug>/`), the dev URL, the slide list, which
interactions are live, the frontmatter written, and any promoted components
(with their registry entries). Offer to flip `status: draft` → `live`.
Deploy is not a step: **in iii, merging to main ships everything via
`.github/workflows/deploy-website.yml` to `iii.dev/roadmap/<slug>/`**; in
other repos `dist/` is a portable static site for whatever CI they use. Do
**not** commit, push, or configure hosting unless asked. Never run `vercel`.
## Rules
- **The shared layer is law**: never edit `<base>/src/**`, the base's build
glue (iii: `website/src/pages/roadmap/` and `scripts/validate-roadmap.ts`;
standalone: `build.mjs` and `vite.config.ts`), tsconfigs, `package.json`,
or the gallery in a deck run.
Sole exception: additive component promotion under
`reference/component-standards.md`, always paired with a `COMPONENTS.md`
entry.
- A deck run's write surface is `<base>/<slug>/**` plus the frontmatter block
of `tech-specs/<slug>/README.md`. Nothing else.
- `tech-specs/` holds markdown only. Never write a non-md file there; never
create a `presentation/` directory inside it.
- Reuse first: consult `COMPONENTS.md` before building any visual; the repo
registry outranks the bundled catalog.
- Slug = the spec directory basename, used identically as the deck dir and the
URL. Never prettify it. `slug` is never a frontmatter field.
- One claim per slide. Put depth behind a `<SpecSheet>` (`<details>`), closed
by default — execs skim, engineers drill.
- Ration the accent (success / active / CTA only). Lowercase copy; identifiers
keep their casing. No em-dashes. No "robust / comprehensive / crucial /
seamless / unlock".
- Ground every claim in the spec. Keep one honest trade-offs/open-questions
beat — honesty reads as credibility.
- Quantify the win: use the spec's numbers verbatim; if you compute one, say so.
## Decisions
Ask the user: the narrative outline (Phase 2, always); output location when
occupied; audience tilt (execs vs engineers; default balanced); fonts offline
vs CDN (default CDN); scope cap when a spec has many domain docs; first-run
base location + gallery identity + the `pnpm-workspace.yaml` edit; any
**modification** to an existing shared component; flipping `status` to `live`.
Decide yourself: which archetype each slide uses; slide order within the arc;
hero line and stats; SVG coordinates and step counts; what goes behind a
`<details>`; copy voice; the frontmatter field values; deck-local vs promote
per the component protocol (announce promotions in the outline and hand-off —
don't ask). Everything under `<base>/src/` and the build glue is settled —
never re-litigate it in a deck run.
## Verify
`pnpm type-check` and the roadmap build (iii: `pnpm --filter iii-website
build`; standalone: `node build.mjs --only=<slug>`) are green with zero
registry/frontmatter warnings; `/browse` shows zero console errors, working
interactions, and a frontmatter-free `#/spec`; the roadmap timeline lists the
spec in date order, its card rendered from the frontmatter; `reference/quality-bar.md` passes; a promotion ran the full
build (`reference/hosting.md` has the layout this all serves).
template/index.html
<!doctype html>
<html lang="en" class="antialiased">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>__TITLE__</title>
<meta name="description" content="__DESCRIPTION__" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Chivo+Mono:wght@400;500;600&display=swap"
/>
<script>
try {
const t = localStorage.getItem('iii-theme');
document.documentElement.dataset.theme =
t === 'dark' || t === 'light' ? t : 'light';
} catch (_) {
document.documentElement.dataset.theme = 'light';
}
</script>
</head>
<body class="bg-bg text-ink font-sans">
<div id="root"></div>
<script type="module" src="./src/main.tsx"></script>
</body>
</html>
template/README.md
# deck content layer
This is one presentation's content layer, scaffolded by the `/presentation`
skill into `<base>/<slug>/` (in iii: `website/roadmap/<slug>/`). It has
no package.json, no config, no lockfile — dependencies, build, and the shared
component library live in the base project one level up. Everything visual
imports from `@lib`; see the base's `COMPONENTS.md` before building anything
new. Dev: `pnpm dev` from the base serves this deck at `/<slug>/`.
template/src/App.tsx
import { Footer } from '@lib/components/Footer'
import { Sheet } from '@lib/components/schematic/Sheet'
import { TopNav } from '@lib/components/TopNav'
import { useHashRoute } from '@lib/hooks/useHashRoute'
import { SpecPage } from '@lib/pages/SpecPage'
import type { ComponentType } from 'react'
import { DECK_META, FOOTER, NAV } from './content/deck'
import { ExamplePage } from './pages/ExamplePage'
import { Hero } from './sections/Hero'
import { PayoffSection } from './sections/PayoffSection'
import { SystemMapSection } from './sections/SystemMapSection'
import { SPEC_DOCS } from './spec-docs'
/**
* Ordered home-page sections. The first is the hero; the rest each carry a DOM
* id matching a NAV entry in content/deck.ts for scroll-spy.
*/
const SECTIONS: ComponentType[] = [Hero, SystemMapSection, PayoffSection]
// The built-in spec viewer — never remove. It renders every markdown file of
// the paired tech-specs/<slug>/ directory with a file sidebar.
const Spec = () => <SpecPage docs={SPEC_DOCS} />
/** deep-dive pages, keyed by the `#/<slug>` route slug. */
const PAGES: Record<string, ComponentType> = {
example: ExamplePage,
spec: Spec,
}
function Home() {
return (
<main>
{SECTIONS.map((Component, i) => (
<Component key={i} />
))}
</main>
)
}
function NotFound() {
return (
<main className="px-4 py-24 @3xl:px-9">
<p className="font-mono text-[14px] lowercase text-ink-faint">
nothing here.{' '}
<a href="#/" className="text-ink hover:text-accent transition-colors">
← back to the overview
</a>
</p>
</main>
)
}
export default function App() {
const route = useHashRoute()
const Page = route.kind === 'page' ? PAGES[route.slug] : undefined
return (
<div className="@container min-h-screen">
<Sheet>
<TopNav route={route} meta={DECK_META} nav={NAV} />
{route.kind === 'home' ? <Home /> : Page ? <Page /> : <NotFound />}
<Footer footer={FOOTER} />
</Sheet>
</div>
)
}
template/src/content/deck.ts
/**
* deck.ts — the one file that describes THIS presentation.
*
* The /presentation skill rewrites this per tech-spec. Everything here is pure
* data (no JSX); the shared chrome (TopNav, Footer) receives it as props from
* App.tsx — shared components never reach into deck content. The ordered list
* of section COMPONENTS and the deep-dive PAGES map live in App.tsx.
*/
import type { DeckMeta, FooterSpec, NavItem } from '@lib/lib/deck-types'
export const DECK_META: DeckMeta = {
wordmarkLabel: 'spec presentation',
}
/**
* top-nav section links. each id must match the `id` passed to a <Section>.
* keep this short (6–9 items) — it is the reader's map of the whole deck.
*/
export const NAV: NavItem[] = [
{ id: 'map', label: 'map' },
{ id: 'payoff', label: 'payoff' },
]
export const FOOTER: FooterSpec = {
eyebrow: 'get started',
headline: 'one file. one command.',
command: 'iii up',
attribution: 'spec presentation — architecture overview',
source: 'source of truth: tech-specs/<your-spec>',
}
template/src/content/example.ts
/**
* example.ts — PLACEHOLDER DATA for the starter deck.
*
* The /presentation skill replaces this with data extracted from the tech-spec
* (often split into per-topic modules). It exists so the template renders and
* builds out of the box, and so there is a concrete, copyable example of the
* shape each archetype expects. Delete it once you have real content.
*/
import type { CliTrack } from '@lib/components/diagrams/CliPlayground'
import type { FanHandler } from '@lib/components/diagrams/FanOut'
import type { FunnelPath } from '@lib/components/diagrams/Funnel'
import type { SeqLane, SeqStep } from '@lib/components/diagrams/SequencePlayer'
import type { RevealStage } from '@lib/components/diagrams/StepReveal'
import type { MapEdge, MapNode, MapNodeInfo } from '@lib/components/diagrams/SystemMap'
/* ---- hero ---- */
export const HERO_STATS = [
{ value: '4', label: 'standalone parts' },
{ value: '1', label: 'declarative file' },
{ value: '0', label: 'manual steps' },
{ value: '100%', label: 'typed contracts' },
] as const
export const HERO_CLAIMS = [
{
title: 'one file',
body: 'the whole system is declared in one place. no scattered config, no second source of truth.',
},
{
title: 'one command',
body: 'bring the entire stack up with a single command. no two-terminal dance, no ordering races.',
},
{
title: 'clean planes',
body: 'each part owns exactly one job and talks only over typed contracts. no hidden coupling.',
},
{
title: 'correct by construction',
body: 'the failure modes that used to need discipline are now impossible by design.',
},
] as const
/* ---- system map (A4) ---- */
export const MAP_NODES: MapNode[] = [
{ id: 'client', x: 40, y: 250, w: 170, h: 60, title: 'client', sub: 'any caller', kind: 'external' },
{ id: 'gateway', x: 320, y: 248, w: 200, h: 66, title: 'gateway', sub: 'routes calls', kind: 'primary' },
{ id: 'worker', x: 650, y: 120, w: 200, h: 66, title: 'worker', sub: 'runs the loop', kind: 'primary' },
{ id: 'store', x: 650, y: 376, w: 200, h: 66, title: 'store', sub: 'durable state', kind: 'secondary' },
{ id: 'watcher', x: 900, y: 248, w: 120, h: 66, title: 'watcher', sub: 'reacts', kind: 'optional' },
]
export const MAP_EDGES: MapEdge[] = [
{
id: 'request',
from: 'client',
to: 'gateway',
d: 'M 210 280 L 320 281',
label: 'request',
lx: 265,
ly: 272,
dur: 2,
},
{
id: 'dispatch',
from: 'gateway',
to: 'worker',
d: 'M 520 268 C 590 250, 600 158, 650 152',
label: 'dispatch',
lx: 600,
ly: 196,
anchor: 'start',
dur: 1.8,
},
{
id: 'persist',
from: 'gateway',
to: 'store',
d: 'M 520 296 C 590 320, 600 404, 650 410',
label: 'persist',
lx: 600,
ly: 372,
anchor: 'start',
dur: 1.8,
},
{
id: 'write',
from: 'worker',
to: 'store',
d: 'M 750 186 L 750 376',
label: 'write result',
lx: 758,
ly: 290,
anchor: 'start',
dur: 1.6,
},
{
id: 'emit',
from: 'store',
to: 'watcher',
d: 'M 850 404 C 920 388, 960 332, 960 314',
label: 'emit event',
lx: 968,
ly: 360,
anchor: 'start',
dur: 2.2,
},
]
export const MAP_INFO: Record<string, MapNodeInfo> = {
client: {
id: 'client',
kindLabel: 'external',
role: 'any caller — a web app, a cli, another service. it only knows the gateway contract.',
sections: [
{
heading: 'calls',
items: [{ name: 'gateway::request', desc: 'submit work and get an id back immediately.' }],
},
],
note: 'replace this node with the real entry points of your system.',
},
gateway: {
id: 'gateway',
kindLabel: 'core',
role: 'the single front door: validates, routes, and hands work to the right plane.',
sections: [
{
heading: 'surface',
items: [
{ name: 'gateway::request', desc: 'accept work, enqueue it, return fast.' },
{ name: 'gateway::status', desc: 'point-in-time read of a request.' },
],
},
],
install: 'add gateway',
},
worker: {
id: 'worker',
kindLabel: 'core',
role: 'runs the actual work loop — durable, resumable, one job at a time.',
sections: [
{
heading: 'surface',
items: [
{ name: 'worker::run', desc: 'execute one unit of work to completion.' },
{ name: 'compose::down', desc: 'stop a supervised worker graph.' },
],
},
],
bullets: ['every step is a durable entry — a crash resumes mid-run.'],
install: 'add worker',
},
store: {
id: 'store',
kindLabel: 'supporting',
role: 'the durable, reactive state — every write emits an event.',
sections: [
{
heading: 'emits',
dotted: true,
items: [
{ name: 'store::written', desc: 'a record changed — bind and render live.' },
{ name: 'store::removed', desc: 'a record was deleted.' },
],
},
],
note: 'pure storage + notification. it runs no business logic of its own.',
},
watcher: {
id: 'watcher',
kindLabel: 'optional',
role: 'an optional reactor: binds store events and does something useful — notify, index, escalate.',
sections: [
{
heading: 'binds',
items: [{ name: 'store::written', desc: 'the only thing it needs to know.' }],
},
],
note: 'optional by design — the core three run with or without it.',
},
}
/* ---- sequence (A5) ---- */
export const SEQ_LANES: SeqLane[] = [
{ id: 'client', label: 'client', x: 110 },
{ id: 'gateway', label: 'gateway', x: 380 },
{ id: 'worker', label: 'worker', x: 640 },
{ id: 'store', label: 'store', x: 880 },
]
export const SEQ_STEPS: SeqStep[] = [
{
from: 'client',
to: 'gateway',
label: 'gateway::request',
title: 'submit work',
desc: 'the client drops a request and gets a request id back immediately — no blocking.',
},
{
from: 'gateway',
to: 'store',
label: 'store::write',
title: 'record it',
desc: 'the gateway persists the new request; the write fires an event.',
event: 'store::written',
},
{
from: 'gateway',
to: 'worker',
label: 'worker::run',
title: 'dispatch',
desc: 'the gateway hands the queued work to a worker to execute.',
},
{
from: 'worker',
to: 'worker',
label: 'do the work',
title: 'run the loop',
desc: 'the worker runs durable steps; a crash resumes from the last committed step.',
},
{
from: 'worker',
to: 'store',
label: 'store::write',
title: 'write result',
desc: 'the worker commits the typed result; the store fires another event.',
event: 'store::written',
},
{
from: 'store',
to: 'client',
label: 'live update',
title: 'render live',
desc: 'the client was subscribed all along — the result appears with no polling.',
},
]
/* ---- payoff (A11) ---- */
export const PAYOFF_METRICS = [
{ label: 'files to edit', before: '7', after: '1' },
{ label: 'commands to boot', before: '5', after: '1' },
{ label: 'terminals open', before: '2', after: '1' },
{ label: 'startup races', before: 'many', after: '0' },
] as const
export const PAYOFF_SOLVES = [
{
problem: 'config scattered across files',
answer: 'one declarative file',
detail: 'a single source of truth the whole stack reads from.',
},
{
problem: 'manual, order-sensitive boot',
answer: 'one command, topo-sorted',
detail: 'dependencies bring up in the right order automatically.',
},
{
problem: 'state changes you have to poll for',
answer: 'events on every write',
detail: 'bind once and render live — never chase status again.',
},
] as const
/* ---- lifecycle / durability (A6) ---- */
export const REVEAL_STAGES: RevealStage[] = [
{
label: 'queued',
tone: 'ink',
caption: 'the request lands and is written as a durable entry before anything runs.',
rows: [
{ k: 'status', v: 'queued' },
{ k: 'step', v: '0 / 3' },
],
},
{
label: 'running',
tone: 'ink',
caption: 'a worker picks it up and commits each step as it goes.',
rows: [
{ k: 'status', v: 'running' },
{ k: 'step', v: '2 / 3' },
],
},
{
label: 'crash',
tone: 'alert',
caption: 'the worker dies mid-step. the in-flight step is lost — but the committed log is not.',
rows: [
{ k: 'status', v: 'interrupted' },
{ k: 'step', v: '2 / 3' },
],
note: 'a crash is a state transition, not lost work',
},
{
label: 'resume',
tone: 'warn',
caption: 'a fresh worker reads the log and continues from the last committed step. nothing replays twice.',
rows: [
{ k: 'status', v: 'resuming' },
{ k: 'step', v: '2 / 3' },
],
},
{
label: 'done',
tone: 'accent',
caption: 'the final step commits and the typed result is written.',
rows: [
{ k: 'status', v: 'done' },
{ k: 'step', v: '3 / 3' },
],
note: 'work lost: none. steps replayed: zero.',
},
]
/* ---- reactive fan-out (A7) ---- */
export const FAN_SOURCE = { label: 'store::write', sub: 'one commit, no publish step' }
export const FAN_TRIGGER = 'store::written'
export const FAN_HANDLERS: FanHandler[] = [
{ id: 'watcher', label: 'watcher::react', desc: 'escalate or act on the change' },
{ id: 'index', label: 'search::index', desc: 'keep the index current' },
{ id: 'metrics', label: 'metrics::count', desc: 'live dashboard tiles' },
]
/* ---- convergence funnel (A8) ---- */
export const FUNNEL_PATHS: FunnelPath[] = [
{ id: 'fire', label: 'fire and forget', desc: 'no record, no retry' },
{ id: 'thread', label: 'detached thread', desc: 'dies with the process' },
{ id: 'retry', label: 'naive retry', desc: 'duplicates on restart' },
]
export const FUNNEL_TARGET = { label: 'durable queue entry', sub: 'committed before ack — exactly-once, resumable' }
export const FUNNEL_REJECT = { label: 'in-memory only', desc: 'lost on crash — removed by design' }
/* ---- cli playground (A3) ---- */
export const CLI_TRACKS: CliTrack[] = [
{
id: 'first-run',
label: 'first run',
lines: [
{ cmd: 'your-cli up', out: ['→ reading your-spec.yml', '✓ stack up — gateway, worker, store'] },
{
cmd: 'your-cli request { input }',
fn: 'gateway::request',
out: ['→ { id: "r_8c2", accepted: true }'],
exit: 0,
},
{ cmd: 'your-cli status r_8c2', fn: 'gateway::status', out: ['✓ done — result ready'] },
],
},
{
id: 'day-2',
label: 'day-2 ops',
lines: [
{ cmd: 'your-cli ps', out: ['ID STATUS UPTIME', 'worker-1 running 4h12m'] },
{
cmd: 'your-cli logs worker-1 --tail 2',
fn: 'ops::logs',
out: ['"handled r_8c1 in 12ms"', '"handled r_8c2 in 9ms"'],
exit: 0,
},
],
},
]
template/src/diagrams/.gitkeep
template/src/main.tsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import '@lib/index.css'
import App from './App'
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
)
template/src/pages/ExamplePage.tsx
import { SequencePlayer } from '@lib/components/diagrams/SequencePlayer'
import { PageShell } from '@lib/components/PageShell'
import { C, CodeBlock, K, M, S } from '@lib/components/schematic/CodeBlock'
import { SEQ_LANES, SEQ_STEPS } from '../content/example'
/**
* A14 — a deep-dive page (`#/example` route). Use one per distinct
* consumer/scenario worth its own full page: a focused sequence plus the code
* a reader would actually write. Cross-link siblings via PageShell `related`.
*/
export function ExamplePage() {
return (
<PageShell
eyebrow="deep dive"
title="a worked example, end to end"
description="a deep-dive page lives at its own #/slug route. give one to each scenario that deserves the full walkthrough — a sequence, the real code, and the contracts behind it."
>
<SequencePlayer title="the example flow" lanes={SEQ_LANES} steps={SEQ_STEPS} />
<CodeBlock title="what the caller writes">
<K>await</K> client.<K>request</K>(<M>{'{'}</M>
{'\n '}input: <S>"do the thing"</S>,{'\n '}on: <M>{'{'}</M> done: (r) <M>{'=>'}</M> render(r) <M>{'}'}</M>,{' '}
<C>{'// bound once, fires live'}</C>
{'\n'}
<M>{'})'}</M>
</CodeBlock>
</PageShell>
)
}
template/src/sections/Hero.tsx
import { Caret } from '@lib/components/schematic/Caret'
import { Cell } from '@lib/components/schematic/Cell'
import { Prompt } from '@lib/components/schematic/Prompt'
import { StatusDot } from '@lib/components/schematic/StatusDot'
import { Terminal, TerminalRow } from '@lib/components/schematic/Terminal'
import { HERO_CLAIMS, HERO_STATS } from '../content/example'
/**
* A1 — the hero. States the WIN in one line plus a three-value subhead; never
* the mechanism. CTAs jump into the map and the payoff. A small terminal makes
* the promise concrete; the stat strip quantifies it.
*/
export function Hero() {
return (
<section id="hero">
<div className="grid grid-cols-1 @4xl:grid-cols-2 gap-x-16 gap-y-12 px-4 pt-16 pb-14 @3xl:px-9 @3xl:pt-24 @3xl:pb-20">
<div className="min-w-0">
<div className="font-mono text-[11px] uppercase tracking-[0.06em] text-ink-faint mb-6">
<Prompt symbol="$">tech-specs / your-spec</Prompt>
</div>
<h1 className="font-mono font-semibold lowercase text-ink text-[44px] leading-[1.05] tracking-[-0.02em] @3xl:text-[64px] @3xl:leading-[1.02]">
one file.
<br />
one command.
</h1>
<p className="mt-6 font-mono text-[14px] leading-[1.7] text-ink-faint lowercase max-w-[56ch]">
the whole system, declared once and brought up in a single step — <span className="text-ink">simple</span>{' '}
to run, <span className="text-ink">structured</span> by design, <span className="text-ink">correct</span> by
construction.
</p>
<div className="mt-8 flex flex-wrap items-center gap-3">
<a
href="#map"
className="inline-flex h-11 items-center bg-ink text-bg border border-ink px-5 font-mono text-[14px] lowercase transition-colors hover:bg-bg hover:text-ink"
>
walk the architecture →
</a>
<a
href="#payoff"
className="inline-flex h-11 items-center bg-bg text-ink border border-ink px-5 font-mono text-[14px] lowercase transition-colors hover:bg-ink hover:text-bg"
>
skip to the payoff
</a>
</div>
</div>
<div className="min-w-0 self-center">
<Terminal title="from zero to running">
<div className="flex flex-col gap-y-2.5">
<TerminalRow
command="your-cli up"
output={
<span className="flex items-center gap-x-2">
<StatusDot pulse />
<span>stack up — gateway, worker, store connected</span>
</span>
}
/>
<TerminalRow
command={
<>
your-cli request <span className="text-ink-faint">{'{ input }'}</span>
</>
}
output={<span className="tabular-nums">{'→ { id: "r_8c2", accepted: true }'}</span>}
/>
<div className="flex items-center gap-x-2">
<Prompt symbol="$" />
<Caret />
</div>
</div>
</Terminal>
<div className="grid grid-cols-2 @lg:grid-cols-4 border-x border-b border-rule bg-rule gap-px">
{HERO_STATS.map((stat) => (
<div key={stat.label} className="bg-bg px-4 py-3.5 min-w-0">
<div className="font-mono text-[20px] font-semibold text-ink tabular-nums leading-none">
{stat.value}
</div>
<div className="mt-1.5 font-mono text-[10px] uppercase tracking-[0.06em] text-ink-faint leading-[1.4]">
{stat.label}
</div>
</div>
))}
</div>
</div>
</div>
<div className="border-t border-rule">
<div className="grid grid-cols-1 @2xl:grid-cols-2 @5xl:grid-cols-4 gap-px bg-rule">
{HERO_CLAIMS.map((claim, i) => (
<Cell
key={claim.title}
title={
<span className="flex items-center gap-x-2.5">
<span className="font-mono text-[11px] text-ink-ghost tabular-nums">0{i + 1}</span>
{claim.title}
</span>
}
className="border-0"
bodyClassName="max-w-[36ch]"
>
{claim.body}
</Cell>
))}
</div>
</div>
</section>
)
}
template/src/sections/PayoffSection.tsx
import { Section } from '@lib/components/Section'
import { PAYOFF_METRICS, PAYOFF_SOLVES } from '../content/example'
/**
* A11 — the payoff. Closes the persuasion loop: a quantified before/after
* scorecard, then a problem → answer table. The reader should leave convinced
* the work is worth doing.
*/
export function PayoffSection() {
return (
<Section
id="payoff"
index="07"
eyebrow="the payoff"
title="smaller surface, stronger guarantees."
lede="the same system, measured before and after. the failure modes that used to need discipline are now gone by construction."
>
{/* scorecard */}
<div className="grid grid-cols-2 @3xl:grid-cols-4 border-x border-t border-rule bg-rule gap-px">
{PAYOFF_METRICS.map((m) => (
<div key={m.label} className="bg-bg px-4 py-5 min-w-0">
<div className="flex items-baseline gap-x-2">
<span className="font-mono text-[13px] text-ink-ghost line-through tabular-nums">{m.before}</span>
<span className="font-mono text-[12px] text-ink-ghost">→</span>
<span className="font-mono text-[24px] font-semibold text-accent tabular-nums leading-none">
{m.after}
</span>
</div>
<div className="mt-2 font-mono text-[10px] uppercase tracking-[0.06em] text-ink-faint">{m.label}</div>
</div>
))}
</div>
{/* problem → answer */}
<div className="mt-8 grid grid-cols-1 @3xl:grid-cols-2 border border-rule bg-rule gap-px">
<div className="bg-panel px-4 py-2.5 font-mono text-[10px] uppercase tracking-[0.14em] text-ink-faint">
the problem
</div>
<div className="bg-panel px-4 py-2.5 font-mono text-[10px] uppercase tracking-[0.14em] text-ink-faint hidden @3xl:block">
the answer
</div>
{PAYOFF_SOLVES.map((row) => (
<div key={row.problem} className="contents">
<div className="bg-bg px-4 py-4 min-w-0">
<div className="font-mono text-[13px] text-alert lowercase">{row.problem}</div>
</div>
<div className="bg-bg px-4 py-4 min-w-0">
<div className="font-mono text-[13px] text-accent lowercase">{row.answer}</div>
<div className="mt-1 font-mono text-[12px] leading-[1.6] text-ink-faint lowercase">{row.detail}</div>
</div>
</div>
))}
</div>
</Section>
)
}
template/src/sections/SystemMapSection.tsx
import { MapDatasheet, SystemMap } from '@lib/components/diagrams/SystemMap'
import { Section } from '@lib/components/Section'
import { SpecRow, SpecSheet } from '@lib/components/SpecSheet'
import { StatusDot } from '@lib/components/schematic/StatusDot'
import { useEffect, useRef, useState } from 'react'
import { MAP_EDGES, MAP_INFO, MAP_NODES } from '../content/example'
/** matches tailwind @5xl container width (64rem) */
const PAIRED_LAYOUT_MIN_WIDTH = 1024
const LEGEND = [
{ swatch: <span className="inline-block size-3 border-[1.25px] border-ink bg-bg" />, label: 'core' },
{ swatch: <span className="inline-block size-3 border border-ink-faint bg-bg" />, label: 'supporting' },
{ swatch: <span className="inline-block size-3 border border-dashed border-ink-faint bg-bg" />, label: 'optional' },
{ swatch: <span className="inline-block size-3 border border-rule bg-bg" />, label: 'external' },
{ swatch: <StatusDot pulse />, label: 'active flow' },
] as const
export function SystemMapSection() {
const [selected, setSelected] = useState('gateway')
const layoutRef = useRef<HTMLDivElement>(null)
const mapRef = useRef<HTMLDivElement>(null)
const [pairedLayout, setPairedLayout] = useState(false)
const [mapHeight, setMapHeight] = useState<number | undefined>()
useEffect(() => {
const layoutEl = layoutRef.current
const mapEl = mapRef.current
if (!layoutEl || !mapEl) return
const sync = () => {
const paired = layoutEl.clientWidth >= PAIRED_LAYOUT_MIN_WIDTH
setPairedLayout(paired)
setMapHeight(paired ? mapEl.offsetHeight : undefined)
}
sync()
const observer = new ResizeObserver(sync)
observer.observe(layoutEl)
observer.observe(mapEl)
return () => observer.disconnect()
}, [])
const info = MAP_INFO[selected] ?? MAP_INFO.gateway
return (
<Section
id="map"
index="01"
eyebrow="system map"
title="a few parts, talking only over typed contracts."
lede="every arrow below is a call or an event — no hidden in-process coupling anywhere. click any node to read its datasheet: each part is useful on its own, and together they are the whole system."
>
<div className="flex flex-wrap items-center gap-x-5 gap-y-2 mb-5">
{LEGEND.map((item) => (
<span key={item.label} className="flex items-center gap-x-2">
{item.swatch}
<span className="font-mono text-[10px] uppercase tracking-[0.06em] text-ink-faint">{item.label}</span>
</span>
))}
</div>
<div ref={layoutRef} className="grid grid-cols-1 @5xl:grid-cols-[minmax(0,1fr)_340px] gap-6 items-stretch">
<div ref={mapRef} className="border border-rule bg-bg p-3 overflow-x-auto min-h-0 self-start">
<div className="min-w-[760px]">
<SystemMap nodes={MAP_NODES} edges={MAP_EDGES} selected={selected} onSelect={setSelected} />
</div>
</div>
<div
className="@5xl:sticky @5xl:top-16 min-h-0 overflow-hidden"
style={pairedLayout && mapHeight ? { height: mapHeight } : undefined}
>
<MapDatasheet
info={info}
className={pairedLayout ? 'h-full' : undefined}
layoutKey={pairedLayout ? mapHeight : 'stack'}
/>
</div>
</div>
<div className="mt-6 grid grid-cols-1 @4xl:grid-cols-2 gap-4">
<SpecSheet title="the wiring, in words" meta={`${MAP_EDGES.length} edges`}>
<div className="flex flex-col">
<SpecRow name="client → gateway::request" type="work in">
any caller drops a request and gets an id back immediately.
</SpecRow>
<SpecRow name="gateway → worker::run" type="dispatch">
queued work is handed to a worker to execute, in order.
</SpecRow>
<SpecRow name="gateway / worker → store" type="persist + emit">
every write lands in the durable store and fires an event.
</SpecRow>
<SpecRow name="store → watcher" type="reactive surface">
optional reactors bind store events and render or act live.
</SpecRow>
</div>
</SpecSheet>
<SpecSheet title="design principles" meta="from the spec">
<div className="flex flex-col">
<SpecRow name="standalone first">
every part is independently installable and useful by itself. cross-part calls are explicit, never
imports.
</SpecRow>
<SpecRow name="one job each">
the gateway routes, the worker runs, the store remembers. anything that grows real logic becomes its own
part.
</SpecRow>
<SpecRow name="reactive by default">
state changes emit events; consumers bind once and render live.
</SpecRow>
</div>
</SpecSheet>
</div>
</Section>
)
}
template/src/spec-docs.ts
/**
* Bundles this spec's markdown for the #/spec page. The glob MUST live
* deck-side (import.meta.glob resolves relative to the importing file); its
* literal is substituted at scaffold time because the relative depth from
* <base>/<slug>/src/ to <specs-dir>/<slug>/ depends on where the base project
* lives (in iii: '../../../../tech-specs/<slug>/*.md').
*/
export const SPEC_DOCS = import.meta.glob('__SPEC_MD_GLOB__', {
query: '?raw',
import: 'default',
eager: true,
}) as Record<string, string>