Files
mnote/design/03-rust-web/process/3-19-rust-web-browser-runtime-module-extraction-v1.md
T

29 KiB
Raw Blame History

3-19 Rust Web 浏览器 Runtime 模块外置化重构 v1

创建时间:2026-05-24 状态:process

触发背景:

  • CodeGraph 对 mnote-web 前端内嵌 JS 的符号定位效果一般。
  • 字面搜索命中太多,尤其是 layout.rs / tree.rs 内的大型 raw string。
  • 需要判断是更换 graph 工具,还是调整源码组织方式。

参考实现:

  • /mnt/Data1T/mnote/reference-code/sidex-main/src/vs/workbench/contrib/files/browser/views/explorerView.ts
  • /mnt/Data1T/mnote/reference-code/sidex-main/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts
  • /mnt/Data1T/mnote/reference-code/sidex-main/src/vs/workbench/contrib/files/browser/explorerService.ts
  • /mnt/Data1T/mnote/reference-code/sidex-main/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts

1. 结论

当前不应优先更换 CodeGraph。主要问题不是 graph 项目选型,而是 mnote-web 的大量浏览器 runtime 仍作为 Rust raw string / HTML inline script 存在。对 CodeGraph、IDE、ast-grep、浏览器 stack trace 来说,这些逻辑首先是 Rust 字符串,不是一等 JavaScript / TypeScript 源文件。

短期可以做“内嵌 JS 提取索引”作为定位辅助,但它只解决查询体验,不改变维护边界。长期重构方向应是:保留 Rust SSR / projection / reducer 的语义主导权,把浏览器端 DOM 绑定、host bridge、transport controller、DND、context menu、resource open adapter 等 runtime 分阶段迁出 Rust raw string,变成可索引、可测试、可复用的 .js.ts 模块。

补充结论(2026-05-24):最近 bugs/0524.md 的修复耗时说明,低效并不只发生在 layout.rs 的 tree/filetree runtime,也发生在 web_shell.rs 的文档页 host adapter 和 leptos-tiptap-spike/src/lib.rs 的编辑器 runtime 巨型文件。本设计只覆盖 03-rust-web 的浏览器 shell runtime 外置化;leptos-tiptap 编辑器命令、附件、history、DOM bridge 需要并行进入 05-editor-mainline 设计,不应塞进本设计里一起实施。

2. 当前代码基线

2.1 主壳内嵌 runtime

  • rust/crates/mnote-web/src/ssr/pages/layout.rs
    • SIDEBAR_TREE_JS 从文件顶部开始,是当前主壳最大浏览器 runtime。
    • 职责混合了:侧栏宽度、工作区切换、page tree / file tree 事件、文件树选择、拖拽、资源打开、附件上传、Page AI panel、账号菜单、本地文件夹 watcher。
    • TREE_LIVE_CONTROLLER_JS 负责 tree live bootstrap、WS / SSE / local-folder event transport、snapshot / delta / resync 分发。
    • PageLayout 通过 <script inner_html={...}> 直接注入这些 runtime。

2.2 Debug tree shell 内嵌 runtime

  • rust/crates/mnote-web/src/routes/tree.rs
    • build_tree_shell_html() 内嵌整页 HTML、CSS、JS。
    • JS 负责 page/filetree/picker 模式、DOM render、keyboard、DND、context menu、runtime reduce endpoint 调用、local fallback。
    • /api/tree/runtime/reduce 已存在,入口为 reduce_tree_shell_runtime()

2.3 已经存在的 Rust 侧良好边界

  • rust/crates/mnote-web/src/tree_shell/runtime_api.rs
    • 已定义 TreeShellRuntimeRequestTreeShellRuntimeResultTreeShellDomPatchTreeShellHostEventTreeShellCommandEvent
  • rust/crates/mnote-web/src/tree_shell/filetree_selection.rs
    • 已定义文件树选择 reducer contract。
  • rust/crates/mnote-web/src/tree_shell/filetree_renderer.rs / page_renderer.rs
    • 已有初始 HTML renderer 与测试。

