14 KiB
14 KiB
Batch B Worker D:Page Aggregate / GFM AST Tail Audit
审计时间:2026-05-21 审计范围:
design/05-editor-mainline/process/5-6-page-aggregate-alignment-checklist-v1.mddesign/03-rust-web/process/3-13-rust-web-local-markdown-gfm-ast-parser-migration-v1.md
一、GFM AST 解析器迁移(3-13)审计
1.1 已完成可追认
| 清单项 | 证据 | 说明 |
|---|---|---|
| 11.1 选型:comrak 解析器 | local_markdown_parser.rs:131-152 使用 comrak::parse_document |
确认选型完成,comrak 为成熟 GFM AST 实现 |
11.2 MarkdownAstDocument 中间结构 |
local_markdown_parser.rs:14-17 |
结构体已定义 |
| 11.2 block 级节点映射(全类型) | local_markdown_parser.rs:19-51 enum MarkdownBlock |
覆盖 paragraph/heading/list/task/quote/code/divider/table/image/mindmap/media |
| 11.2 inline 级节点映射 | local_markdown_parser.rs:103-113 struct MarkdownInline + MarkdownInlineStyles |
覆盖 text/code/bold/italic/strike/underline/link |
| 11.2 task list checked 状态 | local_markdown_parser.rs:210-215 TaskItem.symbol |
正确分离 checked/unchecked |
| 11.2 表格单元格 inline mark | local_markdown_parser.rs:256-259 行内组合子复用 |
已验证 local_folder_source.rs 测试 local_markdown_parser_preserves_pipe_table_as_table_block |
| 11.3 映射集中到单一模块 | local_markdown_parser.rs 全文件 |
手写 parser 读侧分支已从 local_folder_source.rs 迁出 |
| 11.3 block document 与 web_shell 兼容 | local_markdown_parser.rs:443-570 输出格式 |
tiptap 表结构、legacy 样式格式都输出 |
| 11.3 block id 稳定性 | local_markdown_parser.rs:451 format!("local-block-{block_number}") |
基于块序号生成稳定 ID |
| 11.3 frontmatter 保存边界 | local_folder_source.rs:2131-2135 保存时保留 frontmatter |
有测试 local_markdown_save_preserves_frontmatter_and_writes_basic_blocks |
| 11.3 unsupported 节点降级 | local_markdown_parser.rs:173-179 HTML block→paragraph |
转义为普通段落 |
| 11.4 inline code/bold/italic/strike/link 解析 | local_markdown_parser.rs:264-305 |
全部通过 comrak AST 节点→MarkdownInline |
| 11.4 回写 inline mark | local_folder_source.rs:6998-7100 inline_nodes_to_markdown |
测试 local_markdown_save_writes_task_list_and_inline_marks 验证回写 |
| 11.4 表格单元格 inline mark 回写 | local_folder_source.rs:7838-7880 |
测试 local_markdown_save_writes_table_inline_marks + local_markdown_save_round_trips_tiptap_table_marks |
| 11.5 task list 全部 6 项 | 解析→回写→smoke | 测试 local_markdown_parser_preserves_task_list_and_inline_marks 解析 + local_markdown_save_writes_task_list_and_inline_marks 回写 |
| 11.6 pipe table 解析规则 | local_markdown_parser.rs:150 options.extension.table = true |
comrak 原生支持 |
| 11.6 单元格 inline IR 输出 | local_markdown_parser.rs:593-610 tiptap_inline_nodes_to_json |
local_markdown_parser_preserves_pipe_table_as_table_block 验证单元格内 code/bold |
| 11.6 表头/单元格类型稳定性 | local_markdown_parser.rs:230-236 基于 TableRow(true) |
row header 标志通过 AST 保留 |
| 11.6 保存回写保留表格分隔行和列数 | local_folder_source.rs:7838-7880 |
local_markdown_save_writes_table_alignment_markers 验证列对齐标记 |
| 11.7 frontmatter title/mnote_id/页面设置写回 | local_folder_source.rs:2175-2178 |
测试 local_markdown_save_preserves_frontmatter |
| 11.7 正文保存走 page.body.save 命令 | local_folder_source.rs:2204-2205 |
canonicalCommand: "page.body.save" |
11.8 local_markdown 测试组 |
local_folder_source.rs 多个测试 |
覆盖基础块、task list、inline marks、table、附件链接 |
| 11.8 新增 AST 解析器单测 | local_markdown_parser.rs:734-747 |
仅一个测试 markdown_image_parses_as_image_block(较薄弱) |
| 11.8 保存回写单测(round-trip) | local_folder_source.rs local_markdown_save_writes_* 系列 |
task、inline marks、table 均已覆盖 |
| 11.8 浏览器 smoke | scripts/task* 系列 |
task164-page-options-visible-effect-smoke.js 等,GFM smoke 待确认 |
| 11.8 确认 file tree / page tree / page aggregate 不受影响 | web_shell.rs 测试 |
document_shell_renders_local_markdown_with_same_sidebar_surfaces 验证 |
11.9 移除 local_folder_source.rs 临时解析函数 |
读侧已使用 local_markdown_parser.rs |
手写读 parser 已移除,写侧 inline_nodes_to_markdown 保留 |
| 11.9 保留回归测试和兼容层 | 全部测试保留 | 删除策略不会清除验证资产 |
1.2 仍需代码修复
| 清单项 | 当前状态 | 问题描述 | 修复建议 |
|---|---|---|---|
11.2 标题优先级:frontmatter title > H1 > 文件名 |
标记 [x] 但未实现 | parse_markdown_page (local_markdown_parser.rs:86-94) 始终使用 file_stem_title(file_name),完全忽略 frontmatter 中的 title 字段和正文 H1。tree.rs:7307 测试显式断言 frontmatter title 不应出现。 |
在 resolve_local_markdown_page_aggregate 中增加优先级解析:解析 frontmatter 取 title key,若无则扫描 AST 第一个 H1,若无则 fallback 到 file_stem_title。同时修改 tree.rs:7275-7307 测试预期。 |
| 11.4 legacy→Tiptap→legacy 不丢 marks | 标记 [ ] 仍待处理 | web_shell.rs:831-882 的 legacyStylesToTiptapMarks/legacyMarkArrayToTiptapMarks/mergeTiptapMarks 做了一次方向转换,但反向(Tiptap→legacy)转换没有等价的自动化测试。 |
新增 web_shell.rs 测试:构造带所有 mark 类型(bold/italic/code/strike/link)的 editor block → 调用 legacyInlineContentToTiptap → 结果再反转换回 legacy → 断言 marks 未丢失。 |
| 11.6 列对齐、空单元格、带 mark 单元格的降级策略 | 标记 [ ] 仍待处理 | 列对齐已有测试 local_markdown_save_writes_table_alignment_markers(但 checklist 未更新)。空单元格和降级策略无测试覆盖。 |
为空的 table cell 补充 markdown_to_blocks 测试,明确返回空 paragraph 或 skip。为不支持的对齐/跨列行为补充降级断言。 |
| 11.7 保存失败时保留编辑器状态 | 标记 [ ] 仍待处理 | save_local_markdown_page 在冲突/写文件失败时返回 WebError,但无机制保证前端编辑器状态保留。前端 side 的 error handler 未在测试中验证。 |
补集成测试:注入错误的 expected_conflict_detection_key → 断言返回 local_markdown_external_change 错误码 + HTTP 409,且前端编辑缓冲区不清空。 |
| 11.8 web shell 单测:legacy↔Tiptap 转换不丢 marks | 标记 [ ] 仍待处理 | document_shell_bootstrap_preserves_inline_mark_conversion 只验证 JS 转换函数的存在性(字符串包含),未验证实际转换结果。 |
补充利用 app().oneshot() 请求文档页 → 从 HTML 中提取 JS 上下文 → page.evaluate() 调用转换函数 → 验证输出 JSON 中 marks 完整性。 |
| 11.9 手写 parser 标记为过渡实现 | 标记 [ ] 仍待处理 | local_folder_source.rs 中 inline_nodes_to_markdown / editor_blocks_to_markdown_with_rewrite / editor_blocks_to_markdown_for_file 等写侧函数无任何 #[deprecated] 或文档注释标注过渡状态。 |
在写侧函数上加 /// Transitional: will be replaced by AST-based reverse mapping in Step 3 标注,并在模块文档注释中明确过渡路线。 |
| 11.9 web_shell.rs 临时适配分支 | 标记 [ ] 仍待处理 | web_shell.rs:831-882 的 legacyStylesToTiptapMarks 等 JS 函数是前端 legacy→Tiptap 适配层,仍无删除计划。 |
标注 // TODO(step-4): remove when legacy block format is fully deprecated,或跟踪 issue 链接。 |
1.3 追认修正:应标记为 [x] 但未标记
| 清单项 | 证据 | 操作建议 |
|---|---|---|
| 11.6 保存回写保留列对齐标记 | local_markdown_save_writes_table_alignment_markers 测试 |
应勾选 |
| 11.8 浏览器 smoke 验证 checkbox/code/strong 等 | scripts/task* smoke 系列(需确认具体文件名) |
如果存在则勾选 |
1.4 仅参考保留 / future
| 项 | 说明 |
|---|---|
| 11.2 空段落/空表格单元格/空引用块 | 这是 comrak 行为边界,G FM AST 天然处理,但规则未文档化。建议只补一个测试断言当前行为,不必在 P1 前重做。 |
| 11.6 列对齐降级策略 | 列对齐已实现,剩下"空单元格"和"不支持对齐方式的降级"可降为 future |
| 11.9 移除 web_shell.rs 临时适配分支 | 只有 legacy block 格式全面下线后才能移除,当前为 P2+ 项 |
二、Page Aggregate 清单(5-6)审计
2.1 已完成可追认
| 清单项 | 证据 |
|---|---|
| Phase F 全部 4.x 设计层和代码层标准 | 大量补充记录确认 contract 已冻结、page-aggregate-loader.ts 消费 Rust endpoint、写入命令面收口 |
| Phase G 加载链路收口 + 散落 props 收口 | /api/page-aggregate/:id 为主读链、TS builder 降级为 410、page-aggregate-client-state.ts 统一 client state |
| Phase H 最小接入优先级 + 页面设置面板纠偏 | wideLayout/smallText/layoutDensity/showHeadingNumbers/embedDefaultBlockId 全部 wired,Inspector 文案统一 |
| Phase I 标题真相纠偏 + 回归验证 | task110-page-title-single-truth-smoke.js 验证页头/面包屑/sidebar/page tree/file tree/刷新/切页一致性 |
| Phase J 语义边界 + 命令名收口 | 命令名统一为 page.head.updateTitle / page.layout.updateOptions / page.body.save |
| 补充:EditorBlockDocument 块文档优先级 + 原生快照保留 | build_page_aggregate_projection_result 按 editorDocument→blockDocument→documents.content 优先级构建 |
| 补充:page.body.save 原生快照失败语义 | 不可解析 editorDocument 时返回 validation error,不回退到 tiptapDocument |
2.2 仍需代码修复
| 项 | 当前状态 | 问题描述 | 修复建议 |
|---|---|---|---|
P1.1 写侧未完全退出 /api/documents/save |
标记 [ ] 部分解决 | local_folder 已切到 /api/page-body/write,但 Convex workspace 仍走 /api/documents/save |
明确 documents/save 的定位:是 Convex 独占路径还是中间态 compat。如果是前者则更新文档说明,如果是后者则排期移除。 |
| P1.2 tiptap dirty + AI/外部文件变更的 conflict UI | 标记 [ ] 仍待处理 | 本地 folder 已有 external-change-conflict DOM 和 mnote-conflict-* 面板元素(web_shell.rs:4990-5015),但前端 side 的 conflict 流程覆盖测试不完备。 |
补充 Playwright smoke:watch 外部修改文件 → 断言 conflict 面板出现 → 选择"保留当前"或"接受磁盘" → 断言结果正确。 |
| P1.3 pageOptions 收口到 leptos-tiptap island 运行时语义 | 标记 [ ] 部分解决 | 分类已完成(page-option-semantics.ts),但 protectEditing/showBlockRefCount 仍为 planned 未真正 wired。 |
如果 P1 目标不要求这些,建议在文档中明确标注"P2: protectEditing/showBlockRefCount 接线"。 |
| P1.4 AI 写入口对齐"授权文件引用+白名单目录+后台文件写入+前台同步" | 标记 [ ] 部分解决 | 命令面已收口到 page family,但"授权文件引用范围"(allowedRoots/aiAccessScope)的实现未在审计中验证。 | 检查 Hermes tool surface 上是否限制了 allowedRoots,以及 page.body.write 是否做了路径 escape 检查。 |
| Phase G 退出标准 2:新增字段不再需 props 链外扩 | 标记 [ ] 部分解决 | 入口级痕迹消失但新增字段仍需补 loader/route/island 消费链。 | 建议在 page-aggregate-loader.ts + page-aggregate-client-state.ts 中增加一个 passthroughUnknownFields 机制,使新增聚合字段无需修改 loader 即可透传到消费侧。 |
2.3 仅参考保留 / future
| 项 | 说明 |
|---|---|
| Phase G exit 2(props 链不扩容) | 这是理想终态,当前架构下每个新字段仍需 route→loader→state→consumer 链改动。建议标记为 P2 方向性目标,不作为 P1 exit criterion。 |
| AI 写入口完整对齐 | 命令面已收口,但"授权引用"和"后台写入+前台同步"涉及 Hermes skill 层和 BufferStore,建议在专项 Hermes 审计中评估。 |
Convex workspace 写侧仍走 documents/save |
如果 Convex 作为 cloud source 长期保留,documents/save 就是它的正式路径而不是 compat,应调整文档口径。 |
三、下一步最小执行包建议
立即修复(P1,本 worker 批次)
| # | 描述 | 难度 | 涉及文件 |
|---|---|---|---|
| 1 | 修复 11.2 标题优先级:parse_markdown_page 改为 frontmatter > H1 > filename |
低 | local_markdown_parser.rs + tree.rs 测试预期 |
| 2 | 补 11.6 空单元格 / 降级策略测试 | 低 | local_markdown_parser.rs 测试 |
| 3 | 补 11.8 web shell 单测验证 legacy→Tiptap 转换不丢 marks | 中 | web_shell.rs 测试 |
| 4 | 标注写侧函数为过渡实现(11.9) | 低 | local_folder_source.rs 文档注释 |
| 5 | 标注 web_shell.rs 临时 JS 适配为过渡(11.9) | 低 | web_shell.rs JS 注释 |
文档追认(本 worker)
| # | 描述 | 涉及文档 |
|---|---|---|
| 1 | 3-13 清单 11.2 标题优先级应改为 [ ](当前误标为 [x]) | design/03-rust-web/process/3-13-*.md |
| 2 | 3-13 清单 11.6 列对齐已有测试,可追认 [x] | design/03-rust-web/process/3-13-*.md |
| 3 | 5-6 清单补充本次审计时间戳和裁定结论 | design/05-editor-mainline/process/5-6-*.md |
后续优先级(P2+)
| # | 描述 |
|---|---|
| 1 | 写侧从 inline_nodes_to_markdown 手写规则迁移到 AST 反向映射(3-13 Step 3) |
| 2 | 移除 web_shell.rs legacy→Tiptap JS 适配层 |
| 3 | Convex workspace 写侧统一到 page.body.write |
| 4 | conflict UI Playwright smoke |
四、风险
- 标题优先级误标 [x]:如果 frontmatter title 与 filename 不一致,用户看到的是文件名而非意图标题。当前文件取名时 frontmatter title 通常与文件名一致,所以影响有限,但仍须修复。
- 手写写侧函数仍无 AST 反向映射:
inline_nodes_to_markdown等函数维护着一套独立的样式→markdown 规则,与 AST 解析器的理解可能产生细微不一致(如_与*的选择)。不是 P1 blocker,但建议在 Step 3 统一。 documents/save双重语义:对local_folder是 compat,对convex_workspace是正式路径。如果口径不统一,未来新增 workspace type 时会困惑。