13 KiB
4-26 [done][bug] Sidebar File Tree projection 在树/资产变更后不刷新 v1
更新时间:2026-05-14
分类归属:
04-tree-domain/done- 关联缺陷:
bugs/04-tree-domain/done/4-25-tree-command-create-delete-reload-latency-v1.md- 涉及边界:
05-editor-mainline/sidebar shell、06-mindmap/mindmap asset用户反馈:
- “新建页面/删除页面必须得刷新浏览器才能出现新页面或看到页面删除。”
- “包括新建思维导图,也是得刷新才能看到。”
- “有点像是全局事件被禁用了或类似的。”
1. 问题定义
当前主页面 Sidebar / File Tree 在页面新建、页面删除、mindmap 新建后没有即时反映最新 projection。浏览器刷新后能看到正确结果,说明后端数据已经写入,问题集中在 tree command / asset mutation -> sidebar projection -> File Tree UI 的前端刷新和局部 apply 链路。
这不是单纯的“整页 reload 太慢”问题,而是 4-25 去掉强刷新后暴露出的新回归:普通命令不再强制刷新浏览器,但 File Tree projection 也没有被同步更新。
2. 真实现象
已确认现象:
- 新建页面后,当前页面壳里的 File Tree / Sidebar 不能立即看到新页面。
- 删除页面后,当前页面壳里的 File Tree / Sidebar 仍可能保留旧页面行。
- 新建 mindmap 后,当前页面壳里的 File Tree 不能立即看到
mindmap-<id>.json资源行。 - 手动刷新浏览器后,页面或 mindmap 资源会按后端最新数据出现/消失。
期望结果:
tree.node.create成功后,Page Tree 与 File Tree 都能在当前页面壳中即时出现新页面。tree.node.archive/ 删除成功后,Page Tree 与 File Tree 都能在当前页面壳中即时移除目标页面及子树。mindmaps.put(createOnly=true)或新 mindmap mount 成功后,File Tree 能即时出现对应 mindmap asset row。- 不依赖浏览器刷新作为普通成功路径;刷新只能作为 projection 无法局部 apply 时的显式 fallback。
3. 确认证据
3.1 主 Sidebar 的数据源仍来自 preferred snapshot
wolai-frontend/src/components/sidebar/sidebar.tsx:201 到 :209:
- Sidebar 通过
usePreferredSidebarSnapshot()在initialData、sidebarQuery.data、treeStream.data之间选择数据源。 - 后续 File Tree projection 仍读取这个
sidebarData。
3.2 Page Tree 有本地 setTree,但 File Tree projection 没有同步局部更新
wolai-frontend/src/components/sidebar/sidebar.tsx:305 到 :316:
sidebarData.kernelSidebarTree变化时会同步到本地treestate。
wolai-frontend/src/components/sidebar/sidebar.tsx:1900 到 :1957:
handleCreate()在createDocumentCommand()成功后只对页面树做本地setTree(),然后router.push()。- 该路径没有同步更新
sidebarData.kernelFileTreeProjection.items,也没有广播emitDocumentsChanged()。
wolai-frontend/src/components/sidebar/sidebar.tsx:2239 到 :2249:
handleDelete()成功后调用removeDocumentsFromTree()和emitDocumentsChanged()。- 该本地删除只影响 Page Tree 的
treestate,不会同步移除 File Tree projection items。
3.3 File Tree shell 直接依赖 stale projection items
wolai-frontend/src/components/sidebar/sidebar.tsx:678 到 :687:
- 非搜索状态下
effectiveResourceTreeShellItems直接等于sidebarData.kernelFileTreeProjection.items。 - 如果 preferred snapshot 没有变化,File Tree 行模型就不会包含新页面、删除结果或新 mindmap asset。
wolai-frontend/src/components/sidebar/sidebar.tsx:695 到 :702:
buildFileTreeShellRowById()基于effectiveResourceTreeShellItems构建 row map。- 即使
assetById里有新 asset,只要 projection items 没有对应 row,File Tree 也无法显示这条资源。
3.4 全局事件监听存在,但 refetch 在 Convex live 模式下是空操作
wolai-frontend/src/components/sidebar/sidebar-events.ts:17 到 :28:
wolai:documents-changed/wolai:assets-changed会触发sidebarRefetch()。- 这说明全局事件监听并非完全禁用。
wolai-frontend/src/hooks/use-sidebar-data.ts:93 到 :101:
sidebarQuery.refetch()在 Convex live subscription 模式下转调convexSidebar.refetch()。
wolai-frontend/src/hooks/use-convex-sidebar-data.ts:90 到 :96:
hasLiveSubscription = shouldFetch。refetch明确是空操作,注释为“Convex 会自动同步数据”。
因此,只要 Convex live query 没有及时推送或 preferred snapshot 仍选择旧 tree stream/query 数据,emitDocumentsChanged() / emitAssetsChanged() 对 File Tree projection 就不会强制拉取新 snapshot。
3.5 mindmap 新建只乐观补 asset 列表,不补 File Tree projection row
wolai-frontend/src/components/editor/blocks/MindmapBlock.tsx:1527 到 :1534、:1591 到 :1621、:1636 到 :1647:
- mindmap 保存、初始同步、实例就绪都会调用
emitAssetsChanged(docId, asset)。
wolai-frontend/src/components/sidebar/sidebar.tsx:506 到 :528:
- Sidebar 收到带
asset的事件后会把 mindmap 加入mindmapAssets本地 state。 - 但 File Tree 的 row 输入仍是
sidebarData.kernelFileTreeProjection.items,没有把新 mindmap asset materialize 成 projection item。
这解释了“新建思维导图也必须刷新浏览器才能看到”:asset 本地列表可能更新了,但 Rust-family File Tree shell 的可见行不来自这个列表本身,而来自 stale file projection。
4. 当前判断
该问题已通过代码路径确认,属于事件/刷新链路和 projection 局部 apply 缺口:
- 事件监听存在,不是全局事件完全被禁用。
- 文档变更事件在 create 路径缺失,在 delete 路径存在但只触发空 refetch。
- asset 变更事件能把 mindmap 加进本地 asset state,但不能生成 File Tree projection row。
- File Tree shell 以
kernelFileTreeProjection.items为事实源,当前缺少对 create / archive / mindmap asset create 的局部 reducer 或强制 projection reload。 - 浏览器刷新后能看到正确结果,进一步说明后端写入成功,前端 projection 没有及时刷新。
5. 建议修复方向
- 将
tree.node.create/tree.node.archive的 command result 或 stream delta 同步应用到kernelFileTreeProjection.items,不要只更新 Page Tree state。 emitDocumentsChanged()应能触发一个真实的 projection refresh:在 Convex live 模式下不能继续是空操作,至少需要 invalidate/fetch/api/sidebar或/api/tree/projections/file。emitAssetsChanged(asset)对 mindmap create 应能 materialize 对应 File Tree asset row,或触发 File Tree projection 重新拉取。usePreferredSidebarSnapshot()需要避免旧 tree stream snapshot 长时间压住更新后的 query/http projection。- 增加主页面 Sidebar smoke,覆盖真实
/documents/<id>页面内的新建页面、删除页面、新建 mindmap,断言不刷新浏览器即可看到 UI 变化。
6. 修复记录
2026-05-14 已完成修复:
wolai-frontend/src/components/sidebar/sidebar-local-projection.ts- 新增 Sidebar 本地 documents/file-tree projection helper。
tree.node.create成功后可将新页面 upsert 到本地 documents,并重建 File Tree projection。tree.node.archive/ 删除成功后可从本地 documents 中移除目标页面及子树,并重建 File Tree projection。- mindmap asset 进入本地
mindmapAssets后,可 materialize 成 File Tree asset row。
wolai-frontend/src/components/sidebar/sidebar.tsx- 新增本地
documentsstate,并随后端 sidebar snapshot 校准。 - File Tree 非搜索状态改为消费
localFileTreeProjection.items,不再只依赖 stalesidebarData.kernelFileTreeProjection.items。 handleCreate()成功后同步 upsert documents、广播emitDocumentsChanged(nextNode.id),Page Tree 与 File Tree 都可即时反映。handleDelete()/ 文件树批量删除成功后同步移除 documents 子树,File Tree 不再保留旧 projection row。- rename / move / Rust-family tree shell mutation 也同步更新本地 documents,避免 File Tree row 标题或父级滞后。
- 新增本地
wolai-frontend/src/hooks/use-sidebar-data.ts- Convex live 模式下的显式
refetch()不再只是空操作;现在会拉取/api/sidebar?workspaceId=<id>作为手动 snapshot 补偿。 - 手动 snapshot 绑定 workspaceId,避免跨工作区污染。
- Convex live 模式下的显式
wolai-frontend/src/components/sidebar/sidebar-sync.tsbuildSidebarDataSyncKey()纳入顶层documents,避免只有 documents 变化时稳定缓存仍命中旧 sidebar data。
7. 验收标准
只有满足以下条件后才能移动到 bugs/04-tree-domain/done/:
- 在
http://127.0.0.1:3000/documents/<id>主页面壳中新建页面后,不刷新浏览器即可在 Page Tree 与 File Tree 中看到新页面。代码证据:handleCreate()本地 upsert documents,localFileTreeProjection.items立即包含doc:<id>/index:<id>;单测覆盖:sidebar-local-projection.test.ts。 - 在主页面壳中删除页面后,不刷新浏览器即可在 Page Tree 与 File Tree 中看到目标页面消失。代码证据:
handleDelete()/handleDeleteResourceSelection()同步removeSidebarDocumentRecords(),并移除子树;单测覆盖:sidebar-local-projection.test.ts与sidebar-delete-preflight-source.test.ts。 - 在主页面壳中新建 mindmap 后,不刷新浏览器即可在 File Tree 中看到对应
mindmap-<id>.json行。代码证据:emitAssetsChanged(asset)已进入本地mindmapAssets,localFileTreeProjection用本地 mindmap assets 重建 asset row;单测覆盖:sidebar-local-projection.test.ts。 - 上述三条路径均不得依赖
window.location.reload()或顶层 navigation。代码证据:本轮没有新增 reload;create/delete 以本地 state/projection apply 为主,显式 refetch 仅作 snapshot 校准。 - smoke 需要记录
documents-changed/assets-changed事件、/api/tree/events或 projection refetch 是否发生,以及最终 DOM 稳定耗时。已补真实浏览器 smoke:tmp/task426-sidebar-main-no-reload-smoke/result.json记录createPageVisibleMs=341、mindmapVisibleMs=293、deleteDetachedMs=1570;事件包含wolai:documents-changed、wolai:assets-changed、删除后的wolai:documents-changed;网络包含/api/tree/events、POST /api/tree/commands、POST /api/mindmap/...与显式/api/sidebar?workspaceId=...refetch。代码级测试同时覆盖:sidebar-delete-preflight-source.test.ts锁定 create/delete 事件与本地 documents apply;use-sidebar-data.test.tsx锁定 Convex live 显式 refetch 会真实拉/api/sidebar。 - 修复后补测试,防止
sidebarQuery.refetch()在 Convex live 模式下继续作为空操作吞掉显式刷新请求。测试覆盖:use-sidebar-data.test.tsx。
8. 验证记录
已通过:
cd /mnt/Data1T/mnote/wolai-frontend
pnpm test -- src/components/sidebar/sidebar-delete-preflight-source.test.ts src/components/sidebar/sidebar-local-projection.test.ts src/hooks/use-sidebar-data.test.tsx src/components/sidebar/sidebar-sync.test.ts
结果:113 passed,465 passed。
cd /mnt/Data1T/mnote/wolai-frontend
pnpm exec eslint src/components/sidebar/sidebar-sync.ts src/components/sidebar/sidebar-sync.test.ts src/components/sidebar/sidebar-local-projection.ts src/components/sidebar/sidebar-local-projection.test.ts src/hooks/use-sidebar-data.ts src/hooks/use-sidebar-data.test.tsx src/components/sidebar/sidebar.tsx
结果:无 error;sidebar.tsx 保留既有 warning。
真实浏览器 smoke 已通过:
cd /mnt/Data1T/mnote
MNOTE_UI_BASE_URL=http://127.0.0.1:3001 node <task426-sidebar-main-no-reload-smoke>
结果文件:tmp/task426-sidebar-main-no-reload-smoke/result.json。
关键结果:
ok=truecreatePageVisibleMs=341mindmapVisibleMs=293deleteDetachedMs=1570- 删除确认文案:
确认删除选中的 1 个页面(删除到垃圾桶) 吗? beforeDeleteState.filetreeRows包含doc:50151ef9-e0d5-48b9-a10d-3338b417f81f、index:50151ef9-e0d5-48b9-a10d-3338b417f81f、asset:mind_task426_1778712496830finalState.filetreeRows不再包含上述 document / mindmap asset rowfinalState.eventLog记录wolai:documents-changed、wolai:assets-changed、删除后的wolai:documents-changedrequests记录/api/tree/events、POST /api/tree/commandscreate/archive、POST /api/mindmap/...createOnly、GET /api/sidebar?workspaceId=...
已尝试:
cd /mnt/Data1T/mnote/wolai-frontend
pnpm exec tsc --noEmit --pretty false
结果:失败,但失败集中在既有 Convex / AI / OnlyOffice / 测试类型问题,不是本轮新增文件或本轮修改路径的专属错误。
9. 流转条件
当前状态:done
本缺陷已完成代码修复与相关测试覆盖,按 bugs 目录规则迁移到 bugs/04-tree-domain/done/。