import { copyFileSync, cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, writeFileSync, } from "node:fs"; import { tmpdir } from "node:os"; import path from "node:path"; import { fileURLToPath, pathToFileURL } from "node:url"; import { layout as dagreLayout, type EdgeLabel, Graph, type GraphLabel, type NodeLabel, type Point, } from "@dagrejs/dagre"; import rehypeAutolinkHeadings from "rehype-autolink-headings"; import rehypeHighlight from "rehype-highlight"; import rehypeMathjax from "rehype-mathjax"; import rehypeRaw from "rehype-raw"; import rehypeSlug from "rehype-slug"; import rehypeStringify from "rehype-stringify"; import { remarkDefinitionList } from "remark-definition-list"; import remarkFrontmatter from "remark-frontmatter"; import remarkGfm from "remark-gfm"; import remarkGithubAlert from "remark-github-blockquote-alert"; import remarkMath from "remark-math"; import remarkParse from "remark-parse"; import remarkRehype from "remark-rehype"; import { unified } from "unified"; import { visit } from "unist-util-visit"; import { type BoardProposal, type BoardState, boardStateFor, } from "./board-state.ts"; import { type BundleDocumentRecord, type Diagnostic, type DocumentMetadata, type DocumentType, dependencyReferences, parseSystemConfig, references, } from "./documents.ts"; import type { FrontmatterMap, FrontmatterValue } from "./markdown.ts"; export type RenderDocumentInput = BundleDocumentRecord & Readonly<{ markdown: string }>; export type RenderInput = Readonly<{ root: string; currentPhase?: string; documents: readonly RenderDocumentInput[]; diagnostics: readonly Diagnostic[]; }>; export type RenderOutput = Readonly<{ file: string; directory: string }>; type LiveContext = Readonly<{ board: BoardState; revision: string }>; type CoreDocument = Readonly<{ id: string; title: string; path: string; markdown: string; }>; type RenderedDocument = Readonly<{ id: string; title: string; type: string; sourcePath: string; outputPath: string; article: string; text: string; refs: readonly string[]; deps: readonly string[]; label?: string; }>; type MarkdownText = Readonly<{ value: string }>; const GROUPS = ["law", "phase", "spec", "plan", "issue", "research"] as const; const DOCUMENT_ORDER: readonly DocumentType[] = [ "phase", "spec", "plan", "issue", "research", ]; const processor = unified() .use(remarkParse) .use(remarkFrontmatter, ["yaml"]) .use(remarkGfm) .use(remarkDefinitionList) .use(remarkMath) .use(remarkGithubAlert) .use(remarkRehype, { allowDangerousHtml: true }) .use(rehypeRaw) .use(rehypeSlug) .use(rehypeAutolinkHeadings, { behavior: "wrap" }) .use(rehypeMathjax) .use(rehypeHighlight) .use(rehypeStringify, { allowDangerousHtml: true }); // Board re-renders process unchanged documents again on every mutation; the // pipeline output only depends on the markdown source, so memoize it. The text // comes from the same parse the HTML pipeline consumes, because a separate // text-only parse doubles the cold-path parse cost. const PROCESSED_CACHE_LIMIT = 512; const processedCache = new Map< string, Readonly<{ html: string; text: string }> >(); const processedDocument = ( markdown: string, ): Readonly<{ html: string; text: string }> => { const cached = processedCache.get(markdown); if (cached) return cached; const tree = processor.parse(markdown); let text = ""; visit(tree, ["text", "inlineMath", "math"] as const, (node: MarkdownText) => { text += `${node.value} `; }); const processed = { html: rewriteMermaidBlocks( String(processor.stringify(processor.runSync(tree))), ), text: text.trim(), }; if (processedCache.size >= PROCESSED_CACHE_LIMIT) processedCache.clear(); processedCache.set(markdown, processed); return processed; }; export function renderSystemViewSite( input: RenderInput, selected: RenderDocumentInput, ): RenderOutput { const directory = createOutput(input); const rendered = renderAll(input, directory); const diagnostics = [...input.diagnostics, ...referenceDiagnostics(input)]; writePages(directory, rendered, diagnostics, "single"); const page = rendered.find((item) => item.id === selected.metadata.id); if (!page) throw new Error(`No rendered System document: ${selected.metadata.id}`); return { file: path.join(directory, page.outputPath), directory }; } export function renderSystemBookSite(input: RenderInput): RenderOutput { const directory = createOutput(input); const rendered = renderAll(input, directory); const diagnostics = [...input.diagnostics, ...referenceDiagnostics(input)]; writePages(directory, rendered, diagnostics, "book"); const title = `${projectName(input.root)} Dossier`; writeFileSync( path.join(directory, "index.html"), htmlPage({ title, body: bookBody( title, rendered, diagnostics, directory, input.currentPhase, ), mode: "book", mermaidPath: "assets/mermaid.min.js", miniSearchPath: "assets/minisearch.js", }), "utf8", ); return { file: path.join(directory, "index.html"), directory }; } export function renderSystemBoardSite( input: RenderInput, board: BoardState, proposals: readonly BoardProposal[], revision = "", ): RenderOutput { const live: LiveContext = { board, revision }; const directory = createOutput(input); const rendered = renderAll(input, directory, live); const diagnostics = [...input.diagnostics, ...referenceDiagnostics(input)]; writePages(directory, rendered, diagnostics, "book", live); const title = `${projectName(input.root)} Board`; const body = boardBody(title, input, rendered, board, proposals).replaceAll( pathToFileURL(`${directory}${path.sep}`).href, "/", ); writeFileSync( path.join(directory, "index.html"), htmlPage({ title, body, mode: "board", mermaidPath: "assets/mermaid.min.js", miniSearchPath: "assets/minisearch.js", live, }), "utf8", ); return { file: path.join(directory, "index.html"), directory }; } const projectName = (root: string) => { try { return ( parseSystemConfig( readFileSync(path.join(root, ".system/config.json"), "utf8"), ).title ?? path.basename(root) ); } catch { return path.basename(root) || "Project"; } }; const createOutput = (input: RenderInput): string => { const directory = mkdtempSync(path.join(tmpdir(), "system-render-")); copyBundles(input.root, input.documents, directory); copyMermaid(directory); copyMiniSearch(directory); return directory; }; const renderAll = ( input: RenderInput, outputRoot: string, live?: LiveContext, ): readonly RenderedDocument[] => { const coreSources = coreDocuments(input.root); const renderedSources = new Set([ ...coreSources.map((document) => document.path), ...input.documents.map((document) => document.path), ]); const cores = coreSources.map((document) => renderCore(document, outputRoot, renderedSources), ); const authored = orderedSystemDocuments(input.documents).map((document) => renderDocument(input, document, outputRoot, renderedSources, live), ); const current = authored.find( (document) => document.id === input.currentPhase, ); return [ ...cores.filter( (document) => document.id === "CORE-SYSTEM" || document.id === "CORE-MISSION", ), ...(current ? [current] : []), ...cores.filter((document) => document.id === "CORE-RULES"), ...authored.filter((document) => document !== current), ]; }; const orderedSystemDocuments = ( documents: readonly RenderDocumentInput[], ): readonly RenderDocumentInput[] => DOCUMENT_ORDER.flatMap((type) => { const group = documents.filter( (document) => document.metadata.type === type, ); return type === "plan" ? topoDocuments(group) : group.sort(compareDocuments); }); const compareDocuments = ( left: RenderDocumentInput, right: RenderDocumentInput, ) => left.metadata.title.localeCompare(right.metadata.title) || left.metadata.id.localeCompare(right.metadata.id); const topoDocuments = ( documents: readonly RenderDocumentInput[], ): readonly RenderDocumentInput[] => { const sorted = [...documents].sort(compareDocuments); const byId = new Map( sorted.map((document) => [document.metadata.id, document]), ); const visiting = new Set(); const visited = new Set(); const result: RenderDocumentInput[] = []; const visit = (document: RenderDocumentInput) => { const id = document.metadata.id; if (visited.has(id)) return; if (visiting.has(id)) return; visiting.add(id); for (const dependencyId of [ ...dependencyReferences(document.metadata), ].sort()) { const dependency = byId.get(dependencyId); if (dependency) visit(dependency); } visiting.delete(id); visited.add(id); result.push(document); }; for (const document of sorted) visit(document); return result; }; const writePages = ( outputRoot: string, documents: readonly RenderedDocument[], diagnostics: readonly Diagnostic[], mode: "single" | "book", live?: LiveContext, ) => { for (const [index, document] of documents.entries()) { const previous = documents[index - 1]; const next = documents[index + 1]; const controls = mode === "book" ? [ previous ? `` : "", next ? `` : "", ] .filter(Boolean) .join(" ") : ""; writeFileSync( path.join(outputRoot, document.outputPath), htmlPage({ title: document.title, body: `${controls ? `` : ""}${document.article}${diagnosticsHtml(diagnostics)}`, mode, mermaidPath: rel(document.outputPath, "assets/mermaid.min.js"), miniSearchPath: rel(document.outputPath, "assets/minisearch.js"), ...(live ? { live } : {}), }), "utf8", ); } }; const coreDocuments = (root: string): readonly CoreDocument[] => ["SYSTEM.md", "MISSION.md", "RULES.md"].flatMap((name) => { const sourcePath = `.system/${name}`; const absolute = path.join(root, sourcePath); if (!existsSync(absolute)) return []; return [ { id: `CORE-${name.replace(/\.md$/, "")}`, title: name.replace(/\.md$/, ""), path: sourcePath, markdown: readFileSync(absolute, "utf8"), }, ]; }); const renderCore = ( document: CoreDocument, outputRoot: string, renderedSources: ReadonlySet, ): RenderedDocument => { const outputPath = document.path.replace(/\.md$/, ".html"); const article = articleHtml({ id: document.id, title: document.title, type: "law", sourcePath: document.path, frontmatter: undefined, markdown: document.markdown, outputRoot, renderedSources, }); const processed = processedDocument(document.markdown); return { id: document.id, title: document.title, type: "law", sourcePath: document.path, outputPath, article, text: processed.text, refs: [], deps: [], }; }; const renderDocument = ( input: RenderInput, document: RenderDocumentInput, outputRoot: string, renderedSources: ReadonlySet, live?: LiveContext, ): RenderedDocument => { const outputPath = htmlPath(document.path); const article = articleHtml({ ...(live ? { live } : {}), id: document.metadata.id, title: document.metadata.title, type: document.metadata.type, sourcePath: document.path, metadata: document.metadata, frontmatter: document.frontmatter, markdown: document.markdown, outputRoot, documents: input.documents, current: document.metadata.id === input.currentPhase, renderedSources, }); const processed = processedDocument(document.markdown); return { id: document.metadata.id, title: document.metadata.title, type: document.metadata.type, sourcePath: document.path, outputPath, article, text: `${frontmatterText(document.frontmatter)} ${processed.text}`, refs: refsFor(document.metadata), deps: dependencyReferences(document.metadata), ...(document.metadata.type === "phase" ? { label: document.metadata.label } : {}), }; }; const articleHtml = (input: { id: string; title: string; type: string; sourcePath: string; metadata?: DocumentMetadata; frontmatter?: FrontmatterMap; markdown: string; outputRoot: string; documents?: readonly RenderDocumentInput[]; current?: boolean; renderedSources: ReadonlySet; live?: LiveContext; }): string => { const metadata = input.frontmatter ? metadataHtml( input.frontmatter, input.documents ?? [], input.outputRoot, input.metadata, input.live, ) : ""; const backlinks = input.documents ? backlinksHtml(input.id, input.documents, input.outputRoot) : ""; const processed = processedDocument(input.markdown); const body = prefixHeadingAnchors( rewriteLocalRefs( rewriteVisualAssetLinks( processed.html, input.sourcePath, input.outputRoot, ), input.sourcePath, input.outputRoot, input.renderedSources, ), input.id, ); return `