这说明重构不需要推翻 Rust 主线。相反,应该把现有 Rust reducer / renderer 合同作为稳定边界,让浏览器 runtime 外置后继续消费这些合同。

2.4 需要另线处理的编辑器 runtime 巨型文件

  • rust/spikes/leptos-tiptap-spike/src/lib.rs
    • 约一万行,虽然是 Rust 源码而不是 raw string,但混合了 Tiptap bridge、编辑器命令、附件链接增强、上传、history、mindmap node view、DOM overlay、page option、AI surface。
    • CodeGraph 能索引 Rust 符号,但单文件职责过大,worker 仍会反复字面搜索和误判边界。
    • 该文件不属于 03-rust-web shell runtime 外置化范围,应由 05-editor-mainline 拆出编辑器命令和附件/history 相关模块。

3. Sidex / VSCode Explorer 对照结论

Sidex 参考实现的关键价值不是具体 UI,而是模块边界:

  • ExplorerView:视图容器,持有 tree、renderer、context key、focus、layout。
  • ExplorerService:模型、文件变更、refresh、cut/copy 状态、view 注册。
  • FileDragAndDropDND 独立类,包含 onDragOverdropgetDragURIonDragStart
  • fileActions.contribution.ts:命令和快捷键独立注册,通过 context condition 控制可用性。
  • IExplorerView / IExplorerService:视图与服务之间通过接口通信。

MNote 不应照搬 VSCode 的完整 DI / Workbench 架构;当前产品主线也不是 BlockNote-first 或 VSCode clone。但应采用同一类边界原则:

  • 视图绑定与 DOM patch 是浏览器 runtime 职责。
  • 树语义、资源归属、projection、command 语义仍由 Rust kernel / mnote-web reducer 主导。
  • DND、context menu、keyboard、transport、resource open adapter 不应继续混在单个全局 IIFE 中。

4. 目标

  • 让主壳浏览器 runtime 成为一等源文件,能被 CodeGraph / IDE / ast-grep / lint 正常解析。
  • layout.rs 回到 SSR layout 与 bootstrap 注入职责,不再承载万行浏览器行为。
  • /tree debug shell 与主壳 sidebar 复用同一套 tree/filetree runtime 模块,减少两套行为漂移。
  • 保留 Rust tree_shell reducer / renderer / contract 作为语义边界,不把树真相迁到前端。
  • 改善浏览器调试:错误堆栈能落到稳定 JS 模块文件和行号。
  • 为后续 tree live cache、filetree DND、resource tab、local folder watcher 的继续收口降低定位成本。

5. 非目标

  • 不更换 CodeGraph。
  • 不引入完整 React / Next / BlockNote 主链。
  • 不把 Rust kernel / Rust projection 的语义迁回前端。
  • 不一次性重写整个 sidebar。
  • 不在本阶段改变现有 UI 形态、Wolai 对齐目标、tree command 协议或 resource object identity 合同。
  • 不把 debug /tree 重新提升为默认产品入口;它仍是显式 debug/internal 边界。

6. 方案选择

6.1 方案 A:只做内嵌 JS 提取索引

做一个脚本从 Rust raw string 中提取 <script> / *_JS 常量为临时 .js 文件,并带回源文件行号映射。

优点:

  • 改动小。
  • 对定位问题见效快。
  • 不影响 runtime。

缺点:

  • 只是辅助索引,真实维护边界仍然是 Rust 字符串。
  • 浏览器 stack trace、代码 review、模块复用、单测边界不会根本改善。
  • 提取器会成为额外维护成本。

6.2 方案 B:直接整体重写 sidebar runtime

一次性把 SIDEBAR_TREE_JS 全部迁出并重写成新模块体系。

优点:

  • 最终形态干净。
  • 可一次性消除万行 inline script。

缺点:

  • 风险过大。
  • 当前 sidebar 混合了 tree、资源、AI、账号、本地文件夹、多 pane 等多个活跃主线,整体重写容易引入回归。
  • smoke 覆盖面需要非常大,不适合作为第一阶段。

6.3 推荐方案 C:分阶段外置化,先提取边界清楚的 runtime

