46 KiB
3-19 Rust Web 浏览器 Runtime 模块外置化重构 v1
创建时间:2026-05-24 状态:
done触发背景:
- 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
9.4 Batch 3 执行记录(2026-05-24)
Reasonix implementation run:
- run id:
reasonix-2026-05-24T14-22-14-401Z-ea414358 - worktree:
/mnt/Data1T/mnote-wt-3-19-local-upload-b3 - 结果:未采纳。该 run 卡在提交 plan 后没有产生工作区 diff,也没有写出 handoff;主控终止残留 runner / acp 进程并删除临时 worktree。
Codex 主控直接完成的最小切片:
- 新增
rust/crates/mnote-web/browser/local-upload-runtime.js。 - 新增固定 asset route:
GET /api/mnote-browser-runtime/local-upload-runtime.js。 PageLayout在SIDEBAR_TREE_JS前注入type="module"外置脚本。SIDEBAR_TREE_JS中以下上传/附件展示 helper 先做 runtime 代理,保留原实现 fallback:uploadedAssetTitleuploadedAssetUrllocalAssetOpenUrluploadedAssetTypefileTreeIconKindForFileNameisLocalUploadedAssetuploadedAssetExtensionisNonOfficeAttachmentNameattachmentExtensionFromFileNameisPdfAttachmentFileNameisCodeAttachmentFileNameinferCodeAttachmentLanguageattachmentClassForFileNameuploadedAttachmentClassuploadedFileSize
采纳边界:
- 本批次不迁移
uploadFileToMediaAsset、insertUploadedAssetIntoEditor、uploadFilesWithResolvedTarget主流程。 - 本批次不修改
/api/local-folder/assets/upload请求字段、secondary pane 目标选择、resource tab、OnlyOffice、PDF/code 打开逻辑。
验证:
node --check rust/crates/mnote-web/browser/local-upload-runtime.js
cargo fmt --manifest-path rust/Cargo.toml --all --check
cargo test -p mnote-web sidebar_upload_runtime_routes_local_markdown_assets_to_local_folder
node scripts/task479-local-folder-markdown-resource-lifecycle-smoke.js
结果:
local-upload-runtime.jsroute 返回200,content-type=application/javascript; charset=utf-8。task479最终ok=true,覆盖主编辑区上传目录、filetree folder drop、broken link 保留、无 active document 附件 tab、文件树滚动、标题来源、单附件删除保留相邻附件。
9.5 Batch 4 执行记录(2026-05-24)
Reasonix read-only audit:
- run id:
reasonix-2026-05-24T15-27-18-395Z-12afda31 - worker worktree:
/mnt/Data1T/mnote-wt-3-19-local-upload-context-b4 - handoff:
/home/lix/.codex/runtime/reasonix-coding-worker/reasonix-2026-05-24T15-27-18-395Z-12afda31/process-handoff.md - 类型:只读审计,主控已读取 Hindsight recall、
process-handoff.md、process-handoff.json。
采纳内容:
local-upload-runtime.js新增resolveEditorUploadContext、editorRootFromUploadOptions、openEditorUploadFilePicker。SIDEBAR_TREE_JS中保留同名 wrapper 和 inline fallback;优先委托window.__mnoteLocalUploadRuntime,模块加载失败时保留旧行为。- 本批不迁
uploadFileToMediaAsset、uploadFilesWithResolvedTarget、insertUploadedAssetIntoEditor。这些函数仍混合 API POST、file tree refresh、Tiptap chain 插入和 OnlyOffice/resource tab 逻辑,后续需要继续拆边界。 scripts/task472-side-target-secondary-pane-smoke.js补齐/api/local-folder/assets/upload当前必需的uploadIntent=editor.markdown.attach,让 secondary pane smoke 恢复可运行。
已通过验证:
node --check rust/crates/mnote-web/browser/local-upload-runtime.jscargo fmt --manifest-path rust/Cargo.toml --all --checkcargo check --manifest-path rust/Cargo.toml -p mnote-webcargo test --manifest-path rust/Cargo.toml -p mnote-web local_upload_runtime_contains_editor_upload_context_helperscargo test --manifest-path rust/Cargo.toml -p mnote-web sidebar_upload_runtime_routes_local_markdown_assets_to_local_foldernode scripts/task479-local-folder-markdown-resource-lifecycle-smoke.js- 结果:
ok=true - 证据:命令 stdout JSON,覆盖
editor-upload-to-page-resource-dir、filetree-folder-drop-to-target、broken-link-after-real-file-delete、direct-attach-open-without-active-md、editor-delete-one-attachment-preserves-adjacent等检查。
- 结果:
node scripts/task472-side-target-secondary-pane-smoke.js- 结果:
ok=true - 证据:命令 stdout JSON,
assetId=local:asset:README/side-target-resource.md、officeAssetId=local:asset:README/side-target-office.docx。
- 结果:
9.6 Batch 5 执行记录(2026-05-24)
Reasonix read-only audit:
- run id:
reasonix-2026-05-24T15-57-06-903Z-51c4452a - handoff:
/home/lix/.codex/runtime/reasonix-coding-worker/reasonix-2026-05-24T15-57-06-903Z-51c4452a/process-handoff.md - 类型:只读审计,主控已读取 Hindsight recall、
process-handoff.md、process-handoff.json。 - 采纳结论:可采纳;本批是 debug
/treeshell runtime 同构迁出,未改变/api/tree/runtime/reduce合同和 page/filetree/picker 行为。
采纳内容:
- 新增
rust/crates/mnote-web/browser/tree-shell-runtime.js,从tree.rs原 inline runtime 同构迁出。 build_tree_shell_html()保留tree-shell-statebootstrap JSON,改为加载/api/mnote-browser-runtime/tree-shell-runtime.jsmodule script。- 新增
/api/mnote-browser-runtime/tree-shell-runtime.js路由,返回application/javascript; charset=utf-8。 tree.rs测试继续通过include_str!("../../browser/tree-shell-runtime.js")校验 tree shell runtime 的 page/filetree/picker 关键字符串。- 主控复核后额外采纳一个小型结构化 hoist:
buildFileTreeRuntimeEnvironment、reducePageActionWithRuntime、reconcilePageRuntimeResult从 IIFE 内部移到文件顶层,通过runtimeContext显式接收依赖,使 CodeGraph 可以定位这些函数;未改变 runtime API 合同。
已通过验证:
node --check rust/crates/mnote-web/browser/tree-shell-runtime.jscargo fmt --manifest-path rust/Cargo.toml --all --checkcargo check --manifest-path rust/Cargo.toml -p mnote-webcargo test --manifest-path rust/Cargo.toml -p mnote-web tree_shell- 结果:42 个 tree_shell 相关测试通过。
cargo test --manifest-path rust/Cargo.toml -p mnote-web runtime- 结果:57 个 runtime 相关测试通过。
cargo test --manifest-path rust/Cargo.toml -p mnote-web filetree- 结果:28 个 filetree 相关测试通过。
GET /api/mnote-browser-runtime/tree-shell-runtime.js- 结果:
200,content-type=application/javascript; charset=utf-8,浏览器读取内容包含startTreeShellRuntime、tree.ready、hydrateInitialFileTree、reducePageActionWithRuntime、buildFileTreeRuntimeEnvironment、reconcilePageRuntimeResult。
- 结果:
codegraph_search reducePageActionWithRuntime/buildFileTreeRuntimeEnvironment/reconcilePageRuntimeResult- 结果:3 个函数均可定位到
rust/crates/mnote-web/browser/tree-shell-runtime.js。
- 结果:3 个函数均可定位到
- 真实 3000 服务运行态说明:
- 本轮直接访问
/tree?...返回404,说明 debug shell 入口当前不作为公开默认入口暴露;运行态 JS asset 加载由固定 asset route 浏览器验证,debug shell page/filetree/picker HTML 行为由 Rust route 单测覆盖。 - 主壳登录后确认
tree-live-controller.js等已外置 runtime 正常挂载,P4 主壳 filetree runtime 仍待后续切片。
- 本轮直接访问
剩余风险:
type="module"自带作用域,tree-shell-runtime.js已移除外层 IIFE,并只 hoist P3 关键函数;其他 DOM runtime 仍按原局部闭包组织,避免扩大改动。- debug tree shell runtime 与主壳
SIDEBAR_TREE_JS仍是两套 runtime,后续 P4/P5 继续拆主壳 filetree runtime 和 document pane host adapter。
9.7 Batch 6 执行记录(2026-05-25)
Reasonix read-only audit:
- run id:
reasonix-2026-05-24T16-11-49-842Z-94562749 - worktree:
/mnt/Data1T/mnote-worktrees/p4-filetree-audit - handoff:
/home/lix/.codex/runtime/reasonix-coding-worker/reasonix-2026-05-24T16-11-49-842Z-94562749/process-handoff.md - 类型:只读审计,主控已读取 Hindsight recall、
process-handoff.md、工作区 diff 与验证结果。 - 采纳结论:主壳 filetree 第一批应优先迁纯 DOM/字符串读取 helper、path 解析和下载详情构造;DND、paste、菜单 DOM、上传执行链继续留在后续批次。
采纳内容:
- 新增
rust/crates/mnote-web/browser/filetree-runtime.js。 - 新增固定 asset route:
GET /api/mnote-browser-runtime/filetree-runtime.js。 PageLayout在SIDEBAR_TREE_JS前注入type="module"外置脚本。SIDEBAR_TREE_JS中以下 helper 先做 runtime 代理,保留原实现 fallback:fileTreeRowDocumentIdfileTreeRowAssetIddecodeLocalEncodedPathfileTreeRowLocalRelativePathfileTreeRowLocalUploadTargetRelativePathfileTreeRowKindisFileTreeDownloadableAssetRowisFileTreeDownloadableRowfileTreeAssetDownloadDetailfileTreeRowsByRowIdsfileTreeChildCount
- 新增
rust/crates/mnote-web/browser/filetree-context-menu-runtime.js,先承接buildSidebarFileTreeContext与evaluateSidebarFileTreeWhen的 CommandContext / when 表达式求值;菜单 DOM 构建仍留在SIDEBAR_TREE_JS。
采纳边界:
- 本批次不迁移
selectFileTreeRow、openFileTreeContextMenu、pasteSidebarFileTreeClipboard、downloadSelectedFileTreeAssetRows、内部/外部 DND、上传执行链。 fileTreeChildCount虽与 paste sortOrder 有关,但本批仅做同构 runtime 代理并保留 inline fallback,未改变 paste 命令语义。- filetree context menu 本批只迁 CommandContext/when evaluator,不迁 menu item DOM、事件绑定或右键定位逻辑。
已通过验证:
node --check rust/crates/mnote-web/browser/filetree-runtime.jsnode --check rust/crates/mnote-web/browser/filetree-context-menu-runtime.jscargo fmt --manifest-path rust/Cargo.toml --all --checkcargo check --manifest-path rust/Cargo.toml -p mnote-webcargo test --manifest-path rust/Cargo.toml -p mnote-web filetree_runtime -- --test-threads=1- 结果:相关测试通过;本轮另以
sidebar_filetree_runtime_helpers_are_externalized_with_inline_fallback、filetree_runtime_contains_row_accessor_helpers和完整filetree过滤测试复核。
- 结果:相关测试通过;本轮另以
cargo test --manifest-path rust/Cargo.toml -p mnote-web filetree_context_menu -- --test-threads=1- 结果:相关测试通过;本轮另以
filetree_context过滤测试复核。
- 结果:相关测试通过;本轮另以
cargo test --manifest-path rust/Cargo.toml -p mnote-web mnote_browser_runtime_assets_are_explicitly_mounted -- --test-threads=1- 结果:runtime asset route 测试通过。
cargo test --manifest-path rust/Cargo.toml -p mnote-web filetree- 结果:29 个 filetree 相关测试通过。
node scripts/task487-local-folder-tree-live-consumer-smoke.js- 结果:
ok=true,证据文件tmp/task487-local-folder-tree-live-consumer-smoke/result.json。
- 结果:
node scripts/task479-local-folder-markdown-resource-lifecycle-smoke.js- 结果:
ok=true,覆盖 editor upload、filetree folder drop、resource tab missing/error、无 active document 附件 tab、filetree scroll、标题来源、附件删除保留相邻附件;证据包含 stdout JSON 和 smoke 生成截图。
- 结果:
codegraph_search fileTreeRowLocalRelativePath/fileTreeAssetDownloadDetail- 结果:均可定位到
rust/crates/mnote-web/browser/filetree-runtime.js。
- 结果:均可定位到
codegraph_search buildSidebarFileTreeContext/evaluateSidebarFileTreeWhen- 结果:均可定位到
rust/crates/mnote-web/browser/filetree-context-menu-runtime.js。
- 结果:均可定位到
- 真实浏览器轻量验收:
- dev server: 复用 / 启动
MNOTE_WEB_BIND=0.0.0.0:3000 MNOTE_WEB_PUBLIC_BIND=127.0.0.1:3000 cargo run --manifest-path rust/Cargo.toml -p mnote-web --bin mnote-web - 登录后访问
http://127.0.0.1:3000/ document.scripts包含/api/mnote-browser-runtime/filetree-runtime.js与/api/mnote-browser-runtime/filetree-context-menu-runtime.jswindow.__mnoteFileTreeRuntime.fileTreeRowLocalRelativePath和window.__mnoteFileTreeContextMenuRuntime.evaluateSidebarFileTreeWhen均为函数。- 两个 runtime asset 均返回
200,content-type=application/javascript; charset=utf-8。 - 截图证据:
p4-filetree-runtime-mounted.png。
- dev server: 复用 / 启动
9.8 Batch 7 执行记录(2026-05-25)
Reasonix read-only audit:
- run id:
reasonix-2026-05-24T16-25-22-379Z-3e3df753 - worktree:
/mnt/Data1T/mnote-worktrees/p4-filetree-selection-audit - handoff:
/home/lix/.codex/runtime/reasonix-coding-worker/reasonix-2026-05-24T16-25-22-379Z-3e3df753/process-handoff.md - 类型:只读审计,主控已读取 Hindsight recall、
process-handoff.md、工作区 diff 与验证结果。 - 采纳结论:主壳 filetree selection 应独立为
filetree-selection-runtime.js,不并入纯 accessor 的filetree-runtime.js;DND、clipboard、keydown listener 和菜单 DOM 仍留后续批次。
采纳内容:
- 新增
rust/crates/mnote-web/browser/filetree-selection-runtime.js。 - 新增固定 asset route:
GET /api/mnote-browser-runtime/filetree-selection-runtime.js。 PageLayout在SIDEBAR_TREE_JS前注入type="module"外置脚本。SIDEBAR_TREE_JS中以下 helper 先做 runtime 代理,保留原实现 fallback:visibleFileTreeRowssyncSidebarFileTreeSelectionselectSidebarFileTreeRowselectedSidebarFileTreeRowIdsForDragselectedSidebarFileTreeRows
采纳边界:
- 本批次不迁移 click / contextmenu / keydown / dragstart / dragover / drop / dragend 事件监听器。
- 本批次不迁移
pasteSidebarFileTreeClipboard、downloadSelectedFileTreeAssetRows、ensureFileTreeWritableTarget、deleteSelectedSidebarFileTreeRows。 - selection runtime 只承接选择状态同步、shift range、ctrl/meta toggle 和拖拽行 id 读取;文件树命令语义仍由主壳和 Rust API 合同决定。
已通过验证:
node --check rust/crates/mnote-web/browser/filetree-selection-runtime.jscargo fmt --manifest-path rust/Cargo.toml --all --checkcargo check --manifest-path rust/Cargo.toml -p mnote-webcargo test --manifest-path rust/Cargo.toml -p mnote-web filetree_selection_runtime -- --test-threads=1- 结果:2 个 selection runtime 相关测试通过。
cargo test --manifest-path rust/Cargo.toml -p mnote-web mnote_browser_runtime_assets_are_explicitly_mounted -- --test-threads=1- 结果:runtime asset route 测试通过。
- 真实浏览器轻量验收:
- dev server:
MNOTE_WEB_BIND=0.0.0.0:3000 MNOTE_WEB_PUBLIC_BIND=127.0.0.1:3000 cargo run --manifest-path rust/Cargo.toml -p mnote-web --bin mnote-web - 临时 local folder 入口
sourceKind=local_folder&treeView=filetree。 /api/mnote-browser-runtime/filetree-selection-runtime.js返回200,content-type=application/javascript; charset=utf-8。window.__mnoteFileTreeSelectionRuntime.selectSidebarFileTreeRow为函数。- 对两个可见 filetree row 依次触发 click / ctrl-click 后,
data-selected="true"保留两行,tree.filetree.selection.changed派发 2 次,最后事件包含两个selectedRowIds。
- dev server:
10. 分阶段执行清单
归档说明(2026-05-25):本节保留原始分阶段路线,但不再作为当前 done/ 状态的未完成 checklist。已经满足归档条件的范围以第 13 节 Done Gate 为准;仍未实施或未专项验证的条目改为普通状态项,后续需要另起设计或 bug/checklist 承接,不能在本 done 文档中继续以未勾选 checklist 表达。
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 合同决定。- 2026-05-24 /
reasonix-2026-05-24T15-17-16-430Z-18054487只读审计:leptos-tiptap只应发出上传 intent;本地 Markdown 路径、rootUri、/api/local-folder/files/openURL、markdownRelativePath与真实上传落盘都归本设计的 shell host runtime。 - 2026-05-24 / Batch 4 已迁:
resolveEditorUploadContext、editorRootFromUploadOptions、openEditorUploadFilePicker,并保持调用uploadFilesWithResolvedTarget,未同时迁上传主流程。 - 暂不迁:
uploadFileToMediaAsset、uploadFilesWithResolvedTarget、insertUploadedAssetIntoEditor。这些函数同时涉及 API POST、file tree refresh、Tiptap chain 插入、OnlyOffice/resource tab,需在后续批次继续拆边界。
- 2026-05-24 /
- 已保留/需按后续切片复跑:现有 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 保持通过。
- 2026-05-24:
cargo test --manifest-path rust/Cargo.toml -p mnote-web tree_shell通过 42 个 tree shell 相关测试;运行态/tree需 debug route + auth/Convex 或 local workspace 登录态,不作为本批硬门槛。
- 2026-05-24:
- 验证 CodeGraph 能定位
reducePageActionWithRuntime、buildFileTreeRuntimeEnvironment、reconcilePageRuntimeResult等符号。
P4:抽出主壳 filetree runtime
- 部分完成/后续:从
SIDEBAR_TREE_JS中拆出文件树选择、keyboard、clipboard、DND、context menu。- 2026-05-25 / Batch 6 已完成 context menu / filetree row helper 的首个外置切片。
- 2026-05-25 / Batch 7 已完成 filetree selection helper 的首个外置切片。
- 暂不迁:keyboard、clipboard、DND DOM 绑定、菜单 DOM、上传执行链。
- 先拆纯函数和 reducer bridge,再拆 DOM 绑定。
- 2026-05-25 / Batch 6 已迁:filetree 行 accessor、local relative path、download detail、row lookup、child count wrapper,以及 context menu CommandContext / when evaluator。
- 2026-05-25 / Batch 7 已迁:visible rows、selection sync、shift range、ctrl/meta toggle、drag row id helper、selected rows helper。
- 暂不迁:keyboard listener、clipboard action、DND listener、菜单 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 提取脚本;本批未新增提取脚本。
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。