Skip to content

fix(markdown): 修复移动端行内 LaTeX 结构、下标与转义定界符 - #7560

Open
dreamfarer-space wants to merge 2 commits into
makecindy:mainfrom
dreamfarer-space:fix/mobile-inline-latex-structure
Open

dreamfarer-space wants to merge 2 commits into
makecindy:mainfrom
dreamfarer-space:fix/mobile-inline-latex-structure

Conversation

@dreamfarer-space

@dreamfarer-space dreamfarer-space commented Oct 27, 2026 •

Copy link
Copy Markdown

这次改了什么

摘要

修复移动端聊天行内 LaTeX 在原生 Text 的 Unicode 近似转换中丢失数学结构的问题。用户提供的截图里,块级公式正常,但 \dot{c} 显示为 dotc,\frac{2\sqrt{3}}{3}\pi 显示为 frac2√33π,t_s 未转换为下标。

根因不是 KaTeX 资源失效:块级公式走 WebView KaTeX,行内公式由 MessageRenderer.renderInline 调用共享的 latexToUnicodeApproximation。原转换器用不支持嵌套花括号的正则解析分数,随后删除未知命令的反斜杠和分组花括号;重音命令和部分已有 Unicode 下标字符也未覆盖。

改为有深度上限的结构读取,先处理分数、根号和重音,再做符号与上下标近似。另修复共享定界符归一化把双反斜杠字面量误当成公式定界符的问题。

变更类型

  • fix 缺陷修复

范围

  • 关联需求:用户报告的 iOS 行内公式异常;未将现有的工具调用泄漏或桌面错误着色 Issue 当成本问题。
  • 本 PR 包含:共享数学文本转换、定界符转义识别、既有测试文件中的回归。
  • 用户可见变化:
    • \dot{c} → ċ,支持点、双点、帽、横线、向量和波浪重音;多字符重音保留可读的命令式近似。
    • 嵌套分数/根号保留分组;原截图中的常数显示为 ((2√3)/3)π。
    • 分母为乘积、分母含分数、分式后接平方或隐式乘积时保留作用范围,例如 3/(ξωₙ)、1/(a/b)、(a/b)²、(a/b)c。
    • 补齐 ₛ 等已有 Unicode 下标字符,t_s → tₛ。
    • 支持带次数的根号与单 token 参数,保留转义花括号/下划线,消除间距命令的源码残留。
    • \\(literal\\) 等字面量不再被归一化为公式;查找闭合定界符时跳过转义反斜杠。
  • 明确不包含:将原生 Text 行内公式迁移到 KaTeX、块级 KaTeX 引擎/随包资源升级、模型输出内容的数学正确性、横线等其他 Markdown 语法。
  • 是否存在 breaking change:无 API、wire protocol 或持久化格式变更。
  • 多端影响:原生文本转换同时惠及 Mobile 聊天和会话分享 SVG;定界符归一化是 Desktop/Mobile 共用逻辑。SSH/device-link 传输不变。

UI 变化

以下三张均为用户提供的修复前 iOS 截图,不是修复后或本轮实机验收证据。经用户明确授权随 PR 公开。

点导数显示异常 嵌套分式显示异常
用户提供:点导数显示为 dotc 用户提供:嵌套分式显示为 frac2√33π

用户补充:t_s 未转换为下标,分母作用范围不明确

  • 引用的设计规范:docs/design-rules/DESIGN.md §1 内容可读性、§15.13 移动端跨主题要求。仅修复正文中的数学文本,不新增颜色、字体、控件或布局;沿用现有 Light/Dark 样式。两种模式及 iOS/Android 的真实字形/组合重音效果本轮均未实机目检。

怎么验证的

自动验证

基线:0694ed7e001e027a031ffed21a088bd1975132de;当前 head:7f4553c54db8f11843b2ae31a2f31ef5e93a0a61。完整 tree SHA 为 995f918d66b70cf5b90ca31fd8efad5f0dfd76ed,与本地已验证提交 f3a44c6b 完全一致;远端仅作者元数据/DCO 对齐为提交账号。