先从职责边界最清晰、状态输入最明确、验证最容易的模块开始迁出。每个阶段都保持旧行为,完成后跑对应 smoke,再进入下一阶段。

推荐顺序:

  1. resource open / local upload / attachment action adapter
  2. TREE_LIVE_CONTROLLER_JS
  3. debug /tree shell runtime 的 bridge 层
  4. 主壳 filetree selection / keyboard / DND bridge
  5. Page AI panel 等低相关大块 runtime 另立后续设计,不混入本次 tree runtime 重构

推荐理由:

  • 每步可独立验收。
  • 不破坏当前 Rust reducer / projection 主线。
  • 能最快验证 CodeGraph 定位改善。
  • 便于回滚单一模块,而不是回滚整个 sidebar。
  • 最近缺陷集中在上传目标、附件打开、secondary pane、资源 tab、OnlyOffice/PDF/code 打开路径。先拆 resource/local-upload adapter,比先拆 tree live 更能直接降低后续 bug 修复成本。
  • TREE_LIVE_CONTROLLER_JS 仍适合作为低风险模板切片:边界清楚、smoke 现成、能验证 asset route 和 ES module 注入方式。

7. 目标架构

7.1 文件布局建议

新增浏览器 runtime 源文件目录:

rust/crates/mnote-web/browser/
  resource-open-runtime.js
  local-upload-runtime.js
  tree-live-controller.js
  tree-shell-runtime.js
  sidebar-tree-runtime.js
  filetree-selection-runtime.js
  filetree-dnd-runtime.js
  filetree-context-menu-runtime.js
  local-folder-events-runtime.js

如后续决定引入 TypeScript,可迁移为:

rust/crates/mnote-web/browser/src/
rust/crates/mnote-web/browser/dist/

第一阶段建议先用 plain ES module .js,避免为了重构引入额外 build chain。待模块边界稳定后,再评估是否升级 TypeScript。

7.2 Rust 注入边界

Rust 继续负责:

  • SSR HTML layout。
  • bootstrap JSON script,例如 __MNOTE_TREE_LIVE_BOOTSTRAP__
  • initial page tree / file tree HTML。
  • /api/tree/runtime/reduce
  • /api/tree/commands
  • /api/realtime/ws/api/tree/events/api/local-folder/events
  • mnote-web 静态 runtime asset 路由。

Rust 不再负责:

  • 在 raw string 中维护大块浏览器行为。
  • 在 inline script 中拼出完整 DOM runtime。
  • layout.rs 中混合 sidebar、AI、resource、tree live 的所有事件处理。
  • web_shell.rs 中继续堆叠资源打开和 pane host adapter 的长期浏览器逻辑;文档页 host adapter 应逐步迁到外置模块,但编辑器内部 Tiptap 命令仍归 05-editor-mainline

7.3 浏览器模块职责

浏览器 runtime 负责:

  • 读取 bootstrap JSON。
  • 绑定 DOM event。
  • 收集 DOM 当前可见状态,构造 reducer request。
  • 调用 Rust runtime reduce endpoint。
  • 应用 domPatches
  • 执行 hostEvents,例如 open page、open resource、context menu、upload files。
  • 连接 tree live transport 并向页面分发 tree:snapshot / tree:delta / tree:resync

浏览器 runtime 不负责:

  • 决定页面/资源父子真相。
  • 决定资源归属。
  • 决定 tree command 语义。
  • 维护第二套 file tree projection。

8. 合同草案

8.1 Runtime asset manifest

新增或复用类似 leptos_tiptap_manifest() 的 runtime manifest

{
  "schema": "mnote.browser_runtime_manifest.v1",
  "assets": {
    "treeLiveController": "/api/mnote-browser-runtime/tree-live-controller.js",
    "sidebarTreeRuntime": "/api/mnote-browser-runtime/sidebar-tree-runtime.js",
    "treeShellRuntime": "/api/mnote-browser-runtime/tree-shell-runtime.js"
  }
}

8.2 Tree live bootstrap

沿用当前 mnote.tree_live_bootstrap.v1,但由外置模块读取:

