docs-s4: hardening, authoring guide, and workstream close-out (#348) - #562
Merged
Conversation
…nk hardening, and the npm policy (p-docs-s4) docs/contributing/documentation.md answers "I want to document a thing — where does it go, and what may I write?" without reading a generator. The syntax budget is stated from what the in-app renderer actually does rather than from convention: no footnotes, no front matter, no admonitions, no code-fence transforms, and no heading anchors — so `page.md#section` lands at the top of the page in the app while working correctly on the site. That is a constraint to design for, not to route around: link to a page, and if you need to point at one section of a long reference page, the page wants splitting. The <kbd> inconsistency is settled in favour of backticks. QTextBrowser renders <kbd> unstyled, so in the app it looks exactly like the surrounding prose and the distinction it was reaching for is lost; backticks are pure CommonMark and render identically in the app, on the site and on GitHub. Applying it to the seven pages that use <kbd> stays a separate change, as scoped. check:links verifies every internal link and image in a BUILT tree — each version directory, dev/, and latest/ — each against the base it was built with. That is a different check from the adapter's, which reads source and cannot see a reference that only breaks once the pages are emitted; deleting a page or an image from a build is caught here and nowhere else. Outbound links get a non-blocking report instead: link rot in a third party's URL is not a reason a contributor's merge cannot happen, and a gate that fails for reasons nobody here can fix is one people learn to ignore. Each build now stamps the base it used into its output. The checker runs in its own shell — in CI and in a contributor's terminal — where the build's environment is long gone, and a checker that assumed the wrong base reported every link in the build as broken. This was found by following the new guide literally, as its acceptance criterion asks, and it would have failed CI. The stamp also makes latest/ self-describing, which matters because latest/ is a byte copy of the highest version and therefore carries THAT version's base. Its links all resolve; the consequence, now written down rather than left to be rediscovered, is that a reader who opens /latest/ is moved to the pinned version's URL on their first click. It is an entry point, not a browsable mirror. npm dependency policy was absent rather than thin: 374 installed packages, a committed lockfile and a CI licence gate, none of it written down. Both the policy and THIRD_PARTY_LICENSES.md now carry it — scoped to docs-site/, npm ci, the licence gate, a monthly human cadence, and an explicit rule that no npm automation may open pull requests or fail a job for the C++ side. The deliberately stubbed sharp is recorded as the standing example of the LGPL rule biting. Drift fixed: docs/README.md no longer says the tree is arranged so a static-site generator "could be adopted later", and the roadmap no longer describes an _order manifest for guides that was sketched during planning and never built. Closes #348
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 #348 (
docs-s4) and completes the documentation site workstream — ADR-0009.1. The authoring guide
docs/contributing/documentation.md — "I want to document a thing: where does it go, and what may I write?", answerable without opening a generator.
The syntax budget is stated from what the renderer actually does, read out of
md4c.handhelpc/qhp.cpprather than from convention:MD_DIALECT_GITHUBgives tables, strikethrough, task lists and autolinks, and nothing else. No footnotes (md4c has none at all), no front matter (nothing strips it — it renders as literal text), no admonitions, no code-fence transforms.No heading anchors gets its own section, because it is the one that bites:
page.md#sectionlands at the top of the page in the app while working correctly on the site. Framed as a constraint to design for — link to a page, and if you need to point at one section of a long reference page, the page wants splitting.<kbd>is settled: backticks.QTextBrowserrenders<kbd>unstyled, so in the app it looks exactly like the surrounding prose and the distinction it was reaching for is lost; backticks are pure CommonMark and render identically in all three surfaces; and it is already the majority spelling. Applying it to the seven pages that use<kbd>stays a separate change, as scoped.Also covered: the tier decision, the
index.mdmanifest rule (a reference page not linked from it is invisible to the in-app book, and nothing fails), the bridge convention, and image placement — including that the.qhp<files>patterns do not recurse, so a new image folder needs its own patterns or its images are silently absent in-app while the page looks fine on the site.The acceptance criterion, run literally — and it found a bug
The issue asks that a reviewer following the guide adds one page of each tier and sees each appear in its correct pipeline, with no extra steps. I did exactly that:
href="/dev/tutorials/using-my-tool"href="rmmanual:tutorials/using-my-tool"npm run check:linkscheck:linksruns in its own shell, soRM_DOCS_BASEwas gone and it fell back to/while the build had used/dev/. CI would have failed identically —docs-site.ymlruns it as a separate step from the build. Every test I had written was blind to it, because tests construct roots explicitly; it surfaced only from following my own documentation as a reader would.Fixed by making each build stamp the base it used into its output, so the checker reads a fact instead of an environment variable. The throwaway pages were then removed.
2. Link-check hardening
check:linksverifies every internal link and image in a built tree —dev/, each version directory, andlatest/, each against the base it was built with. This is a different check from the adapter's: the adapter reads source and cannot see a reference that only breaks once pages are emitted.Verified against a real two-version assembled tree: 3 roots, 120 pages, 8766 internal references, all resolve.
Outbound links are a report, never a failure (
report:external, pluscontinue-on-error). Link rot in a third party's URL is not a reason a contributor's merge cannot happen, and a gate that fails for reasons nobody in this repository can fix is one people learn to ignore.latest/— a property of #347 that neither its spec nor its PR called outThe checker's first run on a real tree reported 2130 broken references, all in
latest/. Not a false positive:latest/is a byte copy of the highest version, so its pages carry that version's base. Every link resolves — but a reader who opens/latest/is moved to the pinned version's URL on their first click. It is an entry point, not a browsable mirror.I did not redesign it. Making
latest/a redirect stub is what many docs sites do, but #347's spec says "copy of the highest semver version", that sprint is merged, and changing it from inside #348 is out of scope. The checker now asks the right question — checkinglatest/against the base it was built with — and the behaviour is written down in the code and here rather than left to be rediscovered.3. npm dependency policy — absent, not thin
docs/standards/dependencies.mdsaid nothing about npm andTHIRD_PARTY_LICENSES.mdlisted no npm package, despite 374 installed packages, a committed lockfile and a CI licence gate. Writing a cadence on top of nothing would have been a note, not a policy, so both files now carry it: scoped todocs-site/,npm ci, the licence gate and its file-fallback rule (extend it, never weaken it), a monthly human cadence, and the site's build tools recorded — with the deliberately stubbedsharpas the standing example of the LGPL rule biting.No npm automation may open PRs against this repository or fail a job for the C++ side. That is why the cadence is a documented human task rather than a bot: a docs-site advisory that cannot affect the kernel must never appear as a red check on a kernel change.
On the pinned
astro@5.14.1advisories: assessed rather than deferred again. All 17 concern a dev server, SSR, middleware, server islands, an adapter or an image endpoint — none of which exist in a statically prerendered, adapter-less build. The policy now says to judge an advisory by whether it can reach this deployment and to record the assessment, which is what makes a monthly cadence honest rather than a rubber stamp. A bump stays available at the next pass; mixing an unreviewed dependency change into a documentation PR would not be an improvement.4. IP sweep — clean
Swept content, config, site copy, this workstream's commits, and the bodies and comments of #344–#348 against product-parity.md's naming rule. No vendor product name anywhere. Build-dependency names (Astro, Starlight, Node, Pagefind) appear only in tooling and configuration, which the standard permits exactly as for Eigen or Manifold; the runbook says "the hosting app" throughout.
5. Drift check
docs/README.mdsaid the tree was arranged so a static-site generator "could be adopted later" — stale since ADR-0009. Replaced, and it now points at the authoring guide._ordermanifest for ordering guides. It was sketched during planning and never built (Starlight'sautogeneratedoes the job). The docs were wrong, not the implementation — corrected in that direction and said so.Verification
Sabotages — four, and the first proved the wrong thing
check:linkscheck:linksfails (47 refs)check:linksfails, naming the assetversions.jsonI am recording the first because it is the shape of a misleading pass: a sabotage that some other gate catches tells you nothing about the gate you meant to test. The last three break the output rather than the source, which the adapter cannot see, and are the ones that actually exercise the new check.
A correction worth stating
My local link stand-in flagged one new finding — the bridge example inside a fenced code block. There was no precedent in the repo for link-shaped text inside a fence, so rather than guess I installed lychee and ran CI's exact command: 0 errors. lychee skips fenced blocks. The example stays as written, and the 13 "findings" my stand-in reported on both sides of every differential this run were false positives throughout. Harmless, since it was only ever used as a differential — but I should have reached for the real tool sooner.
Out of scope, respected
The sharp/LGPL posture is untouched and no dependency was added. No AWS or hosting resources. No tags, no releases. No labels, milestones or release-gate re-scoping. Existing page content is unchanged beyond what the drift check required; the
<kbd>normalisation, theobjects-signals.mdsplit, theToolIdcoverage hole, and #297 are all left alone. No analytics or third-party scripts.Closing the workstream
docs-s1–docs-s4are complete. On merge I will tick all four boxes on epic #344 and close it, recording that its release-gate items are maintainer hand-runs — the local reader opened fromfile://on macOS/Linux/Windows, and the Help-menu action in a packaged app — which are recorded at the gate, not on the epic.