Skip to content

fix(spec): docs-gen 把嵌套花括号整对包进行内代码,不再切成半对 (#5452) - #5550

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-5452-docs-gen-brace-escape
Aug 5, 2026
Merged

fix(spec): docs-gen 把嵌套花括号整对包进行内代码,不再切成半对 (#5452)#5550
os-zhuang merged 2 commits into
mainfrom
claude/issue-5452-docs-gen-brace-escape

Conversation

@os-zhuang

@os-zhuang os-zhuang commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Fixes #5452

排版说明:GitHub 会把 body 里的引号和尖括号在存储时转义,所以本文刻意不写裸引号/尖括号,证据改用表格与列表呈现(#5452 实施中踩到,已回读存储后的 body 校验过)。

前提复核(先于实现)

issue 的基线是 ed0d2aac0,已前移大半天。在 origin/main @ 5acb93add 上重新核对,3 处存量仍在,前提成立:

content/docs/references/ai/model-registry.mdx:171
content/docs/references/ai/model-registry.mdx:172
content/docs/references/automation/flow.mdx:106

根因

packages/spec/scripts/build-docs.tsescapeMdxDescription 把带定界符的片段包进行内代码,让 MDX 按字面渲染而不是当成 JS 表达式 / JSX 标签。它用 indexOf 找收尾定界符 —— 取的是第一个闭合符,不是配对的那个:

const end = raw.indexOf(close, i + 1);   // 旧:第一个闭合符

所以任何嵌套对都只被包到内层闭合符为止,外层那个漏在代码跨度之外。{{var}} 于是输出成反引号只吃掉前半截、后面跟一个游离的 } —— 恰恰落在「教读者怎么写模板变量」的那几行上,而成对双花括号本身就是被文档的东西。

修法

改成按嵌套深度配对(新增 packages/spec/scripts/lib/escape-mdx.tsfindMatchingClose),整对落进同一个代码跨度。

转义逻辑同时从 build-docs.ts 抽到 scripts/lib/escape-mdx.ts —— 和 #4912format-type.ts 是同一个理由:生成器是带副作用的顶层脚本,在此之前想断言它的输出只能整跑一遍再 grep 产物,这正是本 bug 能在 main 上活下来的原因。

实测:是 5 处,不是 3 处

重新 gen:docs 后改动 4 个页面共 5 行。除 issue 点名的 3 处外,另有两处同一根因但起手只有单个花括号,因此 issue 用的 `{{ grep 天然看不见它们:

页面 修前 修后
ai/model-registry.mdx 171 / 172 `{{var}` + 游离 } `{{var}}`
automation/flow.mdx 106 `{{nodeId.field}` + 游离 } `{{nodeId.field}}`
ai/solution-blueprint.mdx 156 `{ status: { $in: [...] }` + 游离 } 整对包住
api/analytics.mdx 70 `{ cube, query: {...}` + 游离 } 整对包住

⚠️ 验收判据需要更正:issue 那条 grep 不会归零,也不应该归零

issue(及派单)把验收写成「grep -rn 反引号加两个左花括号,应归零」。实测这条判据是错的:该 pattern 匹配「反引号后面跟两个左花括号」,而修好之后的正确输出 `{{var}}` 同样以反引号加两个左花括号开头。所以它修前修后都是 3 命中,内容变了、计数不动。归零只有在「参考文档里根本不许出现模板变量语法」时才成立,而那恰恰与本单目的相反。

两条判据在同一语料(main 的 224 页 vs 本 PR 重生成后)上的实测:

扫描判据 main 本 PR
issue 那条字面 pattern(反引号 + 两个左花括号) 3 3
缺陷形状(以左花括号开头的行内代码跨度,闭合反引号后紧跟游离右花括号) 5 0

下面的门禁测试用的是第二条判据的等价不变量,不是 issue 那条字面 grep。

门禁测试 + 反向验证

packages/spec/scripts/escape-mdx.test.ts(12 例)= 单元 pin + 全语料门禁。

门禁判据是行内代码跨度里的花括号必须配平 —— 配平才是缺陷真正破坏的不变量(包裹把一对切成了两半),所以它能抓住上表那两处非双花括号开头的,并且在措辞改写后依然抓得住。实测语料信噪比:main 的 224 页 5 处违例(即上表 5 行),本 PR 重生成后 0 处,零噪声。

尖括号故意不设语料门禁:尖括号同时是比较运算符,校验规则里的比较式与 SemVer 区间前缀合法地带着不配平的一只,实测 11 处全部正确,设门禁纯属噪声。尖括号的嵌套改由正向单元用例 pin。反斜杠转义过的花括号(模块 JSDoc 那条路径写成反斜杠加花括号)在计数前先剔除。

反向验证 —— 两个方向都实跑了,方向为常规「还原缺陷 → 转红」。findMatchingClose 换回 raw.indexOf(close, i + 1):

方向 1:5 条嵌套定界符单元用例立刻转红,各自报出被切开的形状:

  • PromptTemplate`{{var}}` —— 跨度只吃到内层右花括号,外层漏出
  • flow`{{nodeId.field}}` —— 同上
  • roll-up 的 `{ status: { $in: [...] } }` —— 单层起手的嵌套,同样被切
  • 嵌套泛型类型串(Array 套 Record)—— 尾部第二个闭合尖括号漏出跨度
  • 一行里两个独立的双花括号对 —— 两处各切一次
Tests  5 failed | 7 passed (12)

方向 2:再跑 gen:docs 让语料回到旧转义之下,语料门禁转红,报出的 5 行与 main 上那 5 行逐行一致:

× has no inline-code span with an unbalanced brace
+   content/docs/references/ai/model-registry.mdx:171
+   content/docs/references/ai/model-registry.mdx:172
+   content/docs/references/ai/solution-blueprint.mdx:156
+   content/docs/references/api/analytics.mdx:70
+   content/docs/references/automation/flow.mdx:106

之所以是常规方向而非反转方向:这些用例断言的是修复产生的正向输出形状,不是某个 finding 的消失。单定界符与不配对的用例两个方向都是绿的 —— 这正是 bug 活下来的原因:凡是有人想到去看的形状,转义器都是对的。

还原修复后重跑 gen:docs,工作树与本 PR 提交逐字节一致(git status 干净),生成物可复现。

验证

$ pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2
Test Files  315 passed (315)
     Tests  8022 passed (8022)

$ pnpm --filter @objectstack/spec typecheck
check:test-typecheck: OK

$ pnpm --filter @objectstack/spec check:docs
✅ 239 generated files in sync with packages/spec

$ node scripts/check-nul-bytes.mjs
check-nul-bytes: OK (scanned 5501 tracked text file(s))

刻意不做的三件事

变更集

@objectstack/spec patch。包导出与运行时行为零变化(改的是 scripts/,不进 dist),但参考文档是该包生成器的读者可见产物,被切坏的又正是教语法的那一列,所以走 changeset 而非 skip 标签。

…ting it (#5452)

The reference-docs escaper located a fragment's closing delimiter with
`indexOf` — the first closer, not the matching one — so a nested pair was
wrapped only up to its inner closer and the outer one leaked outside the
inline-code span. `{{var}}` published as `` `{{var}` `` plus a stray `}`,
on exactly the rows documenting template-variable syntax.

The matcher now counts nesting depth. Escaping moves to
`scripts/lib/escape-mdx.ts` (same reason `format-type.ts` was extracted in
#4912: the generator is a side-effecting top-level script, so its output
could only be asserted on by grepping emitted `.mdx`), pinned by
`scripts/escape-mdx.test.ts` — unit cases plus a corpus gate asserting
inline-code brace balance across all 224 generated pages.

Regenerating fixes 5 rows on 4 pages: model-registry.mdx ×2, flow.mdx,
solution-blueprint.mdx, analytics.mdx. The issue counted 3; the last two
start with a single brace, so the `` `{{ `` grep could not see them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018fxLGQdatPbBUvCgiVxg6D
@vercel

vercel Bot commented Aug 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 5, 2026 5:22pm

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling size/m labels Aug 5, 2026
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

No hand-written docs reference the 0 changed package(s). ✅

…#5452)

The reverse verification turns FIVE unit cases red, not four, and the
defect spanned five rows of four reference pages, not three pages — both
numbers were written before the corpus was measured.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018fxLGQdatPbBUvCgiVxg6D
@os-zhuang
os-zhuang marked this pull request as ready for review August 5, 2026 19:16
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 5, 2026
Merged via the queue into main with commit 5543020 Aug 5, 2026
25 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-5452-docs-gen-brace-escape branch August 5, 2026 19:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

docs-gen: .describe() 里的 {{var}} 在生成的参考文档里被转义成 ` {{var} + 游离的 }`(main 上现存 3 处)

2 participants