ADR-0009: A VitePress docs site on GitHub Pages
- Status: Accepted (supersedes the "no docs site" clause of decision 14 in ADR-0001)
- Date: 2026-09-14
- Issue: #143 (T8.4 docs site; decided with the owner 2026-09-13, recorded here in #171)
Context
ADR-0001 decision 14 fixed the docs as "README + docs/*.md, no docs site": one page per concern, read on GitHub, nothing to build. docs/ then grew past twenty pages that cross-link each other by relative path and anchor (js-gate.md, release-ladder.md, native-e2e.md, the ADR folder …), and two problems showed up that plain markdown files cannot answer. There was no search across them and no entry point that ordered them, so a reader had to know the filename first. And nothing checked the links: a renamed page or a moved section left dead relative links that GitHub renders as ordinary text, which is how the docs drifted between PRs. Both are properties of the corpus, not of any one page, so they needed a build step over docs/ — which decision 14 had ruled out before docs/ existed.
Decision
The same markdown files are also a VitePress site over docs/, and bun run docs:build is the link audit.
- Generator = VitePress (
docs/.vitepress/config.mts,bun run docs:dev/docs:build/docs:preview), chosen because it reads the existingdocs/tree in place: relative.mdlinks and anchors are rewritten for the site, so every link keeps working on GitHub too. Local search, dark mode and mermaid (vitepress-plugin-mermaid) come with it; the prose is unchanged. - The build is the link audit.
ignoreDeadLinks: falsemakes any dead relative link failbun run docs:build, and theDocsjob in.github/workflows/ci.ymlruns it on every PR as a required check. The narrow exception list in the config (the AtlaslocalhostURL, theNNNN-titleplaceholder in the ADR template,template-init.mdonce init removed it) is the only way past it. - Publishing = GitHub Pages from
main..github/workflows/docs.ymlbuilds and deploys on push tomainfor paths that can change the site;pages: writeis granted to thedeployjob only, never to the job that runs repo code. Pages → Source: "GitHub Actions" is set bybun run repo:settings:apply --only pages. - Nothing in the site names the template. The title is derived from
package.jsonname(whichbun run initrewrites), the base path and repo links from the environmentdocs.ymlsets (DOCS_BASE,DOCS_REPO). So the site ships as-is into a project created from the template andbun run inithas no rewrite rule for it. - Structure is config, not front matter. The landing page is
docs/index.md; the sidebar groups (Start here / Pipeline / Performance / Testing / Decisions) are listed in the config, and the Decisions group is generated from the files indocs/adr/so a new ADR needs no config edit (#171).
Consequences
- Decision 14 of ADR-0001 keeps everything else it says; only "no docs site" is replaced. "One page per concern under
docs/, read on GitHub" still holds — the site is a second rendering of the same files, never a second copy of the content. - Links are now enforced. A renamed page or a moved anchor turns the
Docscheck red on the PR instead of rotting silently, and links must stay relative with the.mdextension to satisfy both renderings (Conventions → Docs). - Two markdown quirks are now load-bearing:
{{ }}and bare<tag>-looking text outside code spans are parsed by Vue / HTML, and aREADME.mdinside adocs/subfolder needs arewritesentry (adr/README.md→/adr/) to be the folder index. - The template carries VitePress as a devDependency and two more CI surfaces (the
Docsjob anddocs.yml); a generated project inherits both and gets its own Pages site for free, at the cost of a repo setting it must enable. docs/index.md's quick-start action and the sidebar adapt tobun run inithaving deleteddocs/template-init.md, so the generated project's site has no dead entry.
Rejected
- Keeping "no docs site" — accepts no search, no ordered entry point, and no link checking on a corpus this size; the drift this ADR reconciles is the evidence.
- A separate
website/with duplicated prose — two copies to keep in sync, and the GitHub rendering (what an agent and a PR reviewer read) becomes the stale one. - Docusaurus / Nextra — heavier, and both want their own docs tree and front matter; VitePress reads
docs/as it already is. - A link checker without a site (lychee, markdown-link-check) — solves the audit but not the search or the entry point, and would be a second tool to keep aligned with the docs.