node node_modules/vitest/vitest.mjs run packages/maker-shared/src/__tests__/mathMarkdown.test.ts --maxWorkers=1
结果:60/60 passed,单文件耗时 0.815s。

node node_modules/vitest/vitest.mjs run packages/maker-shared/src --maxWorkers=1
结果:104 files / 1961 tests passed,41.74s。

node node_modules/typescript/bin/tsc --noEmit -p packages/maker-shared/tsconfig.json
结果:passed。

node scripts/check-test-hygiene.mjs
结果:PASS,359 files within baseline;未修改基线。

git diff --check
结果:passed。

node scripts/check-dco.mjs --base origin/main
结果:passed,2 commits signed off。

初版回归验证:在独立临时目录将当时的 57 项测试指向基线原版实现,其中 20 项失败;初版补丁 57 项全部通过。本轮新增审核回归后,当前完整文件 60/60 通过。未修改生产源码来运行基线,也未弱化原有断言。

测试价值 — packages/maker-shared/src/__tests__/mathMarkdown.test.ts:

  • 生产入口与可观察结果:直接调用 latexToUnicodeApproximation 与 normalizeMathDelimiters,检查移动端展示文本和传给 Markdown parser 的实际字符串。
  • Bug 依据:三张用户截图中的点导数、嵌套根号分式与 t_s,以及此次同链路审计确认的分母/后缀作用范围、根次数与转义问题。
  • 现有覆盖缺口:既有测试只覆盖扁平分数、简单根号、部分上下标和未转义的数学定界符;未覆盖嵌套结构及命令参数。
  • 最小失败输入:\dot c、\frac{2\sqrt{3}}{3}、t_s、\frac{a}{b}^2、\sqrt[3]{x}、\{x\} 和 \\(x\\),按不同可观察分支使用表驱动表达。
  • tier:unit,纯函数、无网络/账号/真实等待;复用原测试文件。另检查逐字符未闭合输入和超深嵌套不抛错。
  • 实测耗时:单独运行完整文件 0.815s,测试执行 0.160s。

环境说明:pnpm install 被本环境的 npm 网络访问策略中断,offline 重试也缺少 tarball。为运行共享包测试,仅在未跟踪的本地依赖目录补回了锁定版本 estree-walker 3.0.3 的官方 tag 源码;未改 package.json、锁文件或提交依赖目录。以上共享包测试和类型检查实际通过。

手工验证

未执行真实 Cindy App 或 iOS/Android 设备验收。已对照用户原始截图和生产调用链核实输入与输出;这不等于实机验证。

未执行的验证

  • Mobile 的 messageMarkdown.test.ts / mathWebViewHtml.test.ts 已尝试,但收集阶段因依赖安装不完整(最终缺 react-i18next)失败,没有测试用例执行;不计入通过项。
  • 未运行完整 monorepo 测试、Desktop 完整渲染套件、Mobile 完整类型检查或远端 CI。
  • 未启动 Metro、未取得分支/worktree/安装包 __DEV__ 身份链,因此不声称任何截图来自当前分支 App。
  • 建议设备 QA:iOS/Android,Light/Dark,分别检查段落、标题、列表、表格与会话分享中的原截图公式、长分母换行、复制选择和组合重音字形。

风险

风险分类

  • 跨平台差异:Unicode 组合重音的字形依赖系统字体,仍需设备目检。
  • 其他:共享定界符归一化影响 Desktop 和 Mobile 的 Markdown 预处理。

影响与回滚

  • 此处仍是 Unicode 近似,不承诺复杂矩阵/任意 LaTeX 的精确排版;复杂显示公式继续由既有 KaTeX 块渲染。
  • 对嵌套转换设置 32 层上限,花括号/方括号先索引再读取,避免未闭合输入反复扫描到串尾;已有定界符对抗性输入测试保留。
  • 无依赖升级、原生代码、runtime fingerprint/OTA 配置、数据库、提示词或权限边界改动。
  • 回滚:revert 本 PR 的单个源码提交;用户原图仅附在 PR 正文中,不进入源码 diff。

