Files
mnote/design/03-rust-web/done/3-24-local-folder-browser-event-bus-v1.md
T
lix-2026 9551d4c1dc feat(rag): harden post-LightRAG runtime
Retire legacy OCR/media/evidence fallbacks, add local-folder event bus and Page Aggregate guards, and archive completed design checklists.

Validation: cargo test -p mnote-web -- --test-threads=1; cargo test --workspace -- --test-threads=1; git diff --check; codegraph sync .; codegraph_status.
2026-06-07 10:35:21 +08:00

166 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 3-24 Local-folder browser event bus v1
> 创建时间:2026-06-07
>
> 状态:`done`
>
> Owner03-rust-web / 05-editor-mainline / 07-ai
>
> 来源:`design/10-review/done/20-post-lightrag-runtime-hardening-checklist-v1.md` P2
>
> 上位依据:
> - `design/03-rust-web/done/3-3-rust-web-tree-realtime-event-stream-v1.md`
> - `design/03-rust-web/done/3-14-rust-web-tree-realtime-ws-push-v1.md`
> - `design/03-rust-web/process/3-23-sidebar-local-folder-resource-runtime-followup-v1.md`
## 1. 第一结论
当前 local-folder 浏览器侧有多条独立刷新链:
- `tree-live-controller.js` 对 local-folder 使用 `/api/local-folder/events` EventSource,并派发 `tree:local-folder-watch-batch`
- `sidebar-tree-live-apply-runtime.js` 订阅 `tree:local-folder-watch-batch` 后刷新 filetree parent,必要时再刷新 sidebar projection。
- `document-session-runtime.js` 也直接打开 `/api/local-folder/events`,用于当前文档内容刷新。
- `document-resource-tab-runtime.js` 对资源 tab 直接打开 `/api/local-folder/events`,并在部分保存后合成 `tree:local-folder-watch-batch`
- `sidebar-page-ai-runtime.js` 从 agent receipt 合成 `tree:local-folder-watch-batch``mnote:page-ai-tool-write-completed`
这会造成同一 `workspaceId/rootUri` 页面内多个 EventSource、receipt + watcher echo 双刷新、sidebar projection 和 filetree parent 重复请求。P2 不改 Rust watcher 协议,先在浏览器侧增加单例 event bus,把连接、事件归一、订阅和刷新编排收口。
## 2. 非目标
- 不改变 `/api/local-folder/events``/api/realtime/ws``/api/tree/events` 的服务端协议。
- 不在本稿恢复轮询 fallback。
- 不把 event bus 变成新的树/文件/文档事实源;它只转发和归一事件。
- 不一次性重写 sidebar、document session、resource tab、Page AI runtime;按订阅方逐步切换。
- 不改变 Page AI agent 文件编辑的 audit / receipt 语义。
## 3. 输入源
统一入口:新增 `rust/crates/mnote-web/browser/local-folder-event-bus-runtime.js`,挂到 `window.__mnoteLocalFolderEventBus`
输入源:
- `watcher_sse``/api/local-folder/events?rootUri=...&treeLive=true`,当前 local-folder 主输入。
- `tree_ws``/api/realtime/ws`,只在非 local-folder tree live 或未来 local-folder WS 化时接入。
- `tree_sse``/api/tree/events`,作为 tree projection fallback,不直接替代 local-folder watcher。
- `synthetic_page_ai_receipt`Page AI receipt 合成事件。
- `synthetic_resource_write`resource tab 保存后合成事件。
- `explicit_resync`:命令结果或 runtime 主动要求重新取 sidebar/filetree/document。
约束:
- 同一 `workspaceId/rootUri` 页面内只允许一个 local-folder watcher 连接。
- bus 连接 key 以页面内 `rootUri` 为准;`workspaceId` 只作为事件元信息。原因是 tree live bootstrap、document session、resource tab 在 local-folder 页面可能分别拿到空 workspaceId / local-ws workspaceId,若把 workspaceId 放入连接 key 会在同一 rootUri 内重复建 watcher。
- 不同 rootUri 可以有不同 bus instance;默认页面只注册当前 rootUri。
- bus 输出事件必须带 `source``reason``rootUri``workspaceId``revision``changedPaths``affectedParents`
## 4. 输出事件
bus 派发浏览器事件:
- `mnote:local-folder:event-bus-ready`
- `mnote:local-folder:watch-batch`
- `mnote:local-folder:document-changed`
- `mnote:local-folder:resource-changed`
- `mnote:local-folder:filetree-parent-changed`
- `mnote:local-folder:knowledge-rag-source-updated`
- `mnote:local-folder:resync-required`
兼容桥:
- 短期继续派发旧 `tree:local-folder-watch-batch`,但 detail 增加 `viaEventBus=true`
- 短期继续派发旧 `mnote:page-ai-tool-write-completed`document session 可逐步改订阅新事件。
## 5. 订阅方职责
- Sidebar / FileTree
- 订阅 `filetree-parent-changed` 刷新局部 parent。
- 订阅 `resync-required` 刷新 sidebar projection。
- 不直接拥有 watcher 连接。
- Document session
- 订阅 `document-changed`,仅当当前文档路径命中才刷新当前 buffer。
- 不因任意 watch batch 刷新当前文档。
- Resource tab
- 订阅 `resource-changed`,仅当当前资源路径命中才刷新 stat/read。
- resource save 完成后只向 bus 发 synthetic event,不直接广播全局 watch batch。
- Page AI
- receipt 只向 bus 发 `synthetic_page_ai_receipt`
- 由 bus 去重 watcher echo 与 receipt refresh,避免重复 sidebar projection。
- Knowledge RAG UI
- 订阅 `knowledge-rag-source-updated`,只刷新资料库来源状态,不触发文档正文刷新。
## 6. 实施 Checklist
Phase A:只加 bus,不切换行为。
- [x] 新增 `local-folder-event-bus-runtime.js`
- [x] 在 SSR layout 中加载 bus runtime,保证早于 tree live controllerdocument session / resource tab 后续切订阅时继续复用该入口。
- [x] bus 可以接管 `tree-live-controller.js` local-folder EventSource 创建;同一 rootUri 二次 start 返回同一连接。
- [x] bus 继续兼容派发 `tree:local-folder-watch-batch`,并标记 `viaEventBus=true`
- [x] DOM diagnostics
- `data-mnote-local-folder-event-bus="ready"`
- `data-mnote-local-folder-event-bus-connections`
- `data-mnote-local-folder-event-bus-last-source`
- `data-mnote-local-folder-event-bus-last-reason`
Phase A 结果:
- 新增 `/api/mnote-browser-runtime/local-folder-event-bus-runtime.js` runtime asset。
- `tree-live-controller.js` 的 local-folder 分支优先调用 `window.__mnoteLocalFolderEventBus.startLocalFolderWatcher(...)`bus 不可用时保留原 `/api/local-folder/events` 直连 fallback。
- bus 连接 key 已按 `rootUri` 收口,避免 tree live / resource tab 因 workspaceId 解析差异重复建连接。
- 当前只收敛 tree live controller 的 local-folder watcher`document-session-runtime.js``document-resource-tab-runtime.js` 仍在 Phase C 切换,P2 总体验收尚未完成。
Phase B:刷新编排。
- [x] `refreshLocalFolderSidebarSnapshot` 外包到 bus orchestrator:同一 tick 合并 reason 和 parent 列表。
- [x] bus 维护 `pendingFileTreeParents`,同一 parent 在同一 tick 只刷新一次。
- [x] fallback / resync 仍允许刷新 sidebar projection,但必须记录 reason。
- [x] `sidebar-tree-live-apply-runtime.js` 由直接处理 watch batch 改为订阅 bus 输出。
Phase B 结果:
- bus 新增 `mnote:local-folder:sidebar-refresh-requested`,同一 tick 合并 `changedPaths``affectedParents``reasons``resyncRequired`
- `sidebar-tree-live-apply-runtime.js` 订阅 `mnote:local-folder:sidebar-refresh-requested` 后复用 `applyLocalFolderWatchBatch(...)`,并跳过 `viaEventBus=true` 的旧 `tree:local-folder-watch-batch` 兼容事件,避免同一 bus 事件被 sidebar 处理两次。
- `task540` 会连续发两次同 tick `synthetic_page_ai_receipt`,验证最终只产生一次 filetree parent projection 请求,且 content-only receipt 不触发 sidebar projection。
Phase C:订阅方切换。
- [x] `document-session-runtime.js` 不再直接打开 `/api/local-folder/events`;改订阅 `document-changed`
- 正常 bus 可用路径已改订阅 `mnote:local-folder:document-changed` 并复用 `startLocalFolderWatcher(...)`;保留 bus 不可用时旧 EventSource fallback,作为退路而非主路径。
- [x] `document-resource-tab-runtime.js` 不再为每个 resource tab 直接打开 EventSource;改订阅 `resource-changed`
- passive resource tab 已在 bus 可用时订阅 `mnote:local-folder:resource-changed`,并以 `data-mnote-resource-watch-ready="event-bus"` 暴露诊断;保留 bus 不可用时旧 EventSource fallback。
- [x] resource write 完成后向 bus 发 `synthetic_resource_write`
- [x] Page AI receipt 向 bus 发 `synthetic_page_ai_receipt`
Phase Dsmoke。
- [x] 新增 `task540-local-folder-event-bus-single-connection-smoke.js`
- [x] 同一页面打开 sidebar + document + resource tab + Page AI 后,只存在一个 local-folder watcher EventSource。
- Phase D-1`task540` 当前覆盖 sidebar/tree live + document session 同页只创建一个 `/api/local-folder/events` EventSource,并验证 synthetic event 兼容派发带 `viaEventBus=true`resource tab + Page AI 全组合请求计数仍待后续扩展。
- Phase D-2`task540` 已扩展到 passive resource tab,并验证 resource tab watch ready 走 `event-bus``synthetic_page_ai_receipt` 事件经 bus 派发、同页仍只有一个 `/api/local-folder/events` EventSource。尚未覆盖真实 Page AI 面板点击到 receipt 的完整 UI 链路。
- [x] 外部修改当前 `.md` 后,tiptap 可见刷新。
- [x] 外部新增 / 删除文件后,filetree 只刷新受影响 parent。
- [x] Page AI receipt 后,sidebar projection 不重复请求。
- [x] 复跑 `task446``task447``task448` 或相邻 tree realtime smoke。
- `task446` / `task447` / `task448` 当前仍依赖无 workspace 的 `/api/tree/commands` cloud/Convex 旧入口,运行结果为 `convex_retired`,不适合作为 local-folder event bus 验收证据。
- 已改用相邻 local-folder runtime smoke`task435``task436``task535``task540`
Phase C-1 / D-1 结果:
- `document-session-runtime.js` 在 event bus 可用时不再新建按 documentId/resourcePath 分裂的 EventSource,改为共享 root 级 bus 并按 documentId / relativePath 过滤。
- passive `document-resource-tab-runtime.js` 在 event bus 可用时改订阅 `resource-changed`,旧 per-resource EventSource 仅作为 fallback。
- `document-resource-tab-runtime.js` 的 local OCR sidecar refresh 改为优先 `synthetic_resource_write` 发给 bus,旧 `tree:local-folder-watch-batch` 只作为 bus 不可用 fallback。
- `sidebar-page-ai-runtime.js` 的 agent receipt refresh 改为优先 `emitSyntheticWatchBatch(...)`,兼容事件由 bus 统一派发。
- 扩展 `task540` 后发现同 rootUri 下 tree live 与 resource tab 的 workspaceId 解析差异会导致重复连接;已把 bus 连接 key 收口为 `rootUri``workspaceId` 只作为事件元信息。
- `task540` 在 3300 最新 mnote-web 通过,验证当前文档页 + passive resource tab 只创建一个 local-folder watcher EventSource、bus diagnostics ready、`synthetic_page_ai_receipt` source/reason 保留、兼容事件带 `viaEventBus=true`,且 resource-changed 触发当前资源 stat 刷新。
- `task535` 在 3300 最新 mnote-web 通过,验证真实 Page AI clean agent receipt 后当前文档和 filetree 均刷新,且不调用旧保存/写入工具。
- `task436` 在 3300 最新 mnote-web 通过,验证当前打开 local `.md` 外部修改后 tiptap 可见刷新,并覆盖 dirty 冲突保护。
- `task435` 在 3300 最新 mnote-web 通过,验证外部创建/重命名/删除 Markdown 与非 Markdown 资源后 page tree / filetree 原地更新且无 reload。
## 7. 验收与归档条件
- 页面内同一 `workspaceId/rootUri` 只有一个 local-folder watcher 连接。
- sidebar、document session、resource tab、Page AI receipt 都通过 event bus 收敛。
- `tree:local-folder-watch-batch` 兼容事件仍可用,但 detail 标明 `viaEventBus=true`
- P2 smoke 全部通过,并记录请求计数证据。
- 本稿完成后移动到 `design/03-rust-web/done/`