Skip to content

The Docs Platform β€” ADR-112

The Docs Platform β€” ADR-112

One ruling. It selects the technology for a new deployable, apps/descent-docs, and touches nothing that ships to a player: no wire format, no quantity, no game-route dependency. It is an ADR rather than a scaffold-and-go because P8 forbids mandating a technology without recording its seam and its exit cost at adoption time β€” and a documentation platform is precisely the kind of adoption whose exit cost gets discovered instead of recorded, three custom plugins too late.


Status: Accepted Β· Date: 2026-08-08 Β· No ADR is amended or superseded Β· interacts with 110, 111

Context

The architecture corpus is ~60 Markdown files with five lints and a habit of adversarial review. It is read today through GitHub’s renderer, which costs nothing and does nothing: no search, no cross-file Q-ID resolution, no way to render βŠ™ provenance inline, and no pipeline between the corpus and a reader that could enforce anything.

Two planned capabilities decide the shape of the platform:

  1. The Q-ID Registry Resolver β€” an MDX/remark plugin that turns every Q-012-style citation into a live link carrying the quantity’s value, status and provenance from Quantity_Registry.md, so a stale number in prose becomes a build error rather than a discovery.
  2. The schema scrubbing pipeline. ADR-111 closed the core engines; the corpus cites their internals β€” ceiling names, enforcement seams, fuzz-target shapes. Publishing the docs anywhere less private than the repository requires a build stage that scrubs or gates that material, and it must run inside the site build so an unscrubbed page cannot be produced by accident.

Both are content transforms, which is what makes the platform choice mostly a choice of plugin substrate.

Decision

  1. Astro (7.2.0 at adoption) is the site framework, Starlight (0.41.7) the documentation theme, Pagefind the search β€” the latter arriving as Starlight’s own dependency (pagefind ^1.5.2), so it is deliberately not declared separately: one declaration, no version pair to drift.
  2. Static generation only, zero JS by default. The site is a rendering of a Markdown corpus; there is no server state, and every kilobyte of hydration is unearned. Pagefind fits the same constraint: it indexes at build time and serves static fragments, so search adds no server either.
  3. The corpus in docs/ remains the single authoritative source (P6). The site renders it; it never forks it. The scaffold ships with Starlight’s template content as placeholder, and the corpus-ingestion step β€” rendering docs/ through the site rather than copying into it β€” is explicitly future work, because a copy that drifts is the exact W6 defect this corpus keeps a name for.
  4. The app lives at apps/descent-docs, a standalone pnpm project pinned to the same pnpm@11.20.0 by packageManager and guarded by the same preinstall check as the VTT client β€” ADR-110’s installer discipline extended to a second app rather than re-decided.

The five questions (tech-stack-currency.md Β§5)

  1. Single-implementation / single-source: preserved β€” clause 3 is that property stated for content. 2. Structural boundary: the unified/remark plugin API is typed and versioned; a misuse is a build error in the site, never a change to the corpus. 3. Loud failure: an Astro build failure is a red CI job; Pagefind indexing nothing is visible as an empty search UI in the same PR preview. 4. Deployment profiles: emits static files, which is the one artefact every profile can host. 5. Stable or new: Astro 7 is the stable line (latest, with 4.x relegated to a legacy tag); Starlight is 0.x and gets the Kobalte treatment β€” pinned exact, changelog read on every bump.

Exit cost β€” recorded at adoption, because that is when it is cheap (P8)

High, and asymmetric across the three layers:

  • The content is free to leave. It is Markdown in docs/, owned by the corpus rules, and clause 3 means the platform never becomes its home. Whatever replaces Astro renders the same files.
  • The remark/rehype plugins are portable in principle. The Q-ID resolver and the scrubber’s transform logic target the unified AST, which Docusaurus, Next MDX and every serious Markdown pipeline also consume. Written as plain unified plugins with the Astro-specific wiring kept to config, they survive a framework exit as libraries.
  • Everything else does not. Routing, theming, Starlight’s component overrides, content collections, the Pagefind integration and β€” critically β€” the build pipeline the scrubber hooks into are Astro-shaped. Retracting this decision means rewriting the rendering and routing pipeline on another framework (Docusaurus, Next.js, VitePress), and re-verifying the scrubbing gate end-to-end on the new pipeline, because a scrubber that silently stopped running is a disclosure incident rather than a build bug.

The mitigation is a rule, stated here so it binds the first plugin written: custom logic lives in framework-agnostic unified plugins (and, for the scrubber, a stage that can also run as a standalone CLI over the output directory); astro.config.mjs is wiring only. The seam registry row in upgrade-and-supersession.md Β§4 carries this as the swap point.

Alternatives Considered and Why Rejected

  • Status quo β€” GitHub’s renderer. Free, and does nothing: no search, no resolver, no scrubbing pipeline, which are the point. Rejected on capability, not on cost; it remains the fallback if the platform is ever retired.
  • Docusaurus. The closest capability match, and React end-to-end. This workspace has kept React out of every frontend it owns (frontend-guidelines.md Β§2.3 forbids proposing it for the client; Rete.js was chosen elsewhere because it is framework-agnostic), and a docs site is not the place to adopt a second UI framework’s ecosystem, hydration model and upgrade cadence.
  • Next.js. A server runtime and a React app for a corpus that is static files; almost every capability it adds is one this site must not use.
  • VitePress. Genuinely light and fast, but Vue-flavoured Markdown rather than MDX and a thinner theme/plugin surface for the resolver work; choosing it would trade the primary requirement for a smaller dependency tree.
  • mdBook / plain SSG (Eleventy, Hugo). No MDX substrate for the resolver; custom work would start from raw Markdown parsing, which is the expensive half of both planned plugins.

Consequences (including negative)

  • A fourth Node toolchain surface to keep current (client, tools, CI, now docs). Astro majors have shipped roughly yearly; each is a deliberate bump of this app alone, and the register row carries the trigger.
  • Starlight at 0.x means minor versions may break β€” accepted with the exact pin, and it is the likeliest source of routine maintenance here.
  • The scrubbing pipeline being inside the site build couples a security control to this framework β€” that is the largest single component of the exit cost, named above, and the standalone-CLI mitigation exists specifically for it.
  • Until the corpus-ingestion step lands, the site shows template content, and nothing the platform serves is authoritative. Stated so the scaffold is not mistaken for a published corpus.

Rights-holders (ADR-079)

None. The platform renders documents this repository already carries; no new class of data about any person is retained.

Enforcement

  • docs-platform-ci.yml builds the site on every change to apps/descent-docs/** β€” the build is the gate, with the workflow-level shell: bash (pipefail) discipline from the 2026-08-06 audit.
  • packageManager + the preinstall guard enforce the installer, exactly as ADR-110 does for the client.
  • The corpus lints are untouched and remain the authority over docs/ β€” the platform adds a renderer, never a second set of rules.
  • The scrubbing gate, when it lands, needs its own enforcement point (an assertion over the built output, not the pipeline’s presence) β€” named here as an obligation so its ADR cannot forget it.

Technology currency

Checked 2026-08-08 against npm dist-tags, read directly: astro 7.2.0 is latest (the 4.x line is tagged legacy; the alpha/beta tags predate 7.0 and are stale); @astrojs/starlight 0.41.7 is latest and peers astro ^7.0.2; pagefind 1.5.2 is latest and is a direct dependency of Starlight at ^1.5.2, which is why it appears in no manifest of ours. Node engine floor is 22.12; the workspace runs Node 24 in CI.