提交前检查

  • 已 review 完整 diff
  • 每个 commit 都带匹配作者的 DCO Signed-off-by
  • 已注明设计依据与实机验证边界
  • 未提交凭证、令牌、授权文件或依赖目录
  • 已核对受影响文档,现有行内近似/块级 KaTeX 的架构描述仍成立
  • 已如实说明通过项、环境失败与未执行项

审核后续修复(7f4553c)

已核实 Greptile 的两条 P1,均为有效问题并在同一 PR 修复:

  1. 缺少分母的嵌套分式重复计算(discussion_r4391615793):参数读取先只定位源码范围,确认分式的两个参数均存在后才递归转换;单 token 命令的词法扫描按位置与剩余深度缓存,避免无花括号嵌套再引入重复扫描。原输入 "\\frac{".repeat(30) + "x" + "}".repeat(30) 在独立 Node 复现中超过 3 秒未返回而被终止;修复后约 1.86ms 返回可读文本。该时间是本机单次复现实测,不是通用性能承诺。单测保留相同输入并断言结果,不新增真实等待或严格计时测试。
  2. 根号内控制词与后续字母合并(discussion_r4391615809):单原子操作数保留 TeX 分组,等命令映射完成后再去除分组。当前 \\sqrt{\\alpha}x → √αx,带根次数的同类输入也保留希腊符号。两条新增显示用例指向修复前实现时均失败(√alphax),修复后通过。

测试价值补充:仍直接调用同一生产转换入口,覆盖先前未包含的“嵌套不完整 frac”与“控制词操作数后接字母”两个分支;新增 3 条 unit 用例,沿用既有文件,无新依赖/子进程/文件 I/O。隔离超时复现仅用于本轮人工验证,不进入默认 unit。

最新验证:60/60 数学回归、104 文件 / 1961 项共享包单测、共享包 TypeScript、test-hygiene 与差异检查均通过。保留三张用户原图;iOS/Android 实机、Mobile 完整套件及远端 CI 的待验证边界不变。

Signed-off-by: 择梦舟 <158067205+dreamfarer-space@users.noreply.github.com>
@dreamfarer-space
dreamfarer-space marked this pull request as ready for review October 27, 2026 16:48
@dreamfarer-space
dreamfarer-space requested a review from a team as a code owner October 27, 2026 16:48
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ⚠️ Failed 2026-10-27T17:22:16.583232Z
We couldn’t complete this review. Comment @codex review to try again.
7f4553c Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@dreamfarer-space

Copy link
Copy Markdown
Author

@codex review

@greptile-apps

greptile-apps Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 2/5

[Medium impact] The PR affects math text in mobile outputs and shared Markdown parsing.

暂不建议合并;应先修复异常公式导致的渲染阻塞和根号公式显示错误。

Findings

  1. P1 Security 异常分式卡住聊天 ▶
  2. P1 根号后的字母吞掉符号 ▶
Fix with agent prompt
### Issue 1
packages/maker-shared/src/mathMarkdown.ts:438-441
缺少分母的嵌套分式会让解析器反复处理同一段文本。例如,将 `"\\frac{".repeat(30) + "x" + "}".repeat(30)` 放进 `$...$`:`readArg` 先递归转换整个分子,随后发现没有分母,`readCommand` 却只返回命令名的结束位置。外层循环于是再次进入已经解析过的分子,工作量随层数指数增长。

32 层限制只限制调用深度,无法阻止重复计算。移动端在渲染正文时同步调用此函数,会因此卡住。请先确认两个参数的范围,再递归转换,并补充缺少分母的嵌套回归测试。

**How this was verified:** 消息中的 `$...$` 内容会直接进入同步转换函数,而缺少分母时的返回位置会让循环重新解析刚刚递归处理过的分子。

### Issue 2
packages/maker-shared/src/mathMarkdown.ts:448
`operand` 用转换后的字符数决定是否加括号,却返回原始命令文本。例如,`\sqrt{\alpha}x` 会先变成 `√\alpha`,再拼接 `x`;最后 `flattenLatex` 将 `\alphax` 当作未知命令,输出 `√alphax`,而不是保留 `α` 和后面的乘数 `x`。旧实现会保留括号边界,不会发生这种合并。

