You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Part of the Documentation Site workstream (see the epic) · decided in ADR-0009 · specified in the roadmap. First sprint; nothing else in the workstream can start until this lands.
Two independent things, done together because each is worthless without the other: split the authored user guide into a reference tier (dual-source: .qhp + site) and a guides tier (site only), and stand up the Astro Starlight project that consumes it.
As-built facts you must know before touching anything
Verified 2026-07-21. Read them before planning — several are counter-intuitive.
The .qhp generator's file list is NOT a glob — docs/user-guide/index.md IS the manifest.helpc::build_toc() (editor/help_compiler/helpc/toc.cpp:102-137) reads index.md, extracts its markdown links in document order, and admits a link only if it is non-external, not an anchor, does not start with ../ or ./, ends in .md, exists on disk, and is not a duplicate. A page committed under docs/user-guide/ but not linked from index.md is invisible to the entire pipeline. TOC order is exactly index.md link order. Page titles come from each page's first # H1 (toc.cpp:46-56).
A page's slug is its guide-relative path minus .md (toc.cpp:131), so moving create-road.md to reference/create-road.md changes the slug to reference/create-road, which changes its .qhp keyword id (helpc/qhp.cpp:76-82) and its runtime URL qthelp://ai.robomous.roadmaker/doc/<slug>.html (editor/src/help/help_viewer.cpp:52-55) in lockstep.
The F1 map is a static side table, not a helpId() on tools: kToolPages (editor/src/help/help_registry.cpp:13-34, ToolId → slug) and kDockPages (:39-45, dock objectName → slug), resolved focused-dock → active-tool → index (:78-92).
The coverage gate reads the repo from disk.EveryPageResolvesToACommittedGuidePage (editor/tests/test_help_registry.cpp:76-100) asserts, for every tool and dock slug, that RM_DOCS_DIR/user-guide/<slug>.md exists and that index.md contains the literal substring (<slug>.md). RM_DOCS_DIR is defined at editor/tests/CMakeLists.txt:115. Sibling gates in editor/tests/test_help_collection.cpp: EveryTocPageIsServed (:72-87), EveryReferencedImageIsServed (:93-129), EveryTocPageHasAKeyword (:131-145).
shortcuts.md's path is hardcoded at editor/tests/test_shortcut_registry.cpp:129-130 (RM_DOCS_DIR / "user-guide" / "shortcuts.md"); the test slurps the file and asserts it contains the generated block verbatim. Moving that page requires editing that line in the same commit.
Images: main.cpp:85-98 copies <guide>/img/* and <guide>/tutorials/img/* non-recursively; ../-prefixed images are copied on demand and rewritten to img/<basename> (helpc/render.cpp:55-62, 90-97). The .qhp<files> patterns are duplicated per subdirectory because qhelpgenerator wildcards do not recurse (qhp.cpp:87-97), and cover onlypng/gif/jpg. Any new tier folder needs its own <files> patterns and its own image-copy step — this is exactly the class of bug Help pipeline: register tutorials/img/* in the .qhp <files> so tutorial images render in-app #292 was.
Renderer is md4c with MD_DIALECT_GITHUB (helpc/render.cpp:17-26): tables, strikethrough, task lists, autolinks, raw HTML pass through; no footnotes, no front-matter (renders as literal text), no admonition syntax, no code-fence transforms, and no heading id= anchors — so page.md#section links resolve to the page but land at its top.
../*.md links rewrite to https://github.com/Robomous/RoadMaker/blob/main/<path> (render.cpp:43-73); every reference page's trailing "## Reference" section relies on this.
Theme tokens are C++ only.theme::graphite_amber() (editor/src/theme/theme.cpp:225-250) holds 19 named fields; help_style.cpp:9-66 substitutes 7 of them into a template, and the result is committed pre-substituted with flattened hex at editor/resources/help/help.css, gated byte-for-byte by editor/tests/test_help_style.cpp:17-31. QTextBrowser does not support CSS custom properties, so the in-app sheet must stay flattened.
Deliverables
1. Tier restructure of docs/user-guide/
Use git mv (preserve history). Move whole pages only — no content rewriting, no page splitting in this sprint. The proposed classification is in the workstream PR description; it is planning input, so re-verify before moving.
reference/ — the per-tool and per-panel pages.
tutorials/ — the four existing tutorials, kept where they are.
guides/ — created for the guides tier (may start empty or take whatever the classification puts there).
index.md stays at the guide root: it is the ordering manifest (fact 1) and the F1 fallback slug.
In the same commit, update every consumer: kToolPages/kDockPages slugs, index.md link targets (which the gate greps literally), the hardcoded shortcuts.md path, intra-guide relative links between moved pages, and image references. docs/README.md's user-guide links (lines ~24-29) point at several of these pages and must not rot.
2. Narrow the .qhp generator to the reference tier
The generator ingests reference/ (plus index.md) and ignoresguides/ and tutorials/. Removing tutorials from the .qhp path is the point of the sprint — it is what lets guides use richer syntax later. Adjust the synthetic "Tutorials" TOC section (qhp.cpp:53-72) and the tutorial image handling accordingly, and delete what becomes dead rather than leaving it inert.
Keep the coverage philosophy intact: the tool/panel gate now means "every registered tool and panel resolves to an existing reference page".
3. docs-site/ scaffold
New top-level folder. Pinned Astro Starlight, .nvmrc + engines pinning the Node LTS major, committed package-lock.json, npm ci in CI. Every npm dependency MIT/BSD/Apache-2.0-compatible per docs/standards/dependencies.md; record them per that policy. A README.md stating plainly that adapted content is generated and must never be hand-edited.
Theme: reuse the graphite + amber palette via a single export path from theme::graphite_amber() to site CSS custom properties (a generator alongside help_style::css(), or a script parsing the initializer — your call, justify it). Do not transcribe hex values into a second place by hand, and do not change the committed help.css bytes.
4. Adapter script (Node)
Copies docs/user-guide/** into Starlight's content directory and:
synthesizes required title frontmatter from each page's first H1;
rewrites relative links and image references for Starlight routing;
derives reference-tier sidebar order from index.md (the same manifest the .qhp uses, so the two outputs cannot drift), and guides order from folder structure plus an optional _order manifest that the .qhp generator ignores;
fails loudly on a broken link — non-zero exit, naming the source page and target.
Output is gitignored.
5. CI
A new Linux-only workflow (Node), triggered on changes to docs/user-guide/** and docs-site/**: npm ci → adapter → astro build → link check. Extend the coverage philosophy with a check that every F1-required page also exists in the adapted Starlight set, so a page can never be reachable in-app but missing from the site.
The C++ jobs must not change at all. No CMake target invokes npm; .github/workflows/ci.yml C++ jobs stay byte-identical in behavior. Note that ci.yml:461-477 already runs a docs link check globbing docs/**/*.md — make sure the restructure keeps it green.
Acceptance
Both pipelines build green from the restructured source, with no dual-syntax hacks — no page contorted to satisfy both renderers.
The full editor test suite is green, specifically test_help_registry, test_help_collection, test_help_style, test_shortcut_registry.
F1 resolves correctly for every registered tool and panel after the move (verify in a running editor, not only via the gate).
Every F1-required page is present in the adapted Starlight set (enforced in CI).
astro build green on Linux CI; adapter fails the build on a deliberately broken link (prove it with a test or a documented manual check).
No C++/CMake job requires Node.
Out of scope — do not touch
The local reader build, packaging, ROADMAKER_BUNDLE_MANUAL, the Help-menu action, bridge links (docs-s2).
Any publishing workflow, Amplify, versioning (docs-s3).
The authoring guide, dependency-update cadence, IP sweep (docs-s4).
Splitting objects-signals.md or normalizing <kbd> usage — whole-page moves only.
The ToolId-enum coverage hole noted in the epic (StopLine/JunctionSpan/JunctionSurface past the <= Corner loop bound). Tempting because you are in that file; it is a separate defect with its own fix.
MDX, JS components, or any Starlight feature beyond plain Markdown + asides.
Part of the Documentation Site workstream (see the epic) · decided in ADR-0009 · specified in the roadmap. First sprint; nothing else in the workstream can start until this lands.
Two independent things, done together because each is worthless without the other: split the authored user guide into a reference tier (dual-source:
.qhp+ site) and a guides tier (site only), and stand up the Astro Starlight project that consumes it.As-built facts you must know before touching anything
Verified 2026-07-21. Read them before planning — several are counter-intuitive.
.qhpgenerator's file list is NOT a glob —docs/user-guide/index.mdIS the manifest.helpc::build_toc()(editor/help_compiler/helpc/toc.cpp:102-137) readsindex.md, extracts its markdown links in document order, and admits a link only if it is non-external, not an anchor, does not start with../or./, ends in.md, exists on disk, and is not a duplicate. A page committed underdocs/user-guide/but not linked fromindex.mdis invisible to the entire pipeline. TOC order is exactlyindex.mdlink order. Page titles come from each page's first#H1 (toc.cpp:46-56)..md(toc.cpp:131), so movingcreate-road.mdtoreference/create-road.mdchanges the slug toreference/create-road, which changes its.qhpkeyword id (helpc/qhp.cpp:76-82) and its runtime URLqthelp://ai.robomous.roadmaker/doc/<slug>.html(editor/src/help/help_viewer.cpp:52-55) in lockstep.helpId()on tools:kToolPages(editor/src/help/help_registry.cpp:13-34, ToolId → slug) andkDockPages(:39-45, dock objectName → slug), resolved focused-dock → active-tool →index(:78-92).EveryPageResolvesToACommittedGuidePage(editor/tests/test_help_registry.cpp:76-100) asserts, for every tool and dock slug, thatRM_DOCS_DIR/user-guide/<slug>.mdexists and thatindex.mdcontains the literal substring(<slug>.md).RM_DOCS_DIRis defined ateditor/tests/CMakeLists.txt:115. Sibling gates ineditor/tests/test_help_collection.cpp:EveryTocPageIsServed(:72-87),EveryReferencedImageIsServed(:93-129),EveryTocPageHasAKeyword(:131-145).shortcuts.md's path is hardcoded ateditor/tests/test_shortcut_registry.cpp:129-130(RM_DOCS_DIR / "user-guide" / "shortcuts.md"); the test slurps the file and asserts it contains the generated block verbatim. Moving that page requires editing that line in the same commit.main.cpp:85-98copies<guide>/img/*and<guide>/tutorials/img/*non-recursively;../-prefixed images are copied on demand and rewritten toimg/<basename>(helpc/render.cpp:55-62, 90-97). The.qhp<files>patterns are duplicated per subdirectory because qhelpgenerator wildcards do not recurse (qhp.cpp:87-97), and cover onlypng/gif/jpg. Any new tier folder needs its own<files>patterns and its own image-copy step — this is exactly the class of bug Help pipeline: register tutorials/img/* in the .qhp <files> so tutorial images render in-app #292 was.MD_DIALECT_GITHUB(helpc/render.cpp:17-26): tables, strikethrough, task lists, autolinks, raw HTML pass through; no footnotes, no front-matter (renders as literal text), no admonition syntax, no code-fence transforms, and no headingid=anchors — sopage.md#sectionlinks resolve to the page but land at its top.../*.mdlinks rewrite tohttps://github.com/Robomous/RoadMaker/blob/main/<path>(render.cpp:43-73); every reference page's trailing "## Reference" section relies on this.theme::graphite_amber()(editor/src/theme/theme.cpp:225-250) holds 19 named fields;help_style.cpp:9-66substitutes 7 of them into a template, and the result is committed pre-substituted with flattened hex ateditor/resources/help/help.css, gated byte-for-byte byeditor/tests/test_help_style.cpp:17-31.QTextBrowserdoes not support CSS custom properties, so the in-app sheet must stay flattened.Deliverables
1. Tier restructure of
docs/user-guide/Use
git mv(preserve history). Move whole pages only — no content rewriting, no page splitting in this sprint. The proposed classification is in the workstream PR description; it is planning input, so re-verify before moving.reference/— the per-tool and per-panel pages.tutorials/— the four existing tutorials, kept where they are.guides/— created for the guides tier (may start empty or take whatever the classification puts there).index.mdstays at the guide root: it is the ordering manifest (fact 1) and the F1 fallback slug.In the same commit, update every consumer:
kToolPages/kDockPagesslugs,index.mdlink targets (which the gate greps literally), the hardcodedshortcuts.mdpath, intra-guide relative links between moved pages, and image references.docs/README.md's user-guide links (lines ~24-29) point at several of these pages and must not rot.2. Narrow the
.qhpgenerator to the reference tierThe generator ingests
reference/(plusindex.md) and ignoresguides/andtutorials/. Removing tutorials from the.qhppath is the point of the sprint — it is what lets guides use richer syntax later. Adjust the synthetic "Tutorials" TOC section (qhp.cpp:53-72) and the tutorial image handling accordingly, and delete what becomes dead rather than leaving it inert.Keep the coverage philosophy intact: the tool/panel gate now means "every registered tool and panel resolves to an existing reference page".
3.
docs-site/scaffoldNew top-level folder. Pinned Astro Starlight,
.nvmrc+enginespinning the Node LTS major, committedpackage-lock.json,npm ciin CI. Every npm dependency MIT/BSD/Apache-2.0-compatible per docs/standards/dependencies.md; record them per that policy. AREADME.mdstating plainly that adapted content is generated and must never be hand-edited.Theme: reuse the graphite + amber palette via a single export path from
theme::graphite_amber()to site CSS custom properties (a generator alongsidehelp_style::css(), or a script parsing the initializer — your call, justify it). Do not transcribe hex values into a second place by hand, and do not change the committedhelp.cssbytes.4. Adapter script (Node)
Copies
docs/user-guide/**into Starlight's content directory and:titlefrontmatter from each page's first H1;index.md(the same manifest the.qhpuses, so the two outputs cannot drift), and guides order from folder structure plus an optional_ordermanifest that the.qhpgenerator ignores;Output is gitignored.
5. CI
A new Linux-only workflow (Node), triggered on changes to
docs/user-guide/**anddocs-site/**:npm ci→ adapter →astro build→ link check. Extend the coverage philosophy with a check that every F1-required page also exists in the adapted Starlight set, so a page can never be reachable in-app but missing from the site.The C++ jobs must not change at all. No CMake target invokes npm;
.github/workflows/ci.ymlC++ jobs stay byte-identical in behavior. Note thatci.yml:461-477already runs a docs link check globbingdocs/**/*.md— make sure the restructure keeps it green.Acceptance
test_help_registry,test_help_collection,test_help_style,test_shortcut_registry.astro buildgreen on Linux CI; adapter fails the build on a deliberately broken link (prove it with a test or a documented manual check).Out of scope — do not touch
ROADMAKER_BUNDLE_MANUAL, the Help-menu action, bridge links (docs-s2).docs-s3).docs-s4)..qhppath reduces its blast radius but the link rewriter is still wrong for reference-to-reference links.objects-signals.mdor normalizing<kbd>usage — whole-page moves only.ToolId-enum coverage hole noted in the epic (StopLine/JunctionSpan/JunctionSurfacepast the<= Cornerloop bound). Tempting because you are in that file; it is a separate defect with its own fix.Epic: #344