{
  "schema": "mnote.tree_live_bootstrap.v1",
  "transport": "convex-command-log-ws",
  "endpoint": "/api/tree/events",
  "wsEndpoint": "/api/realtime/ws",
  "views": ["page-tree", "file-tree"]
}

8.3 Host bridge

主壳提供最小 host bridge

window.__mnoteTreeHost = {
  openPage(detail) {},
  openResource(detail) {},
  dispatchCommand(command) {},
  applyTreeSnapshot(payload) {},
  applyTreeDelta(payload) {},
  reportRuntimeError(error, context) {}
};

该 bridge 只是浏览器适配层,不是树真相。

8.4 Worker 任务切片合同

外置模块后,ClaudeCode / Reasonix 任务书必须优先指向单一模块和单一 smoke,不再让 worker 直接搜索整个 layout.rs / web_shell.rs

允许读取:
- rust/crates/mnote-web/browser/resource-open-runtime.js
- rust/crates/mnote-web/src/ssr/pages/layout.rs 中 module bootstrap 小段
- scripts/task479-local-folder-markdown-resource-lifecycle-smoke.js

禁止:
- 重写 sidebar 全局 IIFE
- 修改 Rust tree command 语义
- 修改不相关 Page AI panel

采纳 worker diff 的最低条件:

  • diff 落在指定模块和测试内。
  • handoff 说明调用链、未覆盖边界和验证命令。
  • 主控能用 CodeGraph / rg 直接定位被修改符号。
  • 浏览器 smoke 或截图证据可复核。

9. 执行策略

本设计按 Codex 主控、Reasonix 短跑 worker 协同执行:

  • Codex 负责设计口径、模块边界、任务拆分、最终 diff 复核、测试和提交。
  • Reasonix 只承接窄任务,每个 run 必须有任务书、允许文件、禁止事项、输出表格和 handoff。
  • 同时最多启动 2 个 Reasonix worker;每批结束后 Codex 先读取 process-handoff.md/json 和 Hindsight reasonix recall,再决定是否采纳。
  • 每个 worker 完成或超时后都应结束该 run,不在同一个长会话里连续追加不相关任务。
  • Batch 0 只做只读审计,不修改文件、不跑 git 写操作,用来生成热点归属表和风险清单。
  • Batch 1 起才允许实施单模块搬迁;每个实施 worker 只能修改一个 runtime 模块和对应 smoke。

当前建议批次:

  • Batch 0 / R1:只读审计 resource open / local upload / attachment action adapter,输出函数归属表、目标模块、风险和 smoke 建议。
  • Batch 0 / R2:只读审计 leptos-tiptap history / set_content 风险,结果写回 05-editor-mainline 设计,不混入本设计实现。
  • Batch 1:外置 resource-open-runtime.js 的最小可验证切片,优先迁出纯 URL builder 和 layout bridge,不一次迁走 web_shell.rs 的完整 resource tab/session 生命周期。
  • Batch 2:外置 TREE_LIVE_CONTROLLER_JS
  • Batch 3:按审计结果选择 debug /tree bridge 或主壳 filetree runtime。

Batch 0 验收标准:

  • R1 能列出 layout.rs / web_shell.rs 中资源打开、上传、附件动作相关入口和调用位置。
  • R1 能给出“先拆哪个模块”的具体建议,并绑定现有 smoke。
  • R2 的 editor 侧结论必须只作为跨设计参考;不得要求 3-19 修改 Tiptap 内部命令。
  • Codex 必须把可采纳结论整合进本设计或 5-28,未采纳项记录原因。