请在拼接结构转换结果时保留命令边界,并补充根号内为符号命令、根号后接字母的测试。

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

此 PR 用结构读取替换扁平正则,改善行内公式中的分式、根号、重音和下标,并修复转义定界符的识别。

  • Inline math keeps fractions, roots, and accents readable.
  • Markdown keeps escaped math markers out of formulas.

Reviews (1) · Last reviewed commit: "fix(mobile): preserve inline LaTeX struc..." · Reviewed by Greptile

Comment on lines +438 to +441
const first = readArg(end, nesting);
if (!first) return raw;
const second = fraction ? readArg(first.end, nesting) : null;
if (fraction && !second) return raw;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security 异常分式卡住聊天

缺少分母的嵌套分式会让解析器反复处理同一段文本。例如,将 "\\frac{".repeat(30) + "x" + "}".repeat(30) 放进 $...$:readArg 先递归转换整个分子,随后发现没有分母,readCommand 却只返回命令名的结束位置。外层循环于是再次进入已经解析过的分子,工作量随层数指数增长。

32 层限制只限制调用深度,无法阻止重复计算。移动端在渲染正文时同步调用此函数,会因此卡住。请先确认两个参数的范围,再递归转换,并补充缺少分母的嵌套回归测试。

How this was verified: 消息中的 $...$ 内容会直接进入同步转换函数,而缺少分母时的返回位置会让循环重新解析刚刚递归处理过的分子。

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/maker-shared/src/mathMarkdown.ts
Line: 438-441

Comment:
**异常分式卡住聊天**

缺少分母的嵌套分式会让解析器反复处理同一段文本。例如,将 `"\\frac{".repeat(30) + "x" + "}".repeat(30)` 放进 `$...$`:`readArg` 先递归转换整个分子,随后发现没有分母,`readCommand` 却只返回命令名的结束位置。外层循环于是再次进入已经解析过的分子,工作量随层数指数增长。

32 层限制只限制调用深度,无法阻止重复计算。移动端在渲染正文时同步调用此函数,会因此卡住。请先确认两个参数的范围,再递归转换,并补充缺少分母的嵌套回归测试。

**How this was verified:** 消息中的 `$...$` 内容会直接进入同步转换函数,而缺少分母时的返回位置会让循环重新解析刚刚递归处理过的分子。

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

text = `${operand(body)}/${operand(second.text)}`;
} else if (name === 'sqrt') {
const root = rootIndex === undefined ? '√' : rootIndex === '3' ? '∛' : rootIndex === '4' ? '∜' : `√[${rootIndex}]`;
text = `${root}${operand(body)}`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 根号后的字母吞掉符号

operand 用转换后的字符数决定是否加括号,却返回原始命令文本。例如,\sqrt{\alpha}x 会先变成 √\alpha,再拼接 x;最后 flattenLatex 将 \alphax 当作未知命令,输出 √alphax,而不是保留 α 和后面的乘数 x。旧实现会保留括号边界,不会发生这种合并。

请在拼接结构转换结果时保留命令边界,并补充根号内为符号命令、根号后接字母的测试。

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/maker-shared/src/mathMarkdown.ts
Line: 448

Comment:
**根号后的字母吞掉符号**

`operand` 用转换后的字符数决定是否加括号,却返回原始命令文本。例如,`\sqrt{\alpha}x` 会先变成 `√\alpha`,再拼接 `x`;最后 `flattenLatex` 将 `\alphax` 当作未知命令,输出 `√alphax`,而不是保留 `α` 和后面的乘数 `x`。旧实现会保留括号边界,不会发生这种合并。

请在拼接结构转换结果时保留命令边界,并补充根号内为符号命令、根号后接字母的测试。

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Signed-off-by: 择梦舟 <158067205+dreamfarer-space@users.noreply.github.com>
@dreamfarer-space

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Something went wrong. Try again later by commenting “@codex review”.

An unknown error occurred
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@MagicLizi MagicLizi added the touches:core 改动碰到架构核心路径(review-pr 自动维护,仅展示) label Oct 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

touches:core 改动碰到架构核心路径(review-pr 自动维护,仅展示)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants