0071 — Four-tag version filter: Live, Current, Release, Future¶
Status¶
Accepted.
Context¶
The docs site has carried a two-tag phase vocabulary since the tagging system landed: Launch (target state) and Current (shipped state). Pages declare one or both in YAML frontmatter; the MkDocs Material tags plugin enforces the allowed set and produces an auto-generated tag index at tags.md.
Two-tag vocabulary worked while the docset was small and the doc philosophy single-axis (prescriptive target with inline status callouts). It now strains under three pressures the docset has grown into:
- The shipped surface is no longer monolithic. Some shipped behaviour has been verified through gameplay; some has been verified only through code reading. Readers (players, ops, support, engineers) ask different questions of those two slices. A single
Currenttag conflates them. - The roadmap surface is no longer monolithic. Some prescriptive design is a firm 1.0 commitment with an Accepted ADR and a delivery slot; some is exploratory or post-launch. Calling both
Launchmakes the launch commitment indistinguishable from aspiration. - Per-page maturity is no longer monolithic. A single feature doc routinely covers shipped mechanics, a launch-target refinement, and a post-launch extension. The two-tag model forces these into one bucket per page; readers see content out of their lane with no in-page filter.
Three methodology drafts were produced and synthesized (A: classification rigor; B: automation; C: reader experience). The synthesis lives at /Users/mrathbone/.claude/plans/version-tag-methodology-SYNTHESIS.md (working artifact, not part of the published site).
Decision¶
The phase-tag vocabulary becomes a four-tag maturity ladder: Live, Current, Release, Future.
| Tag | Definition |
|---|---|
Live |
Shipped to the gameserver and verified by direct gameplay observation, with the observation recorded in FINDINGS.md within the last 90 days. |
Current |
Shipped on main. Code path reachable end-to-end. Gameplay verification absent or stale. |
Release |
Not yet shipped. Committed to the 1.0 launch via a non-superseded Accepted ADR, the launch roadmap, or an explicit launch-commitment line. Design must be settled. |
Future |
Post-launch, aspirational, exploratory, or design-in-flight. Residual — anything not Live, Current, or Release. |
Filter is a cumulative maturity ladder¶
The reader's selected mode is cumulative, not strict-equality:
| Mode | Shows sections tagged… |
|---|---|
Live |
Live only |
Current |
Live + Current |
Release |
Live + Current + Release |
Future |
all four |
Frontmatter still records strict tags — a page tagged only Release does not gain Current in its frontmatter. The cumulative behaviour is a runtime UI rule, not a tagging rule. The linter validates strict equality on the authoring side.
Section-level markers, not just page-level¶
Pages may carry multiple tags in frontmatter. Inside a page, sections whose maturity diverges from the page's primary lane get an inline marker, canonical syntax:
## Stellar engineering {.version-tag data-version="future"}
A <div class="version-tag" ...>…</div> wrapper handles non-heading content (tables, paragraphs). Unmarked prose inherits the page's frontmatter tags — only divergent sections need a marker.
Multi-value: a section that serves more than one lane uses space-separated tokens in the data attribute, e.g. current release.
Real-time topbar filter¶
A four-chip segmented control in the Material topbar (between the title and search/theme cluster) lets the reader choose mode. Switching is instant — no reload — and applies to:
- the left-nav (out-of-mode pages dim, not hide; still clickable);
- the body (out-of-mode sections fade out over 120ms; in-mode sections fade in);
- the right-side TOC (out-of-mode anchors hidden);
- in-site search (out-of-mode results dimmed, not removed).
Mode persists per-reader via localStorage. ?mode=… URL params override per-view without writing to storage. A ?show=all query forces the per-page "Show all" override.
Composition with inline ✅ 🚧 📐 🐛 status markers¶
The four inline markers stay in FEATURES/ per existing convention. They describe code-vs-spec drift; tags describe release-timeline bucket. They are orthogonal but constrained — the linter enforces this matrix:
| Tag \ Marker | ✅ |
🚧 |
📐 |
🐛 |
|---|---|---|---|---|
Live |
OK | warn | error | warn |
Current |
OK | OK | error | OK |
Release |
warn | OK | OK | warn |
Future |
error | error | OK | error |
Promotion of a Current section to Live requires a verification artifact in the FINDINGS.md Gameplay verifications table.
Meta files are exempt¶
README.md, CONTRIBUTING.md, GLOSSARY.md, CLAUDE.md, AISPEC.md, FINDINGS.md, DECISIONS.md, and tags.md carry no version tag and are always visible regardless of filter selection. The linter skips them via an allow-list.
Toolchain — enforced by lint, not convention¶
scripts/tags/scan_tags.py— repo-state report (frontmatter tags, body markers, coverage gaps).scripts/tags/lint_tags.py— 17 numbered rules T001–T017, autofix for trivial ones. Wired into pre-commit and the Cloudflare build pipeline.scripts/tags/gen_page_tags.py— MkDocs build hook that emits the page-tag JSON map consumed by the JS filter.
Consequences¶
Authoring¶
- Every non-meta
.mdpage must carry frontmattertags:from{Live, Current, Release, Future}. The linter rejects missing/invalid values. - A page may carry one to three tags. Carrying all four is rejected — the page must be split (the split pattern is already documented in
CONTRIBUTING.mdfor the two-tag Launch/Current divergence). - Section-level markers (
{.version-tag data-version="…"}) are added on headings whose maturity diverges from the page's primary lane. Unmarked prose inherits page tags. FEATURES/tagging is mechanically derivable from inline✅ 🚧 📐 🐛markers via the §1.2 mapping. Reviewers do not classifyFEATURES/pages by hand.- ADRs are tagged by the maturity of the decision's implementation, not by ADR status.
ProposedADRs areFuture.Accepted+ shipped isCurrent(orLive);Accepted+ not shipped isRelease. Superseded ADRs inherit the superseder's tags plus a.version-supersededvisual treatment.
Build pipeline¶
mkdocs.yml'stags_allowedbecomes[Live, Current, Release, Future].- The build registers a
hooks:entry forgen_page_tags.pyto inject the per-page tag JSON used by the client-side filter. theme.custom_dir: overridesactivates so the page-tag JSON can be injected into every rendered page.
Migration¶
Launch is mechanically renamed to Release across all currently-tagged docs (~180 files). Current is unchanged. The Live and Future rollouts are separate substantive passes that follow.
Reader UX¶
- First-time visitor lands in
Currentmode (most representative of what readers want). - Out-of-mode pages dim in the left nav with a lane-colored dot indicator, but remain clickable. Clicking lands on an empty-state callout with a mode-switch button.
- Partially-filtered pages show a sticky banner ("Showing 7 of 11 sections") with a per-page "Show all" override.
- Print forces all sections visible, with inline lane chips prefixing each.
- Search returns all results; out-of-mode results are dimmed and clicking auto-reveals the matching section.
Out of scope for this ADR¶
- Per-mode static builds — rejected, multiplicative cost of pages and search indexes is not justified.
- Server-side mode persistence — rejected, pure client-side
localStorage. - Automatic mode detection from referer/UA — rejected, magic mode detection produces magic confusion.
- A fifth
Deprecated/Removedtag — rejected,CLAUDE.mdalready forbids historical narrative. - Mode-cascading frontmatter (e.g. a
Releasetag implyingCurrent) — rejected; cumulative semantics live in the runtime filter, not in the tagging.
Related¶
../tags.md— public tag index and vocabulary reference.../CONTRIBUTING.md§Phase tags — author-facing tag rules.../FEATURES/README.md— inline status convention that composes with version tags.- ADR-0066 — doc philosophy compliance (prescriptive-voice, no historical narrative) — informs the meta-files-exempt rule.