${esc(input.type)} · ${esc(input.id)}${input.current ? 'Current phase' : ""}

${esc(input.title)}

${esc(input.sourcePath)}

${summaryHtml(input.metadata)}
${metadata}${body}${backlinks}
`; }; const summaryHtml = (metadata?: DocumentMetadata): string => { if (!metadata) return ""; const dependencies = dependencyReferences(metadata); const refs = refsFor(metadata).filter((id) => !dependencies.includes(id)); return `

${esc(metadata.type)}${metadata.type === "phase" ? ` · ${esc(metadata.label)}` : ""}${"status" in metadata ? ` · ${esc(metadata.status)}` : ""}${refs.length ? ` · refs ${refs.map(esc).join(", ")}` : ""}${dependencies.length ? ` · after ${dependencies.map(esc).join(", ")}` : ""}

`; }; const backlinksHtml = ( id: string, documents: readonly RenderDocumentInput[], outputRoot: string, ): string => { const backlinks = documents .filter( (document) => document.metadata.id !== id && refsFor(document.metadata).includes(id), ) .sort(compareDocuments); if (!backlinks.length) return ""; return ``; }; const metadataHtml = ( frontmatter: FrontmatterMap, documents: readonly RenderDocumentInput[], outputRoot: string, metadata?: DocumentMetadata, live?: LiveContext, ): string => { const rows = [...frontmatter.entries()] .map(([key, value]) => { const toggle = live && metadata?.type === "plan" && key === "status" ? statusToggle(metadata.id, metadata.status) : undefined; return `${esc(key)}${toggle ?? valueHtml(value, documents, outputRoot)}`; }) .join(""); const boardRow = live && metadata?.type === "spec" ? `board${boardStateToggle(metadata.id, boardStateFor(live.board, metadata))}` : ""; return `
Metadata${rows}${boardRow}
`; }; const statusToggle = (id: string, status: string) => ``; const boardStateToggle = (id: string, state: string) => ``; const valueHtml = ( value: FrontmatterValue, documents: readonly RenderDocumentInput[], outputRoot: string, ): string => { if (value === null) return "null"; if (typeof value === "boolean" || typeof value === "number") return `${String(value)}`; if (typeof value === "string") return linkId(value, documents, outputRoot); if (Array.isArray(value)) return `
    ${value.map((item) => `
  1. ${valueHtml(item, documents, outputRoot)}
  2. `).join("")}
