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
× every registered dashboard has a section on the page
AssertionError: content/docs/analytics/dashboards.mdx has no section for: Sales Performance.
Add a `## label` section listing its tiles (the heading must carry the dashboard's own
`label`, emoji prefix aside) — a dashboard nobody documents is one nobody finds.
Test Files 1 failed (1) Tests 1 failed | 33 passed (34)
{#id} 与 [#id] 两种写法都是这条红。
为什么值得记
报错信息指向错误的方向。 它说「没有 Sales Performance 这一节」,而那一节明明在;真正的原因是标题多了一个 id 后缀。下一个人会去翻标题文字,不会想到守卫的正则。
Unchanged, and still the function both consumers go through (:643 building the heading set, :659 matching a section). It strips only leading non-letters, so a fumadocs explicit id trailing the heading is carried into the label and the match fails. Premise stands, 18 days on.
The fix is S-level and mechanical: one regex plus a case in the existing suite. Correctness is decided by the gate farm, not by judgement.
It is not holdable. A hold needs a Restart-when: something can fire on, and the honest condition here — "when somebody wants to add an explicit id to a dashboards heading" — is not machine-readable. Per the discipline, a card with no mechanism that can wake it does not get held.
For whoever takes it: the card's claim is that dashboards pages can therefore never gain a [#id]. Verify that consequence rather than inheriting it — the fix should be pinned by a case that fails before the change, which means writing a heading with a trailing explicit id and watching the current matcher miss it.
Claim: PM loop round 2
Session: session_01HoTmVdM6mN8HQWQ4pujEsN
Branch: claude/issue-935-heading-label-explicit-id
Worktree: hotcrm-issue-935
Domain: (hotcrm has no domain:* taxonomy — repo-wide seat, objectstack#10282)
File surface: test/docs-drift.test.ts (the headingLabel helper at :588 and whatever its two consumers at :643 / :659 need), plus a content/docs page only if the fix requires a real explicit-id heading to pin against — name it in the report if so. Stop on breach; explain in the report.
Container & model: S (mechanical — a regex and a pinned case; correctness decided by the gate farm), mode:subagent, model: opus
Clause-②: no — a repo-local test helper.
Serial constraints cleared: R2 siblings #1265 (src/objects/knowledge_article.hook.ts) and #1268 (test/script-main-guard.test.ts) do not intersect. ⚠️#1003 is serialised behind this card — it also lands in test/docs-drift.test.ts and is deliberately not dispatched in this batch.
Premise re-verified against origin/main @ ac02bf15: headingLabel is still h.replace(/^[^A-Za-z]+/, '').trim() at :588, still the single path both consumers go through. Grading rationale and the caveat about verifying the "can never gain a [#id]" consequence are in the grading comment above.
{
"issue": 935,
"status": "done",
"branch": "claude/issue-935-heading-label-explicit-id",
"pr": "https://github.com/objectstack-ai/hotcrm/pull/1273",
"premise_still_valid": true,
"summary": "Premise re-verified on origin/main @ ac02bf15 and the card's stated consequence reproduced, not inherited: appending [#sales-performance] to the real `## 📈 Sales Performance` heading in content/docs/analytics/dashboards.mdx turned the drift suite red with the misleading 'has no section for: Sales Performance'. headingLabel() now strips a trailing explicit id before the leading-emoji strip, using fumadocs' own regex copied verbatim from fumadocs-core@16.9.3 dist/mdx-plugins/remark-heading.js (the version apps/docs pins) rather than the [^\\]]+ approximation the card suggested — upstream uses a lazy [^]+? so `Foo [#a] [#b]` strips from the FIRST bracket, and the [^\\]]+ form would disagree with the rendered page. Confirmed the fumadocs spelling is [#id] and never {#id}: content/docs already ships two live [#id] headings (ai-copilot/skills.mdx `### 🚦 Case Triage [#case-triage]`, guides/import-and-export.mdx `### Scheduled export … [#scheduled-export]`), both `###`, which sectionsOf() (`## ` only) never saw. No content/docs page was changed — the pin bites without one; the page mutation was an experiment, restored both times.",
"tests": "All on head 97fde990. RED LEG (helper body reverted to `h.replace(/^[^A-Za-z]+/, '').trim()`, pins kept; mutation confirmed on disk by grepping for BOTH the injected and the removed text before the run — 'fixed body present after: 0 / original body present after: 1' — and restored by a trap on EXIT INT TERM, verified byte-identical afterwards): `test/heading-label.test.ts (3 tests | 2 failed)` — × a section heading still resolves to its label when it carries an explicit id (#935); × does not collapse headings whose visible text differs (#935); 10 probes reported e.g. \"📈 Sales Performance [#sales-performance]\" → \"Sales Performance [#sales-performance]\" (want \"Sales Performance\"); `Tests 2 failed | 77 passed (79)`. GREEN LEG (helper as committed): `Test Files 2 passed (2)` · `Tests 79 passed (79)`. test/docs-drift.test.ts is 76 passed in BOTH legs — the drift suite could never have caught this, which is why the pin is a separate property. END-TO-END, same page mutation run twice with the same on-disk confirmation and trap: before the fix VITEST_EXIT=1 `Tests 1 failed | 75 passed (76)`; after the fix VITEST_EXIT=0 `Tests 78 passed (78)` — the card's 'can never gain a [#id]' consequence held and is now lifted. Every heavy run went through scripts/pm/os-verify-lock.sh; exit codes captured before any pipe (`cmd > file 2>&1; EXIT=$?`), never through tail.",
"gates": {
"pnpm test": "Test Files 122 passed (122) · Tests 2868 passed | 2 skipped (2870)",
"pnpm typecheck": "exit 0",
"scripts/check-source-hygiene.mjs": "✓ source hygiene clean (incl. ✓ no source file over 100KB)",
"scripts/check-source-token-ratchet.mjs": "✓ source token ratchet clean — 82,220 / 38,549 / 134,695, byte-for-byte identical to the pre-change reading, as PM expected for a test/-only diff",
"scripts/check-stackblitz-lock.mjs": "package-lock.json is in sync with package.json (v3).",
"pnpm validate": "exit 0",
"pnpm lint": "exit 0",
"pnpm lint:i18n-gate": "✓ i18n lint gate: 0 `i18n/missing-*` issues",
"pnpm build": "Artifact: dist/objectstack.json (1980.6 KB)",
"changeset status --since=ac02bf15": "exit 0 · NO packages to be bumped (the changeset-check ADDED count is 1)"
},
"files_changed": [
"test/helpers/heading-label.ts (new) — EXPLICIT_ID + headingLabel with the rationale",
"test/docs-drift.test.ts (modified) — imports the helper instead of defining it; both consumers unchanged; 100,190 → 100,429 bytes",
"test/heading-label.test.ts (new) — the three pins",
".changeset/heading-label-explicit-id.md (new) — empty frontmatter"
],
"deviations": [
"FILE SURFACE: the ruling scoped this to test/docs-drift.test.ts (plus a content page if needed). Two new test files were added instead, because the fix does not fit in the declared file. That file is 100,190 bytes on main against the 100KB (102,400) cap in check-source-hygiene.mjs — 97.8% full, 2,210 bytes of headroom. Written in place, the fix plus rationale plus pins measured 107,678 bytes and failed hygiene outright: '✗ no source file over 100KB — 1 violation(s) / test/docs-drift.test.ts:0 105KB / → split the file'. This is #1196 biting exactly as that card predicted; the resolution #1196 records for #1187 (ship as a sibling test file) is the one taken. test/helpers/repo-root.ts (760-byte single-export module) and action-sandbox.ts ↔ action-sandbox.test.ts are the in-repo precedents. Net effect on the declared file is +239 bytes, so the headroom is preserved rather than spent — which matters because #1003 is serialised behind this card into the same file.",
"REGEX SHAPE: the card proposed /\\s*\\[#[^\\]]+\\]\\s*$/. Not used — fumadocs' own lazy [^]+? was copied verbatim instead, because the two disagree on `Foo [#a] [#b]`.",
"CHANGESET ROUTE: empty-frontmatter changeset, not the skip-changeset label. Both are sanctioned by changeset-check.yml; the changeset is reviewable in the diff, cannot be wiped by a label race, and keeps the `changeset status` validation step running (the label skips the whole job). Precedent: .changeset/script-entry-point-guard.md. No label was applied.",
"NO content/docs PAGE WAS ADDED OR CHANGED — the pin bites without a real explicit-id heading, so none was needed.",
"MEASUREMENT NOTE (not a defect): `changeset status --since=<sha>` exits 1 against an uncommitted tree and 0 once committed. The first reading was taken pre-commit and was an artefact of that, not of the changeset."
],
"open_questions": [],
"out_of_scope_findings": [
"filed as #1272: headingLabel() maps every CJK-only heading to the empty string — 4 of the 9 `## ` headings on each zh dashboards page (五个仪表盘 / 你可以改什么 / 数字是从哪里来的 / 提示 and the zh-Hant equivalents) resolve to '' and collapse to one label. Dormant today (no dashboard label is empty or CJK) but the same 'two different headings, one label' hazard one step further along, and it pre-dates this change. Filed rather than folded in: choosing among the three candidate fixes is a ruling, not a mechanical edit. Labelled `finding` + `documentation`, unassigned, no pm:queue.",
"not filed, already open as #1196: the test/docs-drift.test.ts size cap. This PR is a second data point on it rather than a new discovery — noted in the PR body."
]
}
(Replaces the previous comment, which was posted with the os-dev-report HTML-comment marker on its first line. GitHub's body sanitizer ate the marker on write — read back, that comment now starts with the bare code fence, so it is invisible to a marker scan. Same report, marker as literal text. The same sanitizer also ate two short angle-bracket fragments from the first copy; both are spelled out here.)
{
"issue": 935,
"status": "done",
"branch": "claude/issue-935-heading-label-explicit-id",
"pr": "https://github.com/objectstack-ai/hotcrm/pull/1273",
"premise_still_valid": true,
"summary": "Premise re-verified on origin/main @ ac02bf15 and the card's stated consequence reproduced, not inherited: appending [#sales-performance] to the real `## 📈 Sales Performance` heading in content/docs/analytics/dashboards.mdx turned the drift suite red with the misleading 'has no section for: Sales Performance'. headingLabel() now strips a trailing explicit id before the leading-emoji strip, using fumadocs' own regex copied verbatim from fumadocs-core@16.9.3 dist/mdx-plugins/remark-heading.js (the version apps/docs pins) rather than the card's suggested approximation — upstream uses a lazy any-char body, so `Foo [#a] [#b]` strips from the FIRST bracket, where a negated-class body would strip only the last and disagree with the rendered page. Confirmed the fumadocs spelling is [#id] and never {#id}: content/docs already ships two live [#id] headings (ai-copilot/skills.mdx `### 🚦 Case Triage [#case-triage]`, guides/import-and-export.mdx `### Scheduled export … [#scheduled-export]`), both `###`, which sectionsOf() (`## ` only) never saw. No content/docs page was changed — the pin bites without one; the page mutation was an experiment, restored both times.",
"tests": "All on head 97fde990. RED LEG (helper body reverted to the leading-strip-only form, pins kept; mutation confirmed on disk by grepping for BOTH the injected and the removed text before the run — 'fixed body present after: 0 / original body present after: 1' — and restored by a trap on EXIT INT TERM, verified byte-identical afterwards): `test/heading-label.test.ts (3 tests | 2 failed)` — × a section heading still resolves to its label when it carries an explicit id (#935); × does not collapse headings whose visible text differs (#935); 10 probes reported e.g. '📈 Sales Performance [#sales-performance]' → 'Sales Performance [#sales-performance]' (want 'Sales Performance'); `Tests 2 failed | 77 passed (79)`. GREEN LEG (helper as committed): `Test Files 2 passed (2)` · `Tests 79 passed (79)`. test/docs-drift.test.ts is 76 passed in BOTH legs — the drift suite could never have caught this, which is why the pin is a separate property. END-TO-END, same page mutation run twice with the same on-disk confirmation and trap: before the fix VITEST_EXIT=1 `Tests 1 failed | 75 passed (76)`; after the fix VITEST_EXIT=0 `Tests 78 passed (78)` — the card's 'can never gain a [#id]' consequence held and is now lifted. Every heavy run went through scripts/pm/os-verify-lock.sh; exit codes captured before any pipe (redirect to a file, then read $? , then tail), never through a pipe.",
"gates": {
"pnpm test": "Test Files 122 passed (122) · Tests 2868 passed | 2 skipped (2870)",
"pnpm typecheck": "exit 0",
"scripts/check-source-hygiene.mjs": "✓ source hygiene clean (incl. ✓ no source file over 100KB)",
"scripts/check-source-token-ratchet.mjs": "✓ source token ratchet clean — 82,220 / 38,549 / 134,695, byte-for-byte identical to the pre-change reading, as PM expected for a test/-only diff",
"scripts/check-stackblitz-lock.mjs": "package-lock.json is in sync with package.json (v3).",
"pnpm validate": "exit 0",
"pnpm lint": "exit 0",
"pnpm lint:i18n-gate": "✓ i18n lint gate: 0 `i18n/missing-*` issues",
"pnpm build": "Artifact: dist/objectstack.json (1980.6 KB)",
"changeset status": "exit 0 · NO packages to be bumped (the changeset-check ADDED count is 1)"
},
"files_changed": [
"test/helpers/heading-label.ts (new) — EXPLICIT_ID + headingLabel with the rationale",
"test/docs-drift.test.ts (modified) — imports the helper instead of defining it; both consumers unchanged; 100,190 → 100,429 bytes",
"test/heading-label.test.ts (new) — the three pins",
".changeset/heading-label-explicit-id.md (new) — empty frontmatter"
],
"deviations": [
"FILE SURFACE: the ruling scoped this to test/docs-drift.test.ts (plus a content page if needed). Two new test files were added instead, because the fix does not fit in the declared file. That file is 100,190 bytes on main against the 100KB (102,400) cap in check-source-hygiene.mjs — 97.8% full, 2,210 bytes of headroom. Written in place, the fix plus rationale plus pins measured 107,678 bytes and failed hygiene outright: '✗ no source file over 100KB — 1 violation(s) / test/docs-drift.test.ts:0 105KB / → split the file'. This is #1196 biting exactly as that card predicted; the resolution #1196 records for #1187 (ship as a sibling test file) is the one taken. test/helpers/repo-root.ts (760-byte single-export module) and action-sandbox.ts + action-sandbox.test.ts are the in-repo precedents. Net effect on the declared file is +239 bytes, so the headroom is preserved rather than spent — which matters because #1003 is serialised behind this card into the same file.",
"REGEX SHAPE: the card proposed a negated-character-class body. Not used — fumadocs' own lazy any-char body was copied verbatim instead, because the two disagree on `Foo [#a] [#b]`.",
"CHANGESET ROUTE: empty-frontmatter changeset, not the skip-changeset label. Both are sanctioned by changeset-check.yml; the changeset is reviewable in the diff, cannot be wiped by a label race, and keeps the `changeset status` validation step running (the label skips the whole job). Precedent: .changeset/script-entry-point-guard.md. No label was applied.",
"NO content/docs PAGE WAS ADDED OR CHANGED — the pin bites without a real explicit-id heading, so none was needed.",
"MEASUREMENT NOTE (not a defect): `changeset status --since=SHA` exits 1 against an uncommitted tree and 0 once committed. The first reading was taken pre-commit and was an artefact of that, not of the changeset.",
"SANITIZER: the PR body's first version had the fumadocs regex's named-group name eaten inside a fenced code block, leaving a regex that was not the one being claimed. Corrected — the PR now shows the group unnamed and says why, and points at test/helpers/heading-label.ts for the verbatim copy."
],
"open_questions": [],
"out_of_scope_findings": [
"filed as #1272: headingLabel() maps every CJK-only heading to the empty string — 4 of the 9 `## ` headings on each zh dashboards page (五个仪表盘 / 你可以改什么 / 数字是从哪里来的 / 提示 and the zh-Hant equivalents) resolve to the empty string and collapse to one label. Dormant today (no dashboard label is empty or CJK) but the same 'two different headings, one label' hazard one step further along, and it pre-dates this change. Filed rather than folded in: choosing among the three candidate fixes is a ruling, not a mechanical edit. Labelled `finding` + `documentation`, unassigned, no pm:queue.",
"not filed, already open as #1196: the test/docs-drift.test.ts size cap. This PR is a second data point on it rather than a new discovery — noted in the PR body."
]
}
Written in place, the fix plus rationale plus pins measured 107,678 and failed hygiene outright. My ruling was unimplementable and I did not know it when I wrote it.#1196 predicted exactly this ("97.8% of the cap — the next rule added turns the gate red"), and the resolution it records is the one taken.
The part that shows judgement rather than convenience: the net effect on the declared file is +6/−2 lines, ~+239 bytes, so the headroom is preserved rather than spent. That matters concretely — #1003 is serialised behind this card into the same file, and a fix that consumed the remaining 2,210 bytes would have handed the next dev a file that cannot be edited at all.
The regex was read, not recalled — and the difference is load-bearing
The card proposed a negated-character-class body. The dev copied fumadocs' own regex verbatim from fumadocs-core@16.9.3, the version apps/docs pins, and documented the two ways a from-memory version goes wrong:
[#id], never {#id}. A {…} in an .mdx heading parses as a JSX expression and takes the whole page's build down with Could not parse expression with acorn. That was measured, not assumed. It is not an uglier second spelling; it is a page that cannot ship.
The body is [^]+? (lazy), not [^\]]+. On Foo [#a] [#b] fumadocs strips from the first bracket; a negated class strips only the last and disagrees with the page a reader actually sees.
My assumption 3 said I might be wrong about the spelling and asked for ground truth. The answer came back with two live [#id] headings already in content/docs — both ###, which sectionsOf() (## only) never looked at, which is why nobody had hit this.
Three pins, and the third is the one I would not have asked for
An id-bearing heading resolves to the same label as one without — red on the old helper, green on the new.
Headings whose visible text differs do not collapse. Four counter-examples, each failing a different clause: [see #935] (no #), a markdown link (no [# at all), [#a] and more (not at the end), and a backticked `[#x]` (renders as literal code). Plus the intended-behaviour assertion in both directions — two headings differing only in id must resolve together, but one id shared between two different headings must not merge them.
The change is a no-op on the pages as they stand — the old implementation is kept as OLD() and every current heading must resolve identically through both.
I asked for #2 in the dispatch (a guard must not go green by getting more permissive). #3 is the dev's own addition and it is the right instinct: a permissiveness fix is only safe if it changes nothing that already worked, and now that is asserted rather than hoped.
The vacuity guard deserves naming too. The probes are built by matching real page headings against real registered dashboards, so a renamed section or an emptied registry would leave the pin green over nothing. The test asserts the probes cover every dashboard — the state a pin must never silently reach.
The consequence was reproduced, not inherited
I asked for this specifically. Appending [#sales-performance] to the real ## 📈 Sales Performance heading turned the drift suite red with has no section for: Sales Performance — about a page whose section was sitting right there. The message points at the heading text while the matcher is at fault, which is why the next reader re-reads the words and never suspects the guard. Fix applied, same mutation, suite green.
And the observation that makes the separate pin necessary: test/docs-drift.test.ts reports 76 passed in both legs of the ablation. The drift suite could never have caught this itself.
#1272 — filed, and it is exactly the hazard I warned about, one step further along
headingLabel() maps every CJK-only heading to the empty string — 4 of the 9 ## headings on each zh dashboards page collapse to one label. Dormant today (no dashboard label is empty or CJK) and it pre-dates this change.
My dispatch warned against making headingLabel collapse two genuinely different headings. The dev checked, found the property already violated on a different axis, and filed it rather than folding a ruling-shaped fix into a mechanical card. Correct call — choosing among the candidate fixes is a judgement, and this PR's own pin #2 is what will hold whoever takes it honest.
Activity
os-warren commentedon Aug 24, 2026
First-touch grading — promote →
pm:queue.findingdropped in the same stroke.Graded by the
repo:hotcrmPM seat under its standing authority (maintainer 2026-08-20, 「finding 你可以自己定级」).Premise re-verified against
origin/main@53cf8674— measured now, not carried forward from the card or from #1106's 2026-08-11 sweep:Unchanged, and still the function both consumers go through (
:643building the heading set,:659matching a section). It strips only leading non-letters, so a fumadocs explicit id trailing the heading is carried into the label and the match fails. Premise stands, 18 days on.Why promote rather than hold or close:
op/neqare not the spellingsrecord:related_listaccepts #1248's advisory build warnings, objectstack#11529's 50-warning cap).Restart-when:something can fire on, and the honest condition here — "when somebody wants to add an explicit id to a dashboards heading" — is not machine-readable. Per the discipline, a card with no mechanism that can wake it does not get held.For whoever takes it: the card's claim is that dashboards pages can therefore never gain a
[#id]. Verify that consequence rather than inheriting it — the fix should be pinned by a case that fails before the change, which means writing a heading with a trailing explicit id and watching the current matcher miss it.Generated by Claude Code
os-warren commentedon Aug 24, 2026
Claim: PM loop round 2⚠️ #1003 is serialised behind this card — it also lands in
Session:
session_01HoTmVdM6mN8HQWQ4pujEsNBranch:
claude/issue-935-heading-label-explicit-idWorktree:
hotcrm-issue-935Domain: (hotcrm has no
domain:*taxonomy — repo-wide seat, objectstack#10282)File surface:
test/docs-drift.test.ts(theheadingLabelhelper at:588and whatever its two consumers at:643/:659need), plus acontent/docspage only if the fix requires a real explicit-id heading to pin against — name it in the report if so. Stop on breach; explain in the report.Container & model:
S(mechanical — a regex and a pinned case; correctness decided by the gate farm),mode:subagent,model: opusClause-②: no — a repo-local test helper.
Serial constraints cleared: R2 siblings #1265 (
src/objects/knowledge_article.hook.ts) and #1268 (test/script-main-guard.test.ts) do not intersect.test/docs-drift.test.tsand is deliberately not dispatched in this batch.Premise re-verified against
origin/main@ac02bf15:headingLabelis stillh.replace(/^[^A-Za-z]+/, '').trim()at:588, still the single path both consumers go through. Grading rationale and the caveat about verifying the "can never gain a[#id]" consequence are in the grading comment above.Generated by Claude Code
os-warren commentedon Aug 24, 2026
{ "issue": 935, "status": "done", "branch": "claude/issue-935-heading-label-explicit-id", "pr": "https://github.com/objectstack-ai/hotcrm/pull/1273", "premise_still_valid": true, "summary": "Premise re-verified on origin/main @ ac02bf15 and the card's stated consequence reproduced, not inherited: appending [#sales-performance] to the real `## 📈 Sales Performance` heading in content/docs/analytics/dashboards.mdx turned the drift suite red with the misleading 'has no section for: Sales Performance'. headingLabel() now strips a trailing explicit id before the leading-emoji strip, using fumadocs' own regex copied verbatim from fumadocs-core@16.9.3 dist/mdx-plugins/remark-heading.js (the version apps/docs pins) rather than the [^\\]]+ approximation the card suggested — upstream uses a lazy [^]+? so `Foo [#a] [#b]` strips from the FIRST bracket, and the [^\\]]+ form would disagree with the rendered page. Confirmed the fumadocs spelling is [#id] and never {#id}: content/docs already ships two live [#id] headings (ai-copilot/skills.mdx `### 🚦 Case Triage [#case-triage]`, guides/import-and-export.mdx `### Scheduled export … [#scheduled-export]`), both `###`, which sectionsOf() (`## ` only) never saw. No content/docs page was changed — the pin bites without one; the page mutation was an experiment, restored both times.", "tests": "All on head 97fde990. RED LEG (helper body reverted to `h.replace(/^[^A-Za-z]+/, '').trim()`, pins kept; mutation confirmed on disk by grepping for BOTH the injected and the removed text before the run — 'fixed body present after: 0 / original body present after: 1' — and restored by a trap on EXIT INT TERM, verified byte-identical afterwards): `test/heading-label.test.ts (3 tests | 2 failed)` — × a section heading still resolves to its label when it carries an explicit id (#935); × does not collapse headings whose visible text differs (#935); 10 probes reported e.g. \"📈 Sales Performance [#sales-performance]\" → \"Sales Performance [#sales-performance]\" (want \"Sales Performance\"); `Tests 2 failed | 77 passed (79)`. GREEN LEG (helper as committed): `Test Files 2 passed (2)` · `Tests 79 passed (79)`. test/docs-drift.test.ts is 76 passed in BOTH legs — the drift suite could never have caught this, which is why the pin is a separate property. END-TO-END, same page mutation run twice with the same on-disk confirmation and trap: before the fix VITEST_EXIT=1 `Tests 1 failed | 75 passed (76)`; after the fix VITEST_EXIT=0 `Tests 78 passed (78)` — the card's 'can never gain a [#id]' consequence held and is now lifted. Every heavy run went through scripts/pm/os-verify-lock.sh; exit codes captured before any pipe (`cmd > file 2>&1; EXIT=$?`), never through tail.", "gates": { "pnpm test": "Test Files 122 passed (122) · Tests 2868 passed | 2 skipped (2870)", "pnpm typecheck": "exit 0", "scripts/check-source-hygiene.mjs": "✓ source hygiene clean (incl. ✓ no source file over 100KB)", "scripts/check-source-token-ratchet.mjs": "✓ source token ratchet clean — 82,220 / 38,549 / 134,695, byte-for-byte identical to the pre-change reading, as PM expected for a test/-only diff", "scripts/check-stackblitz-lock.mjs": "package-lock.json is in sync with package.json (v3).", "pnpm validate": "exit 0", "pnpm lint": "exit 0", "pnpm lint:i18n-gate": "✓ i18n lint gate: 0 `i18n/missing-*` issues", "pnpm build": "Artifact: dist/objectstack.json (1980.6 KB)", "changeset status --since=ac02bf15": "exit 0 · NO packages to be bumped (the changeset-check ADDED count is 1)" }, "files_changed": [ "test/helpers/heading-label.ts (new) — EXPLICIT_ID + headingLabel with the rationale", "test/docs-drift.test.ts (modified) — imports the helper instead of defining it; both consumers unchanged; 100,190 → 100,429 bytes", "test/heading-label.test.ts (new) — the three pins", ".changeset/heading-label-explicit-id.md (new) — empty frontmatter" ], "deviations": [ "FILE SURFACE: the ruling scoped this to test/docs-drift.test.ts (plus a content page if needed). Two new test files were added instead, because the fix does not fit in the declared file. That file is 100,190 bytes on main against the 100KB (102,400) cap in check-source-hygiene.mjs — 97.8% full, 2,210 bytes of headroom. Written in place, the fix plus rationale plus pins measured 107,678 bytes and failed hygiene outright: '✗ no source file over 100KB — 1 violation(s) / test/docs-drift.test.ts:0 105KB / → split the file'. This is #1196 biting exactly as that card predicted; the resolution #1196 records for #1187 (ship as a sibling test file) is the one taken. test/helpers/repo-root.ts (760-byte single-export module) and action-sandbox.ts ↔ action-sandbox.test.ts are the in-repo precedents. Net effect on the declared file is +239 bytes, so the headroom is preserved rather than spent — which matters because #1003 is serialised behind this card into the same file.", "REGEX SHAPE: the card proposed /\\s*\\[#[^\\]]+\\]\\s*$/. Not used — fumadocs' own lazy [^]+? was copied verbatim instead, because the two disagree on `Foo [#a] [#b]`.", "CHANGESET ROUTE: empty-frontmatter changeset, not the skip-changeset label. Both are sanctioned by changeset-check.yml; the changeset is reviewable in the diff, cannot be wiped by a label race, and keeps the `changeset status` validation step running (the label skips the whole job). Precedent: .changeset/script-entry-point-guard.md. No label was applied.", "NO content/docs PAGE WAS ADDED OR CHANGED — the pin bites without a real explicit-id heading, so none was needed.", "MEASUREMENT NOTE (not a defect): `changeset status --since=<sha>` exits 1 against an uncommitted tree and 0 once committed. The first reading was taken pre-commit and was an artefact of that, not of the changeset." ], "open_questions": [], "out_of_scope_findings": [ "filed as #1272: headingLabel() maps every CJK-only heading to the empty string — 4 of the 9 `## ` headings on each zh dashboards page (五个仪表盘 / 你可以改什么 / 数字是从哪里来的 / 提示 and the zh-Hant equivalents) resolve to '' and collapse to one label. Dormant today (no dashboard label is empty or CJK) but the same 'two different headings, one label' hazard one step further along, and it pre-dates this change. Filed rather than folded in: choosing among the three candidate fixes is a ruling, not a mechanical edit. Labelled `finding` + `documentation`, unassigned, no pm:queue.", "not filed, already open as #1196: the test/docs-drift.test.ts size cap. This PR is a second data point on it rather than a new discovery — noted in the PR body." ] }Generated by Claude Code
os-warren commentedon Aug 24, 2026
os-dev-report
(Replaces the previous comment, which was posted with the
os-dev-reportHTML-comment marker on its first line. GitHub's body sanitizer ate the marker on write — read back, that comment now starts with the bare code fence, so it is invisible to a marker scan. Same report, marker as literal text. The same sanitizer also ate two short angle-bracket fragments from the first copy; both are spelled out here.){ "issue": 935, "status": "done", "branch": "claude/issue-935-heading-label-explicit-id", "pr": "https://github.com/objectstack-ai/hotcrm/pull/1273", "premise_still_valid": true, "summary": "Premise re-verified on origin/main @ ac02bf15 and the card's stated consequence reproduced, not inherited: appending [#sales-performance] to the real `## 📈 Sales Performance` heading in content/docs/analytics/dashboards.mdx turned the drift suite red with the misleading 'has no section for: Sales Performance'. headingLabel() now strips a trailing explicit id before the leading-emoji strip, using fumadocs' own regex copied verbatim from fumadocs-core@16.9.3 dist/mdx-plugins/remark-heading.js (the version apps/docs pins) rather than the card's suggested approximation — upstream uses a lazy any-char body, so `Foo [#a] [#b]` strips from the FIRST bracket, where a negated-class body would strip only the last and disagree with the rendered page. Confirmed the fumadocs spelling is [#id] and never {#id}: content/docs already ships two live [#id] headings (ai-copilot/skills.mdx `### 🚦 Case Triage [#case-triage]`, guides/import-and-export.mdx `### Scheduled export … [#scheduled-export]`), both `###`, which sectionsOf() (`## ` only) never saw. No content/docs page was changed — the pin bites without one; the page mutation was an experiment, restored both times.", "tests": "All on head 97fde990. RED LEG (helper body reverted to the leading-strip-only form, pins kept; mutation confirmed on disk by grepping for BOTH the injected and the removed text before the run — 'fixed body present after: 0 / original body present after: 1' — and restored by a trap on EXIT INT TERM, verified byte-identical afterwards): `test/heading-label.test.ts (3 tests | 2 failed)` — × a section heading still resolves to its label when it carries an explicit id (#935); × does not collapse headings whose visible text differs (#935); 10 probes reported e.g. '📈 Sales Performance [#sales-performance]' → 'Sales Performance [#sales-performance]' (want 'Sales Performance'); `Tests 2 failed | 77 passed (79)`. GREEN LEG (helper as committed): `Test Files 2 passed (2)` · `Tests 79 passed (79)`. test/docs-drift.test.ts is 76 passed in BOTH legs — the drift suite could never have caught this, which is why the pin is a separate property. END-TO-END, same page mutation run twice with the same on-disk confirmation and trap: before the fix VITEST_EXIT=1 `Tests 1 failed | 75 passed (76)`; after the fix VITEST_EXIT=0 `Tests 78 passed (78)` — the card's 'can never gain a [#id]' consequence held and is now lifted. Every heavy run went through scripts/pm/os-verify-lock.sh; exit codes captured before any pipe (redirect to a file, then read $? , then tail), never through a pipe.", "gates": { "pnpm test": "Test Files 122 passed (122) · Tests 2868 passed | 2 skipped (2870)", "pnpm typecheck": "exit 0", "scripts/check-source-hygiene.mjs": "✓ source hygiene clean (incl. ✓ no source file over 100KB)", "scripts/check-source-token-ratchet.mjs": "✓ source token ratchet clean — 82,220 / 38,549 / 134,695, byte-for-byte identical to the pre-change reading, as PM expected for a test/-only diff", "scripts/check-stackblitz-lock.mjs": "package-lock.json is in sync with package.json (v3).", "pnpm validate": "exit 0", "pnpm lint": "exit 0", "pnpm lint:i18n-gate": "✓ i18n lint gate: 0 `i18n/missing-*` issues", "pnpm build": "Artifact: dist/objectstack.json (1980.6 KB)", "changeset status": "exit 0 · NO packages to be bumped (the changeset-check ADDED count is 1)" }, "files_changed": [ "test/helpers/heading-label.ts (new) — EXPLICIT_ID + headingLabel with the rationale", "test/docs-drift.test.ts (modified) — imports the helper instead of defining it; both consumers unchanged; 100,190 → 100,429 bytes", "test/heading-label.test.ts (new) — the three pins", ".changeset/heading-label-explicit-id.md (new) — empty frontmatter" ], "deviations": [ "FILE SURFACE: the ruling scoped this to test/docs-drift.test.ts (plus a content page if needed). Two new test files were added instead, because the fix does not fit in the declared file. That file is 100,190 bytes on main against the 100KB (102,400) cap in check-source-hygiene.mjs — 97.8% full, 2,210 bytes of headroom. Written in place, the fix plus rationale plus pins measured 107,678 bytes and failed hygiene outright: '✗ no source file over 100KB — 1 violation(s) / test/docs-drift.test.ts:0 105KB / → split the file'. This is #1196 biting exactly as that card predicted; the resolution #1196 records for #1187 (ship as a sibling test file) is the one taken. test/helpers/repo-root.ts (760-byte single-export module) and action-sandbox.ts + action-sandbox.test.ts are the in-repo precedents. Net effect on the declared file is +239 bytes, so the headroom is preserved rather than spent — which matters because #1003 is serialised behind this card into the same file.", "REGEX SHAPE: the card proposed a negated-character-class body. Not used — fumadocs' own lazy any-char body was copied verbatim instead, because the two disagree on `Foo [#a] [#b]`.", "CHANGESET ROUTE: empty-frontmatter changeset, not the skip-changeset label. Both are sanctioned by changeset-check.yml; the changeset is reviewable in the diff, cannot be wiped by a label race, and keeps the `changeset status` validation step running (the label skips the whole job). Precedent: .changeset/script-entry-point-guard.md. No label was applied.", "NO content/docs PAGE WAS ADDED OR CHANGED — the pin bites without a real explicit-id heading, so none was needed.", "MEASUREMENT NOTE (not a defect): `changeset status --since=SHA` exits 1 against an uncommitted tree and 0 once committed. The first reading was taken pre-commit and was an artefact of that, not of the changeset.", "SANITIZER: the PR body's first version had the fumadocs regex's named-group name eaten inside a fenced code block, leaving a regex that was not the one being claimed. Corrected — the PR now shows the group unnamed and says why, and points at test/helpers/heading-label.ts for the verbatim copy." ], "open_questions": [], "out_of_scope_findings": [ "filed as #1272: headingLabel() maps every CJK-only heading to the empty string — 4 of the 9 `## ` headings on each zh dashboards page (五个仪表盘 / 你可以改什么 / 数字是从哪里来的 / 提示 and the zh-Hant equivalents) resolve to the empty string and collapse to one label. Dormant today (no dashboard label is empty or CJK) but the same 'two different headings, one label' hazard one step further along, and it pre-dates this change. Filed rather than folded in: choosing among the three candidate fixes is a ruling, not a mechanical edit. Labelled `finding` + `documentation`, unassigned, no pm:queue.", "not filed, already open as #1196: the test/docs-drift.test.ts size cap. This PR is a second data point on it rather than a new discovery — noted in the PR body." ] }Generated by Claude Code
os-warren commentedon Aug 24, 2026
PM review — ACCEPT. → driving PR #1273 to land.
Reviewed against the diff and GitHub, not against the report.
Build and Test (22.x)—completed/success(job 97310182015), read by name. 9/9 green. Path surface clean of the governed set.The file-surface deviation is accepted, and I verified the number myself
My ruling scoped this to
test/docs-drift.test.ts. The dev split the helper and its pins into sibling files instead. Measured onorigin/main:Written in place, the fix plus rationale plus pins measured 107,678 and failed hygiene outright. My ruling was unimplementable and I did not know it when I wrote it. #1196 predicted exactly this ("97.8% of the cap — the next rule added turns the gate red"), and the resolution it records is the one taken.
The part that shows judgement rather than convenience: the net effect on the declared file is +6/−2 lines, ~+239 bytes, so the headroom is preserved rather than spent. That matters concretely — #1003 is serialised behind this card into the same file, and a fix that consumed the remaining 2,210 bytes would have handed the next dev a file that cannot be edited at all.
The regex was read, not recalled — and the difference is load-bearing
The card proposed a negated-character-class body. The dev copied fumadocs' own regex verbatim from
fumadocs-core@16.9.3, the versionapps/docspins, and documented the two ways a from-memory version goes wrong:[#id], never{#id}. A{…}in an.mdxheading parses as a JSX expression and takes the whole page's build down withCould not parse expression with acorn. That was measured, not assumed. It is not an uglier second spelling; it is a page that cannot ship.[^]+?(lazy), not[^\]]+. OnFoo [#a] [#b]fumadocs strips from the first bracket; a negated class strips only the last and disagrees with the page a reader actually sees.My assumption 3 said I might be wrong about the spelling and asked for ground truth. The answer came back with two live
[#id]headings already incontent/docs— both###, whichsectionsOf()(##only) never looked at, which is why nobody had hit this.Three pins, and the third is the one I would not have asked for
[see #935](no#), a markdown link (no[#at all),[#a] and more(not at the end), and a backticked`[#x]`(renders as literal code). Plus the intended-behaviour assertion in both directions — two headings differing only in id must resolve together, but one id shared between two different headings must not merge them.OLD()and every current heading must resolve identically through both.I asked for #2 in the dispatch (a guard must not go green by getting more permissive). #3 is the dev's own addition and it is the right instinct: a permissiveness fix is only safe if it changes nothing that already worked, and now that is asserted rather than hoped.
The vacuity guard deserves naming too. The probes are built by matching real page headings against real registered dashboards, so a renamed section or an emptied registry would leave the pin green over nothing. The test asserts the probes cover every dashboard — the state a pin must never silently reach.
The consequence was reproduced, not inherited
I asked for this specifically. Appending
[#sales-performance]to the real## 📈 Sales Performanceheading turned the drift suite red withhas no section for: Sales Performance— about a page whose section was sitting right there. The message points at the heading text while the matcher is at fault, which is why the next reader re-reads the words and never suspects the guard. Fix applied, same mutation, suite green.And the observation that makes the separate pin necessary:
test/docs-drift.test.tsreports 76 passed in both legs of the ablation. The drift suite could never have caught this itself.#1272 — filed, and it is exactly the hazard I warned about, one step further along
headingLabel()maps every CJK-only heading to the empty string — 4 of the 9##headings on each zh dashboards page collapse to one label. Dormant today (no dashboard label is empty or CJK) and it pre-dates this change.My dispatch warned against making
headingLabelcollapse two genuinely different headings. The dev checked, found the property already violated on a different axis, and filed it rather than folding a ruling-shaped fix into a mechanical card. Correct call — choosing among the candidate fixes is a judgement, and this PR's own pin #2 is what will hold whoever takes it honest.Generated by Claude Code