docs-s2: ship the manual with the app, and bridge the reference pages into it (#346) - #560
Merged
Merged
Conversation
…ence pages into it (p-docs-s2) A release now carries a browser-openable copy of the full illustrated manual, reachable from Help ▸ Open Manual in Browser, alongside the in-app F1 book it already had. F1 and the .qch pipeline are behaviourally unchanged. `npm run build:local` produces the offline reader. Opening from file:// forces three things, each enforced rather than assumed: pages are emitted as <slug>.html, because a browser will not serve index.html for a bare directory over file://; every reference is rewritten relative, because a root-absolute /... resolves against the filesystem root; and search is off, because Pagefind fetches its index over XHR. Switching search off removes the UI with it, and the landing page says where search lives — never a search box that does nothing. check-local-build.mjs verifies the built OUTPUT rather than the transform, so the gate still fails if the rewriting step were dropped from the build. The relativizer treats a reference matching no file as an error rather than rewriting it quietly, which is what catches a link to a renamed page; it is idempotent, and the test proves that by running it twice and comparing bytes. A maintained relative-links integration was rejected in favour of ~60 lines of string work: every npm package here is a permanent obligation under the licence gate. Packaging is opt-in (ROADMAKER_BUNDLE_MANUAL, default OFF) and CMake never invokes npm — the release job builds the manual with Node and passes the finished directory in as ROADMAKER_MANUAL_DIR, so a developer build still needs no Node. The install layout is resolved by a pure function of (executable directory, platform), so all three platforms' answers are checked by one headless test instead of only by the release job; the release smoke test then asserts the manual at that same layout on each platform, plus a page beneath it, since an empty manual/ would pass an index-only check. A reference page may end with a `## Full guide` section linking its tutorial. The heading is the marker, so the authored link stays ordinary Markdown that renders on GitHub; the site emits a normal link and the help compiler emits rmmanual:<slug>, which the viewer resolves against the packaged manual at runtime and opens externally — the path is only knowable at runtime, which is why the compiler emits a scheme rather than a URL. Applied to the 13 reference pages with a matching tutorial. The slug arrives from a generated document, so manual_page_for refuses one that climbs out of the manual. Two defects surfaced on the way and are fixed here. The adapter treated any ../-prefixed link as leaving the guide, so since the tier split every tutorial's link to a reference page left the site for GitHub; it now resolves the target first and only falls back to the repo blob when it lands outside. And the help build's dependency glob still watched tutorials/ while missing reference/, so editing a reference page left the shipped collection stale — which would have made the new bridge gate depend on whether someone had reconfigured. #297 is the help compiler's own ../ rewriting and is untouched. Starlight links a favicon unconditionally and the project shipped none, so all 40 pages carried a dangling reference — a console 404 on a server, a real broken reference under file://. It now points at the app's own icon. Closes #346
This was referenced Aug 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #346 (
docs-s2) — ADR-0009.A release now carries a browser-openable copy of the full illustrated manual, reachable from Help ▸ Open Manual in Browser, alongside the in-app
F1book it already had.F1and the.qchpipeline are behaviourally unchanged.1.
build:local— the file:// readerOpening from
file://forces three things, and each is enforced rather than assumed:format: 'file'— a browser will not serveindex.htmlfor a bare directory overfile://, so pages are<slug>.html./…resolves against the filesystem root and 404s.file://blocks. Switching it off removes the UI with it, so no search box ships that cannot answer; the landing page says where search lives instead.Integration vs. post-processor (the spec asks for a justification). A maintained relative-links integration would be one more npm dependency inside a licence gate this project treats as law, for a transform that is ~60 lines of string work.
scripts/relativize.mjsis dependency-free and idempotent by construction — it only rewrites values beginning with/and produces none — and the test proves that by running it twice and comparing bytes rather than asserting it in a comment.It also treats a reference matching no file in the build as an error rather than rewriting it quietly. That is what catches a link to a page that was renamed, and it is how the dangling favicon below was found.
The gate checks the output, not the transform.
check-local-build.mjsscans the built HTML, so it still fails ifrelativize.mjswere removed, reordered out of the build, or silently skipped a page.2. Release packaging
ROADMAKER_BUNDLE_MANUAL(default OFF) withROADMAKER_MANUAL_DIR. CMake never invokes npm: the release job builds the manual with Node and hands CMake a finished directory, so a developer build still needs no Node. Configuring with the option ON and no directory is a hardFATAL_ERROR, not a silently empty install.Layout per ADR-0009 —
Contents/Resources/manual/(macOS),share/roadmaker/manual/(Linux),manual/beside the exe (Windows).3. Help-menu action
Registered through
shortcut_registryasId::OpenManual— unbound, so it renders nothing on the shortcuts page andF1keeps belonging to the in-app viewer.The layout question is a pure function of (executable dir, platform), so all three platforms' answers are checked by one headless test rather than only by the release job on a machine nobody is watching. The dev-build fallback is the same function returning
nullopt: it shows a pointer at the online docs and saysF1still works — never a raw error, never silence.4. The reference → guide bridge
A reference page may end with a
## Full guidesection whose first link is its tutorial. The heading is the marker, so the authored link stays an ordinary relative Markdown link that renders correctly on GitHub; each generator retargets it for its own output:.qchrmmanual:<slug>, resolved at runtime against the packaged manual and opened in the system browserThe manual's path is only knowable at runtime, which is why the compiler emits a scheme rather than a URL. Applied to the 13 reference pages that have a matching tutorial; none were invented.
The slug arrives from a generated document, so
manual_page_forrefuses one that climbs out of the manual, and refuses a backslash rather than normalising it (it would mean a separator on exactly one platform).Two defects fixed on the way
../links to GitHub.../means "up one directory", not "out of the guide" — and since the tier split the guide has subdirectories. Sotutorials/*→../reference/*left the site for GitHub on 15 existing links, and the bridge could never have been "an ordinary link" without fixing it. It now resolves the target first and only falls back to the repo blob when it genuinely lands outside.CONFIGURE_DEPENDSstill globbedtutorials/*.md(which the pipeline stopped reading in docs-s1) and missedreference/*.md(which it does read), so editing a reference page left the shipped collection stale — and the new bridge gate would have passed or failed depending on whether someone happened to reconfigure.public/, so all 40 pages carried a dangling reference — a console 404 on a server, a genuinely broken reference underfile://. It now points at the app's own icon, copied fromeditor/resources/branding/.#297 is untouched and stays open. It is the help compiler's
../rewriting (helpc/render.cpp); the adapter is a different pipeline in a different language, and the bridge path never reaches the generic rewriter.Verification
ci-macos,-Werror.build:localand the web build both green; licence gate clean (374 packages, all permissive).git ls-files '*.cpp' '*.hpp' | xargs clang-format --dry-run --Werrorclean (CI's exact command).main— 13 findings before, the same 13 after, so these changes add none.Sabotage results — six, each with a checked build exit code
relativize.mjscheck-local-buildfails (21 problems)tutorials/shaping-lanes.mdpagefind: truein the local buildcheck-local-buildfails..refusalOne sabotage (stubbing the bridge rewrite to
if (false)) did not compile, so the test ran a stale binary and reported green. It was replaced with one that compiles. Recording it because the misleading result, not the fixed one, is the reusable lesson.Out of scope, respected
The sharp/LGPL posture is untouched — the
overridesstub andpassthroughImageService()are exactly as they were, and no dependency was added at all. No AWS or hosting resources; CI buildsdistas an artifact and stops. No tags, no releases. No labels, milestones, or release-gate text. Versioned publishing is #347; the authoring guide and dependency cadence are #348.Outstanding — requires a human
Two acceptance items need a display and a packaged build, so they are not attemptable in CI and are not claimed:
file://and navigating between pages and images by hand, on macOS/Linux/Windows.F1still opening the in-app guide in the same build.Everything mechanically checkable about both is gated: the release smoke test asserts the manual exists at the resolved layout on all three platforms plus a page beneath it, and
check-local-build.mjsasserts no root-absolute reference survives. What remains is the human confirmation the release gate asks for.npm auditreports 1 high on the pinnedastro@5.14.1; every advisory concerns the dev server, SSR/middleware, server islands, adapters or the image endpoint — none of which exist in a statically prerendered, adapter-less build. Left for #348, whose deliverable is dependency hygiene and the update cadence.