Skip to content

docs-drift 的 headingLabel() 不容忍 fumadocs 显式标题 id,dashboards 页因此永远加不了 [#id] #935

Closed

Description

@yinlianghui

发现于 #866 的实施(PR #934)。观察类,只报不改 —— 今天没有任何页面在用显式标题 id,所以没有用户碰得到;它是给下一个修锚点的人埋的坑。

事实

test/docs-drift.test.ts:383:

/** Drop the leading emoji so `## 🎧 Customer Service` matches the label. */
const headingLabel = (h: string): string => h.replace(/^[^A-Za-z]+/, '').trim();

它只剥开头的非字母字符。fumadocs 的显式标题 id 写在结尾(## 📈 Sales Performance [#sales-performance]),剥不掉,于是 headingLabel() 返回 Sales Performance [#sales-performance],不再等于 dashboard 的 label。

实测(PR #934 里做过一次,实验后已复原):

× 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] 两种写法都是这条红。

为什么值得记

  1. 报错信息指向错误的方向。 它说「没有 Sales Performance 这一节」,而那一节明明在;真正的原因是标题多了一个 id 后缀。下一个人会去翻标题文字,不会想到守卫的正则。
  2. 它堵死了一条正常的修法。 analytics/dashboards 三语页的小节标题全是 emoji 开头,slug 一律带前导连字符(-sales-performance)。想让指向这些小节的锚点好写,唯一干净的办法就是加显式 id —— 而这条路在这一页上是关着的。content/docs 还剩 17 处悬空锚点(全仓实测),三类新成因:emoji 标题、标题后来加了后缀、zh 页沿用英文锚点 #866 的 #sales-dashboard 三处因此只能去锚,而不是像同批其它两处那样加 id(见 PR docs: clear the remaining 13 dangling in-site anchors across all three locales (#866) #934 第三节)。
  3. 守卫的本意不受影响。 「标题必须带上 dashboard 自己的 label」这条规则跟标题带不带 id 后缀无关;容忍 id 后缀不会放松它。

可能的改法(供 triage,未实现)

在 headingLabel() 里先剥掉尾部的显式 id,再剥前导 emoji:

const headingLabel = (h: string): string =>
  h.replace(/\s*\[#[^\]]+\]\s*$/, '').replace(/^[^A-Za-z]+/, '').trim();

同一页里 sectionsOf() 抽出的 heading 也会带着 id 后缀流到别处,一并看一眼。

顺带一条实测更正(已在 PR #934 记,建议同步进 #867)

fumadocs 的显式标题 id 是 [#id],不是 {#id}。#866 正文的测法段落写的是「显式 {#id} 优先」,那是错的:

标题源码 生成的 id 渲染出的标题文字
### 🚦 Case Triage -case-triage 🚦 Case Triage
### 🚦 Case Triage {#case-triage} -case-triage-case-triage 🚦 Case Triage {#case-triage}
### 🚦 Case Triage [#case-triage] case-triage 🚦 Case Triage

而且 .mdx 里的 {#...} 会被当成 JSX 表达式送进 acorn,整页解析直接失败(Could not parse expression with acorn,micromark-extension-mdx-expression)—— 照 {#id} 写不是渲染难看,是把页面构建打挂。#867 目前的实现约束只写了「必须用同一个 github-slugger」,不足以覆盖显式 id 与 MDX 解析这两层;最稳的做法是守卫直接调用 fumadocs 自己的 remarkHeading 读它吐出的 TOC,而不是复刻规则。

Refs #866 #867 #934

Activity

  1. added
    pm:queueReady for the PM dispatch loop
    and removed on Aug 24, 2026
  2. os-warren commented on Aug 24, 2026

    @os-warren
    Collaborator

    First-touch grading — promote → pm:queue. finding dropped in the same stroke.

    Graded by the repo:hotcrm PM 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:

    test/docs-drift.test.ts:588
      const headingLabel = (h: string): string => h.replace(/^[^A-Za-z]+/, '').trim();
    

    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.

    Why promote rather than hold or close:

    • It is a guard defect, and a guard that mismatches silently is worse than no guard — it reports green while the page it was meant to cover drifts. This repo has already paid for that class twice this round (The Open Tasks related lists never filter out completed tasks: op / neq are not the spellings record:related_list accepts #1248's advisory build warnings, objectstack#11529's 50-warning cap).
    • 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.


    Generated by Claude Code

  3. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Aug 24, 2026
  4. self-assigned this
    on Aug 24, 2026
  5. os-warren commented on Aug 24, 2026

    @os-warren
    Collaborator

    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.


    Generated by Claude Code

  6. os-warren commented on Aug 24, 2026

    @os-warren
    Collaborator
    {
      "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

  7. os-warren commented on Aug 24, 2026

    @os-warren
    Collaborator

    os-dev-report

    (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."
      ]
    }

    Generated by Claude Code

  8. os-warren commented on Aug 24, 2026

    @os-warren
    Collaborator

    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 on origin/main:

    test/docs-drift.test.ts = 100,190 bytes
    check-source-hygiene.mjs cap = 102,400
    headroom = 2,210 bytes
    

    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

    1. An id-bearing heading resolves to the same label as one without — red on the old helper, green on the new.
    2. 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.
    3. 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.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

pm:dispatchedDispatched to a dev agent by /pm-dispatch

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions