Skip to content

docs-s4: hardening, authoring guide, and workstream close-out (#348) - #562

Merged
JArmandoAnaya merged 1 commit into
mainfrom
docs/348-hardening-authoring-guide
Aug 2, 2026
Merged

docs-s4: hardening, authoring guide, and workstream close-out (#348)#562
JArmandoAnaya merged 1 commit into
mainfrom
docs/348-hardening-authoring-guide

Conversation

@JArmandoAnaya

Copy link
Copy Markdown
Contributor

Closes #348 (docs-s4) and completes the documentation site workstreamADR-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.h and helpc/qhp.cpp rather than from convention: MD_DIALECT_GITHUB gives 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#section lands 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. 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 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.md manifest 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:

Step Result
reference page on the site
guide page on the site
bridge rendered as an ordinary site link href="/dev/tutorials/using-my-tool"
reference page in the in-app book + its TOC
guide page in the in-app book absent — the tier split working
bridge in the in-app book href="rmmanual:tutorials/using-my-tool"
npm run check:links 2313 broken references

check:links runs in its own shell, so RM_DOCS_BASE was gone and it fell back to / while the build had used /dev/. CI would have failed identicallydocs-site.yml runs 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:links verifies every internal link and image in a built tree — dev/, each version directory, and latest/, 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, plus continue-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 out

The 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 — checking latest/ 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.md said nothing about npm and THIRD_PARTY_LICENSES.md listed 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 to docs-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 stubbed sharp as 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.1 advisories: 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.md said 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.
  • The roadmap described an _order manifest for ordering guides. It was sketched during planning and never built (Starlight's autogenerate does the job). The docs were wrong, not the implementation — corrected in that direction and said so.
  • Release-gate point 5 and ADR-0009 were checked against as-built and are accurate; no change needed.

Verification

  • 26/26 script tests (9 new for the link checker).
  • Both builds green; the assembled two-version tree checks clean.
  • lychee 0.24.2, CI's exact command: 0 errors.
  • Licence gate clean — 374 packages, no dependency added.
  • Both workflow files parse.
  • No C++ or CMake touched.

Sabotages — four, and the first proved the wrong thing

Sabotage Result
dead link in an authored page fails — but caught by the adapter, so it says nothing about check:links
delete a page from the build check:links fails (47 refs)
delete an image from the build check:links fails, naming the asset
root redirect disagrees with versions.json fails

I 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, the objects-signals.md split, the ToolId coverage hole, and #297 are all left alone. No analytics or third-party scripts.

Closing the workstream

docs-s1docs-s4 are 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 from file:// on macOS/Linux/Windows, and the Help-menu action in a packaged app — which are recorded at the gate, not on the epic.

…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
@JArmandoAnaya
JArmandoAnaya merged commit 5b818d8 into main Aug 2, 2026
18 checks passed
@JArmandoAnaya
JArmandoAnaya deleted the docs/348-hardening-authoring-guide branch August 2, 2026 15:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

docs-s4: hardening + authoring guide + close-out

1 participant