fix(cli): handle escaped dollars around inline math - #7741
Conversation
E2E/component-path reportBaseline reproductionOn current
Post-fix verificationOn macOS 26.5.2 with Node.js v26.4.0 at
Real-terminal evidenceA real TUI before/after comparison using the same ModelStudio model response is now attached in the PR body and in the screenshot comment below. Windows and Linux were not run locally; fork CI covers the available Ubuntu job, while platform-specific skipped jobs remain a maintainer/CI limitation. |
|
|
|
Thanks for the PR! Template looks good ✓ Problem: observed bug with clear evidence. Issue #7740 provides a seven-line corpus with expected vs actual output, root cause analysis, and was reproduced on current Direction: aligned. Inline math rendering is squarely within the CLI's Markdown rendering scope, and this completes the escaped-dollar contract that #7701 started. CHANGELOG has no direct reference to inline math, but the area is clearly relevant. No auth, sandbox, model, or telemetry concerns. Size: not applicable — all changes are in Approach: the scope feels right. Replacing the regex with a bounded scanner is the natural fit here — backslash parity (odd vs even runs) is inherently a counting operation that regex handles poorly, and the three failure modes (literal dollar inside math, formula adjacent to escaped dollar, even-vs-odd runs) all require it. The Moving on to code review. 🔍 中文说明感谢贡献! 模板完整 ✓ 问题:已观测到的 bug,有明确证据。Issue #7740 提供了七行语料及期望/实际输出对比、根因分析,并在当前 方向:对齐。行内公式渲染完全在 CLI Markdown 渲染范围内,本 PR 补全了 #7701 开始的转义美元符号契约。CHANGELOG 无直接引用,但该领域明显相关。不涉及认证、沙箱、模型或遥测。 规模:不适用——所有改动在 方案:范围合理。用有界扫描器替换正则是自然选择——反斜杠奇偶性本质上是计数操作,正则难以处理,而三种失败模式都需要它。 进入代码审查 🔍 — Qwen Code · qwen3.8-max-preview Reviewed at |
There was a problem hiding this comment.
Pull request overview
This PR updates the CLI’s inline-math recognizer to correctly handle escaped dollars inside inline math and adjacent to inline-math boundaries by replacing the previous regex-based approach with a bounded, backslash-parity-aware scanner. It also ensures Markdown backslash-escapes for literal dollars are removed in rendered prose while inline code remains byte-preserving, keeping recognition consistent across rendering, table behavior, width measurement, and /copy inline-latex.
Changes:
- Replace the inline-math regex with a bounded scanner that tracks backslash-run parity and skips escaped dollars while searching for the closing delimiter.
- Unescape Markdown dollar-escapes in rendered prose (including within markup like bold/italic/link labels), while keeping inline code spans verbatim.
- Expand unit + integration coverage for escaped-dollar cases across rendering, table splitting, width measurement, and copy selection.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| packages/cli/src/ui/utils/TableRenderer.tsx | Switch table ANSI rendering to shared tokenization and prose-dollar unescaping. |
| packages/cli/src/ui/utils/pending-rendered-height.test.ts | Add coverage for table splitting when math contains escaped dollars and pipes. |
| packages/cli/src/ui/utils/MarkdownDisplay.test.tsx | Update table rendering expectations to reflect prose dollar unescaping and mixed math. |
| packages/cli/src/ui/utils/InlineMarkdownRenderer.tsx | Use shared merged token stream (markup + math) and unescape dollars in visible prose while preserving code spans. |
| packages/cli/src/ui/utils/InlineMarkdownRenderer.test.tsx | Add full corpus coverage for escaped dollars inside/around inline math and code-span preservation. |
| packages/cli/src/ui/utils/inline-math.ts | Implement bounded parity-aware inline-math scanner + shared merge generator + prose-dollar unescaping helpers. |
| packages/cli/src/ui/utils/inline-math.test.ts | Add tests for escaped-dollar parity, boundary cases, and dollar-unescaping helpers. |
| packages/cli/src/ui/commands/copyCommand.test.ts | Verify /copy inline-latex ordering and preservation of \\$ in copied TeX source. |
| integration-tests/terminal-capture/scenarios/markdown-rendering.ts | Extend integration prompt sample to include escaped-dollar math/prose boundary cases. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| 5. One display math block using $$ fences. | ||
| 6. One checked and one unchecked task list item. | ||
| 7. Literal inline code \`$zz$\` and escaped math source \\\\$xy$. | ||
| 7. Literal inline code \`$zz$\`, escaped prose \\\\$xy$, formula $x + \\\\$5$, and the boundaries \\\\$$x^2$ and $x^2\\\\$$. |
Code ReviewIndependent proposal: I'd replace the single-regex math recognizer with a character scanner that counts the full backslash run for parity, add a merge generator to interleave markup and math tokens in document order, add Markdown unescape helpers for prose, and update all four consumers (React renderer, ANSI table renderer, copy-command expression list, width-measurement span reader). Remove the old combined regex from both renderers. Comparison: the PR matches this almost exactly. The implementation is clean:
No correctness bugs, security issues, or regressions found. No AGENTS.md violations (ESM, no TestingThis is an unattended CI run — no local tmux testing. CI evidence from the PR's own checks: The The author reports 300/300 tests passing across five focused suites on their local macOS environment (author's claim, not independently verified here). Final CI results for
One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。 Not verified: unit test results (CI failed before reaching the test step), Windows/Linux runtime behavior (author tested macOS only). A maintainer can trigger the isolated 中文说明代码审查独立方案: 我会用字符扫描器替换单一正则的公式识别器,通过计算完整反斜杠 run 来判断奇偶性;添加合并生成器按文档顺序交错标记和公式 token;添加 Markdown 反转义辅助函数处理普通文本;更新全部四个消费方(React 渲染器、ANSI 表格渲染器、复制命令表达式列表、宽度测量 span 读取器)。移除两个渲染器中的旧组合正则。 对比: PR 与上述方案几乎完全一致。实现干净: 测试本次为无人值守 CI 运行,无本地 tmux 测试。CI 证据:Ubuntu 测试在 未验证:单元测试结果(CI 在测试步骤前失败)、Windows/Linux 运行时行为。维护者可触发隔离的 — Qwen Code · qwen3.8-max-preview Reviewed at |
|
Confidence: 4/5 — clean implementation that solves a real rendering bug with the right tool; docking one point because CI's npm audit infra failure means no independent test signal yet. The problem is real and well-evidenced — issue #7740 shows three distinct failure modes on current Every change in the diff earns its place. The test additions cover the full corpus from the issue, backslash parity edge cases, code-span isolation, table splitting with escaped dollars, and the copy command. No scope creep, no drive-by refactors. The one reservation: CI didn't produce test results. The Ubuntu job failed at Approval deferred until CI lands green on 中文说明置信度:4/5 —— 干净的实现,用正确的工具解决了真实的渲染 bug;扣一分因为 CI 的 npm 审计基础设施故障导致尚无独立测试信号。 问题真实且有充分证据——issue #7740 展示了当前 diff 中每项改动都有存在价值。测试覆盖了 issue 中的完整语料、反斜杠奇偶边界、代码段隔离、含转义美元的表格拆分和复制命令。无范围蔓延,无顺手重构。 唯一保留:CI 未产生测试结果。Ubuntu 任务在 审批已推迟至 CI 在 — Qwen Code · qwen3.8-max-preview Reviewed at |
|
Added a reproducible terminal-capture scenario and real TUI evidence using the same ModelStudio response corpus on both trees. Before — merge-base After — patched TUI: Both runs used |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
— qwen3.7-max via Qwen Code /review
doudouOUC
left a comment
There was a problem hiding this comment.
No issues found. LGTM! ✅
— qwen3.7-max via Qwen Code /review
Independent local verification report (head
|
Maintainer local verification — real build, real terminal (head
|
| Result | Value |
|---|---|
| Recognition gained | 42 |
| Recognition lost | 7 — every one a fuzz string of backtick/dollar soup |
| Same-count-but-different | 3 — likewise fuzz only |
| Realistic-prose sentences that regressed | 0 of 10 |
| Exceptions thrown, either tree, any input | 0 |
INLINE_MATH_MAX_CHARS behaviour |
identical: 1024 accepted, 1025 rejected on both |
| Currency / shell-var / adjacent-span / code-span guards | no divergence on any realistic input |
The 7 losses are cases like '\$\ 2x5`$`aa$值^' where a code span now wins the position it should have won all along, so I checked whether that is right rather than just rare:
Renderer ↔ copy agreement improved: 40 → 35 disagreements over the same corpus (9 fixed, 4 new, all four new ones fuzz). The change moves the two consumers closer together, which is what the PR claims.
Pre-existing gap worth a follow-up issue (not this PR): the 4 realistic residual disagreements are unchanged from main —
**bold $x$** renderer: no math /copy offers: x
*ital $x$* renderer: no math /copy offers: x
[link $x$](http://example.com) renderer: no math /copy offers: x
https://example.com/$x$ renderer: no math /copy offers: x
i.e. /copy inline-latex still offers formulas nested in bold/italic/link/URL that the renderer never rendered. Identical on the merge-base, so it is neither caused nor worsened here.
4. Mutation matrix over the new scanner — 13 mutants
Each mutant reverts exactly one guard, then runs the PR's own five suites.
| Mutants | Notes | |
|---|---|---|
| Killed | 7 | backslash-run parity, preceding-$ guard, trailing-whitespace guard, adjacent-span guard, the 1024 bound, floor(n/2) unescape semantics, the unescapeMarkdownBeforeMath boundary sentinel |
| Provably equivalent | 3 | see below |
| Survived, non-equivalent | 3 | see below |
The 3 equivalent mutants are dead code, confirmed by probing all 1,616 inputs for any observable difference (zero):
markupIndex <= mathIndexvs<— no markup alternative can start with$, so the tie never occurs.markupRegex.lastIndex = 0—String.prototype.matchAllclones the regex, so the reset can never matter. Harmless defensiveness, not a bug.- the
!nextend-of-string fast path — the closer loop already returnsnullthere.
The 3 real survivors are guards that no test pins:
| Guard | Removing it makes… |
|---|---|
preceding word char (/\w/.test(previous)) |
a$b$, y$x$, costs5$x$ become math |
next char (/[\s\d$]/.test(next)) |
$ x$ becomes math; $$ yields an empty copy entry |
newline (text[closingIndex] !== '\n') |
$x\ny$ spans lines |
These were already untested on main. I mutated the equivalent regex assertions on the merge-base ((?<![\\\w$]), (?![\s\d$]), [^$\n]) and the base suites stayed 108/108 green in all three cases. So this PR neither introduces nor inherits blame for the gap — but it does move those guards from a declarative regex, where they are visually obvious, into imperative code where a future refactor could drop one silently. Cheap to close (see §7).
5. Performance — no new complexity class
The change swaps one bounded regex for a hand-written scanner, so I measured scaling rather than a single length. Five shapes (realistic prose, $-dense, escaped-$-dense, an adversarial longest-scan shape, and backtick soup), 2k → 32k chars:
| Shape | base ms @32k | PR ms @32k | PR/base | base per-doubling | PR per-doubling |
|---|---|---|---|---|---|
| prose | 0.069 | 0.546 | 7.9× | 2.01× | 2.00× |
| dollar-dense | 0.076 | 0.570 | 7.5× | 1.98× | 2.08× |
| escaped-dense | 0.078 | 0.423 | 5.4× | 1.99× | 1.95× |
| longest-scan (adversarial) | 0.077 | 0.802 | 10.4× | 1.93× | 2.02× |
| backtick soup | 0.145 | 0.467 | 3.2× | 1.95× | 1.97× |
Both trees are linear (~2.0× per doubling on every shape, including the adversarial one) — the scanner's inner loop is bounded by the distance to the next unescaped $, so it stays O(n) amortized. The PR pays a 3–10× constant factor, which at a realistic per-line size (~100 chars) is sub-microsecond. No ReDoS class introduced.
6. Test and gate results
| Check | Result |
|---|---|
5 focused suites (inline-math, InlineMarkdownRenderer, MarkdownDisplay, pending-rendered-height, copyCommand) |
300/300 passed — the PR's claim reproduces |
Full packages/cli suite |
15,356 passed, 3 skipped, 4 failed |
| Those 4 failures on the merge-base | identical (extensions/list.test.ts, AuthDialog.test.tsx) — pre-existing, not regressions |
prettier --check on changed files |
clean |
eslint on changed files |
clean |
tsc --noEmit -p packages/cli |
2 TS6305 project-reference errors, identical count on the merge-base — my symlinked-worktree artifact, not the PR |
Note for @gwinthis: the 4 MarkdownDisplay.test.tsx failures you saw do not reproduce here — 186/186 on both trees on macOS, so that looks environment-specific on your side rather than something latent.
7. Optional follow-ups (none blocking)
- Three one-line tests to pin the surviving guards:
a$b$stays prose;$ x$stays prose and$$yields no expression;$x\ny$does not cross the newline. Pre-existing gap, but this PR is the natural place to close it since it rewrote those guards. - Scope note:
\$is now unescaped in prose even when inline math is off (renderMode !== 'render') — 371 of my corpus inputs differ on that path. This is CommonMark-correct ($is escapable ASCII punctuation) and I think it is the right behaviour, but it is broader than the PR title suggests and no test covers the math-off path. Worth one test and a line in the description. - Follow-up issue for the pre-existing renderer/copy disagreement in §3 (math nested in bold/italic/link/URL).
中文版
维护者本地验证 —— 真实构建、真实终端(head cdb897e)
结论:可以合并。 我在真实 pty 中复现了缺陷与修复,并从此前报告未覆盖的角度进行了攻击:针对 merge-base 的 1,586 条输入跨消费者差分、对新扫描器的 13 个变异体矩阵、以及性能伸缩性 A/B。没有阻塞项。文末三条测试覆盖建议均为可选,且在 main 上已经存在同样的缺口。
环境:macOS 26.5、Node v22.23.1,worktree 分别位于 head cdb897e 与 merge-base 0f56e35,node_modules 取自 lockfile 与 PR base 完全一致的 checkout。
1. 真实终端前后对比 —— 生产组件 + 真实 pty
我没有驱动模型,而是用真实 Ink 在真实 pty 中渲染生产组件 <MarkdownDisplay />(@lydell/node-pty → @xterm/xterm → Playwright,即本仓库自带的 integration-tests/terminal-capture 工具链),输入固定为 PR 的七行语料。确定性复现,无需 API key,且用的正是 CLI 渲染模型输出的同一组件。下方 /copy inline-latex 列表由 copy 命令实际调用的同一识别器计算。
七行全部符合声明的契约,/copy inline-latex 从 1 条增加到 4 条,按文档顺序排列,复制出的 TeX 源码保留了 \$(x + \$5、x^2\$)。
2. 守卫面无回归
同一工具链,语料为识别器应当拒绝的全部情况:货币($19.99、$750、$5-$10、costs $5)、shell 变量、行内代码(`\$x$` 在普通文本与表格单元格中均逐字节保持)、CJK 邻接、表格单元格,行为全部相同或更好。唯一的差异正是预期内的:普通文本转义去掉反斜杠,mixed 单元格现在渲染为 x + $5。
3. 与 merge-base 的跨消费者差分
1,586 条输入(81 条手写 + 5 条长度边界 + 1,500 条确定性 fuzz,字母表覆盖 $ / \ / 反引号 / markdown 标记),跑过全部四个真实生产消费者(RenderInline、TableRenderer 的 ANSI 路径、/copy inline-latex 的 findInlineMathExpressions、splitMarkdownTableRow)以及 getPlainTextLength,两棵树各跑一遍后对比。
| 结果 | 数值 |
|---|---|
| 新增识别 | 42 |
| 丢失识别 | 7 —— 全部是反引号/美元符号乱码的 fuzz 串 |
| 数量相同但内容不同 | 3 —— 同样只出现在 fuzz |
| 真实语句中的回归 | 10 条中 0 条 |
| 任一树、任一输入抛异常 | 0 |
INLINE_MATH_MAX_CHARS 行为 |
完全一致:1024 接受、1025 拒绝 |
| 货币 / shell 变量 / 相邻 span / 代码段守卫 | 真实输入上无任何差异 |
那 7 条丢失都是代码段现在赢得了本就该赢的位置,因此我进一步确认了方向是否正确:渲染器与复制路径的分歧从 40 降到 35(修好 9 条,新增 4 条且全为 fuzz)。这正是 PR 所声称的"两个消费者共享同一识别结果"。
值得单独开 issue 的存量问题(不属于本 PR): 4 条真实的残余分歧与 main 完全一致 —— **bold $x$**、*ital $x$*、[link $x$](url)、https://example.com/$x$ 中,渲染器不渲染公式,但 /copy inline-latex 仍然把它们列为可复制项。merge-base 上表现相同,既非本 PR 引入,也未被本 PR 恶化。
4. 新扫描器的变异矩阵 —— 13 个变异体
每个变异体只回退一个守卫,然后运行 PR 自带的五组测试。
- 被杀死 7 个:反斜杠奇偶性、前置
$守卫、尾随空白守卫、相邻 span 守卫、1024 上限、floor(n/2)反转义语义、unescapeMarkdownBeforeMath的边界哨兵。 - 可证明等价 3 个(对全部 1,616 条输入探测,零可观测差异,属于无害的防御性死代码):
markupIndex <= mathIndex与<(没有任何 markup 分支能以$开头,平局不可能发生);markupRegex.lastIndex = 0(matchAll会克隆正则,重置永远不起作用);!next的字符串末尾快速返回(闭合循环本就会返回null)。 - 真实存活 3 个(没有任何测试钉住):前置单词字符守卫(去掉后
a$b$、y$x$、costs5$x$会变成公式);后继字符守卫(去掉后$ x$变成公式,$$产生一条空的复制项);换行守卫(去掉后$x\ny$会跨行)。
这三个缺口在 main 上已经存在。 我在 merge-base 上对等价的正则断言((?<![\\\w$])、(?![\s\d$])、[^$\n])做了同样的变异,base 的测试三次都是 108/108 全绿。所以本 PR 既没有引入这个缺口,也不该为此背锅;但它确实把这些守卫从一眼可见的声明式正则搬进了命令式代码,未来重构时更容易被悄悄删掉。补上成本很低(见第 7 节)。
5. 性能 —— 没有引入新的复杂度类
本次把一个有界正则换成了手写扫描器,因此我测的是伸缩性而非单点耗时。五种形状(真实文本、$ 密集、转义 $ 密集、对抗性最长扫描、反引号乱码),2k → 32k 字符:
| 形状 | base @32k | PR @32k | 倍率 | base 每翻倍 | PR 每翻倍 |
|---|---|---|---|---|---|
| 真实文本 | 0.069 ms | 0.546 ms | 7.9× | 2.01× | 2.00× |
$ 密集 |
0.076 ms | 0.570 ms | 7.5× | 1.98× | 2.08× |
| 转义密集 | 0.078 ms | 0.423 ms | 5.4× | 1.99× | 1.95× |
| 对抗性最长扫描 | 0.077 ms | 0.802 ms | 10.4× | 1.93× | 2.02× |
| 反引号乱码 | 0.145 ms | 0.467 ms | 3.2× | 1.95× | 1.97× |
两棵树都是线性的(所有形状包括对抗形状均为每翻倍约 2.0×)—— 扫描器的内层循环被"到下一个未转义 $ 的距离"所界定,因此摊还仍是 O(n)。PR 付出 3–10× 的常数因子,按真实单行长度(约 100 字符)折算是亚微秒级。未引入 ReDoS 风险。
6. 测试与门禁
| 检查 | 结果 |
|---|---|
| 五组聚焦测试 | 300/300 通过,PR 的说法可复现 |
packages/cli 全量 |
15,356 通过、3 跳过、4 失败 |
| 这 4 个失败在 merge-base 上 | 完全相同(extensions/list.test.ts、AuthDialog.test.tsx),属存量问题,非回归 |
改动文件 prettier --check |
通过 |
改动文件 eslint |
通过 |
tsc --noEmit -p packages/cli |
2 个 TS6305 项目引用错误,merge-base 上数量完全相同 —— 是我 symlink worktree 的环境产物,与 PR 无关 |
补充给 @gwinthis:你看到的 4 个 MarkdownDisplay.test.tsx 失败在我这里没有复现 —— macOS 上两棵树均为 186/186,所以更可能是你那侧的环境问题,而非潜在缺陷。
7. 可选后续(均不阻塞合并)
- 三条一行测试钉住存活的守卫:
a$b$保持普通文本;$ x$保持普通文本且$$不产生表达式;$x\ny$不跨行。缺口虽是存量,但本 PR 重写了这些守卫,是补齐的自然时机。 - 范围提示: 即使行内公式关闭(
renderMode !== 'render'),普通文本中的\$现在也会被反转义 —— 我的语料中有 371 条输入在该路径上产生差异。这符合 CommonMark($属于可转义的 ASCII 标点),我认为行为是对的,但它比 PR 标题暗示的范围更广,且没有测试覆盖关闭公式的路径。建议补一条测试并在描述里加一句。 - 为第 3 节中的存量渲染器/复制分歧(嵌套在 bold/italic/link/URL 中的公式)单独开一个 issue。
— maintainer local verification: real pty + Ink MarkdownDisplay, 1,586-input cross-consumer differential, 13-mutant matrix, perf-scaling A/B
|
Released in v0.21.1. |






What this PR does
This PR completes the CLI's escaped-dollar inline-math contract with a bounded, parity-aware delimiter scanner. Escaped dollars can now appear inside formulas or immediately beside a formula boundary, while Markdown escapes in visible prose are removed and inline code remains byte-preserving.
Rendering, table rendering,
/copy inline-latex, table splitting, and rendered-width measurement continue to share the same recognition result. The existing 1024-character bound and the currency, shell-variable, adjacent-span, and exact-backtick guards remain in place.Why it's needed
The recognizer merged in #7701 correctly stopped an escaped opener from becoming math, but its regex still excluded every dollar from a formula body and inspected only one preceding backslash. As a result, a literal dollar inside math, a formula next to an escaped dollar, and even-vs-odd backslash runs could not be represented correctly. The prose renderer also exposed
\from a Markdown escape instead of displaying the intended literal dollar.This is the narrow follow-up confirmed in #7740. It does not change delimiters or broaden TeX support.
Reviewer Test Plan
How to verify
Enable inline math and render these seven lines exactly:
Confirm that the first formula renders as
x², currency and the compact range remain prose, the prose escape displays asliteral $x$, the mixed formula displays asx + $5, and the two boundary cases display as$x²andx²$. Inline code containing\$must retain the backslash.Run
/copy inline-latexagainst the same output. The four formula sources should be selected in document order, preserving\$in the copied TeX source where present.Evidence (Before & After)
Before — merge-base
7959fdb27:After — patched TUI:
literal \$x$literal $x$$x + \$5$x + $5; copy retainsx + \$5\$$x^2$$followed by renderedx²$x^2\$$x²$\$The baseline was reproduced through the real React/Ink inline renderer and shared copy recognizer on current
main: only the first expression was recognized. On this branch, five focused suites exercise prose rendering, table rendering, source copy, table splitting, width measurement, code-span isolation, backslash parity, currency, and the complete seven-line corpus: 300/300 tests passed.Tested on
Environment (optional)
macOS 26.5.2 with Node.js v26.4.0. A clean
npm cicompleted the repository build and bundle. The CLI package build, typecheck, lint, and five focused suites (300 tests) passed on the rebased head.Risk & Scope
Linked Issues
Fixes #7740
中文说明
本 PR 的改动
本 PR 用一个有长度边界、能够判断反斜杠奇偶性的定界符扫描器,补全 CLI 对转义美元符号与行内公式的处理契约。转义美元符号现在可以出现在公式内部或紧贴公式边界;可见普通文本中的 Markdown 转义会被去除,而行内代码仍逐字节保留。
普通文本渲染、表格渲染、
/copy inline-latex、表格拆分和渲染宽度测量继续共享同一份识别结果。原有的 1024 字符上限、货币、shell 变量、相邻 span 和精确反引号守卫均保持不变。为什么需要
#7701 合入的识别器已经能阻止转义 opener 被误认成公式,但其正则仍禁止公式主体出现任何美元符号,并且只检查紧邻的一个反斜杠。因此,公式内的字面美元、紧邻转义美元的公式以及奇偶反斜杠 run 都无法正确表达;普通文本渲染器还会把 Markdown 转义中的
\直接显示出来,而不是只显示目标美元符号。这是 #7740 中已确认的窄范围后续修复,不改变定界符,也不扩大 TeX 支持。
Reviewer 测试计划
如何验证
启用行内公式,并原样渲染上方七行语料。确认第一行显示为
x²,货币与紧凑价格区间仍是普通文本,普通文本转义显示为literal $x$,混合公式显示为x + $5,两种边界分别显示为$x²与x²$。行内代码中的\$必须保留反斜杠。对同一输出执行
/copy inline-latex。四个公式源码应按文档顺序参与选择,并在适用位置保留复制出的 TeX 源码中的\$。证据(修复前后)
literal \$x$literal $x$$x + \$5$x + $5;复制仍得到x + \$5\$$x^2$$,随后是渲染后的x²$x^2\$$x²$\$的行内代码基线已通过当前
main的真实 React/Ink 行内渲染器与共享复制识别器复现:只有第一个公式被识别。本分支的五组聚焦测试覆盖普通文本渲染、表格渲染、源码复制、表格拆分、宽度测量、代码段隔离、反斜杠奇偶性、货币和完整七行语料,共 300/300 项通过。已测试平台
环境(可选)
macOS 26.5.2、Node.js v26.4.0。全新
npm ci已完成仓库 build 与 bundle;在 rebase 后的 head 上,CLI package 的 build、typecheck、lint 以及五组聚焦测试(300 项)全部通过。风险与范围
关联 Issue
Fixes #7740