advance 1-8 post-mvp execution batches
This commit is contained in:
@@ -0,0 +1,451 @@
|
||||
# 3-13 [done] Rust Web 本地 Markdown GFM AST 解析器迁移方案 v1
|
||||
|
||||
> 更新时间:2026-05-09
|
||||
>
|
||||
> 归档时间:2026-05-21
|
||||
>
|
||||
> 归档说明:GFM AST 读侧、写侧 round-trip、表格/mark/任务列表/保存失败状态保留的回归证据已补齐;`web_shell.rs` 的 legacy marks 适配分支已改判为当前兼容层,不再作为阻塞归档的“必须移除”项。
|
||||
>
|
||||
> 关联:
|
||||
> - `/mnt/Data1T/mnote/rust/crates/mnote-web/src/routes/local_folder_source.rs`
|
||||
> - `/mnt/Data1T/mnote/rust/crates/mnote-web/src/routes/local_markdown_parser.rs`
|
||||
> - `/mnt/Data1T/mnote/rust/crates/mnote-web/src/routes/web_shell.rs`
|
||||
> - `/mnt/Data1T/mnote/design/04-tree-domain/done/4-21-local-folder-convex-unified-tree-source-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/04-tree-domain/done/4-22-local-folder-convex-unified-tree-execution-checklist-v1.md`
|
||||
|
||||
## 1. 结论
|
||||
|
||||
当前本地 Markdown 渲染链路已经进入“读侧 AST、写侧仍在过渡”的 partial 状态:
|
||||
|
||||
- 读侧已新增 Rust `local_markdown_parser.rs`,并基于成熟 GFM AST 解析常见语法
|
||||
- 写侧回 Markdown 仍主要依赖 `local_folder_source.rs` 里的手写规则
|
||||
|
||||
因此当前问题已经不再是“要不要迁到 AST”,而是“如何把剩余写侧与兼容补丁继续收口”。
|
||||
|
||||
本方案当前聚焦的是:继续把 AST 映射到现有 `PageAggregate` / Tiptap bridge,并让剩余手写 parser/回写逻辑逐步退回过渡层,不再作为长期语义来源。
|
||||
|
||||
## 2. 当前问题
|
||||
|
||||
本地链路现在分成两段:
|
||||
|
||||
1. `local_folder_source.rs` 负责读取 `.md`、拆 frontmatter、把正文转成 block。
|
||||
|
||||
1. `web_shell.rs` 负责把 legacy block 转成 Tiptap,再把 Tiptap 保存回 legacy block。
|
||||
|
||||
问题不在 Tiptap 本身,而在前面的 Markdown 语义输入过窄:
|
||||
|
||||
- task list、inline code、bold、italic、strike、link 之前会被吞成纯文本。
|
||||
|
||||
- table 只做了最低限度兼容。
|
||||
|
||||
- 解析逻辑是手写规则,扩展语法时会不断叠条件分支。
|
||||
|
||||
- 读写两端各自维护一份近似规则,容易出现回写不一致。
|
||||
|
||||
## 3. 目标
|
||||
|
||||
- 用 Rust 侧成熟 GFM AST 解析器替换手写 Markdown parser。
|
||||
|
||||
- 保留当前已验证的本地文件夹读写能力。
|
||||
|
||||
- 让 Markdown 语法来源尽量统一,减少“读得懂、写不回”的差异。
|
||||
|
||||
- 继续支持当前已暴露的语法面:paragraph、heading、task list、list、quote、fenced code、divider、pipe table、attachment link、inline code、bold、italic、strike、link。
|
||||
|
||||
- 不改变 `PageAggregate`、树 projection、权限、路由边界。
|
||||
|
||||
## 4. 非目标
|
||||
|
||||
- 不在这一步追求“所有 Markdown 语法无损互转”。
|
||||
|
||||
- 不把 VSCode 源码直接搬进来。
|
||||
|
||||
- 不把 Next/Wolai 导入链路里的 `@blocknote/core markdownToBlocks` 作为本地 source 真相。
|
||||
|
||||
- 不把 tree/domain、Convex 或 editor island 的所有语义一起重写。
|
||||
|
||||
## 5. 方案比较
|
||||
|
||||
### 方案 A:继续扩写手写 parser
|
||||
|
||||
优点:
|
||||
|
||||
- 改动小,能快速补洞。
|
||||
|
||||
缺点:
|
||||
|
||||
- 语法越来越多时会失控。
|
||||
|
||||
- 读写规则难以保证一致。
|
||||
|
||||
- 这条路本质上还是重复造轮子。
|
||||
|
||||
### 方案 B:Rust GFM AST + 语义映射层
|
||||
|
||||
优点:
|
||||
|
||||
- 语法基础来自成熟实现,降低长期维护成本。
|
||||
|
||||
- AST 天然适合做 block / inline 双向映射。
|
||||
|
||||
- 更容易补齐 task list、table、inline mark 等常见 GFM 特性。
|
||||
|
||||
缺点:
|
||||
|
||||
- 需要补一层 AST 到 `PageAggregate` 的适配。
|
||||
|
||||
- 初期需要做迁移和回归测试。
|
||||
|
||||
### 方案 C:复用前端/导入侧解析器
|
||||
|
||||
优点:
|
||||
|
||||
- 可以少写部分逻辑。
|
||||
|
||||
缺点:
|
||||
|
||||
- 本地文件夹主链是 Rust 服务,不适合把语义真相放回 JS 侧。
|
||||
|
||||
- 导入链路和本地打开链路不是同一职责。
|
||||
|
||||
### 推荐
|
||||
|
||||
选方案 B。它最符合当前架构:语义在 Rust 侧收口,前端只消费稳定 projection。
|
||||
|
||||
## 6. 架构设计
|
||||
|
||||
### 6.1 解析层
|
||||
|
||||
新增一个本地 Markdown 解析模块,职责只做三件事:
|
||||
|
||||
- 读取 Markdown 文本并构建 GFM AST。
|
||||
|
||||
- 归一化 frontmatter、正文、块级节点、行内节点。
|
||||
|
||||
- 输出可供 `PageAggregate` 使用的中间表示。
|
||||
|
||||
建议把手写字符串规则替换成“AST -> 中间 IR -> block”的两段式转换,而不是直接从 AST 拼最终 JSON。
|
||||
|
||||
### 6.2 中间 IR
|
||||
|
||||
建议引入一个轻量中间层,表达 block / inline 的稳定语义:
|
||||
|
||||
- block:paragraph、heading、task_item、list_item、quote、code_block、divider、table、media。
|
||||
|
||||
- inline:text + marks(code、bold、italic、strike、underline、link)。
|
||||
|
||||
这样做的目的不是再造一套编辑器模型,而是把 Markdown 语义和 Tiptap/legacy block 解耦。
|
||||
|
||||
### 6.3 输出层
|
||||
|
||||
输出层仍然保留两条现有通路:
|
||||
|
||||
- 读入时:Markdown AST -> block document -> `PageAggregate.body.content`
|
||||
|
||||
- 保存时:editor document -> block document -> Markdown
|
||||
|
||||
`web_shell.rs` 里的 legacy / Tiptap 转换仍然存在,但它应该只负责编辑器适配,不再承担 Markdown 语法识别。
|
||||
|
||||
### 6.4 语法优先级
|
||||
|
||||
第一批必须稳定支持:
|
||||
|
||||
- task list
|
||||
|
||||
- inline code
|
||||
|
||||
- bold / italic / strike
|
||||
|
||||
- link
|
||||
|
||||
- pipe table
|
||||
|
||||
- fenced code
|
||||
|
||||
第二批再扩:
|
||||
|
||||
- nested list
|
||||
|
||||
- setext heading
|
||||
|
||||
- autolink
|
||||
|
||||
- footnote
|
||||
|
||||
- raw HTML
|
||||
|
||||
- block quote 多段落
|
||||
|
||||
## 7. 迁移步骤
|
||||
|
||||
### Step 1:引入 AST 解析器
|
||||
|
||||
先把当前自写 parser 包进一个兼容接口,替换为 Rust GFM AST 实现。
|
||||
|
||||
要求:
|
||||
|
||||
- 读入结果和现在的 `PageAggregate` 输出保持兼容。
|
||||
|
||||
- 已有 smoke 和 Rust 单测先不改大结构。
|
||||
|
||||
### Step 2:统一 block / inline 映射
|
||||
|
||||
把 AST 到 block document 的映射集中到一个明确模块,不再散在 `local_folder_source.rs` 大文件里。
|
||||
|
||||
要求:
|
||||
|
||||
- task list 变成真正的 todo / task item 语义。
|
||||
|
||||
- inline mark 进入统一的 marks 表达。
|
||||
|
||||
- table cell 里的 mark 也能保留。
|
||||
|
||||
### Step 3:统一回写
|
||||
|
||||
把 block document -> Markdown 的输出改成与 AST 语义对齐的反向映射。
|
||||
|
||||
要求:
|
||||
|
||||
- task list 回写为 `- [x]` / `- [ ]`。
|
||||
|
||||
- 行内 mark 回写为标准 Markdown。
|
||||
|
||||
- 不支持的结构明确降级,不静默丢内容。
|
||||
|
||||
### Step 4:清理临时补丁
|
||||
|
||||
当 AST 迁移完成后,删除当前手写 parser 的临时补丁代码,保留测试和兼容层。
|
||||
|
||||
## 8. 测试计划
|
||||
|
||||
### Rust 单测
|
||||
|
||||
至少覆盖:
|
||||
|
||||
- task list 读写往返。
|
||||
|
||||
- inline code / bold / italic / strike / link 往返。
|
||||
|
||||
- table 单元格内 inline mark。
|
||||
|
||||
- frontmatter title 保留。
|
||||
|
||||
- 未支持语法的降级策略。
|
||||
|
||||
### 浏览器 smoke
|
||||
|
||||
至少覆盖:
|
||||
|
||||
- 本地 `.md` 打开后 checkbox 真正出现在 ProseMirror DOM。
|
||||
|
||||
- `code`、`strong`、`em`、`s`、`a` 在正文和表格单元格中真实渲染。
|
||||
|
||||
- 保存后刷新不丢语义。
|
||||
|
||||
### 回归边界
|
||||
|
||||
- 不破坏 `file_tree` / `page_tree`。
|
||||
|
||||
- 不改变 local workspace 的只读边界。
|
||||
|
||||
- 不影响 Convex workspace 链路。
|
||||
|
||||
## 9. 风险
|
||||
|
||||
- GFM AST 到 block 的映射会比手写 parser 更清晰,但初期要补一轮语义归一化。
|
||||
|
||||
- 复杂 Markdown 结构不可能天然无损,必须明确哪些语法是支持、哪些是降级。
|
||||
|
||||
- 如果 AST 解析器选型过于底层,后续表格、任务列表和 inline mark 的兼容代码会变多,所以要优先选能直接拿到 AST 的成熟实现。
|
||||
|
||||
## 10. 完成判定
|
||||
|
||||
这个方案进入实现阶段的条件:
|
||||
|
||||
- 解析器选型明确。
|
||||
|
||||
- AST -> block / inline 映射接口冻结。
|
||||
|
||||
- 当前手写 parser 的临时补丁有对应回归测试。
|
||||
|
||||
- 浏览器 smoke 能稳定证明 task list 和 inline mark 真正渲染出来。
|
||||
|
||||
## 11. 详细 checklist
|
||||
|
||||
### 11.1 选型与边界冻结
|
||||
|
||||
- [x] 确认 Rust 侧 Markdown AST 解析器选型。
|
||||
|
||||
- [x] 明确是否需要 GitHub Flavored Markdown 全集,还是只保留当前本地链路所需的 GFM 子集。
|
||||
|
||||
- [x] 冻结 frontmatter 处理方式:解析器只解析正文,还是 frontmatter 也由同一模块统一处理。
|
||||
|
||||
- [x] 冻结 AST 输出到中间 IR 的边界,不直接在 AST 层拼 `PageAggregate`。
|
||||
|
||||
- [x] 明确 table、task list、inline mark、fenced code、attachment link 的优先级。
|
||||
|
||||
- [x] 明确不支持语法的降级策略,要求可解释且可测试。
|
||||
|
||||
### 11.2 AST -> 中间 IR
|
||||
|
||||
- [x] 建立 `MarkdownAstDocument` / 等价中间结构。
|
||||
|
||||
- [x] 建立 block 级节点映射:paragraph / heading / list / task / quote / code / divider / table / media。
|
||||
|
||||
- [x] 建立 inline 级节点映射:text / code / bold / italic / strike / underline / link。
|
||||
|
||||
- [x] 保证 task list 的 checked 状态进入正确的 task item 节点,而不是外层 list 节点。
|
||||
|
||||
- [x] 保证表格单元格内的 inline mark 不丢失。
|
||||
|
||||
- [x] 保证空段落、空表格单元格、空引用块的处理规则固定。
|
||||
- 追加测试 `markdown_empty_blockquote_parse` / `markdown_table_empty_cells_parse` 固定当前 comrak 行为
|
||||
|
||||
- [x] 统一处理标题优先级:frontmatter title > H1 > 文件名。
|
||||
|
||||
### 11.3 中间 IR -> PageAggregate / block document
|
||||
|
||||
- [x] 把 AST 到 `PageAggregate.body.content` 的映射集中到单一模块。
|
||||
|
||||
- [x] 消除 `local_folder_source.rs` 里散落的手写语法分支。
|
||||
|
||||
- [x] 让 block document 输出保持与 `web_shell.rs` 兼容。
|
||||
|
||||
- [x] 保留 block id 稳定性,避免刷新后整篇文档重建导致的局部状态抖动。
|
||||
|
||||
- [x] 保留 frontmatter 原文和页面设置写回边界。
|
||||
|
||||
- [x] 对 unsupported 节点采用明确降级,而不是静默丢语义。
|
||||
|
||||
### 11.4 Inline mark 双向转换
|
||||
|
||||
- [x] `code` 解析为 inline code mark。
|
||||
|
||||
- [x] `bold` 解析为 strong mark。
|
||||
|
||||
- [x] `italic` 解析为 emphasis mark。
|
||||
|
||||
- [x] `strike` 解析为 strike mark。
|
||||
|
||||
- [x] `link` 解析为带 href 的 link mark。
|
||||
|
||||
- [x] 保存回写时按同一映射反向输出 Markdown。
|
||||
|
||||
- [x] 确保 table cell 内的 inline mark 在读写两端都保留。
|
||||
|
||||
- [x] 确保 legacy block -> Tiptap -> legacy block 不再丢 marks。
|
||||
- `document_shell_bootstrap_preserves_inline_mark_conversion` 已断言 bold/italic/underline/strike/code/link 全部 6 种 core marks
|
||||
|
||||
### 11.5 Task list 双向转换
|
||||
|
||||
- [x] `- [x]` / `- [X]` 进入 checked task item。
|
||||
|
||||
- [x] `- [ ]` 进入未勾选 task item。
|
||||
|
||||
- [x] task item 文本内容走 inline IR,不走纯文本拼接。
|
||||
|
||||
- [x] Tiptap 的 `taskList` / `taskItem` 与 legacy `todo` 对应关系固定。
|
||||
|
||||
- [x] 保存回写时 task item 重新输出为标准 Markdown checkbox 语法。
|
||||
|
||||
- [x] 浏览器 smoke 断言 checked / unchecked 两种状态都出现。
|
||||
|
||||
### 11.6 Table 处理
|
||||
|
||||
- [x] 统一 pipe table 解析规则。
|
||||
|
||||
- [x] 单元格内容通过 inline IR 输出,不再只保留纯文本。
|
||||
|
||||
- [x] 表头 / 普通单元格类型在 AST 映射中保持稳定。
|
||||
|
||||
- [x] 保存回写时保留表格分隔行和列数对齐。
|
||||
|
||||
- [x] 列对齐、空单元格、带 mark 单元格的降级策略要写入测试。
|
||||
|
||||
### 11.7 保存回写
|
||||
|
||||
- [x] 保存链路只依赖 block document / editor document,不依赖 Markdown 手写规则。
|
||||
|
||||
- [x] 前端编辑器保存路径仍能写回 `.md`。
|
||||
|
||||
- [x] task list、inline mark、table 的回写结果可再次被 AST 解析器读回。
|
||||
|
||||
- [x] 保留 frontmatter `title` / `mnote_id` / 页面设置写回逻辑。
|
||||
|
||||
- [x] 保存失败时保留编辑器状态并给出可解释错误。
|
||||
|
||||
### 11.8 回归测试
|
||||
|
||||
- [x] `local_markdown` 测试组覆盖基础块、task list、inline marks、table、附件链接。
|
||||
|
||||
- [x] 新增 AST 解析器单测,验证解析结果和当前手写 parser 预期一致或更强。
|
||||
|
||||
- [x] 新增保存回写单测,验证 round-trip 不丢 task / marks。
|
||||
|
||||
- [x] 新增 web shell 单测,验证 legacy <-> Tiptap 转换不丢 marks。
|
||||
- `document_shell_bootstrap_preserves_inline_mark_conversion` 已扩展断言 bold/italic/underline/strike/code/link
|
||||
|
||||
- [x] 新增浏览器 smoke,验证 checkbox、code、strong、em、strike、link、table cell marks。
|
||||
|
||||
- [x] 确认本地 `.md` 读写 smoke 不影响 file tree / page tree / page aggregate 路由。
|
||||
|
||||
### 11.9 清理与迁移收尾
|
||||
|
||||
- [x] 把手写 parser 标记为过渡实现。
|
||||
- `local_folder_source.rs` 中 `inline_nodes_to_markdown`、`editor_blocks_to_markdown_for_file`、`editor_blocks_to_markdown_with_rewrite` 均已添加中文过渡注释
|
||||
|
||||
- [x] 移除 `local_folder_source.rs` 中不再需要的临时解析函数。
|
||||
|
||||
- [x] 复核 `web_shell.rs` 中 legacy marks 适配分支。
|
||||
- 结论:`legacyStylesToTiptapMarks` / `legacyMarkArrayToTiptapMarks` 仍被 `legacyInlineContentToTiptap` 与 `document_shell_bootstrap_preserves_inline_mark_conversion` 覆盖,用于兼容历史 legacy block bootstrap;当前不安全移除。
|
||||
- 退出条件:当 Rust 侧 Page Aggregate / block document 原生输出完全替代 legacy block bootstrap,且对应 web shell 单测不再断言这些函数时,再单独删除。
|
||||
|
||||
- [x] 保留回归测试和兼容层,不删除验证资产。
|
||||
|
||||
- [x] 迁移完成后把设计稿状态从 `process` 移到 `done`。
|
||||
|
||||
---
|
||||
|
||||
## 12. 审计记录(2026-05-21 Batch B Worker D)
|
||||
|
||||
### 12.1 已完成确认
|
||||
|
||||
以下 [x] 项经代码审查确认实际完成,证据充分:
|
||||
|
||||
- 11.1 全部 6 项:comrak 选型完成,GFM 子集确定,frontmatter 处理方式冻结,IR 边界明确,优先级确定,降级策略明确
|
||||
- 11.2 `MarkdownAstDocument`、block/inline 映射、task list checked 状态、表格 inline 串联全部完成
|
||||
- 11.3 AST→PageAggregate 映射集中到 `local_markdown_parser.rs`,兼容性验证通过
|
||||
- 11.4 code/bold/italic/strike/link 解析+回写全部完成,表格内 inline mark 有集成测试
|
||||
- 11.5 task list 双向转换全部完成,有 round-trip 测试
|
||||
- 11.7 前端保存路径写回 `.md`、round-trip 可读回、frontmatter 保留均有测试覆盖
|
||||
- 11.8 本地 md 读写 smoke 不影响 file tree/page tree/page aggregate
|
||||
|
||||
### 12.2 追认修正
|
||||
|
||||
| 项 | 原标记 | 审计结论 | 说明 |
|
||||
|---|--------|---------|------|
|
||||
| 11.2 标题优先级 | [x] | 应改为 **[ ]** | `parse_markdown_page` 始终使用 `file_stem_title(file_name)`,完全忽略 frontmatter `title` 和正文 H1。`tree.rs:7307` 测试显式断言 frontmatter title 不应出现。**此为误标,需修正。** |
|
||||
| 11.2 标题优先级(复检) | [ ] Batch D Worker B 已修复 |→ **[x]** | `parse_markdown_page` 已改为 frontmatter title > H1 > filename 三层优先级。新增 8 个单元测试覆盖三层优先级、引号包裹、ATX closing marker;已更新旧测试预期值。详见 `local_markdown_parser.rs`。 |
|
||||
| 11.6 列对齐标记 | [ ] | 可追认为 **[x]** | `local_markdown_save_writes_table_alignment_markers` 测试已验证列对齐 marker 保存回写。 |
|
||||
|
||||
### 12.3 仍保留 [ ] 项的状态
|
||||
|
||||
| 项 | 状态 | 说明 |
|
||||
|---|------|------|
|
||||
| 11.2 空段落/空表格单元格/空引用块 | **[x] 关闭** | 已补 `markdown_empty_blockquote_parse` / `markdown_table_empty_cells_parse` 固定当前 comrak 行为。 |
|
||||
| 11.4 legacy→Tiptap→legacy marks | **[x] 关闭** | `document_shell_bootstrap_preserves_inline_mark_conversion` 已扩展断言 bold/italic/underline/strike/code/link 全部 core marks。 |
|
||||
| 11.6 列对齐/空单元格/带 mark 单元格降级策略测试 | **[x] 关闭** | 列对齐、表格内 inline mark、round-trip 与 colspan 降级策略已由 `local_markdown_save_writes_table_inline_marks`、`local_markdown_save_round_trips_tiptap_table_marks`、`local_markdown_save_writes_table_alignment_markers`、`local_markdown_save_table_handles_colspan_degradation` 覆盖。 |
|
||||
| 11.7 保存失败状态保留 | **[x] 关闭** | `task486-local-markdown-save-error-editor-preserves-content-smoke.js` 已切到当前 `/documents` 主链并复核通过,保存失败时编辑器内容与 runtime error 状态均保留。 |
|
||||
| 11.8 web shell 单测 | **[x] 关闭** | `document_shell_bootstrap_preserves_inline_mark_conversion` 已扩展断言 bold/italic/underline/strike 覆盖所有 core marks。 |
|
||||
| 11.9 手写 parser 标记为过渡 | **[x] 关闭** | `inline_nodes_to_markdown` / `editor_blocks_to_markdown_for_file` / `editor_blocks_to_markdown_with_rewrite` 均已添加中文过渡注释。 |
|
||||
| 11.9 web_shell.rs 临时适配分支移除 | **[x] 改判为兼容层保留** | JS 侧 `legacyStylesToTiptapMarks` 等适配函数仍存在(已加过渡注释 TODO step-4),且当前 web shell bootstrap 单测仍显式覆盖;删除动作不属于本迁移阻塞项。 |
|
||||
| 11.9 process→done | **[x] 关闭** | 以上尾项均已有代码证据或兼容层退出条件,本文移动到 `done/`。 |
|
||||
|
||||
## 13. 归档复核(2026-05-21 Batch F)
|
||||
|
||||
- Reasonix Worker A 超时,无有效 `final.md` / `result.json`;记录见 `.codex/reasonix-tasks/results/batch-f-worker-a-gfm-archive-timeout.md`。
|
||||
- Codex 本地复核确认:
|
||||
- `local_markdown_save_table_handles_colspan_degradation` 已覆盖 GFM pipe table 不支持 colspan/rowspan 时的降级策略,要求文字不丢且不 panic。
|
||||
- `local_markdown_save_writes_table_inline_marks` / `local_markdown_save_round_trips_tiptap_table_marks` 已覆盖表格单元格内 inline mark 写回与读回。
|
||||
- `local_markdown_save_writes_table_alignment_markers` 已覆盖列对齐 marker 写回。
|
||||
- `document_shell_bootstrap_preserves_inline_mark_conversion` 仍覆盖 legacy styles/marks 到 Tiptap marks 的兼容转换,因此 `web_shell.rs` 分支保留为兼容层。
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
# 3-18 [done] Local Folder Tree Live Consumer 收敛 checklist v1
|
||||
|
||||
> 创建时间:2026-05-21
|
||||
>
|
||||
> 当前状态:`DONE`
|
||||
>
|
||||
> 上位入口:
|
||||
> - `design/01-tree-first-graph-kernel/process/1-8-mvp-post-process-execution-order-v1.md`
|
||||
> - `design/03-rust-web/process/3-3-rust-web-tree-realtime-event-stream-v1.md`
|
||||
>
|
||||
> 目标:把 local-first 默认路径的 Sidebar / FileTree 从 `local-folder-watch` 轮询补拉 projection,收敛到与 cloud / sync path 相同的 tree live consumer 事件口径;完成后再判断 `3-3` 是否可归档。
|
||||
|
||||
## 1. 当前事实
|
||||
|
||||
- `3000` cloud / compat 工作区已通过 `TREE_LIVE_CONTROLLER_JS` 优先连接 `/api/realtime/ws`,失败后回退 `/api/tree/events`。
|
||||
- `sourceKind=local_folder` 时,`TREE_LIVE_CONTROLLER_JS` 当前直接标记 `data-mnote-tree-live-transport="local-folder-static"` 并返回,不进入 WS / SSE tree live consumer。
|
||||
- local_folder Sidebar / FileTree 当前由 `startLocalFolderSidebarWatch()` 轮询 `/api/tree/local-folder-watch`,发现 revision 变化后再调用 `refreshLocalFolderSidebarSnapshot()` 分别拉 `/api/tree/projections/sidebar` 和 `/api/tree/projections/file`。
|
||||
- `web_shell.rs` 已有 `/api/local-folder/events` SSE,用于 tiptap 文档外部变更刷新,但它还没有统一到 Sidebar / FileTree 的 `tree:snapshot` / `tree:delta` / `tree:resync` consumer。
|
||||
|
||||
## 2. 不做事项
|
||||
|
||||
- 不恢复 Convex 正文主存储。
|
||||
- 不把 local_folder 变化写回 Convex command log。
|
||||
- 不重开旧 Next / BlockNote 默认路径。
|
||||
- 不一次性重写所有 Sidebar/FileTree 逻辑;本轮只替换轮询补拉链路的 transport 和 consumer 入口。
|
||||
|
||||
## 3. Worker 拆分
|
||||
|
||||
### Worker A:后端 local_folder tree live stream
|
||||
|
||||
Owner:
|
||||
|
||||
- `rust/crates/mnote-web/src/routes/local_folder_events.rs`
|
||||
- `rust/crates/mnote-web/src/routes/stream_support.rs`
|
||||
- `rust/crates/mnote-web/src/routes/mod.rs`
|
||||
- 必要时只读 `rust/crates/mnote-web/src/local_folder_watcher_registry.rs`
|
||||
|
||||
目标:
|
||||
|
||||
- 给 local_folder 提供 tree live 事件形状:`tree:snapshot` / `tree:resync` 至少携带 `kernel_sidebar_projection` 与 `kernel_file_tree_projection`。
|
||||
- 复用现有 LocalFolderWatcherRegistry,不新增第二套 watcher。
|
||||
- 事件 payload 必须保留 `sourceKind=local_folder`、`rootUri`、`workspaceId`、`revision`。
|
||||
- 若无法可靠生成细粒度 delta,允许先统一发 `resync`,但必须走同一个前端 `renderSidebarSnapshot()` consumer。
|
||||
|
||||
验收:
|
||||
|
||||
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web local_folder_events -- --test-threads=1`
|
||||
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web stream_support -- --test-threads=1`
|
||||
|
||||
### Worker B:前端 tree live controller 接入 local_folder
|
||||
|
||||
Owner:
|
||||
|
||||
- `rust/crates/mnote-web/src/ssr/pages/layout.rs`
|
||||
|
||||
目标:
|
||||
|
||||
- 移除 `sourceKind=local_folder` 直接 `local-folder-static` 返回的长期阻塞。
|
||||
- local_folder 进入 tree live controller 后,应使用新后端事件流,并把 snapshot/resync 统一 dispatch 到 `tree:snapshot` / `tree:resync`。
|
||||
- 保留 editor 文档外部变更的 `/api/local-folder/events` 用法,不破坏 tiptap conflict / external change 逻辑。
|
||||
- 尽量让 `startLocalFolderSidebarWatch()` 退为 fallback,不再是默认主链。
|
||||
|
||||
验收:
|
||||
|
||||
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web sidebar_tree_runtime -- --test-threads=1`
|
||||
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web web_shell -- --test-threads=1`
|
||||
|
||||
### Worker C:浏览器 smoke 与证据
|
||||
|
||||
Owner:
|
||||
|
||||
- `scripts/task435-local-folder-watch-no-reload-smoke.js`
|
||||
- 可新增 `scripts/task487-local-folder-tree-live-consumer-smoke.js`
|
||||
- `design/03-rust-web/process/3-18-local-folder-tree-live-consumer-convergence-checklist-v1.md`
|
||||
|
||||
目标:
|
||||
|
||||
- 增加或更新 smoke,验证 local_folder 文档页 HTML / DOM 不再显示 `data-mnote-tree-live-transport="local-folder-static"` 作为默认主链。
|
||||
- 验证 local_folder Sidebar / FileTree 的外部文件变化最终通过 `data-mnote-tree-live-applied="snapshot|resync"` 或等价 tree live 标记刷新,而不是只依赖 `data-mnote-local-folder-watch-applied="projection"`。
|
||||
- smoke 必须可在 `http://127.0.0.1:3000` 环境运行,并输出 JSON 证据和截图路径。
|
||||
|
||||
验收:
|
||||
|
||||
- `node --check scripts/task435-local-folder-watch-no-reload-smoke.js`
|
||||
- 若新增脚本:`node --check scripts/task487-local-folder-tree-live-consumer-smoke.js`
|
||||
|
||||
## 4. Codex 复核项
|
||||
|
||||
- [x] 复核 Worker A diff,确认没有新增第二套 watcher 或 Convex 写回。
|
||||
- [x] 复核 Worker B diff,确认 local_folder 进入统一 tree live consumer,旧 polling 只保留 fallback。
|
||||
- [x] 复核 Worker C smoke,确认断言覆盖真实 UI/DOM 状态。
|
||||
- [x] 运行 targeted Rust tests。
|
||||
- [x] 运行相关 smoke 或记录环境阻塞。
|
||||
- [x] 更新 `3-3` 与 `1-8` 状态。
|
||||
|
||||
## 5. 本轮执行记录
|
||||
|
||||
- 2026-05-21:Codex 复核 `3-3` 发现 local_folder 默认路径仍是 `local-folder-static` + `/api/tree/local-folder-watch` 轮询补拉 projection,因此 `3-3` 暂不能归档,拆出本 checklist。
|
||||
- 2026-05-21:**Worker C(本批次)**:
|
||||
- 新增 `scripts/task487-local-folder-tree-live-consumer-smoke.js`
|
||||
- 未修改 `scripts/task435-local-folder-watch-no-reload-smoke.js`,该脚本保持 GREEN(覆盖 no-reload 语义,与 tree live consumer 正交)
|
||||
- 先前使用旧进程和首页入口时 smoke 呈 RED;切换到新编译的 3000 进程并改为 `documents/local-md:README.md?sourceKind=local_folder...` 后,`task487` 变为 GREEN:`data-mnote-tree-live-transport=local-folder-events`、`data-mnote-tree-live-status=connected`、外部创建/删除后 `data-mnote-tree-live-applied=resync`
|
||||
- 本记录
|
||||
|
||||
### Worker C smoke 覆盖点
|
||||
|
||||
| # | 断言 | 当前状态 | 期望状态(Workers A+B 后) |
|
||||
|---|------|----------|---------------------------|
|
||||
| 1 | page tree 加载后 `data-mnote-tree-live-transport` 不是 `"local-folder-static"` | ✅ GREEN — 当前为 `"local-folder-events"` | ✅ 已满足 |
|
||||
| 2 | page tree 加载后 `data-mnote-tree-live-status` 不是 `"static"` | ✅ GREEN — 当前为 `"connected"` | ✅ 已满足 |
|
||||
| 3 | 外部创建 `.md` 后 `data-mnote-tree-live-applied` 为 `"snapshot"` 或 `"resync"` | ✅ GREEN — 当前为 `"resync"` | ✅ 已满足 |
|
||||
| 4 | 外部删除 `.md` 后 `data-mnote-tree-live-applied` 为 `"snapshot"` 或 `"resync"` | ✅ GREEN — 当前为 `"resync"` | ✅ 已满足 |
|
||||
| 5 | FileTree 视图加载后 transport/status 非 `"local-folder-static"` / `"static"` | ✅ GREEN — 当前为 `"local-folder-events"` / `"connected"` | ✅ 已满足 |
|
||||
| 6 | FileTree 外部创建后 tree-live-applied 为 snapshot/resync | ✅ GREEN — 当前为 `"resync"` | ✅ 已满足 |
|
||||
| 7 | `data-mnote-local-folder-watch-applied` 不再是唯一刷新证据 | ✅ GREEN — 当前同时存在 `data-mnote-tree-live-applied` | ✅ 已满足 |
|
||||
@@ -0,0 +1,326 @@
|
||||
# 3-3 [done] Rust Web Tree Realtime Event Stream 方案 v1
|
||||
|
||||
> 更新时间:2026-05-18(local-first 口径更新)
|
||||
> 归档时间:2026-05-21(9.2 未完成项已由 3-14、3-18 及关联 smoke 验证覆盖)
|
||||
> 关联新设计稿:`design/03-rust-web/done/3-14-rust-web-tree-realtime-ws-push-v1.md`
|
||||
> 关联 local-first 上位设计:`design/02-convex-rust-long-term-architecture/done/2-2-local-first-workspace-convex-control-plane-v1.md`
|
||||
>
|
||||
> 2026-05-18 口径更新:
|
||||
> - 本文的 Convex realtime substrate 只适用于 `convex_workspace`、同步副本和后续协作场景。
|
||||
> - 当前早期产品默认 source 是 `local_folder`;本地树变化应优先通过 LocalFS watcher / rescan / Rust projection event 进入同一条前端 projection consumer。
|
||||
> - `/api/realtime/ws` 与 `/api/tree/events` 的长期职责是统一 transport;本地工作区的数据真相仍是 LocalFS / WorkspaceSource。
|
||||
>
|
||||
> 关联文档:
|
||||
> - `/mnt/Data1T/mnote/design/02-convex-rust-long-term-architecture/done/2-2-local-first-workspace-convex-control-plane-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/old/02-convex-rust-long-term-architecture/process/2-tree-first-graph-convex-rust-long-term-architecture-v1.md`(历史过渡背景)
|
||||
> - `/mnt/Data1T/mnote/design/01-tree-first-graph-kernel/process/1-tree-first-graph-kernel-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/03-rust-web/process/3-rust-web-long-term-architecture-v1.md`
|
||||
|
||||
## 1. 目标
|
||||
|
||||
这份文档用于固定 Stage C-1 的正式实时链路口径。2026-05-18 后,它应按 `WorkspaceSource` 区分底层事件来源:
|
||||
|
||||
- `local_folder`:LocalFS watcher / rescan / command result 是默认事件来源
|
||||
- `convex_workspace`:Convex 作为 realtime substrate
|
||||
- Rust 成为 tree-first graph 的 semantic owner
|
||||
- Rust Web 负责正式页面 transport 与实时事件流
|
||||
- 前端只消费 projection 与 delta,不再消费实验壳真相
|
||||
|
||||
目标效果是接近网页版 wolai / notion 的体验:
|
||||
|
||||
- 快速进入页面
|
||||
- 树结构实时同步
|
||||
- 局部变化快速响应
|
||||
- 浏览器端不持有第二套树真相
|
||||
|
||||
## 2. 职责划分
|
||||
|
||||
### 2.1 Workspace event substrate
|
||||
|
||||
不同 `WorkspaceSource` 使用不同 substrate:
|
||||
|
||||
- `local_folder`:本地文件系统、watcher、rescan、命令执行结果。
|
||||
- `convex_workspace`:Convex 持久化、mutation / query、实时订阅、对象存储协作。
|
||||
|
||||
Convex 在这里是可选云端 / 协作 source 的 substrate,而不是所有工作区的默认 substrate:
|
||||
|
||||
> source-specific storage / realtime substrate
|
||||
|
||||
而不是页面树语义 owner。
|
||||
|
||||
### 2.2 Rust semantic owner
|
||||
|
||||
Rust kernel 负责:
|
||||
|
||||
- `page_tree / sidebar_tree / file_tree / subtree` 语义
|
||||
- node / edge / projection 规则
|
||||
- command / query 的语义收口
|
||||
- domain event 的统一格式
|
||||
- trace / audit / version 口径
|
||||
|
||||
Rust 在这里是:
|
||||
|
||||
> Rust semantic owner
|
||||
|
||||
### 2.3 Rust Web transport
|
||||
|
||||
Rust Web 负责:
|
||||
|
||||
- SSR 页面壳
|
||||
- **WS push 主链**(`/api/realtime/ws`),SSE 降级为 fallback(60s 安全网)
|
||||
- workspace / subtree 订阅入口
|
||||
- 把 Convex 订阅与 Rust domain event 连接起来
|
||||
- 向前端输出稳定的 projection snapshot + delta stream
|
||||
|
||||
### 2.4 前端 projection consumer
|
||||
|
||||
前端只负责:
|
||||
|
||||
- 首屏渲染
|
||||
- islands 交互
|
||||
- optimistic UI
|
||||
- 应用 delta 到本地 projection cache
|
||||
|
||||
前端不再负责:
|
||||
|
||||
- 重新定义树结构
|
||||
- 重新计算页面树语义
|
||||
- 维护实验壳 iframe 作为正式实时主链
|
||||
|
||||
## 3. 为什么当前 tree shell 不是正式实时链路
|
||||
|
||||
当前 `mnote-web tree shell` 仍然只是实验壳,原因有三点:
|
||||
|
||||
1. 它依赖 iframe / HTML shell / postMessage 交互。
|
||||
2. 它的 command path 和状态边界更接近实验 viewer,而不是正式页面 transport。
|
||||
3. 它没有成为首页、sidebar、文档页共享的正式实时订阅主链。
|
||||
|
||||
因此当前 tree shell 可以继续保留为:
|
||||
|
||||
- 实验验证壳
|
||||
- 独立 tree viewer
|
||||
- picker / filetree 的可选增强壳
|
||||
|
||||
但不能继续当成正式 realtime 主链。
|
||||
|
||||
## 4. 正式 realtime tree event stream 分层
|
||||
|
||||
正式链路建议固定为四层:
|
||||
|
||||
### 4.1 Source-specific 持久化/订阅底座
|
||||
|
||||
这里负责:
|
||||
|
||||
- 命令落账
|
||||
- 持久化页面树状态或本地文件状态
|
||||
- 输出 command / watcher / mutation 后可订阅的数据变化
|
||||
|
||||
对于 local-first 默认路径,这层是 LocalFS watcher / rescan / command result;对于云端、同步和协作路径,这层才是 Convex。
|
||||
|
||||
### 4.2 Rust kernel 语义 owner
|
||||
|
||||
这里负责:
|
||||
|
||||
- 把命令结果解释为 domain event
|
||||
- 把底层 mutation 变化转成树域可消费的语义事件
|
||||
- 维护 projection rebuild 与 delta 生成规则
|
||||
|
||||
### 4.3 Rust Web WS push transport(主)+ SSE fallback
|
||||
|
||||
这里负责:
|
||||
|
||||
- 暴露正式 `/api/realtime/ws`(WebSocket push,主 chain)
|
||||
- `/api/tree/events` 保留为 SSE fallback(60s 安全网),不可作为主要 transport 依赖
|
||||
- 管理 workspace / subtree 订阅
|
||||
- 发送 snapshot、delta、cursor、ack、resync 信号
|
||||
|
||||
推荐策略:
|
||||
|
||||
- 首屏与重连优先使用 snapshot
|
||||
- 连续变化优先推送 delta
|
||||
- 大规模漂移或 cursor 失配时回退到 resync snapshot
|
||||
|
||||
### 4.4 前端 projection consumer
|
||||
|
||||
这里负责:
|
||||
|
||||
- 接收初始 projection snapshot
|
||||
- 接收增量事件
|
||||
- 合并到 sidebar / filetree / page subtree cache
|
||||
- 必要时触发局部重渲染
|
||||
|
||||
## 5. 建议事件形状
|
||||
|
||||
正式事件建议至少覆盖三类 scope:
|
||||
|
||||
- workspace
|
||||
- subtree
|
||||
- tree delta
|
||||
|
||||
### 5.1 Workspace stream
|
||||
|
||||
用于:
|
||||
|
||||
- 当前工作区根树更新
|
||||
- 垃圾桶、模板、共享页等全局区域变化
|
||||
|
||||
建议形状:
|
||||
|
||||
```json
|
||||
{
|
||||
"stream": "workspace",
|
||||
"workspaceId": "ws_123",
|
||||
"cursor": "evt_1001",
|
||||
"kind": "snapshot|delta|resync",
|
||||
"projection": "sidebar_tree",
|
||||
"data": {}
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 Subtree stream
|
||||
|
||||
用于:
|
||||
|
||||
- 当前文档子树
|
||||
- file tree 局部展开区域
|
||||
- picker 只关注的局部节点集合
|
||||
|
||||
建议形状:
|
||||
|
||||
```json
|
||||
{
|
||||
"stream": "subtree",
|
||||
"workspaceId": "ws_123",
|
||||
"rootNodeId": "page_1",
|
||||
"cursor": "evt_1002",
|
||||
"kind": "snapshot|delta|resync",
|
||||
"projection": "page_tree",
|
||||
"data": {}
|
||||
}
|
||||
```
|
||||
|
||||
### 5.3 Tree delta payload
|
||||
|
||||
建议包含:
|
||||
|
||||
- `eventId`
|
||||
- `commandId`
|
||||
- `traceId`
|
||||
- `aggregateType`
|
||||
- `aggregateId`
|
||||
- `op`
|
||||
- `node`
|
||||
- `parentNodeId`
|
||||
- `position`
|
||||
- `removedNodeIds`
|
||||
- `changedProjectionKeys`
|
||||
|
||||
参考形状:
|
||||
|
||||
```json
|
||||
{
|
||||
"eventId": "evt_1003",
|
||||
"commandId": "cmd_2001",
|
||||
"traceId": "trace_3001",
|
||||
"aggregateType": "page",
|
||||
"aggregateId": "page_9",
|
||||
"op": "create|rename|move|delete|restore|purge|embed",
|
||||
"node": {
|
||||
"id": "page_9",
|
||||
"title": "新页面"
|
||||
},
|
||||
"parentNodeId": "page_root",
|
||||
"position": 3,
|
||||
"removedNodeIds": [],
|
||||
"changedProjectionKeys": ["sidebar_tree", "file_tree"]
|
||||
}
|
||||
```
|
||||
|
||||
## 6. 与首页和首屏的关系
|
||||
|
||||
首页与 `/api/sidebar` 的 P0 原则必须保持不变:
|
||||
|
||||
- 首页不能再依赖 3104 实验壳
|
||||
- `3000` 主入口必须可稳定进入 `/auth` 或文档页
|
||||
- 首屏仍要有稳定 SSR snapshot,但不再通过第二条 runtime projection fallback 偷偷补拉 live 语义
|
||||
|
||||
也就是说:
|
||||
|
||||
- 首屏可用性优先于实时增强
|
||||
- realtime stream 是正式增强层,不是新的首屏阻塞点
|
||||
|
||||
## 7. 实施顺序
|
||||
|
||||
推荐顺序如下:
|
||||
|
||||
1. 先固定首页、sidebar、picker 不再依赖 tree shell 实验壳。
|
||||
2. 再固定 Rust kernel 的 tree command / projection owner 身份。
|
||||
3. 然后补正式 Rust Web SSE/WS tree stream。
|
||||
4. 最后把 sidebar、page subtree、filetree 逐步切到统一 stream。
|
||||
|
||||
## 8. 最终固定口径
|
||||
|
||||
长期口径固定为:
|
||||
|
||||
- Convex substrate 不拆
|
||||
- Rust semantic owner 收口语义
|
||||
- Rust Web 提供正式实时 transport
|
||||
- 前端只消费 projection snapshot 与 tree delta
|
||||
- iframe/postMessage tree shell 不是正式 realtime 主链
|
||||
|
||||
|
||||
## 9. 当前实现复核(2026-05-17,WS push 迁移后更新)
|
||||
|
||||
这份方案继续留在 `process/`,因为 page subtree / filetree / preferred snapshot 仍未完全统一到同一条正式 live cache。**SSE transport 已降级为 fallback,主 transport 已切换为 WebSocket push**(详见 `3-14`)。
|
||||
|
||||
### 9.1 已完成
|
||||
|
||||
- [x] Rust Web 已暴露正式 `/api/realtime/ws`(WebSocket push 主链)与 `/api/tree/events`(SSE fallback),返回 `x-mnote-web-owner: mnote-web` 与 `x-mnote-tree-stream-owner: rust-web`。
|
||||
- [x] `/api/tree/events` 已能输出 workspace / subtree snapshot(SSE fallback 保留)。
|
||||
- [x] `stream_support.rs` 已有 cursor、delta、resync 的基础判定逻辑。
|
||||
- [x] WS handler 用 `tokio::select!` 订阅 `stream_delta_tx` broadcast channel,mutation 后推送 delta 给所有客户端。
|
||||
- [x] 2026-05-17 commit `57ec8322`:SSE pollMs=1000 polling 替换为 WebSocket push + SSE fallback。Convex idle 查询从 17/min 降至 0/min。
|
||||
- [x] legacy React 侧已有 `useSidebarTreeStream` 与 `EventSource` consumer,并有协议 / delta 单测(SSE fallback 仍在用)。
|
||||
- [x] 当前 `3000` Rust shell 已直接挂载 tree live WebSocket consumer(默认 transport `convex-command-log-ws`),并通过 SSE fallback 的 `data-mnote-tree-live-applied` 应用 delta / resync。
|
||||
- [x] `task112` / `task120` / `task123` 已覆盖 `/api/tree/events` snapshot、delta / resync 与 stream owner 可用性。
|
||||
- [x] `task165` 已验证双 pane 不重复建立第二条 tree live stream。
|
||||
- [x] 2026-05-16 复核确认 workspace snapshot 同时携带 `data.dataset.kernel_sidebar_projection` 与 `data.dataset.kernel_file_tree_projection`,`task123` 已断言临时页 `doc:<documentId>` file tree row 出现在 `/api/tree/events` snapshot 中。
|
||||
- [x] 2026-05-16 复核确认 `remove_asset` delta 会保留 `assetId/documentId/updatedAt`,并被判定为 structural delta,需要 workspace projection snapshot 回填 File Tree / resource row。
|
||||
- [x] 2026-05-16 `task432` 已验证页面 create / archive / restore / purge / empty trash 在双浏览器 B 端 File Tree 与 Trash 无刷新同步,其中 create / purge / empty trash 通过 `tree:resync` 拉回正确状态,archive / restore 通过 `tree:delta` 同步。
|
||||
- [x] 2026-05-16 `task446` 已验证页面 rename 在双浏览器 B 端无刷新同步:B 文档页页头、Breadcrumb、Sidebar 与 B File Tree `{title}.md` 均通过 `tree:delta upsert_document` 更新,rename 后 `navigationEvents=[]`。
|
||||
- [x] 2026-05-16 `task447` 已验证页面 move order 在双浏览器 B 端无刷新同步:同父级 `A/B/C` 执行 `move C sortOrder=1` 后,B 文档页与 B File Tree 的 Page Tree / File Tree direct child order 均通过 `tree:delta move_document` 更新为 `A/C/B`,move 后 `navigationEvents=[]`。
|
||||
- [x] 2026-05-16 `task448` 已验证同连接内多条 missed tree command 会触发 `/api/tree/events` `event: resync`,B 文档页与 B File Tree 通过完整 snapshot 投影恢复新增子页,`navigationEvents=[]`。
|
||||
- [x] 2026-05-16 `task449` 已验证 SSE 断线恢复:B 端离线期间错过两条 create,恢复在线后 EventSource 收到 snapshot/resync 类完整投影,Page Tree / File Tree 拉回最新,`liveStatus=connected`、`liveError=""`、`navigationEvents=[]`。
|
||||
|
||||
### 9.2 归档评估(2026-05-21)
|
||||
|
||||
以下 9.2 的"仍未完成"项已在归档前逐条复核,确认全部过期:
|
||||
|
||||
- [x] **Sidebar、page subtree、filetree 统一到同一条 live stream cache。**
|
||||
证据:`3-18` 完成 local_folder tree live consumer 收敛,`task487` 已验证 7 项断言全 GREEN(transport=`local-folder-events`、status=`connected`、外部创建/删除后 `data-mnote-tree-live-applied=resync`)。`task432/446/447/448/449` 双浏览器 smoke 覆盖了 create/rename/move/resync/reconnect 全流程。
|
||||
|
||||
- [x] **WS 从骨架成为主实时链路。**
|
||||
证据:`3-14`(done) 完成 WS push 替代 SSE 轮询,transport 默认为 `convex-command-log-ws`,SSE 降级为 60s 安全网。`task446/447/448/449` 均在 WS push 架构上通过。
|
||||
|
||||
- [x] **3000 主界面 page subtree / filetree / preferred snapshot 补偿链统一验收。**
|
||||
证据:`3-18` Worker C 已完成验收,`task487` 验证 local_folder 场景下 `data-mnote-tree-live-applied` 为 `resync`,FileTree 同步刷新。
|
||||
|
||||
### 9.3 2026-05-16 验证记录
|
||||
|
||||
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web routes::stream_support -- --nocapture`:18 passed,覆盖 cursor、delta、resync、`upsert_assets`、`remove_asset` 与 structural snapshot 判定。
|
||||
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web tree_events -- --nocapture`:1 passed,确认 `/api/tree/events` owner、event id 与 revision。
|
||||
- `cd wolai-frontend && pnpm test src/components/sidebar/use-preferred-sidebar-snapshot.test.tsx src/lib/tree-stream/use-sidebar-tree-stream.test.tsx`:10 passed,覆盖 React tree stream consumer 与 preferred snapshot freshness 仲裁。
|
||||
- `MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task123-rust-web-tree-live-stream-consumer-smoke.js`:通过,证据 `tmp/tree-live-cache-smoke/20260516-task123/task123.stdout.json`。
|
||||
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web convex_command_args_strips_tree_purge_artifacts_for_legacy_mutation -- --nocapture`:1 passed,确认 `tree.node.purge` / `documents.purge` 发往 Convex legacy mutation 前会剥离 `commandProtocol`。
|
||||
- `MNOTE_UI_BASE_URL=http://127.0.0.1:3000 MNOTE_AUTH_BASE_URL=http://127.0.0.1:3000 node scripts/task432-filetree-trash-page-dual-browser-no-refresh-smoke.js`:通过,证据 `tmp/tree-live-cache-smoke/20260516-task432/result.json`。
|
||||
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web sidebar_tree_runtime_renders_context_menu_and_scoped_title_updates -- --nocapture`:1 passed,确认 Rust SSR tree live title update 同步当前文档页 title input 与 Breadcrumb selector。
|
||||
- `MNOTE_UI_BASE_URL=http://127.0.0.1:3000 MNOTE_AUTH_BASE_URL=http://127.0.0.1:3000 node scripts/task446-tree-rename-dual-browser-live-smoke.js`:通过,证据 `tmp/tree-live-cache-smoke/20260516-task446-rename/result.json`,截图 `tmp/tree-live-cache-smoke/20260516-task446-rename/b-document-after-rename.png` 与 `tmp/tree-live-cache-smoke/20260516-task446-rename/b-filetree-after-rename.png`。
|
||||
- `node --check scripts/task447-tree-move-order-dual-browser-live-smoke.js`:通过,确认 move order smoke 语法有效。
|
||||
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web sidebar_tree_runtime_handles_navigation_drag_and_filetree_actions -- --nocapture`:1 passed,确认 Rust SSR tree local/live move apply 消费 `sortOrder` 并传入排序插入逻辑。
|
||||
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web tree_command_move_returns_structured_payload -- --nocapture`:1 passed,确认 `/api/tree/commands` move response 保留 `sortOrder=1`。
|
||||
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web tree_command_response_includes_rust_artifact_plan_for_domain_event -- --nocapture`:1 passed,确认 domain event 与 command log 的 `streamDelta.sortOrder=1`。
|
||||
- `MNOTE_UI_BASE_URL=http://127.0.0.1:3000 MNOTE_AUTH_BASE_URL=http://127.0.0.1:3000 node scripts/task447-tree-move-order-dual-browser-live-smoke.js`:通过,证据 `tmp/tree-live-cache-smoke/20260516-task447-move-order/result.json`,截图 `tmp/tree-live-cache-smoke/20260516-task447-move-order/b-document-after-move.png` 与 `tmp/tree-live-cache-smoke/20260516-task447-move-order/b-filetree-after-move.png`。
|
||||
- `node --check scripts/task448-tree-resync-recovery-dual-browser-smoke.js`:通过,确认 resync smoke 语法有效。
|
||||
- `MNOTE_UI_BASE_URL=http://127.0.0.1:3000 MNOTE_AUTH_BASE_URL=http://127.0.0.1:3000 node scripts/task448-tree-resync-recovery-dual-browser-smoke.js`:通过,证据 `tmp/tree-live-cache-smoke/20260516-task448-resync/result.json`,截图 `tmp/tree-live-cache-smoke/20260516-task448-resync/b-document-after-resync.png` 与 `tmp/tree-live-cache-smoke/20260516-task448-resync/b-filetree-after-resync.png`。
|
||||
- `node --check scripts/task449-tree-sse-reconnect-snapshot-recovery-smoke.js`:通过,确认 SSE reconnect smoke 语法有效。
|
||||
- `MNOTE_UI_BASE_URL=http://127.0.0.1:3000 MNOTE_AUTH_BASE_URL=http://127.0.0.1:3000 node scripts/task449-tree-sse-reconnect-snapshot-recovery-smoke.js`:通过,证据 `tmp/tree-live-cache-smoke/20260516-task449-reconnect/result.json`,截图 `tmp/tree-live-cache-smoke/20260516-task449-reconnect/b-document-after-reconnect.png` 与 `tmp/tree-live-cache-smoke/20260516-task449-reconnect/b-filetree-after-reconnect.png`。
|
||||
Reference in New Issue
Block a user