Uh oh!
There was an error while loading. Please reload this page.
docs(content): one h1 per docs page — 137 deletions, 16 demotions, and a gate - #12262
Merged
Conversation
DocsTitle renders the frontmatter title as the page h1, so a body-level '# ' heading makes the page ship a second one. 137 pages repeated the title verbatim (heading deleted); 16 said something different (demoted to '## ', which keeps the wording and keeps the slug). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fences are stripped before anything looks for a heading: a naive scan reports 205 files, ten of which are shell comments inside working examples. The frontmatter and heading-text models are imported from check-doc-anchors rather than retyped, so the gate cannot disagree with the renderer about what a heading says. Two subtrees are excluded with named owners, and an exclusion whose tree is clean is a FAILURE, so the carve-outs retire themselves. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Aug 25, 2026
os-zhuang
marked this pull request as ready for review
August 25, 2026 16:30
Uh oh!
There was an error while loading. Please reload this page.
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 freeto 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.
Fixes#12236
DocsTitlerenders the frontmattertitleas every doc page's heading-one(
apps/docs/app/[lang]/docs/[[...slug]]/page.tsx), so a body-level#headingcompiles to a second one inside
DocsBody. This removes the second headingfrom every hand-written page and adds a gate so it cannot come back.
The counts are not the card's counts — re-derived, and mine are smaller
The card measured 205 / 129 / 76. Those come from a
^#scan that does not stripfenced code and compares raw markdown byte-for-byte. Reproduced exactly, then
re-derived properly:
#heading#line is a shell or YAML comment inside a fenced code block — it renders as code, never as a headingtitle: services.auditvs# `services.audit`— rendered, the same stringThe ten fence hits are the load-bearing correction. A fence-blind sweep does not
merely over-report by ten, it deletes or demotes a line inside a working
example — ten corrupted copy-pasteable snippets to satisfy a check about HTML
those lines never produced. They are:
The nine inline-code pages are classified as duplicates and their headings deleted.
Demoting them would have left a
## services.auditsitting directly under aheading-one reading
services.audit— the duplicate this card is about, preservedone level down.
What changed: 153 files, exactly two diff shapes
Every one of the 153 removed non-blank lines is a
#heading; all 16 added linesbegin
##. Nothing else rides along. Per the ruling, a heading whose wordingdiffers from the title is demoted, not deleted — the wording survives, and so
does the slug, so no inbound anchor moves.
The 16 demotions, reviewed by hand:
##automation/hook-bodies.mdxconcepts/architecture.mdxconcepts/metadata-driven.mdxdata-modeling/formulas.mdxdeployment/cli.mdxgetting-started/common-patterns.mdxindex.mdxkernel/architecture.mdxpermissions/authentication.mdxpermissions/profiles.mdxpermissions/sharing-rules.mdxplugins/development.mdxprotocol/kernel/i18n-standard.mdxprotocol/objectql/security.mdxprotocol/objectql/state-machine.mdxui/forms.mdxFrontmatter
titleanddescriptionare untouched in this PR — #12237 and#12238 own that surface. Several of those 16 body headings are the better search
wording (
ObjectStack Documentation,Core Architecture,Authentication Guide);that observation is routed to the title card rather than acted on here.
The gate:
pnpm check:docs-single-h1New
scripts/check-docs-single-h1.mjs, wired intoLint & Repo Gates. Its headeris the authority on the rule; the three things worth knowing here:
whole difference between a gate and a hazard, per the table above.
stripFrontmatterandflattenHeadingTextareimported from
check-doc-anchors.mjs,stripFencedBlocksfromcheck-adr-links.mjs.flattenHeadingTextwas already diffed against the realfumadocs pipeline over the whole corpus, and it is what makes the delete/demote
call right on the nine inline-code pages. A private second model would have got
those nine wrong, and a gate that disagrees with the renderer about what a page
says is no better than no gate.
whose tree becomes clean is a
DEAD-EXCLUSIONfailure, so it cannot outliveits reason. A run that judges zero pages fails too.
Those two trees are not finished by this PR, and both are filed:
<h1>— the heading comes from a spec JSDoc header via build-docs.ts #12249 — the 38 pages undercontent/docs/references/**are generated bypackages/spec/scripts/build-docs.tsfrom JSDoc file headers inpackages/spec/src/**. Hand-editing them does not merely get reverted at thenext generator run:
check:docsis a required step that regenerates the treeand fails on any difference, so the hand-fix ships CI-red. The real fix is in
packages/**, which epic epic(docs-site): the site is technically un-indexable — fix robots/sitemap/canonical/OG first, then the keyword shape #12243 declares out of territory.<h1>— three of them need a cascading demotion, not a mechanical one #12250 — the 4 pages undercontent/docs/releases/**.CLAUDE.mdputs ahard stop on that directory, and three of the four are not mechanical: the
#headings in
v15/v16/v17.mdxare genuine top-level dividers with##children, so demoting one makes it a sibling of its own subsections. A correct
fix cascades every descendant, which is a structural rewrite of release notes.
Neither #12249 nor #12250 is closed by this PR, and neither is #12254 below.
Verification, at
274e157a3Rendered pages, before and after, same dev server. The card's own page plus two
more; the "before" leg restores just those files to the base commit so only the
content differs:
/docs/data-modeling/objectsObject Metadata,Object MetadataObject Metadata/docs/ai/skillsAI Skills System,AI Skills SystemAI Skills System/docs(a demotion)Documentation,ObjectStack DocumentationDocumentationOn
/docsthe demoted wording is still on the page as a level-two heading andstill carries the same slug,
id="objectstack-documentation", before and after —so an inbound anchor to it resolves either way.
Anchors — the card's third acceptance box, and the PM's stated risk.
Nothing pointed at any of the 137 deleted headings. No page in the swept set used a
[#custom-id]suffix on its body heading (measured: 0).The gate can go red — reverse-verified on the real corpus from the committed
state, not on a fixture. Reintroducing one heading of each kind, plus a control:
The third mutation — a
#line added inside a bash fence ingetting-started/index.mdx— was correctly not reported: 2 findings, not 3.Each file was then restored with
git checkout HEAD -- <path>and provedbyte-identical to its HEAD blob via
git hash-object, withgit diff HEADempty.The self-test carries 16 cases, both fence spellings included.
Gates run locally, all green at
274e157a3:check:doc-anchors,check:docs-single-h1,check:doc-authoring,check:nul-bytes,check:entry-guard,check:parse-guard,check:docs-redirects,check:docs-audit-scope,check:docs-image-tag,check:role-word,check:quick-reference-counts,check:runtime-services-index,check:required-contexts,check:workflow-status-functions,check:agent-test-spelling,check:cross-package-test-inputs,check:published-readme-links,check:release-notes,check:overlay-whitelist-table,check:error-status-conformance,check:react-page-adapter-contract,check:changeset-gate-self-tests,check:node-version,check:pnpm-filter-targets,check:pnpm-acquisition,check:shard-attestation— the familyscripts/pm/dispatch-gates.mjsderives forthis diff, which already discovers the new gate.
One gate in that family,
check:objectui-changeset, is red — and was red beforethis branch existed. Reproduced on a clean checkout at
8f79379b7with no localchanges: its R7a leg simulates a bash-3.2 shell to prove R7b is not vacuous, and on
macOS the host shell already is bash 3.2, so the control cannot pass. Filed as
#12254 with the measurement; CI is Linux and unaffected.
Declared narrowing — verification ran UNLOCKED.
scripts/pm/os-verify-lock.shcould not take the shared verify lock on this host: no usable
flock. The sharedverify lock is declared Linux-only (
flockis util-linux, and a stock macOS doesnot ship it), so the command below was run directly, without the lock —
a declared narrowing, not a silent one. No serialization guarantee held for this
run, nor for any sibling agent in this container while it ran.
Declared narrowing —
pnpm lintwas run over the diff, not the repo. Threemeasurements, not an assertion:
eslint --print-config content/docs/index.mdxprintsundefined— no flat-configblock matches
.mdx, so all 153 changed content files are outside the lintpopulation entirely. The same command on
scripts/check-docs-single-h1.mjsresolves 2 rules, so the one linted file in this diff really is linted.
--format json. 1 file linted, 0 errors, 0 warnings.eslint.config.mjsnever enables type-awarelinting — its own header states this and the config carries no
parserOptions.projectand no typed rules for any file — so nothing in this diffcan move the verdict on a file it did not touch.
Landing
Docs content plus one gate: nothing is published, so there is no changeset and the
PR carries
skip-changeset. Draft, and expected to stay open for review.Generated by Claude Code