feat: EditorRuntimeActor - 三层缓存/delta/事件架构

Phase A — EditorRuntimeActor 内存缓存层
- 新增 editor_actor.rs: EditorBlockDocument 内存态 + apply_command + load_or_init
- block.rs 四个写工具(replace/insert/delete/move)接入 actor 路径
- editor_actor feature flag(MNOTE_WEB_ENABLE_EDITOR_ACTOR=true 默认开启)
- bridge-runtime 三个核心函数公开化
- rust-toolchain: 1.89 → stable(修复 spike WASM 编译阻塞)

Phase B — 编辑器增量 delta channel
- BlockDelta/DeltaOperation 类型 + actor.build_block_delta()
- leptos-tiptap spike: mnote:editor:block-delta CustomEvent 监听 + JSON patch
- DocumentAiAgentPanel: 拦截 blockDelta → window dispatchEvent
- 工具响应含 blockDelta 字段供前端消费

Phase C — 事件 stream delta
- broadcast channel 在 AppState/actor/SSE 三层贯通
- tree_events SSE 端点发 block.delta 事件
- 旧客户端降级兼容

环境修复
- rustc recursion_limit = 1024(修复 Leptos SSR 类型深度溢出)
- run-convex-deploy.js(封装 Convex function 部署到本地后端 3210)