`; return `${[...value.entries()] .map( ([key, item]) => ``, ) .join("")}
${esc(String(key))}${valueHtml(item, documents, outputRoot)}
`; }; const linkId = ( value: string, documents: readonly RenderDocumentInput[], outputRoot: string, ): string => { const target = documents.find((document) => document.metadata.id === value); if (!target) return esc(value); return `${esc(value)}`; }; const refsFor = (metadata: DocumentMetadata): readonly string[] => references(metadata).map(([id]) => id); const htmlPath = (sourcePath: string) => sourcePath.replace(/index\.md$/, "index.html"); const documentHref = (outputRoot: string, sourcePath: string) => pathToFileURL(path.join(outputRoot, htmlPath(sourcePath))).href; const frontmatterText = (frontmatter: FrontmatterMap): string => [...frontmatter.entries()] .map(([key, value]) => `${key} ${plainValue(value)}`) .join(" "); const plainValue = (value: FrontmatterValue): string => { if (value === null) return "null"; if (typeof value !== "object") return String(value); if (Array.isArray(value)) return value.map(plainValue).join(" "); return [...value.entries()] .map(([key, item]) => `${String(key)} ${plainValue(item)}`) .join(" "); }; const prefixHeadingAnchors = (html: string, prefix: string) => html.replaceAll( //g, (_match, level: string, id: string) => ``, ); const rewriteLocalRefs = ( html: string, sourcePath: string, outputRoot: string, renderedSources: ReadonlySet, ) => html.replaceAll( /\b(href|src)="([^"]+)"/g, (match, attribute: string, target: string) => { if (/^(?:https?:|mailto:|data:|file:|#)/i.test(target)) return match; const [, bare = "", suffix = ""] = target.match(/^([^?#]*)([?#].*)?$/) ?? []; const sourceTarget = path.posix.normalize( path.posix.join(path.posix.dirname(sourcePath), bare), ); const rewritten = /\.md$/i.test(bare) && renderedSources.has(sourceTarget) ? bare.replace(/\.md$/i, ".html") : bare; const absolute = path.join( outputRoot, path.dirname(sourcePath), rewritten, ); return `${attribute}="${pathToFileURL(absolute).href}${suffix}"`; }, ); const rewriteMermaidBlocks = (html: string) => html.replaceAll( /
([\s\S]*?)<\/code><\/pre>/g,
		(_match, source: string) => `
${source}
`, ); const rewriteVisualAssetLinks = ( html: string, sourcePath: string, outputRoot: string, ) => html.replaceAll( /

([\s\S]*?)<\/a><\/p>/g, (match, href: string, label: string) => { const kind = visualAssetKind(href, sourcePath, outputRoot); if (!kind) return match; const title = label.replaceAll(/<[^>]+>/g, ""); const preview = kind === "html" ? `` : kind === "image" ? `${title}` : `Open video`; return `

${label}
${preview}

Open full-size

`; }, ); const visualAssetKind = ( href: string, sourcePath: string, outputRoot: string, ): "html" | "image" | "video" | undefined => { if (/^(?:[a-z]+:|\/|\\|#)/i.test(href)) return undefined; const [bare = ""] = href.split(/[?#]/, 1); if (!bare || bare.includes("\\")) return undefined; const bundle = path.posix.dirname(sourcePath); const target = path.posix.normalize(path.posix.join(bundle, bare)); if (target === bundle || !target.startsWith(`${bundle}/`)) return undefined; if (!existsSync(path.join(outputRoot, target))) return undefined; const extension = path.posix.extname(bare).toLowerCase(); if (extension === ".html" || extension === ".htm") return "html"; if ( [".svg", ".png", ".jpg", ".jpeg", ".gif", ".webp", ".avif"].includes( extension, ) ) return "image"; if (extension === ".mp4" || extension === ".webm") return "video"; return undefined; }; const GROUP_LABELS: Readonly> = { law: "Law", spec: "Specs", phase: "Phases", plan: "Plans", issue: "Issues", research: "Research", }; const boardBody = ( title: string, input: RenderInput, rendered: readonly RenderedDocument[], board: BoardState, proposals: readonly BoardProposal[], ): string => { const authored = new Map( input.documents.map((document) => [document.metadata.id, document]), ); const pages = new Map(rendered.map((document) => [document.id, document])); const specs = input.documents .filter((document) => document.metadata.type === "spec") .sort((left, right) => compareBoardItems(left, right, board)); const issues = input.documents .filter((document) => document.metadata.type === "issue") .sort((left, right) => compareBoardItems(left, right, board)); const plansBySpec = new Map(); for (const document of input.documents) { if (document.metadata.type !== "plan") continue; const plans = plansBySpec.get(document.metadata.spec) ?? []; plans.push(document); plansBySpec.set(document.metadata.spec, plans); } for (const plans of plansBySpec.values()) plans.sort((left, right) => compareBoardItems(left, right, board)); const proposalById = new Map( proposals.map((proposal) => [proposal.id, proposal]), ); const todo = specs.filter( (document) => boardStateFor(board, document.metadata) !== "done", ).length; const open = issues.filter( (document) => boardStateFor(board, document.metadata) !== "done", ).length; const specCards = specs .map((spec) => { const plans = plansBySpec.get(spec.metadata.id) ?? []; const donePlans = plans.filter( (plan) => boardStateFor(board, plan.metadata) === "done", ).length; const planSummary = plans.length ? ` · ${donePlans}/${plans.length} ${plans.length === 1 ? "plan" : "plans"} done` : ""; return `
${boardDisclosure(spec, pages.get(spec.metadata.id), planSummary)}${boardStateButton(spec.metadata.id, boardStateFor(board, spec.metadata))}
${plans.length ? `
${plans.map((plan) => boardPlan(plan, pages.get(plan.metadata.id), board, proposalById.get(plan.metadata.id))).join("")}
` : ""}${boardAssessment(board.items[spec.metadata.id])}${boardProposal(proposalById.get(spec.metadata.id))}
`; }) .join(""); const issueCards = issues .map((issue) => { const refs = issue.metadata.type === "issue" ? issue.metadata.specs : []; const links = refs .map((id) => { const target = authored.get(id); return `→ ${esc(target?.metadata.title ?? id)}`; }) .join(""); return `
${boardDisclosure(issue, pages.get(issue.metadata.id), "")}${boardStateButton(issue.metadata.id, boardStateFor(board, issue.metadata))}
${links ? `` : ""}${boardAssessment(board.items[issue.metadata.id])}${boardProposal(proposalById.get(issue.metadata.id))}
`; }) .join(""); return `
${esc(title)}

Spec toggles never change plan states. Reopening a plan demotes its spec.

${todo} todo · ${open} ${open === 1 ? "issue" : "issues"} open

Specs ${specs.length}

