Repository navigation
Replies: 4 comments
|
渲染层的缺陷是真的,你也把 1.
|
|
谢谢这么快来核,第 3、5 节我接受。先把两个最要紧的结论摆出来:(1) 1. 先回答"哪条臂":settled 臂
顺带修正我之前没说准的一点,正好回答"红字从哪来":红字是第二臂打的,不是第三臂。第二臂 <span class="katex-error" title="ParseError: KaTeX parse error: Can't use function '$' in math mode at position 4: c=d$̲$ …" style="color:#cc0000">c=d$$ …</span>(实测输出, 2.
|
|
你说得对,我上一条那条判断是错的,撤回。 我去把 单行, 你的第(2)点我现在也能解释为什么你说得对。 extensions: [gfm(), cjkFriendlyStrong(), mathCompatibility(), math()],而 const sameLineDollarMathFlow = createMathFlow(…, false) // ← multiline = false它是单行的,名字也直说了 所以交换数组顺序,动的是一个本来就不负责跨行块的流——那条测试自然不会红,你的问题也不会因此修好。这一层我上一条完全搞反了。 那个 settled 的回答很有用你说是 settled 臂,而且流式臂根本不注册 math——这跟源码一致。
所以我上一条问"你测的是哪条臂",答案就是 settled,这也把范围收窄了。 那接下来该看哪里既然跨行块归 不过这一步我没有跑,只是从注册顺序和 一点说明我上一条依据的是别人给我的一份核实材料,我对它做了二次核对(行号和文件都对),但没有自己去看那条测试的输入内容——只看用例名就下了"它覆盖了你的形状"的判断,这是错的。这一条我记下了:用例名不能当断言内容用。 你截图里那段"从第 4 行的 |

Uh oh!
There was an error while loading. Please reload this page.
Summary / 摘要
@deepseek-ai/dsh-client-ui-primitives的 markdown 渲染器(文档预览与聊天消息共用)无法正确解析最常见的 Obsidian / MathJax 风格 display math:两种写法都会出错:
$$开围栏后同一行上的公式被上游mathFlow当成 fence meta,而 mdast 的node.meta在整个渲染模块里没有任何代码读取 → 开首行内容静默消失;$$不算闭合 → math 节点的value把后续正文一路吞到下一个行首$$(没有就吞到文件末尾);katex.tsx的三臂渲染链只能把整段原文以红色katex-errorspan 打印出来 —— 用户看到的就是"一大段红色原文"。English TL;DR: a display-math block that starts its formula on the same line as the opening
$$and closes with$$at the end of a formula line is parsed by micromark's upstreammathFlow, which turns the opening line intomathFlowFenceMeta.mdast-util-mathstores it innode.meta, the renderer only readsnode.value, and since the closing fence is only recognised at the start of a line, the math node swallows every following line up to the next bare$$(or EOF). KaTeX then fails and prints the whole blob as raw red text.mathCompatibility()already contains a same-line dollar flow that would handle this, butparseGfmWithMathregistersmath()after it, and micromark gives the later extension precedence, so that construct is always shadowed.Reproduction / 复现
应用内:把下面的内容写进任意 markdown 文件,用侧边栏文档预览打开(聊天消息同样可复现)。
四种写法对照(
⏎表示换行):$$x+y$$math(value="x+y")$$⏎x+y⏎$$math(value="x+y")$$a=b⏎c=d$$a=b⏎c=dmath(meta="a=b", value="c=d$$")$$a=b⏎c=d⏎$$math(meta="a=b", value="c=d")$$或 EOF最小复现(不需要 DSH 内部源码,只用公开依赖即可观察到关键的第 1、2、3 步):
实测输出:
真实案例:一篇 777 行的 Obsidian 笔记里,以
$$开头的行有 75 处、没有任何一行是"只有$$"的围栏行。第 144 行的$$打开围栏后,\cos\theta=\frac{x}{1}=x,\qquad成为 meta 被丢弃,从第 145 行的\sin\theta=...=y$$起,直到文件末尾全部落进同一个 math 节点(该节点 value 长 19792 字符)。KaTeX 第一层throwOnError: true直接抛KaTeX parse error,随后由errorColor: #cc0000把整段原文渲染成红色,也就是现象里那一大段"乱码"。Current behavior / 当前表现
$$,a=b也照样不见)。$$不闭合,导致公式块吞并后续正文;后果从"一个公式块渲染错"升级为"整篇文档后半部分全毁"。Expected behavior / 预期表现
与 Obsidian、MathJax、KaTeX、VS Code 等实现一致:
$$ ... $$是 display 块,允许在$$同一行开始写公式、允许跨行、允许在任意$$处结束。至少:$$后同一行的内容当作可丢弃的 meta;meta,render.tsx的case 'math'应当把它一起渲染,而不是静默忽略。Root cause / 根因
packages/client/ui-primitives/src/markdown/parse.ts:42:micromark 的
combineExtensions把每个扩展的 constructs 前插(micromark-util-combine-extensions:splice(existing, 0, 0, before)),即数组里越靠后的扩展优先级越高。因此上游math()的mathFlow总是先于本包的mathCompatibility()被尝试。packages/client/ui-primitives/src/markdown/mathCompatibility.ts:325-330的sameLineDollarMathFlow是multiline = false:它只在"上游
mathFlow失败"时才会被用到,而上游mathFlow对任何$$开围栏都成功(afterDollarOpen,mathCompatibility.ts:152-154,只有在开围栏后紧跟第二个$时才nok)。上游
mathFlow允许开围栏行剩余内容作为 fence meta;mdast-util-math@3.0.0将其写入node.meta(enterMathFlowMeta/exitMathFlowMeta),而渲染端只读node.value:render.tsx:338-339在整个 markdown 模块中搜索
.meta,只有render.tsx里的事件metaKey一处,没有任何地方读取node.meta→ 开首行公式静默消失。上游
mathFlow的收尾围栏要求位于行首(其后仅允许空白),所以c=d$$这种行中收尾不闭合,value继续吞并后面的行。本包自己的 dollar flow 也帮不上多行场景:
mathCompatibility.ts:156-169的content()中multiline === false时遇到换行直接nok。(backslashMathFlow,mathCompatibility.ts:318-323,是multiline = true,所以\[ ... \]多行是正常的 —— 这也说明多行支持本来就是设计意图,只是 dollar 分支漏了。)红色原文的出处:
katex.tsx:66-90的三臂链 ——katex.renderToString(value, { displayMode, throwOnError: true })抛错 →{ strict: 'ignore', throwOnError: false }(KaTeX 自己用errorColor渲染错误,内容即原始源码)→ 兜底<span className="katex-error" style={{ color: '#cc0000' }}>{value}</span>。Suggested fix / 建议修复
两处改动需要同时应用:
(
mathCompatibility.ts:281里name: ... ? 'sameLineDollarMathFlow' : 'backslashMathFlow'建议一并改名。)注意:两处必须同时改。实测"只把
multiline改成true"没有任何效果(该 construct 被上游math()抢先),"只交换数组顺序"也没有效果(提前的 dollar flow 仍然是multiline = false)。验证方式:在真实依赖(micromark 4.0.3、micromark-extension-math 3.1.0、mdast-util-math 3.0.0、mdast-util-from-markdown 2.1.0、katex)上把
mathCompatibility()逐行移植后重建parseGfmWithMath,对 13 个形状做改前/改后对照:只有 4 个"应当被修复"的形状发生变化(开首行带文本的多行块、块引用内的同类块、列表项内的同类块、公式块后接正文),其余全部保持一致 —— 未闭合$$(仍按上游语义、meta 丢弃)、行内$$a=b$$、连续两个单行块、\[ ... \]、代码围栏内的$$、转义\$$、单美元行内公式$x+y$。建议补测试:
$$a⏎b$$、$$⏎a⏎$$、$$a$$三种写法都应得到math节点,且前两种的meta为null、value为完整内容。Environment / 环境
0.2.0-rc.2(macOS,Electron 外壳);文档预览由侧边栏 document preview 使用@deepseek-ai/dsh-client-ui-primitives渲染,聊天消息使用同一渲染器。micromark@4.0.3、micromark-extension-math@3.1.0、mdast-util-math@3.0.0、mdast-util-from-markdown@2.1.0、katex。parseGfmWithMath的扩展数组。Impact / 影响面
这不是边角写法:Obsidian、Typora、MathJax 用户与 LLM 导出的数学笔记普遍采用"
$$与公式同行起头 + 多行 + 行尾$$收尾"。本地一个 vault 的统计:218 篇 markdown 笔记中 142 篇出现$$围栏、139 篇含跨行$$块(共 2227 个开围栏);其中 6 篇的 30 个块(60 个围栏)使用了"开首行带公式 / 行尾收尾"的写法,改前触发 13 处 KaTeX 失败并输出红色原文,把围栏改成各自独占一行后降为 0。Workaround / 临时规避
内容侧可以绕开:把开、收
$$各自独占一行,或写成单行$$x$$,或改用 TeX 的\[ ... \](backslashMathFlow已是multiline = true)。但这需要用户批量改写自己的笔记,且渲染器对最常见的数学写法仍然是坏的,建议在渲染器侧修复。All reactions