Skip to content

Commit e1f1502

Browse files
Docs site information architecture: sections, anchors, and site links (#8)
* Plan the docs site information architecture and the upstream debt Records what PR #7 left open, so it stops living in a chat log. Three items about how the published site is organised and linked: - W-D1, sections in the docs. Done in sidebar.ts with categories over existing doc IDs, not by moving files -- moving them would change every published URL, break every relative cross-reference, and hit the positional numbering trap CLAUDE.md warns about. - W-D2, deep links to headings. Sequenced behind onBrokenAnchors: 'throw', because anchors are currently validated by neither checker: onBrokenLinks covers routes rather than fragments, and the gate skips site-absolute targets outright. Adding anchors first would recreate the hole PR #6 and PR #7 closed. - W-D3, README links to the site rather than to GitHub. Flagged as the item that expands scope: src/engine/ and docs.ps1 have no page to link to, so this means authoring new content, and it is blocked on deciding whether those pages should exist at all. The two companion-repo links legitimately stay on GitHub. Two pieces of upstream debt, both belonging in Docusaurus-Template rather than here, following the precedent of #54 and #55 -- fix upstream, then adopt. Nothing in build/ is locally modified; it is vendored verbatim, which is what keeps Invoke-SetupDocs -Overwrite safe to re-run. - W-D4, the gate's link-parsing gap: a target containing whitespace or ')' cannot match its regex and is invisible to the check. Raised in review on PR #6, verified, recorded in plans/02 -- and never actually filed upstream. Confirmed not live here, so it is correctness work rather than an outage. - W-D5, the vendored gate is 33 lines behind upstream, missing an anchor-slug fix that matches GitHub's hyphenation and a root-resolution fallback. Sequenced before W-D2, since it changes which slugs the gate expects and this repository has em dashes in headings. Ordering is stated at the end, with the reasoning for it. None of this is on the MVP critical path; TODO.md W1 remains the next engine unit. * Implement the docs site information architecture Implements W-D1, W-D2, W-D3, and W-D5 from the plan added in the previous commit, in the order that plan set out. W-D4 is upstream work and is filed against Docusaurus-Template separately. W-D5, first because it moves the goalposts for W-D2. The vendored gate was 33 lines behind upstream, missing an anchor-slug fix that hyphenates the way GitHub does (one hyphen per space, not per run, so '## Phase 1 -- Fixes' slugs with the doubled hyphen the removed em dash leaves) and a root-resolution fallback. Every existing anchored link was checked against both the old and new slug rules before syncing: none changed meaning, so this landed without collateral. build/Test-Documentation.ps1 is byte-identical to upstream again. W-D2. onBrokenAnchors: 'throw' set first, then the anchors. Fragments were the one link class nothing validated -- onBrokenLinks resolves routes, and the gate skips site-absolute targets by design -- so adding anchors before closing that would have reopened the hole PR #6 and PR #7 spent their time on. Twelve README links now point at headings rather than whole pages, each computed with the gate's own slug algorithm and checked against the real heading first. W-D1. Sections come from a manual sidebar naming doc ids, not from folders. Grouping by folder would have moved files, and moving files changes every published URL, breaks every relative cross-reference, and runs into the positional numbering CLAUDE.md warns about. Orientation, Contracts, Delivery, Working on It -- with 04-core before 03-story-graph-kind, which is the reading order the specs assert. Every /docs/engine/<slug> URL is unchanged. sidebar_position is removed from all eight pages rather than left contradicting the explicit order, and 04-core's sentence claiming the sidebar reads it was corrected. W-D3. The three README links to code and tooling now have somewhere on the site to point: guide/engine-package and guide/documentation-site, in their own section because they are contributor-facing rather than specifications. The two companion-repository links stay on the code host, which is correct -- they are other repositories. They overlap CLAUDE.md and agent.md only in part and deliberately: those are agent-facing, stay unpublished, and are not what you hand a contributor. The /docs/ landing page is restructured to match the sidebar, and the generated site root is regenerated from the updated README. CLAUDE.md and agent.md no longer describe an autogenerated sidebar or sidebar_position ordering, neither of which is true any more. Validated locally, since pwsh and Docker are unavailable here: every sidebar id resolves to a file, every relative link on the landing page resolves, and all site-absolute links and anchors across the guide pages and the generated root resolve to real pages and real headings. * Fix the sidebar doc ids: Docusaurus strips numeric prefixes Verify Documentation Build failed on the previous commit: Invalid sidebar file at "sidebar.ts". These sidebar document ids do not exist: - engine/01-vision - engine/02-architecture - engine/03-story-graph-kind - engine/04-core Docusaurus's numberPrefixParser strips a leading numeric filename prefix when deriving a doc id -- the same reason the prefix never appears in the URL. So engine/01-vision.md is engine/vision. MVP, TODO, and OPEN-QUESTIONS carry no prefix and resolved fine, which is exactly the four-versus-three split the error showed. My local check verified that docs/docs/<id>.md existed, which confirms a filename rather than a doc id -- so it passed on ids the build then rejected. Replaced with a check that derives ids the way Docusaurus does, strips the prefix, and reports both directions: an id in the sidebar with no file, and a file no sidebar entry references. The second matters as much, since a manual sidebar makes an unlisted page invisible rather than merely misordered. The file comment asserted the wrong rule outright -- "the numeric filename prefixes stay" -- which is the assumption that produced the bug, so it is corrected rather than left to mislead the next edit. The build aborts on sidebar ids before it validates links, so onBrokenAnchors: 'throw' and the twelve new anchored links are still unexercised by CI. * Correct three inaccurate claims in the new guide pages Review found five items. Three are real, and all three are mistakes in prose this PR added -- the guides asserted things about the repository that are not true of it. The Node floor. engine-package said Node 24 is "enforced by engines.node so a local install and CI cannot drift apart". It is not: npm treats engines as advisory unless engine-strict is set, and no .npmrc here sets it. Verified -- there is no .npmrc in the repository at all. A local install on Node 22 warns and proceeds. Reworded to say what the declaration actually does: it states the floor, CI selects it separately, and neither enforces it locally. Adding engine-strict would make the original claim true and is worth considering, but it changes what happens on a contributor's machine and does not belong in a documentation change. The generated-page count. documentation-site said "Two pages are not [authored directly]" and then described the second as ordinary authored content -- contradicting itself in consecutive bullets. There is exactly one generated page: DocumentationRules.psd1 has a single GeneratedFiles entry, docs/src/pages/index.md. Rewritten to say one, and to state plainly that the /docs/ landing page sits next to it in the tree but is not generated and can be edited freely, since that adjacency is what makes the mistake easy. The link checks. documentation-site was headed "Two link checks, and they do not overlap". They do: the gate resolves a #fragment against the target's headings (its line 435), and Docusaurus checks anchors too, so a relative anchored link inside docs/docs/ is covered by both. Replaced with what is actually true -- complementary, overlapping in the middle -- and a table splitting what only each one covers from what both do. The overlap is not waste; the gaps at either end are the point, and the rewrite says which gap opens if either check is relaxed. agent.md checked for the same error and does not have it: it says "between them everything is now gated", which is accurate. All links and anchors re-validated after the edits: none broken. No README change, so the generated site root is untouched.
1 parent ee3872b commit e1f1502

18 files changed

Lines changed: 433 additions & 54 deletions

CLAUDE.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ engine `advance(state, action) → state`.
4646

4747
**Numbering is positional.** Inserting a doc between existing ones means renumbering
4848
everything after it and rewriting every cross-link. Prefer appending. *Reordering* is now
49-
free — set `sidebar_position` — but *inserting* still is not.
49+
free — reorder the list in `docs/sidebar.ts` — but *inserting* still is not.
5050

5151
### Where Drift Happens
5252

@@ -75,11 +75,14 @@ acceptance test with teeth.
7575
The specs are a Docusaurus site. `docs/` is both the Docusaurus overlay and the Docker
7676
build context:
7777

78-
- `docs/docs/engine/` — the markdown. **Sidebar order comes from `sidebar_position` front
79-
matter, not the filename prefix** — that is how `04-core` sits before `03-story-graph-kind`,
80-
matching the stated reading order (`plans/03-docs-rendering-and-navigation.md`).
78+
- `docs/docs/engine/` — the specs. `docs/docs/guide/` — how to work on the code and the
79+
site. **Sidebar order and sections are stated in `docs/sidebar.ts`, not inferred from
80+
filenames or front matter** — that is how `04-core` sits before `03-story-graph-kind`,
81+
matching the stated reading order. **A new page is invisible until it is listed there**;
82+
an id that does not resolve fails the build.
8183
- `docs/docusaurus.config.ts`, `docs/sidebar.ts`**local overrides** of the base image's
82-
defaults (autogenerated sidebar; both Docusaurus broken-link checks are `'throw'` — they
84+
defaults (a manual sidebar — grouping by folder would move files and change every URL;
85+
all three Docusaurus link checks are `'throw'`, anchors included — they
8386
were `'warn'` only while a static file held the site root, and `build/Test-Documentation.ps1`
8487
is the second gate, covering the relative links and anchors Docusaurus resolves differently;
8588
see `agent.md`, *Two link checks*). Spec docs reference

README.md

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@ the site root, which publishes this README. The specs are rendered and cross-lin
2020
## Status
2121

2222
- **Specs:** the MVP contracts are finalized — the story-graph kind
23-
([`03`](https://game-engine.subzerodev.com/docs/engine/story-graph-kind)) and the core
24-
([`04-core`](https://game-engine.subzerodev.com/docs/engine/core)). See [MVP.md](https://game-engine.subzerodev.com/docs/engine/mvp).
23+
([`03`](https://game-engine.subzerodev.com/docs/engine/story-graph-kind#1-the-campaign)) and the core
24+
([`04-core`](https://game-engine.subzerodev.com/docs/engine/core#2-the-gamestate-envelope)). See [MVP.md](https://game-engine.subzerodev.com/docs/engine/mvp#1-the-mvp-in-one-sentence).
2525
Every MVP-blocking gap is now decided; the register
26-
([OPEN-QUESTIONS.md](https://game-engine.subzerodev.com/docs/engine/open-questions) §1) is a decision log.
27-
- **Code:** [`src/engine/`](https://github.com/The-Running-Dev/SubZeroDev.GameEngine/tree/main/src/engine) — seeded PCG32 RNG and canonical serialization,
26+
([OPEN-QUESTIONS.md](https://game-engine.subzerodev.com/docs/engine/open-questions#1-mvp-relevant-gaps--all-resolved) §1) is a decision log.
27+
- **Code:** [`src/engine/`](https://game-engine.subzerodev.com/docs/guide/engine-package) — seeded PCG32 RNG and canonical serialization,
2828
verified bit-identical to reference vectors; toolchain green (15 tests).
29-
- **Next:** [TODO.md](https://game-engine.subzerodev.com/docs/engine/todo) breaks the MVP into ordered units of work
29+
- **Next:** [TODO.md](https://game-engine.subzerodev.com/docs/engine/todo#core) breaks the MVP into ordered units of work
3030
(W0–W19). W0 adds CI and docs-build gates; W1 then adds the core contract types and
3131
module skeleton.
3232

@@ -35,9 +35,9 @@ the site root, which publishes this README. The specs are rendered and cross-lin
3535
| Path | What |
3636
|---|---|
3737
| [`docs/docs/engine/`](https://game-engine.subzerodev.com/docs/engine/vision) | The specs — `01-vision`, `02-architecture`, `04-core` (the API/types), `03-story-graph-kind`, `MVP`, `TODO`, `OPEN-QUESTIONS` |
38-
| [`src/engine/`](https://github.com/The-Running-Dev/SubZeroDev.GameEngine/tree/main/src/engine) | The implementation (TypeScript strict, vitest, determinism-guard eslint) |
38+
| [`src/engine/`](https://game-engine.subzerodev.com/docs/guide/engine-package) | The implementation (TypeScript strict, vitest, determinism-guard eslint) |
3939
| `docs/` | The specs are a Docusaurus site; `docs/docs/` is its content root |
40-
| [`docs.ps1`](https://github.com/The-Running-Dev/SubZeroDev.GameEngine/blob/main/docs.ps1) | Build & serve the docs site |
40+
| [`docs.ps1`](https://game-engine.subzerodev.com/docs/guide/documentation-site#previewing-locally) | Build & serve the docs site |
4141

4242
## Build the Docs Site
4343

@@ -66,11 +66,11 @@ byte-for-byte from a seed and its inputs.
6666

6767
## Where to Start Reading
6868

69-
1. [Vision](https://game-engine.subzerodev.com/docs/engine/vision) — why the platform exists
70-
2. [Architecture](https://game-engine.subzerodev.com/docs/engine/architecture) — every settled decision
71-
3. [The core](https://game-engine.subzerodev.com/docs/engine/core) — the platform as types
72-
4. [Story-graph kind](https://game-engine.subzerodev.com/docs/engine/story-graph-kind) — the flagship content model
73-
5. [MVP](https://game-engine.subzerodev.com/docs/engine/mvp) + [TODO](https://game-engine.subzerodev.com/docs/engine/todo) — what ships first, in order
69+
1. [Vision](https://game-engine.subzerodev.com/docs/engine/vision#1-what-this-is) — why the platform exists
70+
2. [Architecture](https://game-engine.subzerodev.com/docs/engine/architecture#1-the-three-layers) — every settled decision
71+
3. [The core](https://game-engine.subzerodev.com/docs/engine/core#1-the-two-layers-of-engine) — the platform as types
72+
4. [Story-graph kind](https://game-engine.subzerodev.com/docs/engine/story-graph-kind#3-nodes--the-single-content-type-n7) — the flagship content model
73+
5. [MVP](https://game-engine.subzerodev.com/docs/engine/mvp#3-in-scope) + [TODO](https://game-engine.subzerodev.com/docs/engine/todo#core) — what ships first, in order
7474

7575
---
7676

agent.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ only when it would have changed a decision.
4747
identical types). **It recurs on the *view* side too, not just state and content**
4848
whenever a kind mirrors a core concept, check the field lives in exactly one place.
4949
- **Positional numbering** — inserting a doc means renumbering + rewriting every link.
50-
Prefer appending. Sidebar *order* is `sidebar_position` front matter, decoupled from
51-
filenames.
50+
Prefer appending. Sidebar *order and sections* live in `docs/sidebar.ts`, decoupled from
51+
filenames — and a new page must be added there or it never appears.
5252
- **A diff cannot show a rendering bug.** Every spec doc shipped for months with its
5353
metadata fields merged into one run-on paragraph, because markdown joins consecutive
5454
lines — correct markdown, wrong intent. A metadata field or blockquote label needs a

build/Test-Documentation.ps1

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,30 @@ function Find-DocumentationRepositoryRoot {
7575
}
7676
}
7777

78-
$repositoryRoot = Find-DocumentationRepositoryRoot -StartPath $PSScriptRoot
78+
# Normally this script lives inside the project it checks, so its own location
79+
# finds the root. It can also run from outside one -- the published image exposes
80+
# it as Invoke-DocsTest from /PSModule/Scripts, where no '.git' exists above it --
81+
# so fall back to the working directory, which is the mounted project. The script
82+
# location is tried first so a consumer's installed copy keeps resolving to its
83+
# own repository even when invoked from elsewhere.
84+
$repositoryRoot = $null
85+
foreach ($start in @($PSScriptRoot, (Get-Location).Path)) {
86+
if ([string]::IsNullOrWhiteSpace($start)) { continue }
87+
try {
88+
$repositoryRoot = Find-DocumentationRepositoryRoot -StartPath $start
89+
break
90+
}
91+
catch [System.IO.DirectoryNotFoundException] {
92+
continue
93+
}
94+
}
95+
96+
if (-not $repositoryRoot) {
97+
throw [System.IO.DirectoryNotFoundException]::new(
98+
"Could not locate the repository root from '$PSScriptRoot' or " +
99+
"'$((Get-Location).Path)': no '.git' was found in any parent directory."
100+
)
101+
}
79102

80103
if (-not $PSBoundParameters.ContainsKey('SettingsPath')) {
81104
$SettingsPath = Join-Path $repositoryRoot '.config' 'DocumentationRules.psd1'
@@ -240,7 +263,13 @@ function ConvertTo-HeadingSlug {
240263
$value = [regex]::Replace($value, '[`*_~]', '')
241264
$value = $value.Trim().ToLowerInvariant()
242265
$value = [regex]::Replace($value, '[^a-z0-9 \-]', '')
243-
$value = [regex]::Replace($value, '\s+', '-')
266+
# One hyphen per space, not one per run of whitespace. GitHub strips the
267+
# punctuation and hyphenates each remaining space, so '## Phase 1 — Fixes'
268+
# anchors as '#phase-1--fixes' with the doubled hyphen the removed em dash
269+
# leaves behind. Collapsing here produced '#phase-1-fixes', so a file whose
270+
# anchors worked on GitHub failed this gate and vice versa -- and the same
271+
# files are read in both places.
272+
$value = [regex]::Replace($value, '\s', '-')
244273

245274
return $value.Trim('-')
246275
}

docs/docs/engine/01-vision.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
---
2-
sidebar_position: 1
32
---
43

54
# Vision

docs/docs/engine/02-architecture.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
---
2-
sidebar_position: 2
32
---
43

54
# Architecture

docs/docs/engine/03-story-graph-kind.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
---
2-
sidebar_position: 4
32
sidebar_label: Story-Graph Kind
43
---
54

docs/docs/engine/04-core.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
---
2-
sidebar_position: 3
32
sidebar_label: Core Specification
43
---
54

@@ -10,7 +9,7 @@ sidebar_label: Core Specification
109
**Reading order:** logically the core *underlies* the kinds; the filename is `04` only to
1110
avoid renumbering. Read after [`02-architecture.md`](02-architecture.md), before or
1211
alongside [`03-story-graph-kind.md`](03-story-graph-kind.md) — which is the order the
13-
sidebar presents, via `sidebar_position` rather than the filename prefix.
12+
sidebar presents, stated in `docs/sidebar.ts` rather than taken from the filename prefix.
1413

1514
> **Scope of this document**
1615
>

docs/docs/engine/MVP.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
---
2-
sidebar_position: 5
32
slug: mvp
43
---
54

docs/docs/engine/OPEN-QUESTIONS.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
---
2-
sidebar_position: 7
32
sidebar_label: Open Questions
43
slug: open-questions
54
---

0 commit comments

Comments
 (0)