${specCards || '

No specs.

'}
`; }; const compareBoardItems = ( left: RenderDocumentInput, right: RenderDocumentInput, board: BoardState, ) => Number(boardStateFor(board, left.metadata) === "done") - Number(boardStateFor(board, right.metadata) === "done") || compareDocuments(left, right); const boardDisclosure = ( document: RenderDocumentInput, rendered: RenderedDocument | undefined, suffix: string, ) => `
${esc(document.metadata.id)}${esc(suffix)}${esc(document.metadata.title)}
${rendered?.article ?? ""}
`; const boardStateButton = (id: string, state: string) => ``; const boardPlan = ( plan: RenderDocumentInput, rendered: RenderedDocument | undefined, board: BoardState, proposal?: BoardProposal, ) => { const state = boardStateFor(board, plan.metadata); return `
${boardDisclosure(plan, rendered, plan.metadata.type === "plan" ? ` · ${plan.metadata.status}` : "")}${boardStateButton(plan.metadata.id, state)}
${boardAssessment(board.items[plan.metadata.id])}${boardProposal(proposal)}
`; }; const boardAssessment = (entry: BoardState["items"][string] | undefined) => entry ? `${esc(entry.source)} · ${esc(entry.updatedAt)}${entry.note ? ` · ${esc(entry.note)}` : ""}` : 'default'; const boardProposal = (proposal?: BoardProposal) => proposal ? `` : ""; const boardCss = () => String.raw` body[data-mode=board]{padding:10px;background:var(--bg);font:12px/1.35 ui-monospace,SFMono-Regular,Menlo,monospace}.board-top{display:grid;grid-template-columns:1fr auto;gap:10px;align-items:end;border-bottom:2px solid var(--text);padding-bottom:7px;margin-bottom:7px}.board-brand{color:var(--text);font-size:17px;font-weight:900;text-decoration:none;text-transform:uppercase}.board-top p{margin:2px 0 0;color:var(--muted);font-size:10px}.board-top>span{color:var(--muted)}.board-grid{display:grid;grid-template-columns:minmax(0,3fr) minmax(240px,1fr);gap:7px;align-items:start}.board-region{border:1px solid var(--line);background:var(--panel);min-width:0}.board-region>h1{font-size:11px;letter-spacing:.1em;text-transform:uppercase;margin:0;padding:6px 7px;border-bottom:1px solid var(--line)}.board-region>h1 span{float:right;color:var(--muted);font-weight:400}.board-spec-list{display:grid;grid-template-columns:repeat(auto-fit,minmax(290px,1fr));gap:6px;padding:6px;align-items:start}.board-issue-list{display:grid;gap:5px;padding:6px}.board-spec{border:2px solid var(--accent);background:var(--page);min-width:0}.board-spec.is-done{border-color:var(--line);background:var(--bg)}.board-row{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px;align-items:start;padding:6px 7px}.board-bundle{min-width:0}.board-bundle>summary{cursor:pointer;list-style:none;display:grid;grid-template-columns:minmax(0,1fr);padding-left:14px;position:relative}.board-bundle>summary::-webkit-details-marker{display:none}.board-bundle>summary::before{content:'▸';position:absolute;left:0;color:var(--accent)}.board-bundle[open]>summary::before{content:'▾'}.board-kind{display:block;font-size:9px;color:var(--muted);text-transform:uppercase}.board-bundle>summary strong{display:block;font:700 13px/1.15 system-ui,sans-serif;margin-top:1px;overflow-wrap:anywhere}.is-done>.board-row .board-bundle>summary strong{color:var(--muted);text-decoration:line-through}.board-details{grid-column:1/-1;margin:6px 0 0 14px;border-top:1px solid var(--line);padding-top:6px;font-family:system-ui,sans-serif}.board-details>.system-article{border:0;padding:0;background:transparent}.board-details .mast{display:none}.board-details .ledger{margin:.35rem 0}.board-details h1{font-size:19px}.board-details h2{font-size:16px}.board-details h3{font-size:14px}.board-details pre{padding:.6rem}.board-plans{display:grid;gap:4px;border-top:1px solid var(--line);background:var(--bg);padding:4px}.board-plan{border-left:3px solid var(--accent);background:var(--page)}.board-plan.is-done{border-left-color:var(--line);background:var(--bg)}.board-plan .board-row{padding:4px 5px}.board-issue{border-left:3px solid var(--warn);background:var(--page)}.board-issue.is-done{border-left-color:var(--line);background:var(--bg)}.board-issue .board-bundle>summary::before{color:var(--warn)}.board-links{display:grid;gap:2px;padding:0 7px 6px 21px;font-size:10px}.board-state{border:1px solid var(--accent);background:var(--bg);color:var(--accent);font:9px ui-monospace,monospace;padding:3px 5px;cursor:pointer;min-width:43px}.board-issue .board-state{border-color:var(--warn);color:var(--warn)}.is-done>.board-row>.board-state,.board-plan.is-done .board-state,.board-issue.is-done .board-state{border-color:var(--line);color:var(--text)}.board-state:disabled{cursor:not-allowed;border-color:var(--line);color:var(--muted)}.board-assessment{display:block;color:var(--muted);font-size:9px;padding:0 7px 5px 21px}.board-proposal{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px;border-top:1px solid var(--info);padding:5px 7px;color:var(--muted)}.board-proposal b{display:block;color:var(--info);font-size:9px}.board-proposal button{border:1px solid var(--info);background:var(--bg);color:var(--info);font:9px ui-monospace,monospace;padding:3px 6px}.board-empty{color:var(--muted);margin:6px}@media(max-width:760px){.board-top,.board-grid{grid-template-columns:1fr}.board-spec-list{grid-template-columns:1fr}.board-top>span{text-align:left}} `; const boardScript = () => String.raw` const root=document.body;const direct=(node,selector)=>[...node.children].filter(child=>child.matches(selector));const sort=(node,selector)=>{const items=direct(node,selector);const ordered=[...items].sort((a,b)=>Number(a.classList.contains('is-done'))-Number(b.classList.contains('is-done'))||(a.dataset.id||'').localeCompare(b.dataset.id||''));if(ordered.some((item,at)=>item!==items[at]))for(const item of ordered)node.append(item)};const notice=document.querySelector('[data-board-notice]');const message=(text,error=false)=>{notice.textContent=text;notice.className='board-notice is-visible'+(error?' is-error':'');setTimeout(()=>{notice.className='board-notice'},3000)};const stateOf=card=>card.dataset.state||card.dataset.defaultState;const age=value=>{if(!value)return'default';const days=Math.max(0,Math.floor((Date.now()-Date.parse(value))/86400000));return days===0?'today':days===1?'1 day ago':days+' days ago'};function refresh(snapshot){for(const card of document.querySelectorAll('[data-board-item]')){const entry=snapshot.items[card.dataset.id];const state=entry?.state||card.dataset.defaultState;card.dataset.state=state;card.classList.toggle('is-done',state==='done');const button=card.querySelector(':scope > .board-row > .board-state');button.textContent=state.toUpperCase();const assessment=card.querySelector(':scope > [data-assessment]');assessment.textContent=entry?entry.source+' · '+age(entry.updatedAt)+(entry.note?' · '+entry.note:''):'default';assessment.title=entry?.updatedAt||''}for(const spec of document.querySelectorAll('[data-kind="spec"]')){const button=spec.querySelector(':scope > .board-row > .board-state');const unfinished=spec.querySelector('[data-kind="plan"]:not(.is-done)');button.disabled=!spec.classList.contains('is-done')&&Boolean(unfinished);button.title=button.disabled?'Finish every plan first':'No plan states will change'}document.querySelectorAll('[data-plan-list]').forEach(list=>sort(list,'[data-kind="plan"]'));sort(document.querySelector('[data-spec-list]'),'[data-kind="spec"]');sort(document.querySelector('[data-issue-list]'),'[data-kind="issue"]');document.querySelector('[data-spec-count]').textContent=String(document.querySelectorAll('[data-kind="spec"]:not(.is-done)').length);document.querySelector('[data-issue-count]').textContent=String(document.querySelectorAll('[data-kind="issue"]:not(.is-done)').length)}globalThis.systemBoardRefresh=refresh;async function post(path,payload){const response=await fetch(path,{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify(payload)});const result=await response.json();if(!response.ok)throw new Error(result.error||'Board update failed');globalThis.systemBoardSeen?.(result.items);return result}root.addEventListener('click',async event=>{const proposal=event.target.closest('[data-apply-proposal]');const button=event.target.closest('.board-state');if(!proposal&&!button)return;event.preventDefault();const card=(proposal||button).closest('[data-board-item]');try{if(proposal){const result=await post('/api/proposal',{id:card.dataset.id,state:proposal.dataset.applyProposal});proposal.closest('[data-proposal]').remove();refresh(result);message('Assessment applied.')}else{const state=stateOf(card);const target=state==='done'?card.dataset.defaultState:'done';const result=await post('/api/state',{id:card.dataset.id,state:target});refresh(result);message('Board updated.')}}catch(error){message(error.message,true)}});for(const assessment of document.querySelectorAll('[data-assessment]'))if(assessment.dataset.updatedAt)assessment.textContent=assessment.dataset.source+' · '+age(assessment.dataset.updatedAt)+(assessment.dataset.note?' · '+assessment.dataset.note:''); `; const ledgerCss = () => String.raw` .ledger-toggle{border:1px solid var(--accent);background:var(--bg);color:var(--accent);font:700 11px ui-monospace,monospace;padding:2px 8px;text-transform:uppercase}.ledger-toggle:disabled{cursor:progress;opacity:.55}.board-notice{position:fixed;right:10px;bottom:10px;z-index:9;max-width:min(34rem,calc(100vw - 20px));background:var(--panel);border:1px solid var(--line);padding:7px;font:12px ui-monospace,monospace;display:none}.board-notice.is-visible{display:block}.board-notice.is-error{border-color:var(--error);color:var(--error)} `; const ledgerScript = () => String.raw` const element=()=>{let node=document.querySelector('[data-board-notice]');if(!node){node=document.createElement('output');node.className='board-notice';node.setAttribute('data-board-notice','');node.setAttribute('aria-live','polite');document.body.append(node)}return node};const notify=(text,isError=false)=>{const node=element();node.textContent=text;node.className='board-notice is-visible'+(isError?' is-error':'');setTimeout(()=>{node.className='board-notice'},3000)};let known=null;let reloading=false;const request=async(path,payload)=>{const response=await fetch(path,{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify(payload)});const result=await response.json();if(!response.ok)throw new Error(result.error||'Board request failed');if(result&&result.items)known=JSON.stringify(result.items);return result};globalThis.systemBoardSeen=items=>{known=JSON.stringify(items)};const syncLedger=items=>{for(const button of document.querySelectorAll('[data-board-toggle]')){const state=items[button.dataset.id]?.state||button.dataset.defaultState;button.dataset.state=state;button.textContent=state}};const OPEN='system-board-open';const remember=()=>{try{sessionStorage.setItem(OPEN,JSON.stringify([...document.querySelectorAll('.board-bundle[open]')].map(node=>node.closest('[data-board-item]')?.dataset.id).filter(Boolean)))}catch{}};const restore=()=>{try{for(const id of JSON.parse(sessionStorage.getItem(OPEN)||'[]'))document.querySelector('[data-board-item][data-id="'+id+'"] > .board-row > .board-bundle')?.setAttribute('open','')}catch{}};document.body.addEventListener('click',async event=>{const button=event.target.closest?.('.ledger-toggle');if(!button)return;event.preventDefault();button.disabled=true;try{if(button.hasAttribute('data-status-toggle')){const next=button.dataset.status==='approved'?'draft':'approved';await request('/api/status',{id:button.dataset.id,status:next});button.dataset.status=next;button.textContent=next;notify('Plan status is now '+next+'.')}else{const next=button.dataset.state==='done'?button.dataset.defaultState:'done';const result=await request('/api/state',{id:button.dataset.id,state:next});syncLedger(result.items);globalThis.systemBoardRefresh?.(result);notify('Board updated.')}}catch(error){notify(error.message,true)}finally{button.disabled=false}});const poll=async()=>{if(reloading)return;try{const response=await fetch('/api/state');const snapshot=await response.json();if(!response.ok||typeof snapshot.revision!=='string')return;if(snapshot.revision!==document.body.dataset.revision){reloading=true;remember();location.reload();return}const encoded=JSON.stringify(snapshot.items);if(encoded===known)return;known=encoded;syncLedger(snapshot.items);globalThis.systemBoardRefresh?.(snapshot);if(snapshot.problem)notify(snapshot.problem,true)}catch{}};restore();void poll();setInterval(poll,1000);document.addEventListener('visibilitychange',()=>{if(!document.hidden)void poll()}); `; const bookBody = ( title: string, documents: readonly RenderedDocument[], diagnostics: readonly Diagnostic[], outputRoot: string, currentPhase?: string, ): string => { const diagnosticNav = diagnostics.length ? 'Diagnostics' : ""; const utilities = ``; const nav = bookNav(documents, utilities, currentPhase); const diagnosticPanel = diagnostics.length ? `
${diagnosticsHtml(diagnostics, documents)}
` : ""; const current = documents.find((document) => document.id === currentPhase); const stats = [ ...(current ? [ `Current phase ${esc(current.label ?? current.title)}`, ] : []), ...(diagnostics.length ? [`${diagnostics.length} diagnostics`] : []), ]; const status = stats.length ? `

${stats.join("")}

` : ""; return `
${esc(title)}${status}

${esc(title)}

${graphHtml(documents)}
${diagnosticPanel}
${documents .map( (document) => `
${bookArticle(document.article, documents, outputRoot)}
`, ) .join( "\n", )}
`; }; const bookArticle = ( article: string, documents: readonly RenderedDocument[], outputRoot: string, ): string => { let result = article; for (const document of documents) { const href = pathToFileURL(path.join(outputRoot, document.outputPath)).href; result = result.replaceAll(`href="${href}"`, `href="#${esc(document.id)}"`); } return result; }; const bookNav = ( documents: readonly RenderedDocument[], utilities: string, currentPhase?: string, ): string => { const byType = new Map( GROUPS.map((group) => [group, [] as RenderedDocument[]]), ); for (const document of documents) byType.get(document.type as (typeof GROUPS)[number])?.push(document); return `${utilities}${GROUPS.map((group) => { const items = byType.get(group) ?? []; if (!items.length) return ""; return `
${GROUP_LABELS[group]} ${items.length}${items .map((item) => navItem(item, item.id === currentPhase)) .join("")}
`; }).join("")}`; }; const navItem = (item: RenderedDocument, current = false): string => { const label = item.type === "law" ? item.id : `${item.id} ${item.title}`; return `${current ? "● " : ""}${esc(label)}`; }; const graphHtml = (documents: readonly RenderedDocument[]) => { const byId = new Map(documents.map((document) => [document.id, document])); const linked = documents.filter((document) => { const backlinks = documents.some((candidate) => graphRefsForNode(candidate).includes(document.id), ); return graphRefsForNode(document).some((id) => byId.has(id)) || backlinks; }); if (!linked.length) return `

Reference graph

No references yet.

`; const layout = layoutGraph(linked); const paths = layout.edges .map((edge) => { if (edge.points.length === 0) return ""; const [first, ...rest] = edge.points; return ``; }) .join(""); const boxes = layout.nodes .map((document) => { const position = layout.positions.get(document.id); if (!position) return ""; const size = graphNodeSize(document); return `${esc(document.id)} ${esc(document.title)}${esc(shortLabel(document))}${esc(document.type)}`; }) .join(""); const rows = linked.map((document) => { const backlinks = documents .filter((candidate) => graphRefsForNode(candidate).includes(document.id)) .map((candidate) => candidate.id); const graphRefs = graphRefsForNode(document); return `
  • ${esc(document.id)}
    References: ${graphRefs.map((id) => graphLink(id, byId)).join(", ") || "none"}
    Backlinks: ${backlinks.map((id) => graphLink(id, byId)).join(", ") || "none"}
  • `; }); return `

    Reference graph

    ${paths}${boxes}
    Text graph
      ${rows.join("")}
    `; }; const NODE_WIDTH = 168; const NODE_HEIGHT = 44; const GRAPH_PAD = 36; const GRAPH_X_GAP = 112; const GRAPH_Y_GAP = 30; type GraphLayoutEdge = Readonly<{ from: string; to: string; points: readonly Point[]; }>; type GraphPoint = Readonly<{ x: number; y: number }>; type GraphLayout = Readonly<{ nodes: readonly RenderedDocument[]; edges: readonly GraphLayoutEdge[]; positions: ReadonlyMap; width: number; height: number; }>; const layoutGraph = (nodes: readonly RenderedDocument[]): GraphLayout => { const byId = new Map(nodes.map((node) => [node.id, node])); const graph = new Graph(); graph.setGraph({ rankdir: "LR", acyclicer: "greedy", ranker: "network-simplex", align: "UL", marginx: GRAPH_PAD, marginy: GRAPH_PAD, nodesep: GRAPH_Y_GAP, ranksep: GRAPH_X_GAP, edgesep: 12, }); graph.setDefaultEdgeLabel(() => ({})); for (const node of nodes) graph.setNode(node.id, graphNodeSize(node)); for (const node of nodes) for (const ref of graphRefsForNode(node)) if (byId.has(ref)) graph.setEdge(ref, node.id, { weight: node.deps.includes(ref) ? 3 : 1, }); dagreLayout(graph); const positions = new Map(); for (const id of graph.nodes()) { const node = graph.node(id); positions.set(id, { x: (node.x ?? 0) - node.width / 2, y: (node.y ?? 0) - node.height / 2, }); } const edges = graph.edges().map((edge) => ({ from: edge.v, to: edge.w, points: graph.edge(edge).points ?? [], })); const label = graph.graph(); return { nodes, edges, positions, width: Math.max(260, label.width ?? 0), height: Math.max(220, label.height ?? 0), }; }; const graphNodeSize = (node: RenderedDocument) => ({ width: node.type === "law" ? 130 : NODE_WIDTH, height: NODE_HEIGHT, }); const graphRefsForNode = (node: RenderedDocument): readonly string[] => node.deps.length ? node.deps : node.refs; const shortLabel = (document: RenderedDocument) => { if (document.type === "law") return document.title; const [prefix, type, token] = document.id.split("-"); return `${type ?? prefix}-${token ?? ""}`.slice(0, 18); }; const graphLink = (id: string, byId: ReadonlyMap) => { const document = byId.get(id); return document ? `${esc(id)}` : esc(id); }; const referenceDiagnostics = (input: RenderInput): readonly Diagnostic[] => { const diagnostics: Diagnostic[] = []; for (const document of input.documents) { const base = path.dirname(document.path); for (const target of markdownTargets(document.markdown)) { const bare = target.href.split("#", 1)[0] ?? ""; if (!bare || /^(?:https?:|mailto:|#)/i.test(target.href)) continue; const absolute = path.resolve(input.root, base, bare); if (!absolute.startsWith(input.root) || !existsSync(absolute)) diagnostics.push({ code: "render.missing-reference", path: `${document.path}:${target.line}`, message: `Missing referenced asset or document: ${target.href}`, }); } } return diagnostics; }; const markdownTargets = ( markdown: string, ): readonly { href: string; line: number }[] => [...markdown.matchAll(/\[[^\]]*\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g)].map( (match) => ({ href: match[1] ?? "", line: markdown.slice(0, match.index).split("\n").length, }), ); const diagnosticsHtml = ( diagnostics: readonly Diagnostic[], documents: readonly RenderedDocument[] = [], ) => { if (!diagnostics.length && documents.length === 0) return ""; if (!diagnostics.length) return ``; return ``; }; const diagnosticHref = ( diagnostic: Diagnostic, documents: readonly RenderedDocument[], ) => { const source = diagnostic.path.split(":", 1)[0]; const document = documents.find((item) => item.sourcePath === source); return document ? `#${esc(document.id)}` : undefined; }; const copyBundles = ( root: string, documents: readonly RenderDocumentInput[], outputRoot: string, ) => { for (const document of documents) { const parts = document.path.split("/"); const bundle = parts.slice(0, 3).join("/"); const target = path.join(outputRoot, bundle); if (existsSync(target)) continue; mkdirSync(path.dirname(target), { recursive: true }); cpSync(path.join(root, bundle), target, { recursive: true }); } mkdirSync(path.join(outputRoot, ".system"), { recursive: true }); }; // Browser assets ship inside the generated package under `assets/`; the source // checkout falls back to the installed dependency. const browserAsset = ( name: string, specifier: string, file: string, ): string | undefined => { const packaged = fileURLToPath(new URL(`./assets/${name}`, import.meta.url)); if (existsSync(packaged)) return packaged; try { const installed = path.join( path.dirname(fileURLToPath(import.meta.resolve(specifier))), file, ); return existsSync(installed) ? installed : undefined; } catch { return undefined; } }; const copyBrowserAsset = ( outputRoot: string, name: string, source?: string, ) => { if (!source) return; mkdirSync(path.join(outputRoot, "assets"), { recursive: true }); copyFileSync(source, path.join(outputRoot, "assets", name)); }; const copyMermaid = (outputRoot: string) => copyBrowserAsset( outputRoot, "mermaid.min.js", browserAsset( "mermaid.min.js", "mermaid/package.json", "dist/mermaid.min.js", ), ); const copyMiniSearch = (outputRoot: string) => copyBrowserAsset( outputRoot, "minisearch.js", browserAsset("minisearch.js", "minisearch", "../umd/index.js"), ); const rel = (from: string, to: string) => path.relative(path.dirname(from), to).split(path.sep).join("/") || "."; const htmlPage = ({ title, body, mode, mermaidPath, miniSearchPath, live, }: Readonly<{ title: string; body: string; mode: "single" | "book" | "board"; mermaidPath: string; miniSearchPath: string; live?: LiveContext; }>) => ` ${esc(title)} ${live ? `` : ""} ${body}${live ? `` : ""} `; const favicon = (title: string) => { const initial = esc((title.trim()[0] ?? "P").toUpperCase()); const svg = `${initial}`; return `data:image/svg+xml,${svg}`; }; const css = () => String.raw` :root{color-scheme:light dark;--bg:#ededed;--panel:#dbdbdb;--page:#ededed;--text:#121212;--muted:#3b3b3b;--line:#5d5d5d;--accent:#922792;--link:#184e5b;--warn:#6e6c10;--error:#bd172e;--info:#116477;--code:#dbdbdb;--mark:#6e6c10} @media(prefers-color-scheme:dark){:root{--bg:#121212;--panel:#242424;--page:#121212;--text:#ededed;--muted:#8b8b8b;--line:#6f6f6f;--accent:#d86dd8;--link:#7ba1aa;--warn:#f0ee69;--error:#ec6376;--info:#3ec5e3;--code:#242424;--mark:#f0ee69}} body[data-mode=book]{overflow-x:hidden;--book-gutter:26px;--book-nav-width:15rem;--book-top-height:76px} *{box-sizing:border-box}html{scroll-behavior:smooth;max-width:100%;overflow-x:hidden}body{font-family:system-ui,sans-serif;line-height:1.55;margin:0;color:var(--text);background:var(--bg);max-width:100%;overflow-x:hidden}body[data-mode=single]{max-width:78rem;margin:2rem auto;padding:0 1rem}a{color:var(--link)}a:focus-visible,button:focus-visible,input:focus-visible,summary:focus-visible{outline:3px solid var(--accent);outline-offset:2px}button,input{font:inherit}button{cursor:pointer}.brand{font:900 12px ui-monospace,monospace;color:var(--accent);letter-spacing:.1em;text-transform:uppercase;text-decoration:none;align-self:end;padding-bottom:10px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.book-top{position:fixed;top:0;left:0;right:0;z-index:5;display:grid;grid-template-columns:var(--book-nav-width) minmax(0,1fr) max-content;gap:16px;align-items:end;height:var(--book-top-height);background:var(--panel);border-bottom:2px solid var(--line);padding:10px var(--book-gutter);overflow:hidden}.book-search{position:relative;display:grid;gap:4px;font:800 11px ui-monospace,monospace;color:var(--muted);text-transform:uppercase}.search-row{display:flex;gap:8px}.search-row input{width:100%;min-width:0;background:var(--bg);color:var(--text);border:2px solid var(--line);padding:8px;text-transform:none}.search-row button{background:var(--accent);color:var(--bg);border:0;padding:8px 12px;font-weight:900}.search-results{position:absolute;left:0;right:0;top:100%;z-index:8;display:grid;gap:6px;margin:6px 0 0;padding:0;list-style:none;background:var(--bg);border:2px solid var(--line);box-shadow:0 10px 24px color-mix(in srgb,var(--text),transparent 86%)}.search-results:empty{display:none}.search-results li{border-left:4px solid var(--accent);padding:7px 9px;background:var(--panel);text-transform:none}.search-results small{color:var(--muted)}mark{background:color-mix(in srgb,var(--mark),transparent 35%);color:var(--text);padding:0 .1em}.stats{display:flex;gap:8px;margin:0 0 10px;justify-content:end;font-variant-numeric:tabular-nums}.stats span{border:1px solid var(--line);background:var(--bg);padding:4px 7px;white-space:nowrap}.stats .bad{border-color:var(--error);color:var(--error)}.stats .ok{border-color:var(--info);color:var(--info)}.stats .active-phase{border-color:var(--accent)}.stats .active-phase a{font-weight:900;text-decoration:none}.book-shell{display:block;width:100%;max-width:100vw;padding:var(--book-top-height) var(--book-gutter) 16px calc(var(--book-gutter) + var(--book-nav-width) + 16px)}.book-shell>*{min-width:0}.book-nav,.book-toc,.book-head,.system-article,.doc-move,.diagnostics,.graph{background:var(--panel);border:1px solid var(--line)}.book-nav{position:fixed;left:var(--book-gutter);top:var(--book-top-height);bottom:0;width:var(--book-nav-width);z-index:4;padding:10px;overflow:auto;border-top:0}.book-nav details{border-bottom:1px solid var(--line);padding:7px 0}.book-nav summary{font:900 11px ui-monospace,monospace;text-transform:uppercase;color:var(--muted);cursor:pointer}.book-nav a{display:block;text-decoration:none;padding:4px 6px 4px 12px;border-left:3px solid transparent;overflow-wrap:anywhere}.nav-utils{border-bottom:1px solid var(--line);margin-bottom:7px;padding-bottom:7px}.nav-utils a{font:800 11px ui-monospace,monospace;text-transform:uppercase;color:var(--muted)}.book-nav a:hover,.book-nav .current-phase-link{border-color:var(--accent);background:color-mix(in srgb,var(--accent),transparent 88%)}.book-nav .current-phase-link{color:var(--accent);font-weight:900}.count{float:right;color:var(--muted);font-weight:400}.book-main{display:grid;gap:16px;min-width:0;max-width:100%;overflow:hidden}.book-head{padding:18px;overflow:hidden}.book-head h1{margin:0;font:900 clamp(26px,4vw,32px)/1.05 Georgia,serif;overflow-wrap:anywhere}.book-head p{color:var(--muted)}.overview-grid{display:grid;grid-template-columns:1fr;gap:12px}.overview-grid>section,.overview-grid aside{min-width:0}.book-toc{padding:12px;max-height:calc(100vh - 104px);overflow:auto;position:sticky;top:88px;font-size:13px;z-index:1}.book-toc h2{margin-top:0;color:var(--accent);font:800 12px ui-monospace,monospace;text-transform:uppercase}.book-toc a{display:block;color:var(--muted);margin:6px 0;text-decoration:none}.mobile-nav{display:none}.book-section{scroll-margin-top:72px}.system-article{display:block;min-width:0;max-width:100%;padding:28px 40px;background:var(--page);margin-block:0;overflow:hidden}.system-article+.diagnostics{margin-top:1rem}.current-phase-section .system-article{border-left:5px solid var(--accent)}.phase-current{margin-left:.6rem;padding:.15rem .35rem;background:var(--accent);color:var(--bg);font-size:10px}.mast h1{font:800 clamp(24px,4vw,34px)/1.1 Georgia,serif;margin:.2rem 0 1rem;overflow-wrap:anywhere}.eyebrow,.source,.summary{color:var(--muted)}.eyebrow{font:800 11px ui-monospace,monospace;text-transform:uppercase}.source{overflow-wrap:anywhere}.doc-move{display:flex;justify-content:space-between;gap:1rem;margin:1rem 0;padding:1rem}.ledger,.toc{border:1px solid var(--line);background:var(--panel);padding:.7rem;margin:1rem 0}.doc-related{clear:both;margin:1rem 0;padding:.8rem;background:var(--panel);border-left:4px solid var(--accent)}.doc-related summary{color:var(--accent);font:900 12px ui-monospace,monospace;text-transform:uppercase;cursor:pointer}.doc-related a{display:grid;gap:.1rem;padding:.45rem 0;border-top:1px solid var(--line);text-decoration:none}.doc-related span{color:var(--muted);font:800 10px ui-monospace,monospace;text-transform:uppercase}.doc-related code{font-size:11px;color:var(--muted)}.toc ol{margin:.5rem 0}.depth-3{margin-left:1rem}.depth-4,.depth-5,.depth-6{margin-left:2rem}code,pre{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}pre{padding:1rem;overflow:auto;background:var(--code);border:1px solid var(--line)}table{border-collapse:collapse;width:100%;margin:.5rem 0;display:block;overflow:auto}th,td{border:1px solid var(--line);padding:.35rem;text-align:left;vertical-align:top}.ledger ol{margin:0}.diagnostics{border-color:var(--warn);padding:1rem}.diagnostics h2{margin-top:0}.diagnostics strong{color:var(--warn)}.diagnostics.ok{border-color:var(--info)}.diagnostics li{margin:.4rem 0}.graph{padding:1rem;overflow:hidden}.graph-head{display:grid;grid-template-columns:1fr auto;gap:.5rem;align-items:start}.graph h3{margin:0}.graph-head p{margin:.2rem 0 0;color:var(--muted)}.graph-controls{display:flex;gap:.35rem}.graph-controls button{background:var(--bg);color:var(--text);border:1px solid var(--line);padding:.35rem .55rem;font-weight:900}.graph-frame{height:min(62vh,520px);min-height:18rem;border:1px solid var(--line);background:var(--panel);overflow:hidden;touch-action:none;cursor:grab}.graph-frame:active{cursor:grabbing}.graph svg{display:block;width:100%;height:100%;touch-action:none}.graph path{fill:none;stroke:var(--line);stroke-width:1.6;stroke-opacity:.72}.graph a rect{fill:var(--bg);stroke:var(--accent);stroke-width:2}.graph a:hover rect{fill:color-mix(in srgb,var(--accent),transparent 86%)}.graph text{fill:var(--text);font:700 11px ui-monospace,monospace}.graph-node-type{fill:var(--muted);font:800 10px ui-monospace,monospace;text-transform:uppercase}.graph-list{margin-top:.7rem}.graph-list ol{max-height:16rem;overflow:auto}.visual-asset-preview iframe{width:100%;min-height:22rem;border:1px solid var(--line);background:white}.visual-asset-preview img,.visual-asset-preview video{display:block;max-width:100%;max-height:75vh;border:1px solid var(--line);background:white}.hljs{background:transparent}.math{overflow:auto}@media(max-width:1200px){.book-toc{display:none}}@media(max-width:980px){.book-top{grid-template-columns:1fr;position:static;height:auto}.search-results{position:static}.stats{justify-content:start;flex-wrap:wrap}.book-shell{display:block;padding:10px}.book-nav,.book-toc{display:none}.mobile-nav{display:grid;gap:8px;margin-bottom:10px}.mobile-nav details{background:var(--panel);border:1px solid var(--line);padding:8px}.mobile-nav a{display:block;margin:.35rem 0;overflow-wrap:anywhere}.mobile-nav .book-nav{display:block;position:static;height:auto;border:0;padding:0}.overview-grid{grid-template-columns:1fr}.system-article{padding:20px 18px}.mast h1{font-size:clamp(24px,8vw,28px)}.doc-move{flex-wrap:wrap}.doc-related{margin:1rem 0}}@media(max-width:620px){body{font-size:15px}.book-top{padding:10px}.brand{white-space:normal}.book-head h1{font-size:clamp(28px,10vw,38px)}.search-row{display:grid}.search-row button{width:100%}.book-shell{padding:8px}.book-head{padding:14px}.system-article{padding:18px 14px;border-left:0;border-right:0}.stats span{font-size:12px}.graph-head{grid-template-columns:1fr}.graph-controls button{flex:1}.graph-frame{height:12rem}.graph-list{display:block}}@media print{.book-top,.book-nav,.book-toc,.book-head,.doc-move,.mobile-nav{display:none}.book-shell{display:block;padding:0}.system-article{border:0;background:white;color:black;max-width:none;padding:0;font:12pt/1.55 Georgia,serif}.mast h1{break-before:page}.diagnostics{break-inside:avoid;border-color:#777;background:#eee}mark{background:#ddd}} `; const jsonScript = (value: unknown) => JSON.stringify(value).replaceAll("<", "\\u003c").replaceAll("&", "\\u0026"); const graphScript = () => `for(const svg of document.querySelectorAll('[data-graph-svg]')){const world={w:Number(svg.dataset.worldWidth),h:Number(svg.dataset.worldHeight)};let vb=box();if(svg.clientWidth<700)vb={x:0,y:0,w:Math.min(world.w,1100),h:Math.min(world.h,520)};const initial={...vb};set(vb);const pointers=new Map;let drag=null,pinch=null;function box(){const [x,y,w,h]=svg.getAttribute('viewBox').split(/\\s+/).map(Number);return{x,y,w,h}}function set(next){const w=Math.min(Math.max(next.w,180),world.w);const h=Math.min(Math.max(next.h,120),world.h);vb={w,h,x:Math.min(Math.max(next.x,0),Math.max(0,world.w-w)),y:Math.min(Math.max(next.y,0),Math.max(0,world.h-h))};svg.setAttribute('viewBox',[vb.x,vb.y,vb.w,vb.h].join(' '))}function point(e,b=vb){const r=svg.getBoundingClientRect();return{x:b.x+(e.clientX-r.left)/r.width*b.w,y:b.y+(e.clientY-r.top)/r.height*b.h}}function zoomAt(p,f,b=vb){const w=b.w*f,h=b.h*f;set({w,h,x:p.x-(p.x-b.x)*(w/b.w),y:p.y-(p.y-b.y)*(h/b.h)})}function pair(){const a=[...pointers.values()];return[a[0],a[1]]}function distance(){const [a,b]=pair();return Math.hypot(a.clientX-b.clientX,a.clientY-b.clientY)}function center(b=vb){const [a,c]=pair();return point({clientX:(a.clientX+c.clientX)/2,clientY:(a.clientY+c.clientY)/2},b)}svg.addEventListener('wheel',e=>{e.preventDefault();zoomAt(point(e),e.deltaY<0?.85:1.18)},{passive:false});svg.addEventListener('pointerdown',e=>{if(e.target.closest('a'))return;svg.setPointerCapture(e.pointerId);pointers.set(e.pointerId,e);if(pointers.size===1)drag={x:e.clientX,y:e.clientY,v:{...vb}};if(pointers.size===2)pinch={d:distance(),v:{...vb},p:center()}});svg.addEventListener('pointermove',e=>{if(!pointers.has(e.pointerId))return;pointers.set(e.pointerId,e);const r=svg.getBoundingClientRect();if(pointers.size===1&&drag)set({w:drag.v.w,h:drag.v.h,x:drag.v.x-(e.clientX-drag.x)/r.width*drag.v.w,y:drag.v.y-(e.clientY-drag.y)/r.height*drag.v.h});if(pointers.size===2&&pinch)zoomAt(pinch.p,pinch.d/distance(),pinch.v)});function end(e){pointers.delete(e.pointerId);drag=null;pinch=null}svg.addEventListener('pointerup',end);svg.addEventListener('pointercancel',end);svg.closest('.graph').querySelector('.graph-controls').addEventListener('click',e=>{const action=e.target.closest('button')?.dataset.graphZoom;if(action==='in')zoomAt({x:vb.x+vb.w/2,y:vb.y+vb.h/2},.8);if(action==='out')zoomAt({x:vb.x+vb.w/2,y:vb.y+vb.h/2},1.25);if(action==='reset')set(initial)})}`; const searchScript = () => `const data=JSON.parse(document.getElementById('search-data').textContent);const index=new MiniSearch({fields:['id','title','type','text'],storeFields:['id','title','type','text','href']});index.addAll(data);const input=document.getElementById('search');const button=document.getElementById('search-button');const out=document.getElementById('results');const terms=()=>input.value.trim().toLowerCase().split(/\\s+/).filter(Boolean);function appendHits(node,text,parts){let i=0;const lower=text.toLowerCase();while(i=0&&(hit<0||at(node.nodeValue||'').toLowerCase().includes(part))?NodeFilter.FILTER_ACCEPT:NodeFilter.FILTER_REJECT}});const hits=[];while(hits.length<80){const node=walker.nextNode();if(!node)break;hits.push(node)}for(const node of hits){const span=document.createDocumentFragment();appendHits(span,node.nodeValue||'',parts);node.replaceWith(span)}}function render(){const q=input.value.trim();const parts=terms();out.textContent='';if(!q)return;for(const item of index.search(q,{prefix:true,fuzzy:.2}).slice(0,20)){const li=document.createElement('li');const a=document.createElement('a');const body=item.text||'';const lower=body.toLowerCase();const at=Math.min(...parts.map(part=>{const found=lower.indexOf(part);return found<0?Number.POSITIVE_INFINITY:found}));const start=Number.isFinite(at)?Math.max(0,at-40):0;const context=body.slice(start,Number.isFinite(at)?at+120:120);a.href=item.href;a.textContent=item.id+' '+item.title;const small=document.createElement('small');appendHits(small,context,parts);a.addEventListener('click',()=>{highlightArticle(item.id,parts);out.textContent=''});li.append(a,document.createElement('br'),small);out.append(li)}}input.addEventListener('input',render);button?.addEventListener('click',()=>{render();out.querySelector('a')?.click()});`; const esc = (value: string) => value .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """);