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

11 KiB
Raw Blame History

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-batchmnote: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_receiptPage AI receipt 合成事件。
  • synthetic_resource_writeresource 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 输出事件必须带 sourcereasonrootUriworkspaceIdrevisionchangedPathsaffectedParents

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-completeddocument 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,不切换行为。

  • 新增 local-folder-event-bus-runtime.js
  • 在 SSR layout 中加载 bus runtime,保证早于 tree live controllerdocument session / resource tab 后续切订阅时继续复用该入口。
  • bus 可以接管 tree-live-controller.js local-folder EventSource 创建;同一 rootUri 二次 start 返回同一连接。
  • bus 继续兼容派发 tree:local-folder-watch-batch,并标记 viaEventBus=true
  • 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 watcherdocument-session-runtime.jsdocument-resource-tab-runtime.js 仍在 Phase C 切换,P2 总体验收尚未完成。

Phase B:刷新编排。

  • refreshLocalFolderSidebarSnapshot 外包到 bus orchestrator:同一 tick 合并 reason 和 parent 列表。
  • bus 维护 pendingFileTreeParents,同一 parent 在同一 tick 只刷新一次。
  • fallback / resync 仍允许刷新 sidebar projection,但必须记录 reason。
  • sidebar-tree-live-apply-runtime.js 由直接处理 watch batch 改为订阅 bus 输出。

Phase B 结果:

  • bus 新增 mnote:local-folder:sidebar-refresh-requested,同一 tick 合并 changedPathsaffectedParentsreasonsresyncRequired
  • 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:订阅方切换。

  • document-session-runtime.js 不再直接打开 /api/local-folder/events;改订阅 document-changed
    • 正常 bus 可用路径已改订阅 mnote:local-folder:document-changed 并复用 startLocalFolderWatcher(...);保留 bus 不可用时旧 EventSource fallback,作为退路而非主路径。
  • 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。
  • resource write 完成后向 bus 发 synthetic_resource_write
  • Page AI receipt 向 bus 发 synthetic_page_ai_receipt

Phase Dsmoke。

  • 新增 task540-local-folder-event-bus-single-connection-smoke.js
  • 同一页面打开 sidebar + document + resource tab + Page AI 后,只存在一个 local-folder watcher EventSource。
    • Phase D-1task540 当前覆盖 sidebar/tree live + document session 同页只创建一个 /api/local-folder/events EventSource,并验证 synthetic event 兼容派发带 viaEventBus=trueresource tab + Page AI 全组合请求计数仍待后续扩展。
    • Phase D-2task540 已扩展到 passive resource tab,并验证 resource tab watch ready 走 event-bussynthetic_page_ai_receipt 事件经 bus 派发、同页仍只有一个 /api/local-folder/events EventSource。尚未覆盖真实 Page AI 面板点击到 receipt 的完整 UI 链路。
  • 外部修改当前 .md 后,tiptap 可见刷新。
  • 外部新增 / 删除文件后,filetree 只刷新受影响 parent。
  • Page AI receipt 后,sidebar projection 不重复请求。
  • 复跑 task446task447task448 或相邻 tree realtime smoke。
    • task446 / task447 / task448 当前仍依赖无 workspace 的 /api/tree/commands cloud/Convex 旧入口,运行结果为 convex_retired,不适合作为 local-folder event bus 验收证据。
    • 已改用相邻 local-folder runtime smoketask435task436task535task540

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 收口为 rootUriworkspaceId 只作为事件元信息。
  • 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/