fix(cli): align inline math recognition - #7701
Conversation
E2E test reportBaseline reproductionA read-only harness using the exact
Post-fix verificationOn macOS 26.5.2 with Node.js v26.4.0:
On Ubuntu Linux x86_64 (kernel 7.0.0-28) with Node.js v22.23.1 and npm 10.9.8:
Remaining gapNo fresh model/API-driven terminal capture was run for the rebased final diff because a global |
|
✅ Qwen Triage finished — CI landed green on ✅ Qwen Triage 已完成 —— |
|
Thanks for the PR! Template looks good ✓ Problem: observed bug with solid evidence. Issue #7699 documents the exact recognizer failures (single-char expressions rejected, escaped dollars mishandled, copy command ignoring code spans), with a per-consumer behavior table and a root-cause confirmation from a maintainer. The issue carries Direction: aligned. This fixes a consistency bug in the existing inline-math feature (#3680), not a new direction. The maintainer's comment on #7699 explicitly invited a community PR with this exact fix direction (shared module, fixed lookahead, escaped-dollar guard, code-span awareness). CHANGELOG has no direct reference to inline math, but the area is an existing shipped feature with a confirmed regression. Size: not applicable — no core module paths touched. All changes are in Approach: the scope feels right. The PR does exactly what the issue and maintainer prescribed — extract one shared recognizer ( Moving on to code review. 🔍 中文说明感谢贡献! 模板完整 ✓ 问题:已观测到的 bug,证据充分。Issue #7699 记录了识别器的具体故障(单字符表达式被拒绝、转义美元符号处理错误、copy 命令忽略代码段),附有各消费路径的行为对照表和维护者的根因确认。Issue 带有 方向:对齐。这修复的是现有行内公式功能(#3680)的一致性 bug,不是新方向。维护者在 #7699 的评论中明确欢迎社区 PR,且修复方向完全一致(共享模块、修正前瞻、转义美元守卫、代码段感知)。 规模:不适用——未触及核心模块路径。所有改动在 方案:范围合理。PR 完全按照 issue 和维护者的建议执行——提取共享识别器( 进入代码审查 🔍 — Qwen Code · qwen3.8-max-preview Reviewed at |
Code ReviewIndependent proposal: given the issue's root-cause analysis, I would (1) create a shared Comparison with the diff: the PR matches this proposal exactly. The implementation is clean and correct:
No critical blockers or AGENTS.md violations found. The test coverage is thorough — single-char, CJK-adjacent, escaped dollars (opening and closing), single/multi-backtick code spans, currency, shell variables, bounds, table cells, and copy-command ordering. CI Test EvidenceThe PR's CI workflow ( Final CI results for
One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。 Not verified: Windows and macOS behavior (CI jobs skipped for fork PRs). The author reports local testing on macOS and Linux. The maintainer has independently verified the previous head ( 中文说明代码审查独立方案: 根据 issue 的根因分析,我会 (1) 创建共享模块 与 diff 的对比: PR 完全匹配此方案。实现干净正确:
未发现关键阻塞问题或 AGENTS.md 违规。测试覆盖全面。 CI 测试证据PR 的 CI 工作流正在审查提交上运行。Ubuntu 单元测试仍在进行中;Windows 和 macOS 测试作业被跳过(fork PR 的标准行为)。 未验证:Windows 和 macOS 行为(CI 作业因 fork PR 被跳过)。维护者已在 macOS 上对前一个 head( — Qwen Code · qwen3.8-max-preview Reviewed at |
|
Confidence: 4/5 — solid, well-scoped bugfix that does exactly what the issue and maintainer prescribed; the new commit closes the last untested guard. Only non-blocking notes are the skipped Windows/macOS CI (standard for fork PRs) and This is what a good community bugfix looks like. The problem is real and well-documented (#7699, maintainer-confirmed, The new commit ( The maintainer's independent end-to-end verification of the previous head (real TUI, real clipboard, 1,226-input differential, 7-mutation matrix, performance profiling) gives high confidence that the behavioral change is correct and complete. The new commit is test-only, so that verification carries forward. If I had to maintain this in six months, I would thank the author — the shared module means the next inline-math fix only needs to happen in one place instead of four. Approval deferred until CI lands green on 中文说明置信度:4/5 — 扎实、范围合理的 bugfix,完全按照 issue 和维护者的建议执行;新提交补上了最后一个未测试的 guard。唯一的非阻塞备注是 Windows/macOS CI 被跳过(fork PR 的标准限制)以及 这是一个优秀的社区 bugfix 范例。问题真实且记录充分(#7699,维护者确认, 新提交( 维护者对前一个 head 的独立端到端验证(真实 TUI、真实剪贴板、1,226 条输入差分、7 项变异矩阵、性能分析)为行为变更的正确性和完整性提供了高度信心。新提交仅为测试,因此该验证继续有效。 审批已推迟,等待 CI 在 — Qwen Code · qwen3.8-max-preview Reviewed at |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
There was a problem hiding this comment.
Pull request overview
This PR introduces a shared, bounded inline-math recognizer for the CLI so that prose rendering, table rendering, /copy inline-latex, and pending table measurement agree on what counts as inline math (including single-character expressions) while keeping escaped dollars and backtick-delimited inline code literal. This addresses the inconsistency described in #7699.
Changes:
- Added a centralized inline-math/inline-code recognition module and updated renderers to use its regex sources.
- Unified table-splitting and streaming/pending-height math-span detection on the same span reader.
- Updated unit + integration coverage to lock in behavior for single-char/CJK-adjacent math, escaped dollars, inline code spans (including multi-backtick), and table-cell parsing.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| packages/cli/src/ui/utils/TableRenderer.tsx | Swaps local inline-math/code patterns for shared recognizer sources in table rendering. |
| packages/cli/src/ui/utils/pending-rendered-height.ts | Uses the shared inline-math span reader for table-row splitting during pending height estimation. |
| packages/cli/src/ui/utils/pending-rendered-height.test.ts | Adds regression coverage to ensure escaped dollars don’t “hide” table separators. |
| packages/cli/src/ui/utils/MarkdownDisplay.test.tsx | Verifies table math rendering stays consistent with escaped/code-span literal handling. |
| packages/cli/src/ui/utils/InlineMarkdownRenderer.tsx | Replaces ad-hoc regexes with shared sources and normalizes math/code handling in plain-text measurement. |
| packages/cli/src/ui/utils/InlineMarkdownRenderer.test.tsx | Adds tests for single-char/CJK-adjacent math, escaped dollars, code spans, and width measurement. |
| packages/cli/src/ui/utils/inline-math.ts | New shared inline math + inline code span patterns and helper APIs. |
| packages/cli/src/ui/utils/inline-math.test.ts | New tests defining the bounded recognition contract (length bound, escapes, code spans). |
| packages/cli/src/ui/commands/copyCommand.ts | Switches /copy inline-latex selection to the shared inline-math finder (excluding code spans/escaped dollars). |
| packages/cli/src/ui/commands/copyCommand.test.ts | Adds coverage for correct selection/numbering with single-char math and literal contexts. |
| integration-tests/terminal-capture/scenarios/markdown-rendering.ts | Extends the markdown rendering scenario prompt to include single-char math and literal escaped/code cases. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const cleanText = normalizedText | ||
| .replace(/\*\*(.*?)\*\*/g, '$1') | ||
| .replace(/\*(.*?)\*/g, '$1') | ||
| .replace(/_(.*?)_/g, '$1') | ||
| .replace(/~~(.*?)~~/g, '$1') |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed — no blockers. 1 Suggestion-level finding(s) could not be anchored to a changed line and were dropped; nothing further to act on here.
中文说明
已审查——无阻断问题。 1 条建议级发现无法锚定到改动行,已丢弃;此处无需进一步处理。
— qwen3.7-max via Qwen Code /review
doudouOUC
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline. Not reviewed: chunk 1, chunk 2 — no agent reported covering these; nobody read them. Not reviewed: Agent 0: Issue fidelity & root-cause ownership, Agent 1a: Line-by-line correctness, Agent 2: Security, Agent 3: Code quality, Agent 4: Performance & efficiency, Agent 5: Test coverage, Agent 6a: Undirected audit — attacker mindset, Agent 6b: Undirected audit — 3 AM oncall mindset, Agent 6c: Undirected audit — six-months-later maintainer, Agent 1b: Removed-behavior audit, Agent 1c: Cross-file tracer, Agent 7: Build & test verification — its prompt was built, but no agent on record was launched with it. Not reviewed: reverse audit — its prompt was built, but no agent was launched with it — the pass that hunts what the rest of the review missed ran, if at all, without the method its brief carries, and cannot be certified. Not reviewed: verification — its prompt was built, but no agent was launched with it, so the posted findings cannot be counted as verified.
— qwen3.7-max via Qwen Code /review
| 4. Inline math $x$ and $y = \\\\frac{-b \\\\pm \\\\sqrt{b^2 - 4ac}}{2a}$. | ||
| 5. One display math block using $$ fences. |
There was a problem hiding this comment.
[Suggestion] This scenario adds valuable inline-math content (items 4–5, 7), but no CI workflow or npm test command collects the terminal-capture scenarios — the only entry point is the manual capture:markdown-rendering script. If the rendering or /copy latex integration for math regresses, this scenario silently produces stale screenshots without any automated gate catching it.
Concrete cost: the three bugs this PR fixes (single-char $x$, escaped \$xy$, code-span `$xy$`) now have new scenario assertions that will never run in CI.
Consider either adding a CI step that runs capture:markdown-rendering (or the whole terminal-capture suite), or adding an assertion-based integration test for the math rendering + /copy latex flow in a vitest-collected file.
— qwen3.7-max via Qwen Code /review
Maintainer verification — local real-run reportI rebuilt and re-verified this PR end-to-end on real hardware (no mocks of the code under test), comparing head Verdict: the fix is real, correctly scoped, and I recommend merging. Two non-blocking notes are at the bottom. SetupTwo isolated worktrees (PR head and merge-base), each with its own real
Environment: macOS 15.7.7 (arm64), Node v22.23.1, npm 10.9.8. 1. The PR's own suite reproduces exactly2. The new tests are load-bearing (A/B against base source)I copied the PR's four modified test files verbatim onto the base source, leaving base's recognizers in place. 9 tests fail, and they cover all four consumers — so nothing here is a vacuous test:
3. Real TUI end-to-end (built CLI in a pty)I booted the built CLI in a pty against a mock model streaming the identical markdown sample to both trees, and captured the real terminal: Four user-visible defects on
4. Real clipboard end-to-endI then drove This is the sharpest demonstration of the bug. On 5. Cross-consumer differential, 1,226 inputsI drove all four consumers (real ink render, real
The 9 residual disagreements all need an unbalanced code span immediately adjacent to a bare 6. Mutation matrix — 5 of 7 guards proven load-bearingI reverted each guard in
7. Performance — no complexity regression
Both trees are already ~O(n²) on this shape (base grows 3.84× per doubling, PR 3.91×) — the exact-run backtick matching adds a constant ~2.2×, not a new complexity class, so this is not a new ReDoS surface. On realistic prose there is no measurable change (139-char paragraph with math + code + prices: 82.6 µs → 79.5 µs), and even the worst 2,000-char adversarial line (2.70 ms) stays below the 6.9 ms both trees spend on an ordinary 2,000-char plain line. 8. CI-equivalent gates
Non-blocking notesa. it('rejects a formula whose closing dollar is escaped', () => {
expect(findInlineMathExpressions(String.raw`A $x\$ B`)).toEqual([]);
expect(findInlineMathExpressions(String.raw`Total $a b\$ end`)).toEqual([]);
});Worth adding to b. 中文说明Maintainer 本地真实验证报告我在本机对本 PR 做了完整的端到端复核(被测代码本身没有任何 mock),对比 head 结论:问题真实存在,修复方向正确、范围得当,建议合并。 两条非阻断性说明见文末。 环境搭建两个隔离 worktree(PR head 与 merge-base),各自拥有独立的真实
环境:macOS 15.7.7(arm64)、Node v22.23.1、npm 10.9.8。 1. PR 自带测试完全复现6 个测试文件、364 个测试全部通过,与 PR 描述一致。 2. 新增测试确实有效(对 base 源码做 A/B)把 PR 的 4 个测试文件原样拷到 base 源码上、保留 base 的识别器,结果 9 个测试失败,且覆盖了全部四个消费路径,说明没有空转测试:
3. 真实 TUI 端到端(pty 驱动已构建的 CLI)用 pty 启动已构建的 CLI,接一个 mock model 向两棵树流式输出完全相同的 markdown,并截取真实终端画面(见上方第一张图)。
4. 真实剪贴板端到端随后在实时 TUI 中执行 这是对该 bug 最直观的证明:在 5. 跨消费路径差分:1,226 条输入用四个真实消费路径(真实 ink 渲染、真实
残留的 9 条不一致都需要一个不配对的代码段紧邻裸 6. 变异测试:7 个 guard 中 5 个被证明有效逐个回退 7. 性能:没有复杂度回退以 8. 等价于 CI 的检查
非阻断性说明a. 闭合 it('rejects a formula whose closing dollar is escaped', () => {
expect(findInlineMathExpressions(String.raw`A $x\$ B`)).toEqual([]);
expect(findInlineMathExpressions(String.raw`Total $a b\$ end`)).toEqual([]);
});建议补进 b. Verified locally by the maintainer: two built worktrees, real pty TUI runs, real system clipboard, 1,226-input cross-consumer differential, and a 7-mutation guard matrix. |
|
@qwen-code /triage |
|
Triage re-run completed without a new review. The stage comments above were updated with the latest result. View workflow run. |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship — CI landed green after the review. ✅
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
中文说明
— qwen3.7-max via Qwen Code /review
|
Released in v0.21.1. |


What this PR does
This PR gives the CLI one bounded inline-math recognition contract across prose rendering, table rendering, source-copy selection, and pending table measurement. It recognizes single-character expressions, keeps escaped dollars and exact backtick-delimited code spans literal, and preserves the existing currency and shell-variable safeguards.
The regression coverage exercises CJK-adjacent formulas, one- and multi-backtick code spans, table cells, source-copy ordering, the 1024-character bound, raw currency text, and rendered-width measurement.
Why it's needed
The existing lookahead requires at least two content characters, so valid expressions such as
$x$and$α$remain raw. The four consumers also handle context independently: escaped dollars can become math, while source copy can select expressions that the renderer correctly leaves inside inline code.Keeping those paths on the same recognizer prevents the displayed output, copied TeX source, table layout, and streaming bookkeeping from disagreeing. This fixes #7699.
Reviewer Test Plan
How to verify
Enable inline math and render$xy$ `. Longer code: ``a `$zz$` b``. Price: $20 and $30.` The two single-character expressions should render without delimiters; the escaped expression, both code spans, and both prices should remain literal.
Values $x$、$α$。 Literal \$xy$. Code: \Run
/copy inline-latexagainst assistant output containing those cases followed by$x$and$\alpha$. Onlyxand\alphashould participate in inline-LaTeX numbering.Render a table containing
$x$,\$xy$, a multi-backtick code span, and$P(A|B)$. Single-character math should render, literal contexts should remain source, and the pipe inside recognized math should not create another column.Evidence (Before & After)
$x$/$α$\$xy$`$xy$`xy$20/$30The pre-fix behavior was reproduced with the exact current-main recognizers. The post-fix component suite exercises the real renderer, copy command, table renderer, and pending-height paths.
Tested on
Environment (optional)
macOS 26.5.2 with Node.js v26.4.0: 6 focused test files and 364 tests passed.
npm run build,npm run typecheck,npm run lint, andnpm run bundlepassed; the final CLI package build, typecheck, and lint also passed after the last source refinement.Ubuntu Linux x86_64 (kernel 7.0.0-28) with Node.js v22.23.1 and npm 10.9.8: a fresh clone of commit
cce61724ecompletednpm ci(including the full build and bundle), then passed CLI typecheck, lint, and the same 364 focused tests.Risk & Scope
$...$support remains opt-in and currency guards remain in place.Linked Issues
Fixes #7699
中文说明
本 PR 的改动
本 PR 为 CLI 的普通文本渲染、表格渲染、源码复制选择和流式表格测量建立一个有长度边界的统一行内公式识别契约。它可以识别单字符公式,让转义美元符号和使用精确反引号长度界定的代码段保持字面形式,同时保留现有的货币和 shell 变量防护。
回归测试覆盖 CJK 相邻公式、单反引号与多反引号代码段、表格单元格、源码复制顺序、1024 字符上限、货币文本和渲染宽度测量。
为什么需要
现有前瞻至少要求两个内容字符,因此
$x$和$α$等有效公式会保持原始源码。四个消费路径还会分别处理上下文:转义美元符号可能变成公式,而源码复制可能选中渲染器已经正确保留在行内代码中的表达式。让这些路径使用同一个识别器,可以避免显示输出、复制的 TeX 源码、表格布局和流式状态计算彼此不一致。本 PR 修复 #7699。
Reviewer 测试计划
如何验证
启用行内公式并渲染$xy$ `. Longer code: ``a `$zz$` b``. Price: $20 and $30.`。两个单字符公式应当去掉定界符后渲染;转义表达式、两个代码段和两处价格应保持字面形式。
Values $x$、$α$。 Literal \$xy$. Code: \对包含上述情况并在末尾带有
$x$和$\alpha$的 assistant 输出运行/copy inline-latex。只有x与\alpha应参与行内 LaTeX 编号。渲染一个包含
$x$、\$xy$、多反引号代码段和$P(A|B)$的表格。单字符公式应正常渲染,字面上下文应保留源码,已识别公式内的竖线不应产生新列。证据(修复前后)
$x$/$α$\$xy$`$xy$`xy$20/$30修复前行为通过当前
main的原始识别器精确复现。修复后的 component suite 覆盖真实的渲染器、复制命令、表格渲染器和 pending-height 路径。已测试平台
环境(可选)
macOS 26.5.2,Node.js v26.4.0:6 个 focused 测试文件、364 个测试全部通过。
npm run build、npm run typecheck、npm run lint和npm run bundle全部通过;最后一次源码收紧后,CLI package 的 build、typecheck 和 lint 也再次通过。Ubuntu Linux x86_64(kernel 7.0.0-28),Node.js v22.23.1、npm 10.9.8:从 commit
cce61724e全新 clone 后完成npm ci(包括完整 build 与 bundle),随后通过 CLI typecheck、lint 和同一组 364 个 focused 测试。风险与范围
$...$支持仍需显式启用,货币防护保持不变。关联 Issue
Fixes #7699