ADR 0015: The documentation website is Astro Starlight, built from the repository's Markdown
- Status: accepted (WP-156)
- Date: 2026-10-26
- Amends: ADR 0007 decision 6 and its WP-81b site layout (the Pages index), ADR 0009 decision 3 (a second, separate Bun project)
Context
Section titled “Context”GitHub Pages (https://me.xantiagoma.com/xtandard-image/, the custom domain of xantiagoma.github.io) served a single link page, the Scalar API page and the JSON Schema files (WP-81b). The documentation itself, about 7,600 lines of Markdown in docs/, the root files and clients/*/README.md, could only be read on GitHub: no search, no navigation between documents, and nothing a user can bookmark outside the repository view.
Constraints:
- The Markdown in the repository stays the single source of truth and keeps rendering on GitHub, where it uses relative links (
CONFIG.md#x,../README.md,adr/0006-….md,../deploy/). No document moves or gets a copy to maintain. /schema/v<N>/config.schema.jsonis the config schema’s$id. SchemaStore and every editor modeline depend on it, so the schema URLs and/api/must keep serving the same bytes.- JavaScript tooling runs on Bun (ADR 0009), and npm packages follow the ADR 0006 allowlist, checked by
scripts/check-ts-licenses.ts. - The TypeScript client’s workspace and its
tsworkflow must not change behaviour.
Options
Section titled “Options”| Option | For | Against |
|---|---|---|
| Astro Starlight | Pagefind search, sidebar, light/dark themes, an edit link and an in-page table of contents built in; Markdown pages with frontmatter; an MIT link-validation plugin (starlight-links-validator); runs on Bun |
A Node toolchain (~290 packages) for one static site |
| VitePress | Small, fast, local search | Vue; no first-party link validation; theming the brand takes more work |
| Docusaurus | Mature, versioned docs | React plus a much larger tree; MDX v3 is stricter with the existing Markdown (raw < and {) |
| MkDocs Material | Excellent search and navigation | Python toolchain next to Bun; the free edition’s licence is fine, but its insiders features are paid |
| Hand-written static generator | No dependencies | Search, themes, TOC and accessibility would all be ours to build |
Decision
Section titled “Decision”- Generator: Astro Starlight in
site/, a separate Bun project with its ownpackage.jsonandbun.lock, like thedeploy/*Node tooling. Exact pins:astro7.4.2,@astrojs/starlight0.42.7 andstarlight-links-validator0.27.0, all MIT, alldevDependenciesof a private package. Astro runs on the Bun runtime (bun --bun astro build).- Not a member of the root workspace. The root scripts fan out with
bun run --filter '*' <script>, so a member namedbuildortestwould run the site frombun run buildandbun run testin thetsworkflow, and the rootbun.lock(installed bytsandclients) would grow by Astro’s tree, sharp included. As a separate project the TypeScript client, the root lockfile and thetsworkflow do not change.
- Not a member of the root workspace. The root scripts fan out with
- Sync, not move.
site/scripts/sync.ts(a pre-build step) copies each file named insite/manifest.tsintosite/src/content/docs/(generated, gitignored) and adapts the copy:- the H1 becomes the page
titleand is removed, with a logo line above it (the client READMEs); the first paragraph becomesdescription;editUrlopens the source file on GitHub; - relative links to files that are pages become site routes under the base, with their
#fragment; absolutegithub.com/…/blob/main/links to such files (the client READMEs use them) become routes too; - relative links to other repository files and directories become absolute GitHub URLs (
blob/main/…,tree/main/…), and images become raw URLs or, fordocs/assets/, the site’s copy; - a relative link to a file that does not exist fails the sync; a Markdown file under
docs/that is not in the manifest fails it too; - GitHub alerts (
> [!WARNING]) become Starlight asides. Fenced code and inline code spans are never rewritten. Long documents are not split: Starlight’s in-page table of contents (H2 and H3) does that job, and the GitHub versions stay intact.
- the H1 becomes the page
- Link checks. The build fails on a broken internal link at three levels: the sync (missing files),
starlight-links-validator(every Markdown link and#fragment, relative links forbidden), andsite/scripts/check-dist.tsafter the build (every localhref,srcandsrcsetin every HTML file, including the sidebar, the home page and links into the static files, and every fragment against the target page’s ids). - Static files stay byte for byte.
scripts/pages-build.shkeeps its$idcheck and now assembles only the non-Astro files (schema/v<N>/…,schema/latest/…,api/index.html,docs/assets/*,.nojekyll) intodist/pages-static/, which is Astro’spublicDir.check-dist.tscompares each of them with the built site. The old link page (docs/pages/index.html) becomes the site page “Config schema and OpenAPI files” (site/content/reference/schemas.md). - Site-only pages are few: the home page (
site/content/index.mdx), the schema reference and the ADR overview (site/content/). Everything else comes from the repository. - Internal documents (
AGENT-WORKFLOW.md,go-standards.md,docs/research/,docs/playground/RESEARCH.md) are published under Project → Contributing → Internals, collapsed, with a banner, and excluded from search. Public documents link to them (FORMATS cites the stack research, CONTRIBUTING the Go standards), so leaving them out would send readers to GitHub mid-page, while search results for users stay free of them. - Base and origin.
siteishttps://me.xantiagoma.comandbaseis/xtandard-imageby default;pages.ymlpasses the origin and base thatactions/configure-pagesreports (SITE_ORIGIN,SITE_BASE), so a domain change needs no code change. The schema$idkeepsxantiagoma.github.io, which redirects to the custom domain. - CI.
pages.ymlbuilds the site with Bun on pull requests that touch the docs or the site (no deploy) and on pushes tomain, where it deploys.task pages:buildandtask pages:devrun the same steps locally.
Licences
Section titled “Licences”bun run licenses in site/ runs scripts/check-ts-licenses.ts site. Beyond the ADR 0006 list it accepts, for this run-only build tree only, the licences deploy/check/licenses.ts accepts for its tools: BlueOak-1.0.0 (lru-cache, sax, common-ancestor-path), CC0-1.0 (mdn-data, data) and MPL-2.0 (lightningcss, the CSS minifier; file-level copyleft, which ADR 0006 allows even when shipped). sharp’s prebuilt libvips (@img/sharp-libvips-*, LGPL-3.0-or-later, one package per platform) is a named exception: it is dynamically loaded, which ADR 0006 allows even in shipped artefacts, and here it only runs during the build. The published site contains only the HTML, CSS and JS that Astro and Starlight (MIT) generate, Pagefind’s index and runtime (MIT), and the repository’s own files.
Consequences
Section titled “Consequences”- The documentation has search, navigation and a stable URL per document, without a second copy of any document.
- A new document under
docs/fails the site build until it gets a place insite/manifest.ts; a new client directory fails it until it gets a sidebar label. - A broken link or anchor in any synced document now fails a pull request, also in documents that had broken links before (the WP-156 build found one in ROADMAP).
- Astro moves fast (7.x at the time of writing); the exact pins and the lockfile keep builds reproducible, and upgrades are deliberate.
- No custom web fonts: the brand fonts (Urbanist, JetBrains Mono) are OFL-1.1, outside the npm allowlist, and the site would otherwise load them from a third-party CDN. The logo lockups are SVG paths and need no font.