29 KiB
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.rsSIDEBAR_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.rsbuild_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- 已定义
TreeShellRuntimeRequest、TreeShellRuntimeResult、TreeShellDomPatch、TreeShellHostEvent、TreeShellCommandEvent。
- 已定义
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-webshell runtime 外置化范围,应由05-editor-mainline拆出编辑器命令和附件/history 相关模块。
3. Sidex / VSCode Explorer 对照结论
Sidex 参考实现的关键价值不是具体 UI,而是模块边界:
ExplorerView:视图容器,持有 tree、renderer、context key、focus、layout。ExplorerService:模型、文件变更、refresh、cut/copy 状态、view 注册。FileDragAndDrop:DND 独立类,包含onDragOver、drop、getDragURI、onDragStart。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 注入职责,不再承载万行浏览器行为。 - 让
/treedebug shell 与主壳 sidebar 复用同一套 tree/filetree runtime 模块,减少两套行为漂移。 - 保留 Rust
tree_shellreducer / 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,再进入下一阶段。
推荐顺序:
- resource open / local upload / attachment action adapter
TREE_LIVE_CONTROLLER_JS- debug
/treeshell runtime 的 bridge 层 - 主壳 filetree selection / keyboard / DND bridge
- 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和 Hindsightreasonixrecall,再决定是否采纳。 - 每个 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-tiptaphistory /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
/treebridge 或主壳 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核对关键符号位置;Hindsightreasonixbank 可 recall 到该 handoff。
可采纳结论:
resource-open-runtime.js是 Batch 1 更合适的首个实施目标,但第一刀应只迁layout.rs中资源打开的纯函数和 bridge,例如:buildOnlyOfficeOpenPathbuildLocalFileOpenUrlopenLocalOfficeFileInActiveTabopenLocalResourceInActiveTabopenConvexAssetFromFileTreeopenEditorAttachmentDetailopenEditorAttachmentEditTab
web_shell.rs中openResourceInActiveTab、createResourceTabDom、openTiptapResourceTab、replacePaneDocument、createEditorViewBinding等实际属于 resource tab / document pane host 生命周期,风险高,不应塞进 Batch 1 的同一个 worker。local-upload-runtime.js是第二个高价值目标;真实入口是uploadFileToMediaAsset、insertUploadedAssetIntoEditor和 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 route:
GET /api/mnote-browser-runtime/resource-open-runtime.js。 PageLayout在SIDEBAR_TREE_JS前注入type="module"外置脚本。SIDEBAR_TREE_JS中以下 helper 先做 runtime 代理,保留原实现 fallback:buildOnlyOfficeOpenPathbuildLocalFileOpenUrllocalFilePathFromAssetId
采纳边界:
- 本批次没有迁移
openConvexAssetFromFileTree、openEditorAttachmentDetail、openEditorAttachmentEditTab等复杂行为函数。 - 本批次没有迁移
web_shell.rs的openResourceInActiveTab、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 route:
GET /api/mnote-browser-runtime/tree-live-controller.js。 PageLayout将原 inlineTREE_LIVE_CONTROLLER_JS替换为type="module"外置脚本。layout.rs不再保留大块TREE_LIVE_CONTROLLER_JSraw 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返回200,content-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/events500,这是既有 local folder event stream 问题,不属于本批次 route/module 加载回归。
CodeGraph 复核:
- 初始外置文件仍包在 IIFE 内,CodeGraph 只能识别外层匿名符号,不能定位
startWithWebSocket。 - Codex 将外置 module 调整为模块级函数声明,并把启动 guard 放到底部。
- 复核结果:
codegraph_search startWithWebSocket->rust/crates/mnote-web/browser/tree-live-controller.jscodegraph_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.jsexecuteLocalFileTreeExternalDrop/uploadFileToMediaAsset/insertUploadedAssetIntoEditor->local-upload-runtime.jsstartWithWebSocket/startWithSseFallback->tree-live-controller.jsselectFileTreeRow/openFileTreeContextMenu->filetree-selection-runtime.js/filetree-context-menu-runtime.jscreateEditorViewBinding/replacePaneDocument->document-pane-host-runtime.js,归03-rust-web与05-editor-mainline交界,需单独切片。
- 为
layout.rs/tree.rs中计划迁出的 script 增加最小字符串快照测试,防止迁移时漏挂载。 - 建立 smoke 基线:
node scripts/task487-local-folder-tree-live-consumer-smoke.jsnode scripts/task446-tree-rename-dual-browser-live-smoke.jsnode scripts/task472-side-target-secondary-pane-smoke.jsnode 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 的纯函数和 bridge;web_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.jsnode scripts/task479-local-folder-markdown-resource-lifecycle-smoke.js
- 验证 CodeGraph 或等价结构搜索能定位
openResource、buildOnlyOfficeOpenPath、executeLocalFileTreeExternalDrop。
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.rs中tree-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 能定位
reducePageActionWithRuntime、buildFileTreeRuntimeEnvironment、reconcilePageRuntimeResult等符号。
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.jsdocument-session-runtime.jsdocument-conflict-panel-runtime.js
- 只迁 host/session/pane 生命周期,不迁 Tiptap 编辑器内部命令。
- 与
05-editor-mainline的编辑器 runtime 拆分保持边界:host 负责挂载、pane、session、资源 tab;editor 负责命令、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 或等价结构搜索定位以下浏览器符号:
startWithWebSocketstartWithSseFallbackbuildOnlyOfficeOpenPathopenEditorAttachmentEditTabreducePageActionWithRuntimebuildFileTreeRuntimeEnvironmentselectFileTreeRowexecuteLocalFileTreeExternalDropopenFileTreeContextMenuopenResource
验收标准:
- 这些符号不再只出现在 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 分为
debugTreeShellHost与workspaceSidebarHost,共享纯 runtime,不共享 debug UI。
- 控制:host bridge 分为
- 风险:前端模块继续膨胀成新的巨文件。
- 控制:单文件超过约 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-web与05-editor-mainline边界混淆。- 控制:本设计只处理 shell/browser host runtime;Tiptap 命令、history、附件链接增强、编辑器 DOM overlay 由
05-editor-mainline设计处理。
- 控制:本设计只处理 shell/browser host runtime;Tiptap 命令、history、附件链接增强、编辑器 DOM overlay 由
13. Done Gate
本设计移动到 done/ 前必须满足:
TREE_LIVE_CONTROLLER_JS已从layout.rs迁出。- debug
/treeshell 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-ai或05-editor-mainline设计,避免 tree runtime 重构扩大范围。 - 是否保留内嵌 JS 提取脚本:如果 P1-P3 推进顺利,可只作为临时诊断工具;如果短期没有资源迁出大块 runtime,则保留脚本辅助 CodeGraph。