Skip to content

GitHub Pages architecture

how the website is derived from the repository and checked for drift

Rendered from docs/GITHUB_PAGES_ARCHITECTURE.md — the same Markdown GitHub shows.

The website at https://majordomus.dev/ is a projection of this repository. It is built the way Majordomus asks projects to build their own instruction files: one canonical source, generated outputs, and a check that fails when the two drift.

Purpose

Let a visitor understand in about two minutes what Majordomus is, what it guarantees, what is only advisory, what is not built, and how to start, without any sentence on the site being maintained separately from the repository that backs it.

Canonical sources

layerownslives inedited by hand
product truthpolicy schema, profiles, the worker instructions, the shell CLIshare/skeleton/**, bin/majordomus, lib/**, share/commands.yamlyes
the Rust executableevery capability with its schemas, exposures, stability, provenance, benchmark and cache policy; the modules; the command line with the examples of every commandcapability! and module! declarations under apps/majordomus-cli/src/capability/builtin/, apps/majordomus-cli/src/cli.rs (clap for the structure, cli::EXAMPLES for the examples)yes
the executable's narrativewhat the crate is, owns and refuses; discovery, transports, side effects, architectureapps/majordomus-cli/README.md, docs/CAPABILITIES.md, docs/MCP.mdyes
benchmark evidencethe regression policy and the accepted baselines.ai/repo/benchmarks/rust/policy.yaml, .ai/repo/benchmarks/rust/baseline.<platform>.jsonpolicy yes; a baseline by majordomus bench baseline update
registry projectionsthe OpenAPI document, the registry manifest, the capability, module and benchmark references, the command line, and the manifest of the whole generated set — each document in every encoding it is committed indocs/generated/** (artifacts.md is the generated index of it)never — majordomus generate
registry dataseteverything the site renders about the executable: descriptors, modules, command line, MCP and HTTP surfaces, benchmarks, the indexsite/data/registry/registry.jsonnever — majordomus generate site
public narrativewhat it is, why, how, what it refusesREADME.md, docs/*.mdyes
claimsevery capability with status, source, implementation, testdocs/CLAIMS.yamlyes
marketing copythe hero's positioning sentences, section leads, button labels; no capability claims, no numbers, 80-line budgetsite/data/marketing.tomlyes
navigationthe intents, their dropdown items and hrefs, and the routes that moved ([[redirects]], rendered as Zola aliases)site/data/nav.tomlyes
claim detailwhat each claim means, how it works, how to see it, what it does not cover, why it existsdocs/claims/<id>.mdyes
product featuresone file per feature: the references naming what it is made of, and the editorial decisions nothing can infer; every surface, count and route beside it is derived (docs/PRODUCT.md, ADR 23).ai/repo/features/<id>.mdyes
case studiesthe recognition moments, each with its homepage hook in front mattersite/content-src/why/*.mdyes
skillsthe repository's skills, one directory each; the site reads the catalogue lib/skills.sh derives from the source class skill.ai/repo/skills/<id>/SKILL.mdyes
rendering referencerepresentative Markdown for visual validationsite/content-src/render-test.mdyes
derived datastable JSON the templates readsite/data/generated/*.jsonnever
the layer's directory contractsevery directory of .ai/, the contract it carries or the one that exempts it, and the countssite/data/generated/context.json (from majordomus context list --json)never
derived release artifactthe claims matrix as Markdowndocs/SITE_CLAIMS.mdnever
derived contentcanonical Markdown with generated front matter; one page per profile, claim, status, responsibility, command, doctrine, use case, application, milestone, issue, case study, skill, module and capabilitysite/content/{docs,profiles,guarantees,supervises,commands,doctrines,use-cases,applications,plan,why,registry,skills}/, render-test.md, architecture.mdnever
derived routes and links for the executableone route per module and per capability, the executable's pages, the API anchor, the source on GitHub, the claims attached to each surfacesite/data/generated/executable.jsonnever
the native command line, as the site renders itthe command tree flattened, each command with its route, usage, arguments, children and executed examplessite/data/generated/cli.json (from docs/generated/cli.json, via scripts/lib/cli-site.jq)never
build provenancethe commit, its time and its cleanliness, the site's input hash, the registry and index fingerprints, and each composed surface with the commit its producer declaredsite/data/build.json, served as /build.jsonnever — scripts/site-build, not committed
the crate's referencethe rustdoc pages of apps/majordomus-cli, composed at /rustdoc (RUSTDOC.md)target/web/rustdoc/, with the surface.json that declares what it was built fromnever — scripts/rust-check --doc, not committed
presentationZola templates and Tera 2 componentssite/templates/**yes
styling entryTailwind v4 + Flowbite v4 directivessite/tailwind.cssyes
behaviourtheme toggle, the diagram viewer, the chart componentsite/theme.js, site/diagrams.js, site/charts.jsyes
outputthe static sitesite/public/**never

What the publishing clone must be able to answer

The site links to the repository on its forge, and scripts/ci/link-check decides those links against git rather than over the network: commit/<sha> and compare/a...b are resolved in the history, releases/tag/<t> and tree/<tag> in the tags. A clone that was not given those cannot decide such a link. It must then refuse rather than guess — and scripts/site-check counts a refusal as a failure, so the publication stops.

That is not a hypothetical cost. Between 2026-09-14 18:34 and 2026-09-15 00:0x the deploy checked out at depth 1, which truncates the history and fetches no tags. Six links to v0.3.1, v0.5.0 and v0.6.0 — tags that exist and carry published releases — were reported as non-existent, 2302 commit links were undecidable, publish was skipped on every run, and the live site stayed at 8cf457000 for seven hours while master moved three merges ahead. Every gate was green throughout, because nothing asked whether the site was current.

So the requirement is declared, not written into the workflow:

# .ai/repo/ci/pages.yaml
deploy:
  checkout:
    history: full      # renders as fetch-depth: 0
    tags: required     # actions/checkout fetches none at a shallow depth
whatwhere
the requirementdeploy.checkout in .ai/repo/ci/pages.yaml
the depth it impliesscripts/pages checkout — prints 0
the clone a run was givenscripts/pages checkout --verify — exit 10 when it cannot decide a ref
the workflow held to the modeltest/cases/97_pages_fast_path.sh

The workflow is an adapter over that, the same way its paths: block is an adapter over scripts/pages paths. Changing what the publisher needs from its clone is an edit to the model and nowhere else: the depth, the verification and the test all read it. A test that asserted fetch-depth: 0 literally would hold one patch in place; the case compares the two and fails when they disagree, whichever of them moved.

--verify runs in the deploy job immediately after checkout, so a runner that does not honour the declaration fails there, naming the depth — rather than three steps later inside link-check, where a checkout problem presents as a link defect.

When a publication fails: the scheduled retry

A push-triggered run that fails leaves the site where it was, and nothing asks again. On 2026-09-14 the site stood seven hours while every run failed. So .github/workflows/pages.yml also runs on a clock, trigger.schedule in .ai/repo/ci/pages.yaml, every thirty minutes — the window scripts/ci/pages-check judges by (OWED_AFTER, 1800 s); a schedule slower than the window cannot notice what the window calls owed. test/cases/97_pages_fast_path.sh holds the workflow's cron to the model's.

A scheduled run asks before it works, in a small job of its own (owed):

stateremedyrun ends
the site is currentnone; nothing else runsgreen
gh-pages does not carry the trunk's sitethe rustdoc and deploy jobs run as on a pushred
gh-pages is right, GitHub's own build erroredscripts/pages rebuildred

Red is deliberate: the run repaired a fault the push-triggered run should not have left, and healing without reporting would hide a fault that recurs every thirty minutes. The intervention job says what was owed and what was done.

A rebuild is a different action from a redeploy. Deploying an identical tree changes nothing when GitHub's builder is what failed, and a request for a build preempts a build in flight: asking while GitHub is building kills that build. scripts/pages rebuild therefore asks only when the build of the commit errored and nothing is building, reads that state through scripts/pages built --porcelain (the one reader of it), exits 12 without asking when the state cannot be read or a build is in flight, and reads the state again after asking. "gh-pages is right" is measured, not assumed: the owed job chooses the rebuild only when the top-level commit of gh-pages' build.json is the trunk commit the run checked out. An errored build of an older tree — a push deploy that failed before publishing, over a build that had errored too — is deployed, because rebuilding it would republish the stale site on every run. test/cases/625_a_rebuild_is_asked_only_when_it_helps.sh holds each of those, the last by running the workflow's own remedy step against both trees.

Projection pipeline

Two generators, one graph. The Rust executable projects its own registry; the site generator projects the canonical files and consumes the executable's projections through one machine-readable boundary, docs/generated/registry.json (and openapi.json); Zola renders templates over what both wrote. scripts/derive runs the stages in order, scripts/derive-check proves the committed result, and no stage reads its own output.

flowchart TB
    subgraph canonical
        R[capability! and module! declarations, src/cli.rs with cli::EXAMPLES]
        K[share/kinds.yaml, schemas, providers, policy]
        D[README, docs/*.md, docs/claims, CLAIMS.yaml, RESPONSIBILITIES.yaml]
        C[share/commands.yaml, use-cases.yaml, applications.yaml]
        P[.ai/repo/project, rules, benchmarks]
    end
    subgraph stageA[stage A · majordomus generate, code only]
        GA[docs/generated: openapi, registry, capabilities, cli, modules/*.md — json+yaml+md as each document is committed · share/allow/*.txt · AGENTS.md, CLAUDE.md]
    end
    subgraph stageB[stage B · scripts/generate-site-data]
        GB[site/data/generated/*.json · site/content/** · docs/SITE_CLAIMS.md, PLAN_STATUS.md]
    end
    subgraph stageC[stage C · majordomus generate, over the index]
        GC[site/data/registry/registry.json · docs/generated/benchmarks.* · docs/generated/artifacts.*]
    end
    subgraph build[scripts/site-build]
        Z[zola + Tailwind → site/public · build.json]
    end
    R --> GA
    K --> GA
    D --> GB
    C --> GB
    P --> GB
    GA -->|registry.json, openapi.json, cli.json| GB
    R --> GC
    P --> GC
    GB -->|the derived documents are objects of the index| GC
    GB --> Z
    GC --> Z
stagegeneratorreadswriteschecked by
Amajordomus generatethe Rust declarations, share/, the policy and the provider templatesdocs/generated/{openapi,registry,cli}.{json,yaml}, docs/generated/{capabilities,cli}.md, docs/generated/modules/*.md, share/allow/*.txt, the provider bootstrapsmajordomus generate --check
Bscripts/generate-site-dataevery canonical file (--inputs lists them) and stage A's registry.json and openapi.jsonsite/data/generated/*.json, site/content/**, docs/SITE_CLAIMS.md, docs/PLAN_STATUS.mdscripts/generate-site-data --check (by input hash)
Cmajordomus generatethe Rust declarations and the index of the layer, which now holds the documents stage B wrotesite/data/registry/registry.json, docs/generated/benchmarks.{md,json,yaml}, docs/generated/artifacts.{json,yaml,md}majordomus generate --check
buildscripts/site-buildstages B and C, the templates, the assets, and the artifact of every published static surface docs/generated/web.json namessite/public/** (with each such surface composed at its mount), site/data/build.json, site/static/build.jsonscripts/site-check, scripts/site-probe

The boundary between the executable and the site is data, in both directions of reading: the site generator reads the registry manifest for the ids it turns into routes and never parses Rust; the templates read the registry dataset for every fact about a capability and never restate one; the executable knows nothing of Zola. The manifest is a projection used as an interface between stages, not a second truth: a capability exists in one place, and generate --check refuses a manifest that no longer says what the declarations say.

Why stage C exists: the dataset lists every object of the layer with its size, and the derived documents stage B writes are objects of the layer, so the dataset is derived after them. Stage C's outputs are read by nothing but the site build, so the graph has no cycle and a second scripts/derive on a clean tree changes nothing — git status stays empty, which test/cases/51_derived_artifacts_committed.sh and the release criterion require.

scripts/generate-site-data reads every canonical input, normalises it once, and writes:

filefromwhat
project.jsonbin/majordomus, README.md, LICENSEname, version, tagline, licence, commands, exit codes
profiles.jsonshare/skeleton/profiles/*.yamlevery profile, every field
policy.jsonshare/skeleton/policy.yamlthe policy as structure, plus the raw text
capabilities.jsondocs/CLAIMS.yamlevery claim; the generator fails on a missing path or an untested guaranteed claim
commands.jsonshare/commands.yaml, docs/CLI.md, the test casesevery shell command with its semantics, narrative and evidence
catalogue.json.ai/repo/use-cases/, .ai/repo/applications/the use cases and applications, cross-referenced
doctrines.jsonthe rule packagesevery doctrine with its enforcement chain
plan.json.ai/repo/project/milestones, issues, the dependency graph, derived status
openapi.jsondocs/generated/openapi.jsonthe HTTP API in the shape api.html and api-tag.html render, with the one page every operation and schema is published on (scripts/lib/openapi-site.jq)
artifacts.jsondocs/generated/artifacts.jsonthe generator's own manifest — declarations only, carrying schema: 1 like every other file of that directory with the manifest's own schema id under manifest_schema, moved under site/data/ so registry-artifacts.html can load it; nothing is added and no file is named in the template. Sizes and hashes are dropped: two of the manifest's subjects are written by stage C, so a copy carrying them could never converge with stage B
executable.jsondocs/generated/registry.json, capabilities.jsonone route per module and per capability, the executable's pages, API anchors, sources on GitHub, the claims attached to each surface by the path of their implementation (scripts/lib/executable-site.jq)
lifecycle.jsonlib/finish.sh, share/skeleton/ai/repo/workflows/task-lifecycle.md, share/standard/majordomus/outcome vocabulary, divergence labels, lifecycle steps, the ten principles (the rules tagged principle)
diagrams.jsonthe files aboveMermaid source projected from data
readme.jsonREADME.mdthe sections the homepage renders, by heading; a renamed heading fails the build
docs.jsondocs/README.mdthe documentation index
source.jsonthe inputsversion, input hash, generator version, input list; no commit (see build provenance)

site/content/docs/*.md is written from docs/*.md with a generated front matter, links rewritten to site routes, and the whole body wrapped in a Tera raw block so that Tera never templates canonical Markdown. The tag itself cannot be written out here: a literal closing raw tag in a canonical document would end the wrapper the projection puts around it. site/content/registry/executable.md is apps/majordomus-cli/README.md through the same projection.

Derived files, classified

classwhatexamplesrule
canonicalauthored, the only place a fact livesthe Rust declarations, share/*.yaml, docs/*.md, docs/CLAIMS.yaml, .ai/repo/**, templates, nav.toml, marketing.tomledited by hand
derived, committeda projection reviewers see on GitHub and CI compares with its sourcesdocs/generated/**, share/allow/**, AGENTS.md, CLAUDE.md, site/data/generated/**, site/data/registry/**, docs/SITE_CLAIMS.md, docs/PLAN_STATUS.mdregenerated with scripts/derive, committed with the change that moved it; scripts/derive-check refuses drift
derived, build onlyrebuilt on every build, never committedsite/content/{docs,registry,commands,...}/, site/static/{app.css,js/,openapi.json,build.json}, site/data/build.json, site/public/**, target/web/rustdoc/gitignored; scripts/site-check proves the build's own guards (13e, 13a) and that every composed surface is this build's (13m)
local, ephemeralthis checkout's state.ai/local/**, apps/majordomus-cli/target/never a source of anything the site shows

The generator is an input, and it is not in the table

Every row above classifies a file. The classification is incomplete in one way that has already cost this repository a near-miss: the derived files are a projection of the canonical sources as read by one executable, and that executable is not a file in the tree. Two builds of the same declared version can project different registries.

So an executable borrowed from another worktree — through MAJORDOMUS_BIN, which is how a session avoids a ten-minute rebuild — is a third input, and an unsuitable one fails in the worst available direction: it reports the tree stale, printing a remedy that regenerates, and regenerating rewrites each document from a model the tree no longer holds. On 2026-09-10 that removed about 9,700 lines from docs/generated/changelog.json and exited zero.

Before deriving with an executable you did not just build:

env -u MAJORDOMUS_SHARE "$MAJORDOMUS_BIN" generate --check   # must say: in sync

and after any derivation, before committing:

git diff --stat -- docs/generated site/data                  # mass deletion is corruption

project.a-tool-that-cannot-match-the-tree-refuses is the rule; the guard that enforces it lives in the executable, so it holds for every caller rather than for one script.

Where do I edit this?

I want to changeeditnever edit
a Rust capability's title, description, schema, stability, tags, benchmark or cache policyits capability! declaration under apps/majordomus-cli/src/capability/builtin/docs/generated/**, site/data/registry/registry.json, any /registry/ page
how a capability is exposed (MCP tool, resource, HTTP route, CLI path)the exposure of the same declarationdocs/generated/openapi.json, /docs/api/, /registry/mcp/
a module's title, description or stabilityits module! declarationdocs/generated/modules/*.md, /registry/modules/
the native command line: a command, an argument, a default, a value setthe clap declaration in apps/majordomus-cli/src/cli.rsdocs/generated/cli.md, docs/generated/cli.json, site/data/generated/cli.json, /registry/cli/, /docs/cli/**
an example of a native command, or adding a command that has nonecli::EXAMPLES in the same file, beside the declarationan example written into a template, a page or cli.md: the pages render the argument vectors the crate's tests execute
the benchmark regression thresholds.ai/repo/benchmarks/rust/policy.yamlthe policy table on /registry/benchmarks/
an accepted baselinemajordomus bench baseline update on the platform it measuresthe baseline JSON by hand
what the executable is, owns, refuses; discovery; transports; side effectsapps/majordomus-cli/README.md/registry/executable/
a shell command's category, stage, reads, writes, syntax, exit codesshare/commands.yaml (semantics) and docs/CLI.md (narrative)commands.json, /commands/<name>/
a use case or an application.ai/repo/use-cases/, .ai/repo/applications/catalogue.json, its route
a claimdocs/CLAIMS.yaml and docs/claims/<id>.mdcapabilities.json, docs/SITE_CLAIMS.md, /guarantees/
a doctrinethe rule file under .ai/repo/rules/ or the vendored packagedoctrines.json, /doctrines/
a milestone or an issue.ai/repo/project/**plan.json, docs/PLAN_STATUS.md, /plan/
a profile, the policy's schemashare/skeleton/**profiles.json, policy.json
a kind or a schemashare/kinds.yaml, share/schemas/, or .ai/repo/knowledge/ for this repositoryshare/allow/*.txt, the kinds table
the provider bootstraps.ai/repo/policy.yaml, the provider templatesAGENTS.md, CLAUDE.md, GEMINI.md
how a page lookssite/templates/**, site/tailwind.csssite/data/generated/** to make a template's life easier
which pages the executable's section has, or how claims attach to its surfacesscripts/generate-site-data, scripts/lib/executable-site.jqa route list in a template or in nav.toml
the editorial navigationsite/data/nav.toml — intents and their fixed pagesan entry per module or capability: those routes are enumerated by the generator, and site-check refuses one typed here
the homepage's wordssite/data/marketing.tomla number, a claim word

After any edit in the left column: just derive, review the diff of the derived files, just derive-check, commit both together.

Markdown rendering

Canonical Markdown must stay readable on GitHub, so the site uses only syntax GitHub renders natively. scripts/lib/project-markdown.awk projects three GitHub-native constructs into site components, on the derived copy only:

in the canonical fileon the site
```mermaid fence<pre class="mermaid">, named, linked and given its controls by the build, drawn client-side in the viewer (DIAGRAMS.md); the source stays visible without JavaScript
> [!NOTE], [!TIP], [!IMPORTANT], [!WARNING], [!CAUTION]Flowbite alert
a pipe tablewrapped in overflow-x-auto so wide tables scroll inside their container

Everything else is Zola's Markdown renderer inside a Flowbite Typography container (format dark:format-invert): headings with anchors, lists, task lists, footnotes, blockquotes, and class-based syntax highlighting ([markdown.highlighting] style = "class", themes ayu-light and ayu-dark). Zola writes the two theme stylesheets into site/public/; scripts/site-build scopes the dark one under .dark so class-based dark mode selects it. site/content-src/render-test.md exercises every construct and is linked from nowhere.

Mermaid

Client-side, from the pinned npm package, vendored to static/js/mermaid.min.js at build. Chosen over build-time SVG because the Mermaid CLI needs a headless browser in CI and the site must stay reproducible from bash, Node and Zola alone. No page loads the build with a script tag: site/diagrams.js is deferred and fetches it once the page has been parsed. It initialises with securityLevel: 'strict', takes its colours from the page's design tokens, and draws again when the theme changes. Four diagrams are projected from data, never drawn by hand: the task lifecycle from the outcome vocabulary, the policy-to-instruction-file graph from the policy's projections, the site pipeline from the generator's input list, and the core model from the README. A failed diagram leaves its source visible and never breaks the page.

After Zola, scripts/site-build runs scripts/lib/diagrams.mjs over the built pages: every diagram gets an id (diagram-<name>), the list of its nodes, the viewer's toolbar and a list of the links inferred from the identities in site/data/generated. The viewer, the address scheme and the chart component, which draws a table the page already renders with the vendored Apache ECharts build, are described in DIAGRAMS.md (ADR 0152).

Zola structure

site/
  config.toml          base_url, class-based highlighting, heading anchors, footnotes
  content/             _index and one stub per page; docs/ and render-test.md are generated
  content-src/         hand-written pages that get the same projection as docs
  templates/
    base.html          head, theme-before-paint, navbar, footer, Flowbite init, Alpine
    components.html    Tera 2 components: badge, code, card, section_head, diagram
    index.html         homepage: the business hero, then the sections, all from generated data
    page.html          standalone page with breadcrumb and Typography body
    docs-section.html  /docs/ index from the section's pages
    docs-page.html     document with sidebar navigation
    profiles.html policy.html guarantees.html getting-started.html architecture.html
    404.html
    partials/          navbar, footer, theme-toggle, breadcrumb, profile-cards, mermaid
  data/marketing.toml  the only hand-written prose
  data/generated/      written by generate-site-data, committed, checked
  tailwind.css theme.js diagrams.js
  static/              images copied from assets/; app.css and js/ written at build
  public/              output

Zola 0.23 runs Tera 2: components replace macros, import no longer exists, array indexing is x[1], undefined variables are errors, filter and slice are gone in favour of list comprehensions and Python-style slicing.

Tailwind, Flowbite, Alpine

site/tailwind.css follows the Flowbite quickstart for Tailwind v4: @import "tailwindcss", @import "flowbite/src/themes/default", @plugin "flowbite/plugin", @plugin "flowbite-typography", @source for node_modules/flowbite and the templates, and @custom-variant dark (&:where(.dark, .dark *)) from the dark-mode guide. Flowbite v4's semantic tokens (bg-neutral-primary-soft, text-heading, border-default, rounded-base) are used throughout. The theme names Inter first; it is not loaded, so the stack falls to the system font on purpose.

Flowbite's JavaScript is not loaded. The only two things the site used it for, the navbar's mobile collapse (data-collapse-toggle) and its dropdowns (data-dropdown-toggle), are site/nav.js, about 2 KB, which base.html loads deferred and site-check requires exactly once per page. Removing the 134 KB bundle took the homepage from 338555 to 207140 bytes of scripts and stylesheets. Flowbite's styles stay; they are CSS, compiled into app.css.

The homepage is a projection of site/data/registry/product.json, which the executable writes from the features of the layer, the capability registry and the web topology. Every chapter, mark, count, provider, kind and rule on it is read from that dataset; the template names none of them, and scripts/site-check fails a template, a generator or a navigation file that names a feature route or a provider's own file. The hero's second column is the answer majordomus product matrix prints, rendered as a table rather than padded text so the columns align at every width and a screen reader reads a table rather than a picture of one. The words around it — the title, the mantra, the section leads and the button labels — are marketing.toml's, which carries no number and no capability claim and is held to a sixty-line budget.

The one thing a person decides is which features are chapters and in what order, and that decision lives in the feature files themselves (featured and weight), not here: a feature becomes a chapter by declaring itself one. docs/PRODUCT.md is the contract, ADR 23 is the decision, and project.product-surface-derived is the rule.

Routes that moved are declared once, in nav.toml under [[redirects]], beside the routes that did not. The generator turns each into a Zola alias on the page it points at, so the old address answers with a redirect; a redirect whose target the generator did not produce fails the build rather than publishing a link into nothing. An alias page is a redirect and not a page, so scripts/lib/site-pages.awk exempts it from the contract a page is held to.

Alpine.js is vendored and used for two things: the copy button on code snippets (x-data, x-on:click, x-text) and the raw-policy disclosure on /policy/. Both degrade: the code and the policy are in the HTML; only the control disappears without JavaScript.

Custom CSS: one rule, [x-cloak]{display:none!important}, inline in base.html, required by Alpine's documentation. Inline style="" attributes are forbidden and scripts/site-check fails on one.

Flowbite guidance consulted

The Flowbite LLM entry point (flowbite.com/docs/getting-started/llm/) points at llms.txt, which is an index. The pages read and followed, on 2026-09-04: getting-started/quickstart (Tailwind v4 directives), getting-started/javascript (initFlowbite), customize/dark-mode (@custom-variant, toggle markup and script, reproduced in theme.js), components/navbar, components/card, components/badge, components/footer, components/typography. The repository's installed flowbite is 4.0.2, matching the documented token vocabulary.

Languages

The site is written once, in English, and offered in every language site/i18n/languages.json declares (ADR 0218). Nobody writes a second language: each copy is derived from the English build.

piecewhat it is
site/i18n/languages.jsonthe source language, the languages offered, each named in itself
site/i18n/<lang>/memory/*.jsonlthe translation memory: one line per English segment, keyed by its hash
site/i18n/<lang>/guide.jsonthe style and glossary every translator of that language is given
site/i18n/<lang>/edition.tomlthe language the directory declares, which english-only-check holds to its declaration
scripts/lib/site-i18n.mjsthe == languages pass of scripts/site-build: segments every page, writes /<lang>/
scripts/site-translatecarries the missing segments to a translator and its answers into the memory
scripts/lib/site-i18n-probe.mjsfollows every link of every page, in every language, in Chromium
partials/language-switch.html, partials/language-detect.htmlthe switch in the bar, and the choice of language on arrival

The pass. After Zola, the accessibility pass and the diagrams, every built page is cut into segments — each run of inline content between block boundaries, and each attribute a reader meets (title, alt, aria-label, the description) — with inline markup replaced by numbered placeholders (<t1>…</t1>, and <t2/> for code and images). Code, pre, SVG and anything marked translate="no" is never a segment. The copy under /<lang>/ is the English page with every segment the memory knows replaced, every internal link pointed at that language's copy, lang, the canonical URL and the hreflang alternates set, the switch pointed at each copy and the machine-translation notice revealed. What the memory does not know stays English, is counted in /<lang>/i18n.json, and is listed with the pages it is on in target/site-i18n/missing.json. A build calls no model: it is a pure function of the English pages and the memory.

Translating. A changed English segment has a new key, so its old translation is never served.

scripts/site-build                                   # lists what the memory lacks
scripts/site-translate status                        # how much is open
scripts/site-translate run --advisor <id>            # translate through an advisor of the catalogue
scripts/site-translate export --out DIR --min-pages 2 --batch-chars 16000   # batches for subagents
scripts/site-translate import DIR/*.json             # admit filled batches into the memory

A translation is admitted only when it carries every placeholder of its English exactly once, nested the same way; anything else is refused and the English is served.

A value inside a sentence. The memory keys a segment by its whole text, digits included, so a sentence that carries a count, a date or a version gets a new key each time the value changes, and its copy falls back to English until the memory is filled again. Until a number in a segment is a placeholder of its own (I3758), a template wraps each derived value in an inline element the pass does not enter: <data value="…">, or a <span translate="no">. That element stands in the segment as a void placeholder, so the words around it keep one key whatever the value is. The plan's figures are written this way (site/templates/plan-section.html); a block element marked translate="no" is a boundary instead, and splits the sentence in two.

The reader's language. The switch is Flowbite's navbar language dropdown: every language named in itself, lang and hreflang on each link, aria-current on the one being read. A reader who arrives from outside the site on an English page, has chosen nothing and whose browser prefers a declared language is sent to that page's copy in it; a choice made with the switch is stored and kept. A link followed inside the site, a reload and a crawler stating no such preference are never redirected, so every URL means the page it names.

Authored pages. A page written in another language ([extra] lang, declared in english-only-allow.txt) is carried into each copy unchanged. Its copies name it canonical, and a pair of authored counterparts ([extra] alternate) keeps its own alternates.

Every link, every language. scripts/lib/site-i18n-probe.mjs loads every page of every copy in Chromium, answered from the build itself, and holds each to its language: every link lands on a built file, a link inside a copy stays in that copy's language, the switch leads to each copy and marks the current one, and the alternates name each other both ways.

Adding a language. One entry in languages.json, a guide.json, an edition.toml, its declaration in scripts/ci/english-only-allow.txt, and a translation run. No template, generator or page changes.

The bar of the other surfaces. /ui wears the site's bar (scripts/ui navbar), and its language switch leads to the site's front page in each language until the UI itself is offered in them (intent one-bar-and-one-language-on-every-surface).

Routes

Every tile on the homepage is a link to a page with its own title, description, canonical URL and Open Graph metadata. The route classes and their sources:

routesourcetemplate
/site/data/registry/product.json and product-graph.json (chapters, interfaces, matrix, providers, kinds, doctrine), marketing.toml (the words), readme.json (the finish contract), distribution.json (the install command), the why sectionindex.html
/why/, /why/<slug>/site/content-src/why/*.mdwhy-section.html, why.html
/features/, /features/<id>/, /features/matrix/site/data/registry/product.json and product-graph.json, with each page's prose read from the feature's own filefeatures-section.html, feature.html, features-matrix.html
/getting-started/project.json, policy.json, lifecycle.jsongetting-started.html
/supervises/, /supervises/<slug>/readme.json (What it does rows, cross-linked to commands and claims by keyword)supervises-section.html, responsibility.html
/commands/, /commands/<name>/commands.json (each command's section of docs/CLI.md)commands-section.html, command.html
/profiles/, /profiles/<slug>/profiles.jsonprofiles.html, profile.html
/policy/policy.json including the raw filepolicy.html
/guarantees/, /guarantees/<status>/, /guarantees/<id>/capabilities.json, docs/claims/<id>.mdguarantees.html, status.html, claim.html
/limitations/, /roadmap/readme.json sections by headingreadme-section.html
/architecture/site/content-src/architecture.md, source.json, diagrams.jsonarchitecture.html
/docs/, /docs/<doc>/docs/*.md listed in docs/README.mddocs-section.html, docs-page.html
/docs/api/, /docs/api/<tag>/, /docs/api/shared/, /openapi.jsonopenapi.json (from docs/generated/openapi.json)api.html (the index), api-tag.html (a page per tag, and shared types)
/docs/cli/, /docs/cli/<command path>/cli.json (from docs/generated/cli.json, itself the clap declaration and cli::EXAMPLES)docs-cli.html, docs-cli-group.html, docs-cli-command.html
/doctrines/, /doctrines/<slug>/doctrines.jsondoctrines-section.html, doctrine.html
/use-cases/, /use-cases/<id>/, /applications/, /applications/<id>/catalogue.jsonuse-cases-section.html, use-case.html, applications-section.html, application.html
/plan/, /plan/dag/, /plan/<id>/plan.jsonplan-section.html, dag.html, milestone.html, issue.html
/evidence/, /map/claims-graph.json, architecture.jsonevidence.html, map.html
/registry/site/data/registry/registry.json, executable.jsonregistry.html
/registry/executable/apps/majordomus-cli/README.md, executable.json (claims)registry-executable.html
/registry/modules/, /registry/modules/<id>/the dataset's modules, executable.jsonregistry-modules.html, registry-module.html
/registry/capabilities/, /registry/capabilities/<slug>/the dataset's descriptors, benchmarks and coverage, executable.json (module, API anchor, source, claims, tests)registry-capabilities.html, registry-capability.html
/registry/cli/the dataset's cli (the clap declaration)registry-cli.html
/registry/mcp/the dataset's mcp (tools, resources, protocol)registry-mcp.html
/registry/benchmarks/the dataset's benchmarks (targets, coverage, policy, baselines)registry-benchmarks.html
/registry/artifacts/artifacts.json (the generator's own manifest)registry-artifacts.html
/rustdoc/, and every page rustdoc writes beneath ittarget/web/rustdoc/, written by scripts/rust-check --doc and composed by scripts/site-build from docs/generated/web.json— (rustdoc's own pages; index.html is the producer's landing page)
/build.jsonscripts/site-build— (served raw)
/render-test/site/content-src/render-test.md, noindexdocs-page.html

A capability's route slug is its id with . and _ replaced by - (objects.search → /registry/capabilities/objects-search/, repository.scope_classify → /registry/capabilities/repository-scope-classify/): Zola slugifies every path that way, so the dataset names the route Zola actually serves. The API reference's operation anchors are HTML ids Zola never touches, so they replace only the . (#op-objects-search, #op-repository-scope_classify); executable.json carries both spellings (slug, api_anchor), so the two link each other without a table.

A native command's route is its path under /docs/cli/, one segment per word, with a trailing slash: ["majordomus", "bench", "baseline", "update"] is /docs/cli/bench/baseline/update/. The rule is one function in the executable (cli::route), the route it produced is carried in docs/generated/cli.json, and every consumer downstream — the site generator, the templates, site-check, site-probe — reads that field rather than deriving one of its own. A command that has commands under it is a Zola section so that its children have routes beneath it, and a command that has none is a page; which it is comes from the data, so a command that grows a subcommand changes shape by itself. site-check compares the declared routes and the built ones as sets, both ways: a command with no page fails, and a page whose command the executable no longer has fails as an orphan.

/docs/cli/ belongs to the native command line, the Rust executable's. docs/CLI.md specifies the shell tool bin/majordomus — a different program, whose structured reference is /commands/ — and renders at /docs/cli-specification/. The rule that turns a document into a route is one function in the site generator (doc_slug), and the route it produced is recorded in docs.json, so the link rewriting, the pages and site-check read it instead of each computing a slug. Do not describe share/commands.yaml or docs/CLI.md as sources for the Rust executable: they are the shell tool's, and the two command surfaces are documented apart on purpose.

Navigation is site/data/nav.toml: a handful of intents (Why, Get started, Concepts, Plan, Trust, Executable, Reference), rendered as Flowbite dropdowns on desktop and as labelled flat lists inside the collapsed menu on phones. It holds editorial intent — which fixed pages an intent leads to — and never an enumeration: the modules and capabilities are listed by the generator, and scripts/site-check refuses a module or capability route typed into it. scripts/site-check verifies every navigation and homepage link resolves, every tile class has its page, for the executable's section that every module and capability of the dataset has its page and no page exists without its entry, and for the command line that the routes under /docs/cli/ are exactly the commands the executable declares, that each page renders its own usage, arguments and example anchors and links up and down, and that no command route is typed by hand into a template, the generator or the navigation. scripts/site-probe then fetches every declared command route from a running server and reads the deepest command page back.

Responsive strategy

Mobile-first: base classes describe the phone layout, sm:/md:/lg:/xl: add columns. Every <pre> and <table> sits in a scroll container, either an explicit overflow-x-auto wrapper or a Typography container carrying [&_pre]:overflow-x-auto and [&_table]:overflow-x-auto. Grid items that hold code carry min-w-0. Validation is measured, not eyeballed: scripts/site-probe builds a copy under the deployment prefix, serves it locally, loads every route in headless Chrome inside iframes of 320, 390 and 1280 px and asserts document.scrollWidth == clientWidth, then exercises the mobile menu, a navigation dropdown and the theme toggle and counts rendered Mermaid diagrams. It runs in the Pages workflow and skips with a notice where no Chrome exists. test/cases/09_site_mobile_first.sh lints the built HTML for the structural causes of overflow without a browser.

Accessibility

One h1 per page, <main>, <nav aria-label>, a skip link, aria-current on active navigation, aria-expanded/aria-controls on the menu toggle, aria-label on icon-only controls, role="note" on callouts, visible focus rings (focus:ring-*, never removed), Flowbite's contrast tokens in both themes. Everything except the copy button and the disclosure works without JavaScript.

GitHub Pages deployment

The site is published by .github/workflows/pages.yml, which triggers directly on a push to master whose paths can change the site and runs beside validate.yml rather than after it. Its deploy job proves the committed site/data/generated is current for the tree by comparing the tree's canonical input hash with the one source.json carries (scripts/generate-site-data --fingerprint), renders it (scripts/pages build), runs every static check over the output (scripts/pages check), and pushes gh-pages. A tree whose committed derived data is stale is refused rather than regenerated.

The crate's rustdoc reference is part of those bytes and is not committed, so a rustdoc job of its own, which the deploy job needs, produces it from the same commit through the composite action .github/actions/rustdoc — the one the validation workflow uses too — and hands it over as an artifact of the run. The deploy job fetches that tree before it renders, scripts/site-build composes it at /rustdoc from docs/generated/web.json, and scripts/site-check refuses a composed surface that is missing or was built from another commit (section 13m). Once the site serves the deployed commit, scripts/pages verify-rustdoc checks the public reference against it, and that step is a hard one: a deployment whose public reference is absent, broken or stale fails the run. RUSTDOC.md has the whole path and ADR 86 the decision.

Everything that decides whether a change may merge — the behavioural suite, the crate's gates, coverage, the macOS suite, the benchmark check and the browser probe — runs on the same commit in validate.yml and gates the ci status. The publication path carries what can make the published bytes wrong and nothing else; the reasoning, the budgets and the measurements are GITHUB_PAGES_PERFORMANCE.md, and CI.md has the validation shape. The rust job runs majordomus generate --check and the site job scripts/generate-site-data --check; locally scripts/derive-check (just derive-check) runs both, and scripts/derive (just derive) regenerates every derived artifact in dependency order.

GitHub Pages serves the gh-pages branch (source: branch, path /), and whoever pushes that branch deploys. One script does it, scripts/site-deploy: it refuses a dirty tree and a commit origin/master does not contain, runs the gate (generate-site-data --check, majordomus generate --check, site-build, site-check, --probe on request), copies site/public into a worktree of gh-pages with .nojekyll, commits it with the source commit in the message, and pushes. GitHub's own "pages build and deployment" then serves it in under a minute; the footer of every page names the commit it was built from, so the deploy is verifiable with curl. Unchanged output is not pushed twice, and a --dry-run shows the commit it would push. test/cases/96_site_deploy.sh runs the whole path against a local bare remote: every refusal, the first deploy, the no-op redeploy, and the check that site/public was built from HEAD.

The job of .github/workflows/pages.yml is the gated form of the same command: it builds and checks the commit and then runs scripts/site-deploy --skip-build. A person runs scripts/site-deploy (or just site-deploy) when the operator wants the site live without waiting for the Actions queue, and that path — no queue in front of it at all — is why the branch source is kept instead of the native Pages artifact flow, which would make a workflow run the only way to publish. The procedure is .ai/repo/skills/deploy-site/SKILL.md. Pointing Pages at the branch is a one-time scripts/site-deploy --configure-pages.

The documentation a deploy publishes

The build writes /docs/index.json last, with scripts/lib/docs-index.mjs. It reads the pages the build just produced, the composed surfaces' among them (the crate's reference at /rustdoc), so it lists no route of its own. Each page has its route, its title and its description; an entity page is also joined to its object, and the index carries the /build.json identity of the build. The build fails when a route that site/data/publication.toml publishes, or an entity page, was not built. After the site serves the pushed commit, the workflow runs scripts/pages verify-docs --commit <sha>. The public index must name that commit, and each of the following must answer 200: the documentation root, every route the catalogue publishes a kind at, one page of every entity kind, /openapi.json and /build.json. Run the same command by hand against any deployed commit. test/cases/500_docs_index.sh holds the generator and the probe to their refusals against a served fixture.

The UI a deploy publishes

The site carries majordomus ui at /ui/ as a snapshot read at the published commit (ADR 0141, docs/MAJORDOMUS_UI.md). The workflow's ui-snapshot job builds the executable from that commit and runs majordomus web report ui-snapshot; it runs beside the rustdoc job, and the deploy job waits for both. After scripts/pages check and before the push, the deploy job places the snapshot at the mount its own surface.json declares. It refuses a snapshot that is absent, one read at another commit, and a mount the site itself already built.

The Cockpit is published the same way at /cockpit/ (ADR 0147). The same job runs majordomus web report cockpit-snapshot after the UI's, because the Cockpit's snapshot reads first what the UI's links to and then leads those links to its own pages; both directories are uploaded once that step is over. The deploy job places every snapshot the model declares at the mount the topology gives it, and after the push scripts/pages verify-ui --surface cockpit asks the public site for it.

The snapshot is a generated surface, known only where its producer ran, so it is not in the committed topology and scripts/site-build does not compose it: a local build of the site has no /ui/ unless that step is repeated by hand. The site's checks do not judge its pages.

That a publication holds it is declared once, in .ai/repo/ci/pages.yaml under deploy.snapshots: the served surface it is a snapshot of, where the producer writes it, the producer, and the attribute its pages state their commit with. The mount is the topology's mount of that surface, and scripts/pages snapshots prints the resolved rows. Three readers hold a publication to that entry (project.the-published-ui-is-the-trunks):

  • scripts/site-deploy refuses a site whose tree has no snapshot at the mount, one read at another commit than HEAD, or one that declares another mount, and says how to place it. A deploy from a terminal therefore cannot publish the site without /ui/. --release-metadata changes two files of what is already served and is not asked.
  • scripts/pages verify-ui asks the public site, once it serves the deployed commit: /ui/surface.json names that commit, the landing page's banner states it, /ui/snapshot.json records it and says what ended the reading, and every page the record lists answers 200. It prints how many pages it asked for, how many --max-pages left unasked, how many the snapshot itself left out and what stopped it. Exit 10 is a UI that is missing, another commit's or not whole; 12 is a site that never answered. The deploy's the UI it publishes step runs it and is not continue-on-error.
  • The pages-live gate asks the same probe of the published commit (section 2c of scripts/ci/pages-check), so the live /ui/ is held to the trunk within the window the site is.

test/cases/2983_the_published_ui_is_the_trunks.sh holds all three against a served fixture.

The release the site offers

Which release is the latest is one fact with three public surfaces: the homepage (the navbar badge, the hero and the trust strip's data-release), /releases/latest.json, which the installer resolves, and the newest GitHub release. A release reaches them in that order — GitHub, then latest.json from the release's publish job (scripts/site-deploy --release-metadata, ADR 0131), then the pages from the build of master that job asks for — and the record that archives it lands later still, through its own pull request.

So the pages do not read the record. scripts/site-build derives the release the site offers as the newer of the record's projection (site/data/registry/distribution.json) and the releases/latest.json that origin/gh-pages serves, read from git and named in build.json (release.source, release.published_from); base.html sets offered from it and every page reads offered. When the published release is the newer, the build serves its two documents, so the metadata beside the pages names what they offer. scripts/site-build --release prints the derivation alone.

It is checked three times: scripts/site-check (section 17b) refuses a build whose homepage, /build.json and /releases/latest.json disagree, or whose two committed projections of the latest release do; the deploy's the release it offers step runs scripts/pages release-live once the site serves the new commit; and scripts/ci/pages-check asks the same probe as section 5 of pages-live, on the schedule and at finish. A disagreement younger than deploy.release_window (.ai/repo/ci/pages.yaml) after GitHub's release is a publication in flight; older, or a site offering a version GitHub has not published, is a failure. The rule is project.the-site-offers-the-release-it-published.

When a deploy does not happen

Publication is two steps owned by two parties, and either can be missing while everything visible stays green. This repository pushes gh-pages; GitHub's own "pages build and deployment" turns that branch into the served site, and it has its own status, which can be errored for a branch that was pushed perfectly — it was, on 2026-09-10 at 17:35, and the site served the previous commit for 27 minutes with nothing red anywhere.

The workflow's own conclusion does not answer the question either. pages.yml serialises its runs and never cancels the one in flight (cancel-in-progress: false; cancelling it starved publication on 2026-09-12, when every superseding run was itself superseded), but GitHub drops an older pending run for a newer one, and a cancelled run is not a failure: on 2026-09-09/10, while a push still cancelled the run in flight, fourteen of seventeen cancelled runs had already pushed gh-pages before they died, and no reader could tell them from the three that had not. So the run says which it was — an annotation and a line in its summary — and cancellation itself cannot strand the site, because a pending run is only ever dropped for a newer run of the same workflow, and the run in flight always finishes: the chain ends in a run that publishes or in one that goes red.

What is left stale is a run that never published and whose successor then failed to build. Nothing in the deploy can report that, because by then there is no deploy. The reader is the pages-live gate (scripts/ci/pages-check), which runs on every full validation and asks four things: the published commit is on master, master has not moved past it beyond the deploy window (30 minutes, sized from a 25-minute Actions queue observed on 2026-09-10), the live site serves what gh-pages published, and GitHub's own build of that branch did not error. A section of its own asks a fifth: the reference at /rustdoc/ is whole and was built from the commit the site serves, by the same scripts/pages verify-rustdoc the deploy runs. Another asks the same of the UI's snapshot at /ui/, by scripts/pages verify-ui. The trigger paths it uses to decide whether a publication is owed are scripts/pages paths — the same list that is the workflow's own paths: — so a commit that cannot change the site does not owe a deploy.

The site answers at its own domain, and one file keeps it there. site/static/CNAME names the host; the build copies it into site/public like any other static file and scripts/site-deploy publishes it with the rest. GitHub Pages reads that file on every deploy and unsets the custom domain the moment a published tree stops carrying it — which would take the advertised install command down with the site, so scripts/site-check refuses a build whose CNAME and base_url disagree. Those are the two places the host is written, and there is no third: base_url is what every generated link and every check resolves against, CNAME is what GitHub reads. The project address the site was published at before, under korczis.github.io, redirects here.

Sync guarantee

scripts/derive-check is the one read-only gate; it composes the generators' own checks and names every stale artifact before it exits:

  • majordomus generate --check derives every projection of the registry, the schemas, the policy and the index again and compares byte for byte: docs/generated/**, share/allow/**, the provider bootstraps, site/data/registry/registry.json.
  • scripts/generate-site-data --check regenerates into a temporary directory and diffs every generated file, docs/SITE_CLAIMS.md and docs/PLAN_STATUS.md against the committed ones; source.json is compared by its input hash, which covers every canonical file the generator reads, docs/generated/registry.json and docs/generated/openapi.json among them, so a change to the executable's code moves the site's hash.

The chain is tested edge by edge. apps/majordomus-cli/tests/projections.rs proves that a descriptor changed in code moves the OpenAPI document, the reference, the manifest and the site dataset, and only the sections that project it (an exposure removed moves the routes and the coverage, not the tools; a description moves the descriptor everywhere), and that the dataset's tools are the MCP surface's and its routes the document's. test/cases/95_executable_reference.sh proves that a capability added to the manifest gains its route, its index entries and its links from the site generator alone, that one removed loses them with no orphan left, and that a manifest naming an unknown module, a missing source file or another schema is refused. scripts/site-check proves the rendered side: every module and capability of the dataset has its page, no page exists without its entry, the CLI, MCP and benchmark pages render every command, tool, target and baseline, every capability page links its module and its operation, and no template, generator or navigation file names a capability by hand. test/cases/11_site_derivation.sh changes the version, a profile, a principle, a policy value, a claim and a document heading in a scratch copy and asserts each change appears in the derived data; test/cases/10_site_data.sh proves a missing path or an untested guaranteed claim fails the generator; test/cases/51_derived_artifacts_committed.sh proves the committed derived files match their sources and that none embeds the commit it lands in.

The commit a page was built from is not a derived file's business: scripts/site-build writes site/data/build.json (for the footer, the architecture page and the roadmap's "observed at") and site/static/build.json, served at /build.json, with the commit, that commit's time (commit_time, never the build's clock), whether the tree was dirty, the site's input hash, the registry and index fingerprints, and under surfaces each composed surface with the commit its producer declared; scripts/site-check proves the served file names HEAD and the data the site was built from, and that every composed surface was built from that same commit, so a deployment can be verified from outside against the commit that was meant to deploy.

Local development

npm ci                      # Tailwind, Flowbite, Alpine, Mermaid — pinned
brew install zola           # or the release binary; CI pins 0.23.4
just derive                 # every committed derived artifact, in order (scripts/derive)
just derive-check           # is every committed derived artifact current? writes nothing
just test                   # the gates CI would run for this working tree (scripts/ci/run-plan)
scripts/site-serve          # generate, build, serve the built pages at http://127.0.0.1:1111/
scripts/rust-check --doc    # the crate's reference, target/web/rustdoc — site-build composes it and refuses without it
scripts/site-build          # production build into site/public/
scripts/site-check          # the static checks CI runs
scripts/site-probe          # the browser-measured checks (needs Chrome; --quick for one page per section)
SITE_PROBE_JOBS=4 scripts/site-probe   # as CI runs it: four routes at a time

The one workflow after a change to anything canonical — a Rust declaration, a document, a claim, a command's semantics — is: just derive, review the diff of the derived files (it is the change, seen from every projection), just derive-check, commit both. Forgetting the first step is caught by the third, and by CI.

Adding new canonical data

Add the field to the file that owns it, read it in scripts/generate-site-data, render it in a template, and extend test/cases/11_site_derivation.sh with the change-and-assert pair. Commit the regenerated site/data/generated/. Do not add a field to the JSON by hand.

Adding new site components

Define a Tera 2 component in site/templates/components.html, using Flowbite markup from the component's documentation page, with mobile classes first. Call it as {{ <name attr="…" /> }}. If it needs a class Tailwind cannot see in the templates, it does not belong here.

What must never be edited manually

docs/generated/**, share/allow/**, site/data/registry/**, site/data/generated/**, site/content/{docs,profiles,guarantees,supervises,commands,doctrines,use-cases,applications,plan,why,registry}/**, site/content/render-test.md, docs/SITE_CLAIMS.md, docs/PLAN_STATUS.md, site/static/app.css, site/static/js/**, site/static/images/**, site/static/openapi.json, site/static/build.json, site/data/build.json, site/public/**, CLAUDE.md, AGENTS.md. Change the canonical file; just derive; rebuild.

Claims this document defines

Each links to its own page with implementation, test and where it is used.

Searches the titles and descriptions of every page this site publishes.