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
{{ message }}
Repository navigation
docs content: the 38 generated reference pages still render two <h1> — the heading comes from a spec JSDoc header via build-docs.ts #12249
38 generated pages under content/docs/references/** still render two <h1>. The heading is not in the MDX — it comes from a JSDoc file header in packages/spec/src/**, copied verbatim into the page by packages/spec/scripts/build-docs.ts. #12236 could not fix them and carved the tree out of its new gate.
pages under content/docs/** with a body-level `# ` heading 195
of which generated (carry the AUTO-GENERATED marker) 38
body `# ` headings inside those 38 pages 43 (41 distinct texts)
Two examples, and where the text really lives:
content/docs/references/data/context-tokens.mdx:11 # Why this lives in `spec`
<- packages/spec/src/data/context-tokens.zod.ts:10 * # Why this lives in `spec`
content/docs/references/kernel/*.mdx # <Name> Protocol
<- the same JSDoc file header, rendered by renderFileDescription()
build-docs.ts line ~422 inserts fileDesc into the page with no heading-level
normalisation:
Hand-editing the 38 MDX files is reverted by the next generator run, and check:docs (pnpm --filter @objectstack/spec check:docs, a required step in Lint & Repo Gates) regenerates the tree and fails on any difference — so the hand-fix would have shipped CI-red.
A — normalise in the generator. Demote every ATX heading in fileDesc by one level at emit time in build-docs.ts. One place, fixes all 38 and every future one, regenerates 38 tracked files. Note ## TypeScript Usage is already emitted at level 2, so a plain #→## demotion lands the file-description headings as its siblings, which is the right level for them.
B — fix the JSDoc sources. 41 headings across packages/spec/src/**. Same output, distributed edit, nothing stops the next one.
A is the contract-first shape: the generator owns the page's heading levels, so the level rule belongs where the page is emitted, not in every comment that feeds it.
Acceptance
no page under content/docs/references/** contains a body-level # heading
pnpm --filter @objectstack/spec check:docs is green with the regenerated tree committed
the content/docs/references/** carve-out is deleted from scripts/check-docs-single-h1.mjs — that gate FAILS when an excluded tree becomes clean, so this is mechanical, not a thing to remember
This one leaves the epic's declared territory: the duplicated heading originates in a packages/spec/src JSDoc file header that build-docs.ts copies verbatim, so the fix is in the generator or in the spec source — and the epic declared apps/docs/** + content/docs/** only. Per the repo's cross-seat rule, anything touching packages/spec belongs to the domain:spec seat, which is its single owner; the epic PM does not dispatch it.
It stays a sub-issue of #12243 because the epic's definition of done says no page renders more than one <h1>, and these 38 pages are part of that count. Treat it as the epic's Blocked-by on the spec lane rather than as epic work.
Note for whoever takes it: hand-editing the generated pages ships CI-red — check:docs regenerates the tree and fails on any hand-edit. The generator is the only place this can be fixed.
File surface: packages/spec/scripts/build-docs.ts (option A — demote ATX headings in fileDesc at emit time), regenerated content/docs/references/** (38 pages, repo tooling only — never hand-edited, check:docs regenerates and fails on hand-edits per the epic PM's note), and scripts/check-docs-single-h1.mjs (delete the content/docs/references/** carve-out — acceptance per triage). No overlap with the other in-flight card ([finding] unregistered dogfood proof tag admin-platform-admin-standing — pre-existing check:liveness warning, recurrence of #10773's class #12216: proof-registry.mts).
Container & model: node scripts/pm/dispatch-gates.mjs --tier packages/spec/scripts/build-docs.ts scripts/check-docs-single-h1.mjs content/docs/references derived at origin/main 5d4d93a: no path-derived mandate. Content judgment (matches triage): JSDoc heading levels move at render time, no schema accept/reject behaviour changes → opus.
Clause-②: no (both limbs no).
Serial constraints: generated-tree card — any merge conflict in content/docs/references/** goes through bash scripts/pm/os-regen-merge.sh, never text-merge.
{
"issue": 12249,
"verdict": "implemented",
"status": "done",
"branch": "claude/issue-12249-single-h1-references",
"pr_number": 12551,
"pr_url": "https://github.com/objectstack-ai/objectstack/pull/12551",
"premise_still_valid": true,
"head_commit": "c4c84fa",
"diff_summary": "42 files, +453/-127. Option A as triaged. packages/spec/scripts/lib/file-description.ts: renderFileDescription now renumbers the rendered fragment so its SHALLOWEST heading sits at ctx.sectionLevel, moving every other heading with it (prose lines only). packages/spec/scripts/build-docs.ts: new PAGE_SECTION_LEVEL = 2 constant, used for its own '## TypeScript Usage' emission and injected as ctx.sectionLevel. packages/spec/scripts/file-description.test.ts: 9 new unit pins + 2 corpus limbs (70 tests total). scripts/check-docs-single-h1.mjs: the content/docs/references/** carve-out deleted, header prose and self-test reworked around the remaining releases/** entry. 38 regenerated pages under content/docs/references/**. No packages/spec/src/** JSDoc header touched (that was option B, rejected).",
"pages_regenerated": 38,
"carve_out_deleted": true,
"checks_run": [
"check:docs-single-h1 EXIT=0 - self-test 17 cases pass; corpus verdict line: 394 page(s) under content/docs/ carry no body-level '# ' heading (1 subtree(s) excluded). Judged population grew 356 -> 394.",
"@objectstack/spec check:docs EXIT=0 - verdict line: 229 generated files in sync with packages/spec",
"@objectstack/spec check:scripts-typecheck EXIT=0 - tsconfig.scripts.json; --listFiles confirms all three edited TS files are in the program",
"vitest scripts/file-description.test.ts EXIT=0 - Tests 70 passed (70)",
"pnpm lint (repo-wide eslint . --no-inline-config) LINT_EXIT=0 - FULL run, not narrowed",
"check:nul-bytes EXIT=0; check:cross-package-test-inputs EXIT=0; check:test-source-alias EXIT=0; check:doc-anchors EXIT=0; check:changeset-gate-self-tests EXIT=0",
"Full family from `node scripts/pm/dispatch-gates.mjs` (no path list passed): 27 root gates all PASS + 5 filtered gates - ran earlier in the branch; the list above is the re-run on the final commit c4c84fa",
"REVERSE VERIFICATION: replacing the withHeadingsAtSectionLevel call with the identity turns 9 of 70 cases red - every level-1 case plus both corpus limbs, the first reporting exactly the 43 headings the issue measured on main. The two cases about what the shift must NOT touch (already-at-level-2, #NoSpace) stay green, as do the 61 pre-existing cases. Mutation confirmed on disk BEFORE the run: anchor grep count 1 -> 0, injected line 0 -> 1, blob 2581ae65 -> 615e62fb. Restore leg proved by `git diff HEAD` empty, trap-guarded with absolute paths. No build involved - the test imports ./lib/file-description by relative path, so vitest transforms source directly and no dist/ can make the reading stale."
],
"premise_notes": "Premise VERIFIED on fresh origin/main (c0f5e8f, branch point). Re-measured independently: 38 pages under content/docs/references/** with a body-level '# ' heading, all 38 carrying the AUTO-GENERATED marker, 43 heading lines, 41 distinct texts - matching the issue body exactly. Rendering every packages/spec/src/**/*.zod.ts module header confirmed the same 38 sources. build-docs.ts line ~422 inserted fileDesc with no heading normalisation, as described. One measurement CHANGED the implementation shape versus the issue's suggested plain '#'->'##' demotion: 26 further described modules already start their headings at level 2 (105 headings), so a blanket +1 would have demoted those correctly-levelled headings and regenerated 26 extra pages. The shipped fix therefore renumbers relative to the block's shallowest heading, which only ever demotes and touches exactly the 38. Max heading depth in any level-1-bearing description is 2, so no overflow past level 6 (a shift that would is a loud throw).",
"new_findings_filed": [],
"out_of_scope_findings": [],
"instrument_disclosures": [
"check:generated reports '1 of 14 artifact(s) stale: api-surface/' with its own reason printed: 'packages/spec/dist holds no .d.ts declarations - the package is not built'. api-surface/ is derived from the BUILT .d.ts (build-api-surface.ts: 'the built dist - run after build') and this worktree has no packages/spec/dist. That limb is NOT MEASURED here, not red. The limb this change owns is green in the same run: 'check:docs content/docs/references/**'. This commit's 42 files contain no packages/spec/src/**, no package.json and no export change, so nothing in this diff can move api-surface/. CI builds and owns it.",
"check:doc-formula-expressions and check:doc-security-posture first exited 1 with 'PREREQUISITE NOT MET' (unbuilt @objectstack/formula / @objectstack/lint) - the gates' own text says 'Nothing was measured... NOT a finding'. That reading was DISCARDED, both packages built, gates re-run: GATE1_EXIT=0, GATE2_EXIT=0.",
"gen:docs itself refused to run against a missing packages/spec/json-schema tree rather than emitting pages from stale input. Ran gen:schema first, then gen:docs. Working as designed; recorded because the first regeneration attempt produced no output.",
"Two FileDescriptionContext literals were missed by my first edit pass. vitest stayed GREEN (types are erased at runtime); check:scripts-typecheck caught them as TS2345. Fixed and both re-run before the final commit - so the reported 70/70 is on the corrected tree.",
"LABEL WRITE USED THE DECLARED FALLBACK, not the additive endpoint. The additive POST /issues/12551/labels returned HTTP 403 ('GitHub access is not enabled for this session') from this dev seat, so skip-changeset was applied via read -> union -> whole-set write through MCP issue_write. Read-back after the labeler bot settled confirms labels = ['size/l','skip-changeset'] - the bot's size/l was preserved, nothing erased."
],
"summary": "Fixed in the generator per option A. renderFileDescription renumbers a module header's headings so the fragment's shallowest one sits at the page's section level (2), which build-docs.ts now names as PAGE_SECTION_LEVEL and injects as context - the same seam, and the same argument, as fromCategory (#6484): a .zod.ts header cannot know what page it will be embedded in. The shift is scoped to prose lines using the renderer's existing classifyLines model, so a '# ' inside a fenced or indented code block is untouched; that is why it lives beside classifyLines rather than as a regex over the emitted string in build-docs.ts, since a fence-blind pass corrupts working snippets to satisfy a rule about HTML those lines never produce. Regenerated with repo tooling only: exactly 38 pages, and every one of the 89 changed lines is an ATX heading. The references carve-out is deleted from scripts/check-docs-single-h1.mjs and its self-test reworked so the dead-exclusion limb now guards the remaining releases/** entry; the gate's judged population grows 356 -> 394. No changeset: this PR publishes nothing (packages/spec 'files' excludes scripts/), so it carries skip-changeset instead. Draft PR, no auto-merge; CI convergence is the PM's to review.",
"open_questions": []
}
Activity
os-zhuang commentedon Aug 25, 2026
Routing, from the epic PM for #12243.
This one leaves the epic's declared territory: the duplicated heading originates in a
packages/spec/srcJSDoc file header thatbuild-docs.tscopies verbatim, so the fix is in the generator or in the spec source — and the epic declaredapps/docs/**+content/docs/**only. Per the repo's cross-seat rule, anything touchingpackages/specbelongs to thedomain:specseat, which is its single owner; the epic PM does not dispatch it.It stays a sub-issue of #12243 because the epic's definition of done says no page renders more than one
<h1>, and these 38 pages are part of that count. Treat it as the epic'sBlocked-byon the spec lane rather than as epic work.Note for whoever takes it: hand-editing the generated pages ships CI-red —
check:docsregenerates the tree and fails on any hand-edit. The generator is the only place this can be fixed.huangyiirene commentedon Aug 26, 2026
Claim:
session_01E5LFCYBJ3q2s6yW6oMLxwy(domain:spec PM seat R7, seat post #6017) — branchclaude/issue-12249-single-h1-references.packages/spec/scripts/build-docs.ts(option A — demote ATX headings infileDescat emit time), regeneratedcontent/docs/references/**(38 pages, repo tooling only — never hand-edited,check:docsregenerates and fails on hand-edits per the epic PM's note), andscripts/check-docs-single-h1.mjs(delete thecontent/docs/references/**carve-out — acceptance per triage). No overlap with the other in-flight card ([finding] unregistered dogfood proof tagadmin-platform-admin-standing— pre-existing check:liveness warning, recurrence of #10773's class #12216: proof-registry.mts).packages/**outside its territory; the sub-issue link stays for the epic's checklist only.node scripts/pm/dispatch-gates.mjs --tier packages/spec/scripts/build-docs.ts scripts/check-docs-single-h1.mjs content/docs/referencesderived at origin/main5d4d93a: no path-derived mandate. Content judgment (matches triage): JSDoc heading levels move at render time, no schema accept/reject behaviour changes → opus.content/docs/references/**goes throughbash scripts/pm/os-regen-merge.sh, never text-merge.Generated by Claude Code
huangyiirene commentedon Aug 26, 2026
os-dev-report
{ "issue": 12249, "verdict": "implemented", "status": "done", "branch": "claude/issue-12249-single-h1-references", "pr_number": 12551, "pr_url": "https://github.com/objectstack-ai/objectstack/pull/12551", "premise_still_valid": true, "head_commit": "c4c84fa", "diff_summary": "42 files, +453/-127. Option A as triaged. packages/spec/scripts/lib/file-description.ts: renderFileDescription now renumbers the rendered fragment so its SHALLOWEST heading sits at ctx.sectionLevel, moving every other heading with it (prose lines only). packages/spec/scripts/build-docs.ts: new PAGE_SECTION_LEVEL = 2 constant, used for its own '## TypeScript Usage' emission and injected as ctx.sectionLevel. packages/spec/scripts/file-description.test.ts: 9 new unit pins + 2 corpus limbs (70 tests total). scripts/check-docs-single-h1.mjs: the content/docs/references/** carve-out deleted, header prose and self-test reworked around the remaining releases/** entry. 38 regenerated pages under content/docs/references/**. No packages/spec/src/** JSDoc header touched (that was option B, rejected).", "pages_regenerated": 38, "carve_out_deleted": true, "checks_run": [ "check:docs-single-h1 EXIT=0 - self-test 17 cases pass; corpus verdict line: 394 page(s) under content/docs/ carry no body-level '# ' heading (1 subtree(s) excluded). Judged population grew 356 -> 394.", "@objectstack/spec check:docs EXIT=0 - verdict line: 229 generated files in sync with packages/spec", "@objectstack/spec check:scripts-typecheck EXIT=0 - tsconfig.scripts.json; --listFiles confirms all three edited TS files are in the program", "vitest scripts/file-description.test.ts EXIT=0 - Tests 70 passed (70)", "pnpm lint (repo-wide eslint . --no-inline-config) LINT_EXIT=0 - FULL run, not narrowed", "check:nul-bytes EXIT=0; check:cross-package-test-inputs EXIT=0; check:test-source-alias EXIT=0; check:doc-anchors EXIT=0; check:changeset-gate-self-tests EXIT=0", "Full family from `node scripts/pm/dispatch-gates.mjs` (no path list passed): 27 root gates all PASS + 5 filtered gates - ran earlier in the branch; the list above is the re-run on the final commit c4c84fa", "REVERSE VERIFICATION: replacing the withHeadingsAtSectionLevel call with the identity turns 9 of 70 cases red - every level-1 case plus both corpus limbs, the first reporting exactly the 43 headings the issue measured on main. The two cases about what the shift must NOT touch (already-at-level-2, #NoSpace) stay green, as do the 61 pre-existing cases. Mutation confirmed on disk BEFORE the run: anchor grep count 1 -> 0, injected line 0 -> 1, blob 2581ae65 -> 615e62fb. Restore leg proved by `git diff HEAD` empty, trap-guarded with absolute paths. No build involved - the test imports ./lib/file-description by relative path, so vitest transforms source directly and no dist/ can make the reading stale." ], "premise_notes": "Premise VERIFIED on fresh origin/main (c0f5e8f, branch point). Re-measured independently: 38 pages under content/docs/references/** with a body-level '# ' heading, all 38 carrying the AUTO-GENERATED marker, 43 heading lines, 41 distinct texts - matching the issue body exactly. Rendering every packages/spec/src/**/*.zod.ts module header confirmed the same 38 sources. build-docs.ts line ~422 inserted fileDesc with no heading normalisation, as described. One measurement CHANGED the implementation shape versus the issue's suggested plain '#'->'##' demotion: 26 further described modules already start their headings at level 2 (105 headings), so a blanket +1 would have demoted those correctly-levelled headings and regenerated 26 extra pages. The shipped fix therefore renumbers relative to the block's shallowest heading, which only ever demotes and touches exactly the 38. Max heading depth in any level-1-bearing description is 2, so no overflow past level 6 (a shift that would is a loud throw).", "new_findings_filed": [], "out_of_scope_findings": [], "instrument_disclosures": [ "check:generated reports '1 of 14 artifact(s) stale: api-surface/' with its own reason printed: 'packages/spec/dist holds no .d.ts declarations - the package is not built'. api-surface/ is derived from the BUILT .d.ts (build-api-surface.ts: 'the built dist - run after build') and this worktree has no packages/spec/dist. That limb is NOT MEASURED here, not red. The limb this change owns is green in the same run: 'check:docs content/docs/references/**'. This commit's 42 files contain no packages/spec/src/**, no package.json and no export change, so nothing in this diff can move api-surface/. CI builds and owns it.", "check:doc-formula-expressions and check:doc-security-posture first exited 1 with 'PREREQUISITE NOT MET' (unbuilt @objectstack/formula / @objectstack/lint) - the gates' own text says 'Nothing was measured... NOT a finding'. That reading was DISCARDED, both packages built, gates re-run: GATE1_EXIT=0, GATE2_EXIT=0.", "gen:docs itself refused to run against a missing packages/spec/json-schema tree rather than emitting pages from stale input. Ran gen:schema first, then gen:docs. Working as designed; recorded because the first regeneration attempt produced no output.", "Two FileDescriptionContext literals were missed by my first edit pass. vitest stayed GREEN (types are erased at runtime); check:scripts-typecheck caught them as TS2345. Fixed and both re-run before the final commit - so the reported 70/70 is on the corrected tree.", "LABEL WRITE USED THE DECLARED FALLBACK, not the additive endpoint. The additive POST /issues/12551/labels returned HTTP 403 ('GitHub access is not enabled for this session') from this dev seat, so skip-changeset was applied via read -> union -> whole-set write through MCP issue_write. Read-back after the labeler bot settled confirms labels = ['size/l','skip-changeset'] - the bot's size/l was preserved, nothing erased." ], "summary": "Fixed in the generator per option A. renderFileDescription renumbers a module header's headings so the fragment's shallowest one sits at the page's section level (2), which build-docs.ts now names as PAGE_SECTION_LEVEL and injects as context - the same seam, and the same argument, as fromCategory (#6484): a .zod.ts header cannot know what page it will be embedded in. The shift is scoped to prose lines using the renderer's existing classifyLines model, so a '# ' inside a fenced or indented code block is untouched; that is why it lives beside classifyLines rather than as a regex over the emitted string in build-docs.ts, since a fence-blind pass corrupts working snippets to satisfy a rule about HTML those lines never produce. Regenerated with repo tooling only: exactly 38 pages, and every one of the 89 changed lines is an ATX heading. The references carve-out is deleted from scripts/check-docs-single-h1.mjs and its self-test reworked so the dead-exclusion limb now guards the remaining releases/** entry; the gate's judged population grows 356 -> 394. No changeset: this PR publishes nothing (packages/spec 'files' excludes scripts/), so it carries skip-changeset instead. Draft PR, no auto-merge; CI convergence is the PM's to review.", "open_questions": [] }Generated by Claude Code
spec: keep the package-lifecycle header inside the description corpus…
Bind published response contracts for the 17 unbound client-SDK metho…
Retire the paper metadata-customization protocol with its full coupli…