跳转至

笔记正文 < 比较符截断后续内容(#533)

范围与现象

笔记正文出现 <0.5B、p<0.05、<-1 这类小于号后紧跟数字或符号的写法(模型参数量、统计显著性、延迟指标等)时,该段落之后的全部内容在编辑器中不再显示;磁盘上的文件完整无损,重开依旧复现。

原因

三层问题叠加:

  1. 反序列化管线挂载了 remarkMdx(用于支持 <div> / <center> / <iframe> 等内嵌 HTML)。MDX 规定 < 之后只能是名字起始字符(字母、$、_)、/、> 或空白,其余一律让 micromark 直接抛错:Unexpected character '0' (U+0030) before name。<0.5B 属于必崩形态。
  2. @platejs/markdown 捕获该错误后走 markdownToSlateNodesSafely 回退,其中的 splitIncompleteMdx 用自己的标签名字符集(数字、. 都算合法名字符)扫描全文,把 <0.5B)… 误判为「未闭合的标签」,在 < 处把文档切成两段。
  3. 截出的「不完整」半段只按内联 Markdown 解析且只取第一个块,其后所有段落、列表、标题被静默丢弃——这就是「显示不全」的直接来源。

修复

prepareMarkdownForDeserialize(打开文件、外部 / Agent 重载、粘贴、嵌入投影、导出共用的唯一预处理入口)新增 escapeStrayLessThan 预处理:

  • 必崩的裸 < 改写为 &lt; 实体:MDX 令牌器与 Plate 的 htmlToJsx 都无法把实体误认成标签,remark 解析时又解码回字面 <,编辑器显示不变。
  • 跳过不该动的区域:代码围栏、$$ 块级公式、行内代码、行内数学。
  • 保留合法形态:<div …>、</span>、<>、<!-- 注释 -->、a < b(< 后跟空白本就合法);</3 这类坏闭合同样转义。
  • 检查前置反斜杠奇偶性:序列化端会把文本 < 写回为 \<,二次加载不重复转义,serialize → deserialize 往返稳定。

选择 &lt; 而非 \< 的原因:htmlToJsx 的标签改写正则不识别反斜杠转义,\<0.5B … <div> 这种「转义后仍能在更远处配到 >」的文档仍会被改写成非法 JSX 再次崩溃;实体形式彻底没有 < 字符,两条路径都免疫。

后续:只放行支持的标签

533 只转义「< 后面不能作为标签名」的比较符。<Constraints>、<AI Paper Analyst>、<id> 的下一个字符是字母,MDX 会把它当成开始标签;没闭合就抛错,Plate 再从全文第一个 <name(包括行内代码里的 `<input>`)切开,后面的块同样消失。

编辑器实际只支持 <div>、<center>、<iframe>、<p> / <p align>、<br>、<u>、<sub>、<sup>、<mark>、<kbd>、<callout>。同一预处理现在按这个白名单转义其余 <(代码块、公式、已闭合的原始 HTML 块内部除外)。不再为每一种占位标签单独打补丁。

后续:别的 MDX 解析错误同样会绕过这里

escapeStrayLessThan 只防住「< 让 micromark 抛错」这一种 MDX 失败。正文里的 { / }(例如 LaTeX 写法 \max\{a,b\}、\(...\{...\}\))会让 acorn 抛错,Plate 的回退仍然会从某处 <(包括行内公式里的 $\rho<1$)把文档切成两段、丢掉后文。后续修复在预处理里实体化花括号,并在 MDX 仍失败时整篇改用无 MDX 管线重解析,见 note-markdown-mdx-brace-truncation.md。

回归验证

  • test/markdown-stray-less-than.test.ts:转义 / 保留边界单元测试(何时转义、标签与注释原样保留、代码围栏与块公式跳过),issue 原文反序列化后全部块保留,以及 \< round-trip 不产生二次转义。