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:
@@ -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 验证。
|
||||
@@ -0,0 +1,68 @@
|
||||
# 5-12 [done][bug] 页面设置刷新后 runtime 属性回退 v1
|
||||
|
||||
> 更新时间:2026-05-16
|
||||
>
|
||||
> 分类归属:
|
||||
> - `05-editor-mainline/done`
|
||||
> - 涉及边界:`Page Aggregate / Rust Web document shell / leptos-tiptap island runtime page options`
|
||||
|
||||
## 1. 问题定义
|
||||
|
||||
在 Page Aggregate 单一真源验收中,同一临时页写入标题、正文和页面设置后,`/api/page-aggregate/:id` 已能回读最新 `layout.pageOptions`,刷新后的 `.document-shell` 也保留 `wideLayout=true / smallText=true / layoutDensity=compact`。
|
||||
|
||||
但首轮综合 smoke 发现,刷新后 `document.documentElement` 上的 `data-page-wide-layout / data-page-small-text / data-layout-density` 仍可能是默认值,island root 上也缺少对应 runtime 属性。该问题会让页面设置的部分 runtime DOM 层与 Page Aggregate / SSR 壳层不一致。
|
||||
|
||||
## 2. 复现步骤
|
||||
|
||||
1. 启动 `http://127.0.0.1:3000`。
|
||||
2. 新建临时页面。
|
||||
3. 通过页面头写入唯一标题。
|
||||
4. 通过主编辑器写入唯一正文。
|
||||
5. 通过页面设置 UI 写入 `wideLayout=true`、`smallText=true`、`layoutDensity=compact`。
|
||||
6. 等 `/api/page-aggregate/:id` 回读最新标题、正文和页面设置。
|
||||
7. 刷新页面后检查 `documentElement`、`.document-shell`、island root 和 `.editor-surface` 的 runtime page option 属性。
|
||||
|
||||
## 3. 根因
|
||||
|
||||
`rust/crates/mnote-web/src/ssr/pages/layout.rs` 中 `initializePageUiSurfaces` 原先通过 `setTimeout(initializePageUiSurfaces, 0)` 调度。
|
||||
|
||||
在文档页加载时,这个调用可能早于嵌入的 `__MNOTE_PAGE_AGGREGATE__` JSON 脚本和 island DOM 完整可用,导致 `applyPageOptionsToShell()` 读取到默认 pageOptions,或只更新了部分 DOM 层。后续 SSR 壳层仍显示最新值,但 `documentElement` 和 island root 可能保持默认属性。
|
||||
|
||||
## 4. 修复
|
||||
|
||||
`rust/crates/mnote-web/src/ssr/pages/layout.rs` 新增 `scheduleInitializePageUiSurfaces()`:
|
||||
|
||||
- 如果 `document.readyState === "loading"`,等 `DOMContentLoaded` 后再 `setTimeout(initializePageUiSurfaces, 0)`。
|
||||
- 如果文档已经可用,保持原有异步调度。
|
||||
|
||||
这样初始化会在 Page Aggregate JSON 与文档 DOM 解析完成后再应用 page options。
|
||||
|
||||
## 5. 验证
|
||||
|
||||
真实 3000 smoke:
|
||||
|
||||
```bash
|
||||
MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task-page-aggregate-refresh-persistence-smoke.js
|
||||
```
|
||||
|
||||
证据:
|
||||
|
||||
- `tmp/page-aggregate-refresh-persistence-smoke/mp88fr6k.json`
|
||||
- `tmp/page-aggregate-refresh-persistence-smoke/mp88fr6k.png`
|
||||
- `tmp/page-aggregate-refresh-persistence-smoke/latest.stdout.json`
|
||||
|
||||
验证结果:
|
||||
|
||||
- 刷新前后 `head.title=Page Aggregate refresh mp88fr6k`。
|
||||
- 刷新前后 `body.revision=1`、`conflictDetectionKey=tree_1778929070007_1:1`。
|
||||
- 刷新前后 `blockDocument.blocks[0].text=Page Aggregate refresh body mp88fr6k`。
|
||||
- 刷新前后 `layout.pageOptions.wideLayout=true`、`smallText=true`、`layoutDensity=compact`。
|
||||
- 刷新后页头、`.ProseMirror` 正文、设置控件、`documentElement`、`.document-shell`、island root 和 `.editor-surface` 均保持最新值。
|
||||
|
||||
配套命令:
|
||||
|
||||
```bash
|
||||
node --check scripts/task-page-aggregate-refresh-persistence-smoke.js
|
||||
cargo test --manifest-path rust/Cargo.toml -p mnote-web page_aggregate
|
||||
cargo test --manifest-path rust/Cargo.toml -p mnote-web documents_options_route_executes_page_layout_update_options
|
||||
```
|
||||
@@ -0,0 +1,95 @@
|
||||
# 5-13 [done][bug] Tree rename live 后当前文档页 chrome 未同步 v1
|
||||
|
||||
> 更新时间:2026-05-16
|
||||
>
|
||||
> 分类归属:
|
||||
> - `05-editor-mainline/done`
|
||||
> - 关联边界:`Rust Web document shell / Tree Realtime Live Cache / Page title chrome`
|
||||
|
||||
## 1. 问题定义
|
||||
|
||||
双浏览器 rename live cache 验收中,A 端通过正式 `/api/tree/commands` 执行 `tree.node.rename` 后,B 端已经收到 `/api/tree/events` 的 `tree:delta upsert_document`。
|
||||
|
||||
修复前 B 端 Sidebar / Page Tree 与 File Tree 行标题会更新,但当前打开目标文档页的页头标题输入框、Breadcrumb 和 `document.title` 仍停留旧标题。
|
||||
|
||||
## 2. 复现步骤
|
||||
|
||||
1. 启动 `http://127.0.0.1:3000`。
|
||||
2. 运行双浏览器 smoke:
|
||||
|
||||
```bash
|
||||
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
|
||||
```
|
||||
|
||||
3. A 端创建 root / target 页面并执行 `{ action: "rename", workspaceId, documentId, title }`。
|
||||
4. B 端保持目标文档页和 File Tree 页面不刷新,等待 live update。
|
||||
|
||||
## 3. 实际表现
|
||||
|
||||
RED 证据中 B 端状态为:
|
||||
|
||||
- `liveApplied=delta`
|
||||
- `liveError=""`
|
||||
- `treeEvents[1].op=upsert_document`
|
||||
- `sidebarTitle={renamedTitle}`
|
||||
- `fileTreeTitle={renamedTitle}.md`
|
||||
- `titleInputValue` 仍为旧标题
|
||||
- `breadcrumbTitle` 仍为旧标题
|
||||
- `documentTitle` 仍为旧标题
|
||||
|
||||
证据路径:
|
||||
|
||||
- `tmp/tree-live-cache-smoke/20260516-task446-rename/result.json`
|
||||
|
||||
## 4. 根因
|
||||
|
||||
`rust/crates/mnote-web/src/ssr/pages/layout.rs` 中 Rust SSR tree live controller 在处理 title-only `upsert_document` 时调用 `updateTitleEverywhere(documentId, title)`。
|
||||
|
||||
该函数原先只更新 Page Tree / File Tree 行标题,没有同步当前打开文档页的 chrome:
|
||||
|
||||
- `[data-page-title-input="true"][data-document-id="<id>"]`
|
||||
- `.wolai-breadcrumb-current [data-page-title-current]`
|
||||
- `document.title`
|
||||
|
||||
因此 tree realtime delta 已到达,树行也已更新,但文档页头仍保留旧快照。
|
||||
|
||||
## 5. 修复
|
||||
|
||||
`rust/crates/mnote-web/src/ssr/pages/layout.rs` 的 `updateTitleEverywhere(documentId, title)` 增加当前文档 chrome 同步:
|
||||
|
||||
- 当前文档的 title input 更新为新标题,并同步 `data-title-last-saved` / `data-title-save-status`。
|
||||
- 当前 document pane 内的 `[data-page-title-current="true"]` 更新为新标题。
|
||||
- 当前 URL 对应文档时,更新 `document.title` 与 topbar Breadcrumb。
|
||||
|
||||
同时补充字符串合同单测,确保当前页 title input 与 Breadcrumb selector 留在 SSR tree live controller 中。
|
||||
|
||||
## 6. 验证
|
||||
|
||||
配套单测:
|
||||
|
||||
```bash
|
||||
cargo test --manifest-path rust/Cargo.toml -p mnote-web sidebar_tree_runtime_renders_context_menu_and_scoped_title_updates -- --nocapture
|
||||
```
|
||||
|
||||
结果:`1 passed`。
|
||||
|
||||
真实 3000 smoke:
|
||||
|
||||
```bash
|
||||
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`
|
||||
|
||||
验证结果:
|
||||
|
||||
- B 文档页 `documentTitle/titleInputValue/breadcrumbTitle/sidebarTitle` 均为新裸标题。
|
||||
- B File Tree `fileTreeTitle={renamedTitle}.md`。
|
||||
- B 端 `liveApplied=delta`、`liveError=""`。
|
||||
- rename 后 `navigationEvents=[]`,没有刷新或导航。
|
||||
Reference in New Issue
Block a user