Repository navigation
fix(spec): the generated upgrade guide keeps a surface in one code span when it carries its own backticks - #27724
Conversation
…an when it carries its own backticks The protocol upgrade guide wrapped every conversion's and semantic entry's `surface` in a single-backtick code span. 183 of the 516 semantic entries mark their own inline code inside the surface, and CommonMark closes a single-backtick span at the first backtick inside it, so those lines rendered as alternating code and prose fragments. Two conversion surfaces carry a `|`, which split their table row into extra cells. Both render sites now go through one helper, `codeSpan` in `scripts/lib/markdown-code-span.ts`: the span is fenced one backtick longer than the longest run inside the text (CommonMark's rule), padded when the text starts or ends with a backtick, and byte-identical for a backtick-free text. A table row escapes `|` in every cell. `readCodeSpan`, the reading half of the same rule, lets the pin decode what the generator wrote. The pin (`scripts/upgrade-guide-surface-span.test.ts`) covers the helper, the real entry set (every surface round-trips), and the generated guide read back span by span, so a new entry cannot bring the defect back. The workaround note in `18.visibility-predicate-has-argument-refused.ts` is dropped. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ihL2gP8YEs7CoWrbVsSAi
…bling tests stop forbidding backticks in a surface
Applies the independent review's findings on the previous head.
- The generated-guide pin splits each conversion row into cells as GFM does
(every pipe ends a cell, inside a code span or not, except an escaped one)
and expects exactly four, with the surface span filling its own cell; its
lit control shows the old single-backtick row of a pipe-carrying surface
splitting into extra cells. Before this the pin could not see the pipe
defect the row escape fixes.
- Two sibling tests pinned the workaround the card drops ("No backticks in
surface: the upgrade guide renders it inside a code span") on real entries;
the sentence is now false and the assertion forbade what is legal, so both
pairs go.
- The generator's refusal names the entry; the helper's docblocks say
"no backtick and no leading or trailing space" instead of "backtick-free",
and give the table-row reason for refusing a line ending; the reader treats
a lone carriage return as a line ending and strips padding only around
spaces; the conversion entry-set pin asserts no surface carries a backslash,
where GFM renderers part ways on an escaped pipe.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ihL2gP8YEs7CoWrbVsSAi
… pipe-carrying conversions Applies the delta review's findings. The lit control for the table-row escape starts from a literal old-style row with a pipe in its surface, so it keeps biting after the two retirement kits that carry a pipe today age out one major on; the real-registry loop follows with no count floor. The count floors on the other real-set pins drop to "more than zero", the liveness they need. The conversion-surface pin forbids an escaped pipe, the one pair GFM renderers read two ways, instead of any backslash. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ihL2gP8YEs7CoWrbVsSAi
…-2ul8sb Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ihL2fi9zfJhUTubyJYb6V
📓 Docs Drift Check
What this run could not see
Coarse fallback — 167 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
|
domain:spec#4 · marchtian · session_017ihL2fi9zfJhUTubyJYb6V 席位核对 · ACCEPT (lane part) · card #27532 · 2026-10-29T20:52Z
Contract-review-handoff: triage — CONTRACT_REVIEW_TIER unavailable (HTTP 429 "You've reached your Fable limit", 2026-10-29T17:05Z) · head 34de285 |
|
triage#1 · os-sales · session_01L6bhgV4fMGJHSdSdx3u9mK Contract-review-claim: session_01L6bhgV4fMGJHSdSdx3u9mK · head 34de285 · 2026-10-29T21:17Z Generated by Claude Code |
|
triage#1 · os-sales · session_01L6bhgV4fMGJHSdSdx3u9mK Contract reviewServed-tier: Inputs read: card #27532 (body and all 5 comments: triage 6496306402, release 6500852824, claim 6501175459, claim amendment 6506624943, os-dev-report 6508030760), PR #27724 (body, 7-file list, net diff from the files endpoint, comments 6508021606 / 6508068835 / 6508551079) and the 53 check-runs on the head. The hits are read off ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgment
Implemented-by: VERDICT: PASS Generated by Claude Code |
|
domain:spec#4 · marchtian · session_017ihL2fi9zfJhUTubyJYb6V Late record, posted by the seat after landing: the card thread ( Independent full Fable review of this PR (AGENTS.md: every spec, skills or ADR change carries one before merge). Run as an isolated subagent (model fable, effort xhigh) with the card, the order and the whole diff; three rounds, each verdict quoted verbatim below. Posted 2026-10-31T12:09Z. Round 1 — head 94a629e (fix commit 8e4d77a merged over origin/main 0e33108)
Delta — d5876c7 on 94a629e (every round-1 finding applied)
Delta 2 — 8515184 on d5876c7 (the delta's findings applied; test file only)
DispositionEvery must-fix: none in any round. Every should-fix and nit from rounds 1 and 2 is applied in the two follow-up commits, except the 178-file comment sweep, which the binding ruling (triage 6496306402, "the 516 entries are not hand-edited") keeps out of this PR: it is disclosed in the PR body's acceptance notes and handed to the seat as a follow-up decision in the os-dev report on #27532. The two delta-2 nits are wording in one test comment; the reviewer called them not worth a line, so no fourth commit: the PR body's acceptance note names the two renderers measured and that cmark-gfm was not. Generated by Claude Code |
Requested by 田騏 · project thread
Fixes #27532
Clause-②: no
Metadata-protocol: none
维护者速读
surface按 CommonMark 规则包进一个代码段(围栏比内部最长反引号串多一个),转换表单元格另外转义|;两处渲染点共用一个入口,516 条条目一条未手改。surface,而 183 条语义条目在surface里自带反引号,渲染出来是代码与正文交替的碎片;两条带|的转换条目会把表格行撕成 6 格。protocol-upgrade-guide.md与文档站页面),不动 schema、取值、错误码;不含反引号的surface字节不变。回滚 = revert 本 PR。packages/spec/scripts/lib/markdown-code-span.ts(围栏与补空格规则)→packages/spec/scripts/build-upgrade-guide.ts的tableRow/surfaceSpan两处调用 →packages/spec/scripts/upgrade-guide-surface-span.test.ts的真实条目集钉子。pnpm --filter @objectstack/spec exec vitest run scripts/upgrade-guide-surface-span.test.ts,预期 15 个测试全绿;把任一渲染点改回单反引号再跑,预期变红。Fable review: not run — the account's Fable quota is exhausted; the PR waits for the contract review (maintainer, 2026-10-29).
What changes
packages/spec/scripts/lib/markdown-code-span.ts(new):codeSpan(text)fences a one-line text with a backtick string one longer than the longest backtick run inside it, and pads one space on each side when the text starts or ends with a backtick or a space. That is CommonMark §6.1's own rule for code that contains backticks. A text with no backtick and no edge space comes out byte-identical to the old single-backtick span. A blank or multi-line text is refused, and the refusal names the entry.readCodeSpanis the reading half of the same rule; only the pin uses it.packages/spec/scripts/build-upgrade-guide.ts: both render sites (the conversion table row and the semantic list item) go through onesurfaceSpan(id, surface)door. The conversion row is built bytableRow, which escapes|in every cell. GFM ends a cell at an unescaped pipe even inside a code span, and two live conversion surfaces carry one (navigation-mode-overlay-values-removed,form-view-presentation-types-removed).migrate metaprints. A longer fence keeps one code span per surface and shows the author's backticks literally. No entry is edited.18.visibility-predicate-has-argument-refused.ts: the workaround note ("no backticks insurface") is dropped. Its surface is untouched.export-job-family-retirement.test.ts,package-upgrade-family-retirement.test.ts: each drops oneexpect(entry!.surface).not.toMatch(/BACKTICK/)line. Those lines forbade backticks in a surface only because of this defect. Every other assertion in both tests stands..changeset/27532-upgrade-guide-surface-span.md:patchon@objectstack/spec.protocol-upgrade-guide.mdis in the package'sfiles[]and is generated at publish, so the shipped bytes move.packages/spec/protocol-upgrade-guide.mdandcontent/docs/protocol-upgrade/are both gitignored and generated at publish or docs build.docs/protocol-upgrade-guide.mdis a pointer stub that nothing generates.git ls-fileslists no rendered guide.Tests
packages/spec/scripts/upgrade-guide-surface-span.test.ts(15 tests) has three layers:codeSpan/readCodeSpan. A new entry cannot bring the defect back, whatever backticks it carries.tsx build-upgrade-guide.ts --out). Its bytes are read back span by span: each semantic line yields its surface verbatim followed by→. Each conversion row is exactly four GFM cells, and the surface is one span filling its cell. A second lit control: the old row shape of a pipe-carrying surface splits into more than four cells, on a literal row and on every real pipe-carrying conversion.Ablation on head
34de285ecf, each leg throughscripts/ablation-replace.mjs, with anchor hit, blob change and restore all verified on disk. There is nodist/leg: the test imports../srcand spawns the script source by relative path.`${s.surface}`: 1 failed / 14 passed (action-descriptor-is-async-retiredfirst). Restored, blob == HEAD.navigation-mode-overlay-values-removed: 6 cells, not 4). Restored, blob == HEAD.Acceptance notes
|in remark, markdown-it and marked alike.summaryand the load-window cell are now pipe-escaped too, becausetableRowescapes every cell. No current summary carries a pipe, so no byte moves there today.Verification
All of it was run on
34de285ecf: the branch merged withorigin/maina75ddba178, with a clean merge and no generated-file conflict. The branch delta vs main is unchanged at 7 files, +356 / −8.spec tests and typecheck
pnpm --filter @objectstack/spec test: Test Files 754 passed (754), Tests 22406 passed, 1 todo; exit 0.pnpm --filter @objectstack/spec typecheck: exit 0.tsconfig.scripts.json --listFilescompiles all three touched script files.derived gates (dispatch-gates --commands, 86 commands)
check:upgrade-guide,check:adr-0087-registration --base origin/main,check:changeset-no-major,check:empty-changeset,check:spec-changes,check:docs,check:api-surface,check:nul-bytes,check:cross-package-test-inputsandcheck:pm-dispatch-gates.check:doc-formula-expressionsandcheck:lean-entry-closurefirst exited 3 (PREREQUISITE NOT MET, nodist/). Both exit 0 after building their closures.check:dual-build-cjs-loads: NOT MEASURED. It exits 3 because about 93 packages have nodist/in this worktree. The diff moves no package's built output; CI's Lint & Repo Gates runs it after the build.--ranreconciliation: 86 derived, 85 run, 1 NOT-MEASURED (derived from the recorded exit 3), 0 UNRUN.🤖 Generated with Claude Code