9.1 Batch 0 执行记录(2026-05-24

Reasonix R1 run

  • run id: reasonix-2026-05-24T12-38-22-672Z-6ffcfa23
  • handoff: /home/lix/.codex/runtime/reasonix-coding-worker/reasonix-2026-05-24T12-38-22-672Z-6ffcfa23/process-handoff.md
  • 类型:只读审计,无工作区改动。
  • Codex 复核:已抽样用 rg 核对关键符号位置;Hindsight reasonix bank 可 recall 到该 handoff。

可采纳结论:

  • resource-open-runtime.js 是 Batch 1 更合适的首个实施目标,但第一刀应只迁 layout.rs 中资源打开的纯函数和 bridge,例如:
    • buildOnlyOfficeOpenPath
    • buildLocalFileOpenUrl
    • openLocalOfficeFileInActiveTab
    • openLocalResourceInActiveTab
    • openConvexAssetFromFileTree
    • openEditorAttachmentDetail
    • openEditorAttachmentEditTab
  • web_shell.rsopenResourceInActiveTabcreateResourceTabDomopenTiptapResourceTabreplacePaneDocumentcreateEditorViewBinding 等实际属于 resource tab / document pane host 生命周期,风险高,不应塞进 Batch 1 的同一个 worker。
  • local-upload-runtime.js 是第二个高价值目标;真实入口是 uploadFileToMediaAssetinsertUploadedAssetIntoEditor 和 debug shell 的 executeLocalFileTreeExternalDrop,不是 uploadFilesToLocalFolderTarget
  • TREE_LIVE_CONTROLLER_JS 仍适合作为 Batch 2:边界清楚,现有 smoke 对 WS/SSE fallback 覆盖更明确。

本轮发现的 worker 协同问题:

  • Reasonix 能产出有用的只读审计表,但 runner 在产出 completed 结果后底层 reasonix acp 进程没有自动退出;主控必须显式检查并清理本轮启动的 worker 进程。
  • Hindsight recall 可找到 handoff,但召回结果会混入同日其它 Reasonix 经验;主控仍必须读取本次 process-handoff.md/json 原文。

9.2 Batch 1 执行记录(2026-05-24

Reasonix implementation run

  • run id: reasonix-2026-05-24T12-49-55-633Z-be2844cb
  • worktree: /mnt/Data1T/mnote-wt-3-19-resource-open-b1
  • handoff: /home/lix/.codex/runtime/reasonix-coding-worker/reasonix-2026-05-24T12-49-55-633Z-be2844cb/process-handoff.md
  • Codex 采纳方式:从隔离 worktree 选择性合入主工作区;未提交 worker branch。

已落地的最小切片:

  • 新增 rust/crates/mnote-web/browser/resource-open-runtime.js
  • 新增固定 asset routeGET /api/mnote-browser-runtime/resource-open-runtime.js
  • PageLayoutSIDEBAR_TREE_JS 前注入 type="module" 外置脚本。
  • SIDEBAR_TREE_JS 中以下 helper 先做 runtime 代理,保留原实现 fallback
    • buildOnlyOfficeOpenPath
    • buildLocalFileOpenUrl
    • localFilePathFromAssetId

采纳边界:

  • 本批次没有迁移 openConvexAssetFromFileTreeopenEditorAttachmentDetailopenEditorAttachmentEditTab 等复杂行为函数。
  • 本批次没有迁移 web_shell.rsopenResourceInActiveTab、resource tab DOM、session、pane host 生命周期。
  • 固定 route 使用 include_str! 暴露单个 JS 文件,不提供动态 path,因此本批次不需要引入通用路径校验。

验证:

cargo fmt --manifest-path rust/Cargo.toml --all --check
cargo test -p mnote-web sidebar_tree_runtime_opens_office_assets_through_resource_shell
cargo test -p mnote-web sidebar_tree_runtime_opens_pdf_and_code_assets_with_builtin_tools
node --check rust/crates/mnote-web/browser/resource-open-runtime.js

全部通过。

9.3 Batch 2 执行记录(2026-05-24

Reasonix implementation run

  • run id: reasonix-2026-05-24T13-14-25-162Z-f98927dc
  • worktree: /mnt/Data1T/mnote-wt-3-19-tree-live-b2
  • handoff: /home/lix/.codex/runtime/reasonix-coding-worker/reasonix-2026-05-24T13-14-25-162Z-f98927dc/process-handoff.md
  • Codex 采纳方式:读取 Hindsight recall 和 handoff 后,从隔离 worktree 选择性合入主工作区;未提交 worker branch。

已落地的切片:

  • 新增 rust/crates/mnote-web/browser/tree-live-controller.js
  • 新增固定 asset routeGET /api/mnote-browser-runtime/tree-live-controller.js
  • PageLayout 将原 inline TREE_LIVE_CONTROLLER_JS 替换为 type="module" 外置脚本。
  • layout.rs 不再保留大块 TREE_LIVE_CONTROLLER_JS raw string;测试改为 include_str! 外置文件内容。

采纳边界:

  • 本批次只做同构搬迁,不改 tree live 语义。
  • WS 优先、SSE fallback、local-folder events、snapshot/delta/resync/block.delta 分发保持原逻辑。
  • 后端 /api/realtime/ws/api/tree/events/api/local-folder/events 未修改。

验证:

cargo fmt --manifest-path rust/Cargo.toml --all --check
cargo test -p mnote-web tree_live_controller_marks_transport_and_closes_source_on_pagehide
node --check rust/crates/mnote-web/browser/tree-live-controller.js

全部通过。

浏览器轻量验收:

  • dev server: npm run dev:hot
  • 页面:http://127.0.0.1:3000/,测试账号快速登录后进入主壳。
  • /api/mnote-browser-runtime/tree-live-controller.js 返回 200content-type=application/javascript; charset=utf-8
  • document.scripts 中存在 1 个 tree live module script。
  • window.__mnoteTreeLiveControllerStarted === true
  • <html> 上出现 data-mnote-tree-live-transport="disabled"data-mnote-tree-live-status="disabled",证明外置 module 已执行。
  • 控制台仍出现 /api/local-folder/events 500,这是既有 local folder event stream 问题,不属于本批次 route/module 加载回归。

CodeGraph 复核:

  • 初始外置文件仍包在 IIFE 内,CodeGraph 只能识别外层匿名符号,不能定位 startWithWebSocket
  • Codex 将外置 module 调整为模块级函数声明,并把启动 guard 放到底部。
  • 复核结果:
    • codegraph_search startWithWebSocket -> rust/crates/mnote-web/browser/tree-live-controller.js
    • codegraph_search startWithSseFallback -> rust/crates/mnote-web/browser/tree-live-controller.js

10. 分阶段执行清单

P0:定位辅助与基线保护

  • 记录当前 CodeGraph 对内嵌 JS 的限制,确认不更换 graph 项目。
  • 可选新增 scripts/extract-embedded-js-for-index.js,生成临时索引文件,不进入 runtime。
  • 先生成一份“热点函数归属表”,把最近 bug 中高频搜索的函数归到待拆模块:
    • openResource / openLocalOfficeFileInActiveTab / openEditorAttachmentEditTab -> resource-open-runtime.js
    • executeLocalFileTreeExternalDrop / uploadFileToMediaAsset / insertUploadedAssetIntoEditor -> local-upload-runtime.js
    • startWithWebSocket / startWithSseFallback -> tree-live-controller.js
    • selectFileTreeRow / openFileTreeContextMenu -> filetree-selection-runtime.js / filetree-context-menu-runtime.js
    • createEditorViewBinding / replacePaneDocument -> document-pane-host-runtime.js,归 03-rust-web05-editor-mainline 交界,需单独切片。
  • layout.rs / tree.rs 中计划迁出的 script 增加最小字符串快照测试,防止迁移时漏挂载。
  • 建立 smoke 基线:
    • node scripts/task487-local-folder-tree-live-consumer-smoke.js
    • node scripts/task446-tree-rename-dual-browser-live-smoke.js
    • node scripts/task472-side-target-secondary-pane-smoke.js
    • node scripts/task479-local-folder-markdown-resource-lifecycle-smoke.js

P1:外置 resource open / local upload adapter

  • 新增 rust/crates/mnote-web/browser/resource-open-runtime.js
  • 新增 rust/crates/mnote-web/browser/local-upload-runtime.js,或先与 resource open 合并为单个不超过 800 行的 resource-open-runtime.js
  • 第一刀只迁 layout.rs 中 resource open 的纯函数和 bridgeweb_shell.rs 的 resource tab DOM、session、pane host 生命周期保持在后续 document-pane-host-runtime.js 批次。
  • SIDEBAR_TREE_JS / web_shell.rs 中迁出资源打开路径适配,不改变现有 open intent
    • page row -> 当前 pane page tab
    • filetree doc row -> Markdown page tab
    • local Markdown asset -> active resource tab / secondary pane
    • pdf/code -> 对应 lightweight viewer
    • office -> OnlyOffice path builder
  • SIDEBAR_TREE_JS 中迁出本地文件夹上传目标计算和 adapter 调用;资源归属语义仍由 Rust API / kernel 合同决定。
  • 保持现有 smoke
    • node scripts/task472-side-target-secondary-pane-smoke.js
    • node scripts/task479-local-folder-markdown-resource-lifecycle-smoke.js
  • 验证 CodeGraph 或等价结构搜索能定位 openResourcebuildOnlyOfficeOpenPathexecuteLocalFileTreeExternalDrop

P2:外置 TREE_LIVE_CONTROLLER_JS

  • 新增 rust/crates/mnote-web/browser/tree-live-controller.js
  • layout.rs 只注入 __MNOTE_TREE_LIVE_BOOTSTRAP__<script type="module" src="...">
  • 新增 /api/mnote-browser-runtime/tree-live-controller.js 路由,返回 application/javascript; charset=utf-8
  • 保持现有 WS 优先、SSE fallback、local-folder event fallback 行为。
  • 浏览器断言:
    • document.documentElement.dataset.mnoteTreeLiveTransport 正确。
    • 外置 module 实际执行并设置 window.__mnoteTreeLiveControllerStarted
  • WS 推送仍更新 page/filetree。
  • WS 不可用时仍 fallback 到 SSE。

P3:外置 debug /tree shell runtime bridge

  • tree.rstree-shell-state 后的大块 JS 迁到 browser/tree-shell-runtime.js
  • build_tree_shell_html() 只渲染 HTML、CSS、bootstrap JSON 和 module script。
  • 保持 /api/tree/runtime/reduce 合同不变。
  • debug shell page/filetree/picker 三个模式 smoke 保持通过。
  • 验证 CodeGraph 能定位 reducePageActionWithRuntimebuildFileTreeRuntimeEnvironmentreconcilePageRuntimeResult 等符号。

P4:抽出主壳 filetree runtime

  • SIDEBAR_TREE_JS 中拆出文件树选择、keyboard、clipboard、DND、context menu。
  • 先拆纯函数和 reducer bridge,再拆 DOM 绑定。
  • 主壳保留 window.__mnoteSidebarTreeRuntimeStarted 一类启动防重保护,但启动函数迁到外置模块。
  • 与 Rust FileTreeSelectionState 合同对齐,不在前端新增第二套选择真相。
  • 覆盖:
    • 多选、shift range、ctrl/meta toggle。
    • cut/copy/paste。
    • 内部拖拽 move/copy。
    • 外部文件拖入 local folder。
    • readonly target 拒绝。

P5:抽出 document pane host adapter

  • web_shell.rs 的 inline module 中拆出文档 pane host adapter,例如:
    • document-pane-host-runtime.js
    • document-session-runtime.js
    • document-conflict-panel-runtime.js
  • 只迁 host/session/pane 生命周期,不迁 Tiptap 编辑器内部命令。
  • 05-editor-mainline 的编辑器 runtime 拆分保持边界:host 负责挂载、pane、session、资源 tabeditor 负责命令、history、附件链接、DOM overlay。
  • 覆盖:
    • primary / secondary pane 切换
    • conflict panel unmount 清理
    • attachment tab 打开
    • resource tab error placeholder

P6:收尾与旧 inline script 瘦身

  • layout.rs 中只保留小型 bootstrap 和脚本引用。
  • tree.rs 中不再内嵌大型 runtime JS。
  • web_shell.rs 中只保留 page aggregate/bootstrap JSON 和 module script 引用。
  • 更新 CodeGraph 索引,确认新增 JS 模块被索引。
  • 删除或降级 P0 提取脚本:如果真实模块已覆盖主要 runtime,提取脚本只保留为历史/临时工具。

11. 测试与验收

11.1 Rust 测试

cd /mnt/Data1T/mnote/rust
cargo test -p mnote-web tree_shell
cargo test -p mnote-web filetree
cargo test -p mnote-web runtime

11.2 Browser smoke

node scripts/task446-tree-rename-dual-browser-live-smoke.js
node scripts/task447-tree-move-order-dual-browser-live-smoke.js
node scripts/task448-tree-resync-recovery-dual-browser-smoke.js
node scripts/task449-tree-sse-reconnect-snapshot-recovery-smoke.js
node scripts/task472-side-target-secondary-pane-smoke.js
node scripts/task479-local-folder-markdown-resource-lifecycle-smoke.js
node scripts/task487-local-folder-tree-live-consumer-smoke.js

11.3 CodeGraph 验收

迁移后必须能通过 CodeGraph 或等价结构搜索定位以下浏览器符号:

  • startWithWebSocket
  • startWithSseFallback
  • buildOnlyOfficeOpenPath
  • openEditorAttachmentEditTab
  • reducePageActionWithRuntime
  • buildFileTreeRuntimeEnvironment
  • selectFileTreeRow
  • executeLocalFileTreeExternalDrop
  • openFileTreeContextMenu
  • openResource

验收标准:

  • 这些符号不再只出现在 Rust raw string 中。
  • codegraph_status 显示新增 JS 文件被索引。
  • 字面搜索命中数量减少,定位结果能落到具体 runtime 模块。
  • worker 任务书能以“单模块 + 单 smoke”派发,不再要求 worker 搜索整个 layout.rs / web_shell.rs

12. 风险与控制

  • 风险:模块加载路径错误导致主壳首屏无交互。
    • 控制:第一阶段沿用现有 no-store asset route;每次切片保留 smoke。
  • 风险:外置模块改变执行时机。
    • 控制:保留 DOMContentLoaded / immediate start 逻辑;迁移前后记录启动 DOM 标记。
  • 风险:debug shell 与主壳复用时把 debug 行为带进主路径。
    • 控制:host bridge 分为 debugTreeShellHostworkspaceSidebarHost,共享纯 runtime,不共享 debug UI。
  • 风险:前端模块继续膨胀成新的巨文件。
    • 控制:单文件超过约 800 行时必须拆职责;DND、selection、transport、resource open、context menu 分文件。
  • 风险:外置 JS 无类型约束导致合同漂移。
    • 控制:第一阶段用 Rust JSON contract 测试 + browser smoke;稳定后再评估 TypeScript 或 JSDoc typedef。
  • 风险:resource open adapter 迁出时顺手改变 open target 行为。
    • 控制:先建立 open intent 表,迁移只做同构搬迁;secondary pane、active tab、new window fallback 必须由 smoke 覆盖。
  • 风险:03-rust-web05-editor-mainline 边界混淆。
    • 控制:本设计只处理 shell/browser host runtimeTiptap 命令、history、附件链接增强、编辑器 DOM overlay 由 05-editor-mainline 设计处理。

13. Done Gate

本设计移动到 done/ 前必须满足:

  • TREE_LIVE_CONTROLLER_JS 已从 layout.rs 迁出。
  • debug /tree shell runtime 大块 JS 已从 tree.rs 迁出,或已有明确豁免说明。
  • 主壳 filetree runtime 的核心 selection / DND / context menu 至少完成一个模块外置切片。
  • resource open / local upload adapter 至少完成一个高频 bug 模块外置切片。
  • 新增 JS runtime 文件能被 CodeGraph 索引。
  • 至少一组 tree realtime smoke 和一组 local folder filetree/resource smoke 通过。
  • layout.rs / web_shell.rs 不再继续新增大型 inline browser runtime。

14. 后续问题

  • 是否引入 TypeScript:建议等 P1-P3 完成后再决定,不作为本设计前置。
  • 是否引入 bundler:当前不需要;plain ES module 足够完成第一阶段。
  • 是否把 Page AI panel 一并迁出:不建议混在本设计中。Page AI panel 应另起 07-ai05-editor-mainline 设计,避免 tree runtime 重构扩大范围。
  • 是否保留内嵌 JS 提取脚本:如果 P1-P3 推进顺利,可只作为临时诊断工具;如果短期没有资源迁出大块 runtime,则保留脚本辅助 CodeGraph。