ref: design/07-ai/process/7-13-page-block-editor-runtime-actor-v1.md
This commit is contained in:
lix-2026
2026-05-16 22:03:30 +08:00
parent d8bfaea306
commit f292c6710a
101 changed files with 13618 additions and 2416 deletions
@@ -0,0 +1,74 @@
# 4-41 [done][bug] tree.node.purge commandProtocol 泄漏到 Convex validator v1
> 更新时间:2026-05-16
>
> 分类归属:
> - `04-tree-domain/done`
>
> 关联文档:
> - `/mnt/Data1T/mnote/design/10-review/process/08-kernel-architecture-next-priority-review-and-checklist.md`
> - `/mnt/Data1T/mnote/design/03-rust-web/process/3-3-rust-web-tree-realtime-event-stream-v1.md`
## 1. 问题定义
`tree.node.purge` / `documents.purge` 的 Rust execution plan 会携带 `commandProtocol` 审计字段,用于标记正式 tree command 与 compat alias 的关系。但发往 Convex legacy mutation `documents:purge` 时,这个字段不应进入 mutation args。
实际运行中,`task432-filetree-trash-page-dual-browser-no-refresh-smoke.js` 在 purge 阶段触发 Convex validator 502
```text
ArgumentValidationError: Object contains extra field `commandProtocol` that is not in the validator.
Object: {commandProtocol: {...}, id: "..."}
Validator: v.object({id: v.string()})
```
这会阻断双浏览器 File Tree / Trash no-refresh 验收中的彻底删除阶段。
## 2. 根因
`rust/crates/bridge-runtime/src/lib.rs``documents.purge | tree.node.purge` plan 会生成:
```json
{
"id": "...",
"commandProtocol": {
"family": "tree",
"owner": "rust-runtime-kernel",
"preferredCommandName": "tree.node.purge",
"compatCommandName": "documents.purge"
}
}
```
`rust/crates/mnote-web/src/transport/convex.rs``convex_command_args_for_plan` 只对 create / rename / archive / restore / move 等命令调用 `strip_tree_artifact_fields`,漏掉了 `tree.node.purge``documents.purge`
## 3. 修复
`rust/crates/mnote-web/src/transport/convex.rs` 已将以下命令加入 legacy mutation artifact 剥离名单:
- `tree.node.purge`
- `documents.purge`
新增回归测试:
- `convex_command_args_strips_tree_purge_artifacts_for_legacy_mutation`
## 4. 验证
已通过:
```bash
cargo test --manifest-path rust/Cargo.toml -p mnote-web convex_command_args_strips_tree_purge_artifacts_for_legacy_mutation -- --nocapture
cargo test --manifest-path rust/Cargo.toml -p mnote-web convex_command_args_strips_tree_archive_artifacts_for_legacy_mutation -- --nocapture
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
```
真实 smoke 证据:
- 修复前失败:`tmp/task432-filetree-trash-page-dual-browser-no-refresh-smoke/result.json` 曾记录 `documents:purge``commandProtocol` 额外字段返回 502。
- 修复后通过:`tmp/tree-live-cache-smoke/20260516-task432/result.json``ok=true`,覆盖 create / archive / restore / purge / empty trash 在 B 端 File Tree 与 Trash 无刷新同步。
## 5. 状态
当前状态:`done`
该缺陷已在真实代码中修复,并通过单测与真实 3000 双浏览器 smoke 验证。
@@ -0,0 +1,80 @@
# 4-42 [done][bug] tree move live delta sortOrder 被 SSR shell 忽略 v1
> 更新时间:2026-05-16
>
> 分类归属:
> - `04-tree-domain/done`
>
> 关联文档:
> - `/mnt/Data1T/mnote/design/10-review/process/08-kernel-architecture-next-priority-review-and-checklist.md`
> - `/mnt/Data1T/mnote/design/03-rust-web/process/3-3-rust-web-tree-realtime-event-stream-v1.md`
## 1. 问题定义
`/api/tree/events``tree:delta move_document` 已携带 `sortOrder`,但 Rust SSR 文档 shell 在 B 端应用 live delta 时只移动到目标父节点末尾,没有按 `sortOrder` 重排直系子节点。
真实双浏览器 RED smoke
```bash
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`
- RED fixture:同父级初始顺序 `A/B/C`A 端执行 `move C -> parent=root, sortOrder=1`,预期 B 端顺序 `A/C/B`
- RED 结果:B 端收到 `tree:delta``op=move_document``sortOrder=1``liveApplied=delta``liveError=""`,但 Page Tree / File Tree DOM 顺序仍保持 `A/B/C`,直到 `waitForExpectedOrder` 超时。
## 2. 根因
`rust/crates/mnote-web/src/ssr/pages/layout.rs` 中:
- `applyMoveDocumentDelta(data)` 只读取 `documentId``parentId`,未读取 `sortOrder`
- `moveDocumentRowForMode(mode, documentId, parentId)` 无排序参数,最终固定 `targetContainer.appendChild(node)`
- `tree:local-command` 的 optimistic move 分支也只传 `{ documentId, parentId }`,会在本地命令成功后先把节点放到末尾。
事件生成链路不是根因:`tree.subtree.move` command payload、domain event `streamDelta``/api/tree/events` delta 均已携带 `sortOrder=1`
## 3. 修复
`rust/crates/mnote-web/src/ssr/pages/layout.rs` 已补齐:
- `sortOrderFromDelta(data)`:兼容读取 `sortOrder` / `sort_order`
- `insertTreeNodeAtSortOrder(targetContainer, node, sortOrder)`:按目标父节点直系 `.tree-node` sibling index 插入,缺少排序时保持 append 行为。
- `moveDocumentRowForMode(mode, documentId, parentId, sortOrder)`:消费排序参数。
- `applyMoveDocumentDelta(data)`:把同一 `sortOrder` 同步应用到 Page Tree 与 File Tree。
- `tree:local-command` move 分支:把 `body.sortOrder` 传入 `applyMoveDocumentDelta`
同时补充字符串合同测试,防止后续再次丢失排序消费。
## 4. 验证
已通过:
```bash
node --check scripts/task447-tree-move-order-dual-browser-live-smoke.js
cargo test --manifest-path rust/Cargo.toml -p mnote-web sidebar_tree_runtime_handles_navigation_drag_and_filetree_actions -- --nocapture
cargo test --manifest-path rust/Cargo.toml -p mnote-web tree_command_move_returns_structured_payload -- --nocapture
cargo test --manifest-path rust/Cargo.toml -p mnote-web tree_command_response_includes_rust_artifact_plan_for_domain_event -- --nocapture
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
```
GREEN smoke 证据:
- `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`
验证结果:
- B 文档页和 B File Tree 页面均收到 `/api/tree/events`
- `tree:delta move_document` payload 含 `sortOrder=1`
- B 文档页 Page Tree / File Tree direct child order 均为 `A/C/B`
- B File Tree 页面 Page Tree / File Tree direct child order 均为 `A/C/B`
- move 后 `navigationEvents=[]`,确认没有通过刷新或跳转掩盖 live order 更新。
## 5. 状态
当前状态:`done`
该缺陷已在真实代码中修复,并通过 Rust 合同测试与真实 3000 双浏览器 smoke 验证。