Fumadocs documentation site
Status: frozen for implementation on 2026-09-26 (operator decisions below). Remove this specification when the checks below run in hooks and CI and AGENTS.md, the README, and the effect-house overlay describe the new layout.
Goal
Hand-written documentation is written where its scope is: repository-wide pages in the root content/, and each app, package, or tool's pages in its own content/. apps/docs only renders them: it holds the Fumadocs site code and no authored pages. The pages are MDX with mdxcn components, published as one static site.
The root docs/ folder stops being where docs are written. It holds only generated artifacts of any file type: Markdown rendered from the Fumadocs pages at the paths agents and GitHub readers already use, and outputs of other generators. Every file in it names its generator.
Vocs is removed.
Operator decisions (2026-09-26)
- Fumadocs replaces Vocs. The site is a TanStack Start SPA (
create-fumadocs-apptemplatetanstack-start-spa), scaffolded with the official CLI and--pm bun, not hand-written from memory. - Hand-written docs are MDX, written in a
content/folder at the scope they describe: the repository root for repository-wide pages, and the workspace for a workspace's pages. They use mdxcn components where a component explains better than prose.apps/docsrenders them and holds no content. - Root
docs/contains the Markdown artifacts generated from the Fumadocs pages. - The Fumadocs agent instructions (
https://fumadocs.dev/llms.txt; append.mdxto any docs URL for Markdown) are the reference. Read the relevant page before using a feature, and never pin an older version.
Lead decisions (conservative defaults)
- What moves. Every hand-written page moves with
git mv, keeping its content and history; only frontmatter (title,description) and links change. Cross-cutting pages (docs/*.md,docs/system-walkthrough.mdx,docs/specs/*.md) go tocontent/<section>/. A page about one workspace goes to that workspace'scontent/: the guidespackages/domain/src/receipt/README.mdandpackages/domain/src/placements/README.mdbecomepackages/domain/content/receipt.mdxandplacements.mdx.README.md,STATE.md,AGENTS.md/CLAUDE.mdfiles,.agents/skills/**, and.githubtemplates stay where they are: harnesses and GitHub read them in place. - Generated paths. Cross-cutting pages keep today's read paths (
docs/system.md,docs/specs/<name>.md, …), so links fromAGENTS.md,STATE.md, the effect-house overlay, and the homelab projects-tier extension keep working. A workspace's pages are generated todocs/<apps|packages|tools>/<name>/**.md. Moved per-workspace pages change path, so the move rewrites every link to them, and the build's link check fails on any stale one. - Each generated file is marked. It begins with a do-not-edit marker that names its MDX source. A check fails when a generated file differs from a fresh render (drift), and also when someone edits it by hand. Hooks and CI run that check.
- Specs. A spec's source is
content/specs/<name>.mdx.docs/specs/<name>.mdis its generated read path. The spec convention inAGENTS.mdandtools/conventions/src/check.tsmoves to the source path. A worker bound to a spec edits the source and reads either one. - Where documentation is written (operator decisions, 2026-09-26). Two kinds of source, one site:
- Repository-wide documentation lives in
content/at the repository root (operator decision, 2026-09-26), and its folders are the site's sections:system/,architecture/,operations/,specs/,testing/,model/. The formal modelsdocs/model/authority.alsandcontexts.cmlmove tocontent/model/, and the site renders or links them from there. - Each workspace (every
apps/*,packages/*, andtools/*package) may have its owncontent/folder beside itspackage.json. The site scansapps/*/content/**,packages/*/content/**, andtools/*/content/**. Each workspace gets one section, named from its layout description intools/conventions/src/layout.ts; itscontent/folders are its subsections. A workspace withoutcontent/still gets a section: its generated summary (description, entry points fromexports, constructs), from the same source as itsAGENTS.mdguide. Generated API references belong to their workspace's section: the Placements TypeDoc reference goes underpackages/domain. - Folder names and
meta.jsongive the navigation order; nothing lists pages by hand. Use Fumadocs' support for multiple content sources if its docs confirm it; otherwise generate one content index from the scan. Either way the scan is the only list. content/holds only.mdx,.md,meta.json, and assets. The layout check declarescontent/for every workspace and enforces that.- The exception registry
docs/effect-exceptions.jsonis a tool's input, not documentation, so it moves totools/conventions/effect-exceptions.json. - Update the readers (
tools/scripts/model.ts,tools/conventions/src/{layout,exceptions}.ts) in the same commit. A file that a generator writes may stay indocs/, whatever its type.
- Repository-wide documentation lives in
- Inputs and outputs (operator decision, 2026-09-26). The documentation has two inputs and one producer:
- Inputs:
content/folders (hand-written prose, at the root and in each workspace) andsrc/(code: JSDoc and construct contracts, types, the OpenAPI contract, packageexports, the formal models). - Producer:
apps/docsis the only thing that turns inputs into documentation artifacts. It renderscontent/and calls the extractors that readsrc/(the construct and contract reader intools/conventions, the OpenAPI output ofpackages/http-api, the TypeDoc reference). The extractors stay where they are and export their data;apps/docsowns the rendering. - Outputs: the static site for people, and
docs/for agents and GitHub readers: Markdown at stable paths plusllms.txtandllms-full.txt. content/holds hand-written pages only. No generator writes intocontent/, so a file there is always authored, and a file indocs/is always generated. Generated pages such as the construct catalogue and the API references are produced byapps/docsstraight into the site anddocs/.- Harness instruction files are not documentation artifacts: the generated part of each
AGENTS.mdstays in place.just guides writewrites it from the same extractor function that produces the workspace summary on the site, so the two cannot disagree.
- Inputs:
- mdxcn. The existing graph components in
apps/docs/components/mdxcn(withprovenance.json) move into the Fumadocs app's MDX component map, under their existing license and provenance. New mdxcn components are installed through its registry CLI, never copied by hand. - HTTP API reference. The Vocs site had an HTTP API page generated from the OpenAPI contract. Keep it: generate it with the Fumadocs OpenAPI integration from the
packages/http-apiOpenAPI output, so the page stays derived from the contract. - Start section.
README.mdandSTATE.mdstay in place. The Start section includes them as they are, and nothing is generated for them. - Drafts. Untested drafts of the link plugin, the generator and the check from the first pass are in
/srv/share/projects/vektorprogrammet/fumadocs-drafts-0926/. They assumecontent/docs/and a rootmodel/folder, so retarget them. - Hosting. The GitHub Pages workflow
.github/workflows/docs.ymlkeeps publishing frommain, with the new static output directory, and still includes the Placements API reference. No other deployment is created.
Done when
just docs buildbuilds the static site from a clean checkout. Every moved page renders, has atitleand adescription, and appears in the navigation frommeta.jsonin today's section order: Start, the system pages, Operations, Specs, Testing. Links between pages resolve; the build fails on a broken internal link.just docs generatewritesdocs/**: cross-cutting pages at their current paths, and each workspace underdocs/<apps|packages|tools>/<name>/.just docs checkfails on a drift and on a hand edit (negative controls for both), and runs injust check, the pre-commit hook, and hosted Checks. 2a. Addingcontent/intro.mdxto a workspace that had none makes that page appear in the workspace's section and indocs/, with no configuration change (test). Every workspace appears in the navigation.- Every file in
docs/is the output of a registered generator, and a hand-authored file there fails the layout check (negative control). The generators are listed in one place, with the output path of each. 3a. No file under anycontent/folder is written by a generator. A generator that writes there fails the layout check (negative control). rg -l vocsfinds nothing outside git history and the changelog.site.ts,sync-pages.ts, andvocs.config.tsare gone.- The mdxcn graph components render on the system walkthrough page.
- The docs workflow publishes the new build to Pages on
main: the first run after landing succeeds, and the site answers. just checkand the hosted Checks and Tests workflows pass.
Documentation that must change
docs/ stops being where docs are written, so every instruction that tells a writer to edit a file there changes to the source in the right content/ folder (the repository root for repository-wide pages, the workspace for its own), and says that docs/ is a generated read path:
AGENTS.md(Authority, the spec convention, the documentation rules),README.md,apps/docs/AGENTS.md,tools/conventions/AGENTS.md,STATE.md(Lead handoff), and the effect-house overlay.- Generated guides and the layout description in
tools/conventions/src/layout.ts. - Outside this repository: the cross-project rule "specs live in
docs/specs/" in/srv/share/projects/homelab/docs/PROJECTS.md(lines 186-193 and 433). The lead changes it in the homelab repository to say this: a repository that generates its docs keeps the spec source where its docs site says, and still serves the read pathdocs/specs/.
Non-goals
A new docs domain, provider, or search backend beyond what the template configures. Rewriting page content. Moving STATE.md, the AGENTS.md files, or skills.