fix(footnotes): stop ancestor blocks from poisoning note definitions - #133
Conversation
collect_definitions() remembers every id-bearing descendant of each matched block using the outer block's inner text as note content. In dual-language WeRead chapters a chapter-root wrapper spans nearly the whole chapter, so footnote anchors were indexed with entire flattened chapter text (both language variants concatenated) as the note body. The generated wr-book-footnote asides then pasted those blobs into KOReader's in-page footnote flow at every marker; one real book produced 60 giant asides of up to ~53k chars alongside 546 legit ones. remember_definition() now: - rejects candidates longer than MAX_NOTE_TEXT_BYTES outright, with a warning log - keeps the shortest candidate per anchor instead of first-win: the true note is the smallest region describing the anchor, and ancestor captures are strict supersets of it - refuses pure-symbol candidates, so backlink arrows can no longer displace real note text via a shared id Adds regression specs for wrapper poisoning, shortest-wins ordering, long-but-legit notes, and arrow-only candidates.
|
请模板提交 cr |
|
已按 有一项想说明:测试小节里的「KOReader integration」我留空了。本 PR 只改动 截图一节也标了不适用:这是渲染缺陷修复,没有新增或改动 UI、菜单与交互。设备侧的验证结果(正文 4117 段、470866 个可见字符零丢失)写在测试说明里。 |
|
这里 has_word_or_cjk_char() 只判断了 %w 和 0xE4~0xE9,好像会漏掉日文假名、韩文以及其他非 ASCII 字符。 如果脚注只有这些文字,可能会被误认为纯符号直接丢掉。这里是不是直接针对已知的回链符号过滤会更安全一些? |
… script whitelist Review feedback: has_word_or_cjk_char() only knew %w and the CJK UTF-8 lead bytes 0xE4-0xE9, so notes written only in kana, hangul, Cyrillic or any other non-ASCII script were mistaken for pure backlink glyphs and dropped. Invert the test: a candidate is rejected only when every codepoint it holds is a symbol or punctuation (arrows/dingbats/enclosed numbers U+2000-U+2FFF, CJK punctuation U+3000-U+303F, Latin-1 symbols U+0080-U+00BF, variation selectors, BOM). Any other codepoint -- Latin letters, kana, hangul, CJK, emoji -- counts as note text, so the backlink glyph guard keeps working without whitelisting scripts.
已改成反向过滤(f65cf22):新增
没有采用手列回链符号清单,主要是清单列不全: spec 补了两组用例:假名/韩文/西里尔文/CJK 扩展区 B/注标混合文本的脚注必须保留(旧实现下会失败),以及 |
变更说明
修复繁简双语书籍中脚注定义被祖先块污染的缺陷。
collect_definitions()会把整章正文误存为单条脚注定义,导致在 KOReader 默认的页内脚注流中,每个脚注标记都渲染出一大段拍平的章节正文,读者看到大量重复文字。相关:#137(调试同一本书时发现的
font-size: 0根字号问题,已另开 PR)。类型
Bugfix 要求
无关联 issue(上游此前无人报告过此问题),提供复现步骤。
复现步骤(修复前):
clean_note_text拼接在一起),页面上出现大段重复文字实测环境:KOReader v2026.07.1,Kindle。
根因:
collect_definitions()遍历 BLOCK_TAGS 时,对每个匹配到的外层块,会用外层块的内部文本去记录其下所有带 id 的后代标签。在繁简双语章节中,章节根级 wrapper 几乎覆盖整章,同时包含正文锚点与章末注释,于是每个脚注锚点都被索引成了整章拍平文本。先到先得的守卫(definitions[anchor])让问题进一步恶化:一旦被污染的祖先捕获先于真正的定义块被记录,正确的短文本就再也无法替换它。实测数据(受影响书籍):生成 606 条 aside,其中 60 条为被污染的巨型条目,最长约 53500 字符,而正常注释的中位长度约 130 字符。
修复:
remember_definition()现在会:MAX_NOTE_TEXT_BYTES(6000 字节,约 2000 个中日韩字符)的候选,并记录一条警告日志。真实注释远低于此阈值,整章捕获远高于此阈值↩)无法顶替真实注释文本Feature 要求
不适用,本 PR 为 bugfix,未新增特性。
测试
bash scripts/run_lua_specs.shbash scripts/check_lua_namespace.shluacheck main.lua _meta.lua weread specKOReader integration测试说明:
非公开 WeRead API
脚本路径:
复现命令与脱敏结果:
模块结构
未新增或移动任何模块。仅修改既有的
weread/lib/footnotes.lua,并新增spec/footnotes_spec.lua。命名空间保持不变,未引入根级lib/、ui/模块,也未引入裸lib.*、ui.*模块键。截图
不适用。本 PR 是渲染缺陷修复,未新增或修改 UI、菜单、弹窗与交互。
Checklist
settings/weread.lua、API key、cookie、token、x-wrpa-*或私人书籍内容。weread/lib/、weread/ui/命名空间规范。(不适用:未新增或移动模块)weread/lib/i18n.lua。(不适用:未修改用户可见文本)scripts/中提交可独立运行、可复现的 Python 验证脚本,并填写了复现命令和脱敏结果。(不适用:不涉及)