Files
mnote/.codex/reasonix-tasks/results/batch-b-worker-d-pageaggregate-gfm-tail.md
T

144 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Batch B Worker DPage Aggregate / GFM AST Tail Audit
> 审计时间:2026-05-21
> 审计范围:
> - `design/05-editor-mainline/process/5-6-page-aggregate-alignment-checklist-v1.md`
> - `design/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` 全部 wiredInspector 文案统一 |
| 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 smokewatch 外部修改文件 → 断言 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 2props 链不扩容) | 这是理想终态,当前架构下每个新字段仍需 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 时会困惑。