- 补齐 design/10-review 执行清单、验收标准与相关设计治理记录 - 迁移已完成的 tree、mindmap、runtime fallback、AI kernel 等设计和缺陷条目 - 推进 Rust Web runtime、tree/sidebar、page aggregate、mindmap 与 OnlyOffice 路由侧验证支撑 - 增加 task177-task180 smoke/audit 脚本及前端相关测试覆盖
13 KiB
4-25 [done][bug] 树命令新建/删除后强制刷新导致卡顿 v1
更新时间:2026-05-14
分类归属:
04-tree-domain/done- 关联缺陷:
bugs/05-editor-mainline/done/5-11-mindmap-ghost-assets-and-tree-command-latency-v1.md用户反馈:
- “删除页面和新建页面都很慢,不知道为什么这么卡。”
1. 问题定义
当前页面树/文件树的新建页面、删除页面等命令,在服务端命令成功后仍会触发整页刷新或完整树刷新。用户感知是:新建和删除并不是局部更新,而是卡一下、等整套页面壳或树壳重新加载。
这属于 tree command -> projection update -> UI apply 链路问题,应归到 04-tree-domain,而不是只作为编辑器 UI 问题处理。
2. 真实现象
用户反馈:
- 新建页面慢。
- 删除页面慢。
- 卡顿与同轮 mindmap 文件树重复增长一起出现,怀疑树刷新链路被频繁触发。
期望结果:
tree.node.create成功后,树 UI 局部插入新节点并进入重命名/导航状态。tree.node.archive成功后,树 UI 局部移除节点或移动到回收站 projection。- 只有 delta 无法应用、projection 缺失、SSE 断线或数据不一致时,才进入 resync/reload fallback。
- 普通新建/删除不应默认整页 reload。
3. 证据
3.1 Rust tree shell 有硬刷新路径
rust/crates/mnote-web/src/routes/tree.rs:2976 到 :2986:
scheduleRefresh()在 80ms 后执行window.location.reload()。- 如果带
renameRowId,则通过window.location.assign(url.toString())重新加载。
调用点:
- 文件树删除:
rust/crates/mnote-web/src/routes/tree.rs:2791到:2824 - 新建页面:
rust/crates/mnote-web/src/routes/tree.rs:4309到:4340 - 重命名:
rust/crates/mnote-web/src/routes/tree.rs:4373到:4406 - 移动:
rust/crates/mnote-web/src/routes/tree.rs:4448到:4502
这条路径会让 tree shell 命令体验明显慢于纯 delta / reducer 更新。
3.2 主页面已有 tree live controller,但命令后仍刷新
主页面已经监听 tree live:
rust/crates/mnote-web/src/ssr/pages/layout.rs:3881到:3917处理tree:snapshot/tree:delta/tree:resyncrust/crates/mnote-web/src/ssr/pages/layout.rs:3939到:4050启动/api/tree/eventsEventSource
也就是说,命令完成后理论上可以由 delta/resync 更新 projection;当前硬 reload 与 live projection 机制重复。
3.3 命令本身已有 delta hint
Bridge runtime 对树命令生成 stream delta hint:
tree.node.create:rust/crates/bridge-runtime/src/lib.rs:9545到:9592tree.node.archive:rust/crates/bridge-runtime/src/lib.rs:9655到:9702
当前前端没有把这些结果作为默认局部更新来源,而是在 tree shell 中继续 reload。
3.4 React Sidebar 路径也存在重刷链
子代理只读调查发现 React Sidebar 路径也存在重刷:
- 新建页面:
wolai-frontend/src/components/sidebar/sidebar.tsx:1799、:1847附近在 command 后await refreshTree()再跳转。 - 删除页面:
wolai-frontend/src/components/sidebar/sidebar.tsx:2148附近删除后等待 command、refreshTree()、广播文档变化/跳转。 - Next API 的
api/tree/commandscreate/delete 还会执行 workspace/default scaffold、bridge mutation、artifact 记录等多段流程。
这说明卡顿不只来自单个 reload,而是命令后刷新链路偏重。
3.5 tree stream overview 查询曾经全量扫描 workspace 日志
/api/tree/events 的 live 轮询依赖 bridgeLogs:listWorkspaceOverview 读取最新 command/domain event 窗口。2026-05-13 复核发现,该查询原先对 command_logs 和 domain_events 都是按 workspace 全量 .collect() 后再在内存中过滤、排序和分页;日志量增长后,这会让每轮 SSE 轮询成本随历史日志线性增长。
本轮已完成代码侧收口:
command_logs增加workspace_id + created_at + id及 status/page/block 常用过滤索引。domain_events增加workspace_id + created_at + id及 status/aggregate 常用过滤索引。listWorkspaceOverview改为按索引读取有界窗口,不再全量 collect 后分页。
这只能降低 tree live polling 的背景成本,并不等于新建/删除的强刷新问题已经修完。React Sidebar 的 refreshTree() 和 Rust tree shell 的 scheduleRefresh() 仍需单独收口。
3.6 2026-05-14 tree live 局部 apply 进展
本轮已完成主页面 Rust 3000 下的部分局部 apply 收口:
rust/crates/mnote-web/src/transport/convex.rs:tree create / rename / archive / restore / move 及 compat documents mutation 发往 legacy Convex mutation 前剥离 artifact-only 字段,修复documents:softDelete因streamDeltaHint/domainEventPlan等额外字段触发 validator 502 的问题。rust/crates/mnote-web/src/ssr/pages/layout.rs:tree:delta的move_document会局部更新页面树与 File Tree 行的data-parent-id;remove_document会局部移除页面树与 File Tree 行。- 已通过:
cargo test -p mnote-web convex_command_args_strips -- --nocapture。 - 已通过:
cargo test -p mnote-web sidebar_tree_runtime -- --nocapture。 - 已通过:
node scripts/task177-tree-move-archive-live-smoke.js,覆盖 move 后页面树 / File Tree 行父节点更新、archive 后两棵树局部移除,以及文档页头保持稳定。
这说明 move / archive 的 Rust 3000 主页面 live reducer 已有可验证进展;当时普通新建/删除、React Sidebar refreshTree() 与耗时阈值仍待后续章节继续收口。后续 3.7-3.9 已补齐这些流转条件。
3.7 2026-05-14 Rust tree shell create / delete no-reload 验收
本轮已完成 Rust /tree debug shell 的普通新建 / 删除强刷新收口:
rust/crates/mnote-web/src/routes/tree.rs:新增addTreeItemLocally()/applyCreatedDocumentLocally(),convex_workspacecreate 成功后直接插入本地树项并进入 inline rename;只有本地 apply 失败时才 fallback 到scheduleRefresh()。rust/crates/mnote-web/src/routes/tree.rs:新增removeTreeItemEverywhere()/applyRemovedDocumentLocally(),convex_workspacedelete 成功后从本地 projection 移除对应页面及子项;只有删除后仍能在本地 row map 中看到目标行时才 fallbackscheduleRefresh()。scripts/task179-tree-create-delete-no-reload-smoke.js:真实浏览器覆盖/tree?mode=filetree的 create + delete,以及/tree?mode=page的 create;断言操作后 URL 不变、对应 mode 内无新增顶层 navigation。- 已通过:
cargo test -p mnote-web tree_shell_returns_interactive_html_document -- --nocapture。 - 已通过:
cargo test -p mnote-web tree_shell -- --nocapture。 - 已通过:
node scripts/task179-tree-create-delete-no-reload-smoke.js,最新结果ok=true;filetree.createMs=120、filetree.deleteMs=178、page.createMs=120、两个 mode 的navigationEvents.length=0。
3.8 2026-05-14 React Sidebar 直接 create / delete 收口进展
React Sidebar 的直接新建 / 删除路径已完成一层去阻塞:
wolai-frontend/src/components/sidebar/sidebar.tsx:handleCreate()继续使用createDocumentCommand()结果本地setTree()插入节点并导航,但不再同步await refreshTree()。wolai-frontend/src/components/sidebar/sidebar.tsx:新增removeDocumentsFromTree(),handleDelete()与handleDeleteResourceSelection()在 delete command 成功后本地移除页面节点,不再同步等待整树 refetch;附件删除仍保留 asset 专用刷新链。wolai-frontend/src/components/sidebar/sidebar-delete-preflight-source.test.ts:新增源级回归断言,防止页面 create/delete 成功路径重新出现同步await refreshTree()。- 已按 TDD 验证:新增测试先失败,修改后通过。
- 已通过:
pnpm test -- src/components/sidebar/sidebar-delete-preflight-source.test.ts(Vitest 实际执行 112 个测试文件、459 个测试)。 - 已通过:
pnpm exec eslint src/components/sidebar/sidebar.tsx src/components/sidebar/sidebar-delete-preflight-source.test.ts,无 error;sidebar.tsx保留既有 warning。
3.9 2026-05-14 Rust-family React shell mutation 本地 apply 收口
Rust-family React shell 的 host mutation 回调已从“命令成功后默认整树刷新”改为可局部 apply:
wolai-frontend/src/components/sidebar/tree-shell-dom-host.tsx:tree.node.created/tree.node.renamed/tree.subtree.moved成功后把 command result 中的documentId、parentId、title、sortOrder、workspaceId、execution传回 Sidebar。wolai-frontend/src/components/sidebar/tree-shell-iframe-host.tsx:legacy iframe bridge 同步转发 mutation payload 中的局部 apply 字段,保持兼容。wolai-frontend/src/components/sidebar/sidebar.tsx:handleTreeShellMutation对 create / rename / move 分别本地插入、重命名、移动页面树;只有未知事件或缺少必要字段时保留refreshTree()兜底。wolai-frontend/src/components/sidebar/sidebar-delete-preflight-source.test.ts:新增 RED -> GREEN 源级回归断言,防止onTreeMutation -> refreshTree()重新退化成默认整树 refetch。- 已通过:
pnpm test -- src/components/sidebar/sidebar-delete-preflight-source.test.ts(Vitest 实际执行 112 个测试文件、460 个测试)。 - 已通过:
pnpm exec eslint src/components/sidebar/sidebar.tsx src/components/sidebar/sidebar-delete-preflight-source.test.ts src/components/sidebar/tree-shell-host.tsx src/components/sidebar/tree-shell-surface.tsx src/components/sidebar/tree-shell-dom-host.tsx src/components/sidebar/tree-shell-iframe-host.tsx,无 error;保留既有 warning。
4. 当前判断
当前慢的核心不是“Convex 一定慢”,而是树命令执行后缺少轻量本地 apply:
- 命令 result / delta hint 已经具备局部更新信息。
- UI 仍经常走
refreshTree()、window.location.reload()或 full snapshot。 resync_required类事件会触发完整 workspace snapshot,结构性变化越多,越容易造成卡顿。- tree stream overview 全量日志扫描已在代码侧改为有界窗口查询,并已有
task123/task120/task177证明 Rust 3000 下可输出并消费相关 live 事件。 - move / archive 已具备局部 DOM reducer 证据。
- Rust
/treedebug shell 的convex_workspace普通新建 / 删除已经通过无 reload 与耗时 smoke。 - React Sidebar 直接 create/delete 成功路径已改成本地更新,不再同步等待
refreshTree()。 - Rust-family React shell 的 create / rename / move host mutation 回调已改成本地 apply,不再默认触发整树 refetch;未知事件或缺字段才保留刷新兜底。
5. 建议验证
修复前先补一条计时 smoke:
- 登录真实测试账号。
- 新建
TEST-TREE-LATENCY-<timestamp>页面。 - 记录
POST /api/tree/commands响应耗时。 - 记录下一次
tree:delta/tree:resync到达耗时。 - 断言过程中是否发生
window.location.reload()或顶层 navigation。 - 删除该测试页面,重复同样计时。
- 输出新建/删除从点击到 DOM 稳定的总耗时。
6. 建议修复方向
tree.node.create成功后直接用 command result 插入本地节点,并只在后台等待 live delta 校准。tree.node.archive成功后直接从当前 projection 移除节点,并只在后台等待 live delta 校准。scheduleRefresh()改成显式 fallback,只有 reducer 无法应用时才调用。- React Sidebar 的
refreshTree()应去重、节流,并避免与 SSE/full snapshot 同时触发。 - smoke 增加无 reload 断言和耗时阈值。
- tree stream 查询模型已完成第一步优化;后续若 polling-backed SSE 继续作为长期实现,还需要补连接数、日志量、延迟边界测试。
7. 流转条件
当前状态:done
Rust tree shell 的 create / delete 强刷新子项已关闭;React Sidebar 直接 create/delete 同步刷新等待已关闭;Rust-family React shell 的 create / rename / move host mutation 回调已改为本地 apply,不再默认整树 refetch。
只有满足以下条件后才能移动到 bugs/04-tree-domain/done/:
- 新建页面普通路径不再默认整页 reload。Rust
/treeconvex_workspace的 File Tree 与 Page Tree create 已由task179验证。 - 删除页面普通路径不再默认整页 reload。Rust
/treeconvex_workspace的 File Tree delete 已由task179验证。 - command result / tree delta 能局部更新页面树与文件树 projection。create 走
applyCreatedDocumentLocally();delete 走applyRemovedDocumentLocally();move/archive 由task177验证主页面与 File Tree DOM reducer。 - fallback reload 只在明确异常条件下触发,并有可观测标记。当前 create/delete 仅在本地 apply 失败或目标行未移除时 fallback
scheduleRefresh()。 - smoke 记录新建/删除耗时,并通过无 reload 断言。
task179最新记录filetree.createMs=120、filetree.deleteMs=178、page.createMs=120,对应 mode 内 navigation 为 0。 - Rust-family React shell 的
onTreeMutation -> refreshTree()不再在 create / rename / move 成功后默认整树 refetch。handleTreeShellMutation已按 create / rename / move 本地 apply,未知事件或缺字段才刷新兜底,并由sidebar-delete-preflight-source.test.ts防回归。