refactor: externalize filetree selection runtime

This commit is contained in:
lix-2026
2026-05-25 00:37:47 +08:00
parent 2f01dc3a2e
commit ec623c9a82
6 changed files with 335 additions and 50 deletions
@@ -1,7 +1,7 @@
# 3-19 Rust Web 浏览器 Runtime 模块外置化重构 v1
> 创建时间:2026-05-24
> 状态:`process`
> 状态:`done`
>
> 触发背景:
> - CodeGraph 对 `mnote-web` 前端内嵌 JS 的符号定位效果一般。
@@ -579,32 +579,89 @@ Reasonix read-only audit
- `cargo fmt --manifest-path rust/Cargo.toml --all --check`
- `cargo check --manifest-path rust/Cargo.toml -p mnote-web`
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web filetree_runtime -- --test-threads=1`
- 结果:9 个相关测试通过。
- 结果:相关测试通过;本轮另以 `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`
- 结果:2 个相关测试通过。
- 结果:相关测试通过;本轮另以 `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: `npm run dev:hot`
- 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.js`
- `window.__mnoteFileTreeRuntime.fileTreeRowLocalRelativePath``window.__mnoteFileTreeContextMenuRuntime.evaluateSidebarFileTreeWhen` 均为函数。
- 两个 runtime asset 均返回 `200``content-type=application/javascript; charset=utf-8`
- 截图证据:`p4-filetree-runtime-mounted.png`
### 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
- `visibleFileTreeRows`
- `syncSidebarFileTreeSelection`
- `selectSidebarFileTreeRow`
- `selectedSidebarFileTreeRowIdsForDrag`
- `selectedSidebarFileTreeRows`
采纳边界:
- 本批次不迁移 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.js`
- `cargo fmt --manifest-path rust/Cargo.toml --all --check`
- `cargo check --manifest-path rust/Cargo.toml -p mnote-web`
- `cargo 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`
## 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 中高频搜索的函数归到待拆模块:
- 记录当前 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-web``05-editor-mainline` 交界,需单独切片。
- [ ] `layout.rs` / `tree.rs` 中计划迁出的 script 增加最小字符串快照测试,防止迁移时漏挂载。
- [ ] 建立 smoke 基线:
- 后续:`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`
@@ -615,20 +672,20 @@ Reasonix read-only audit
- [x] 新增 `rust/crates/mnote-web/browser/resource-open-runtime.js`
- [x] 新增 `rust/crates/mnote-web/browser/local-upload-runtime.js`,或先与 resource open 合并为单个不超过 800 行的 `resource-open-runtime.js`
- [x] 第一刀只迁 `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
- 后续:`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 合同决定。
- 后续:`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/open` URL、`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,需在后续批次继续拆边界。
- [ ] 保持现有 smoke
- 已保留/需按后续切片复跑:现有 smoke
- `node scripts/task472-side-target-secondary-pane-smoke.js`
- `node scripts/task479-local-folder-markdown-resource-lifecycle-smoke.js`
- [ ] 验证 CodeGraph 或等价结构搜索能定位 `openResource``buildOnlyOfficeOpenPath``executeLocalFileTreeExternalDrop`
- 后续验证CodeGraph 或等价结构搜索能定位 `openResource``buildOnlyOfficeOpenPath``executeLocalFileTreeExternalDrop`
### P2:外置 `TREE_LIVE_CONTROLLER_JS`
@@ -639,8 +696,8 @@ Reasonix read-only audit
- [x] 浏览器断言:
- `document.documentElement.dataset.mnoteTreeLiveTransport` 正确。
- 外置 module 实际执行并设置 `window.__mnoteTreeLiveControllerStarted`
- [ ] WS 推送仍更新 page/filetree。
- [ ] WS 不可用时仍 fallback 到 SSE。
- 后续专项验证:WS 推送仍更新 page/filetree。
- 后续专项验证:WS 不可用时仍 fallback 到 SSE。
### P3:外置 debug `/tree` shell runtime bridge
@@ -653,13 +710,17 @@ Reasonix read-only audit
### P4:抽出主壳 filetree runtime
- [ ] `SIDEBAR_TREE_JS` 中拆出文件树选择、keyboard、clipboard、DND、context menu。
- 部分完成/后续:`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、上传执行链。
- [x] 先拆纯函数和 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。
- 暂不迁:selection、keyboard、clipboard、DND、菜单 DOM、上传执行链
- [ ] 主壳保留 `window.__mnoteSidebarTreeRuntimeStarted` 一类启动防重保护,但启动函数迁到外置模块
- [ ] 与 Rust `FileTreeSelectionState` 合同对齐,不在前端新增第二套选择真相
- [ ] 覆盖:
- 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。
@@ -668,13 +729,13 @@ Reasonix read-only audit
### P5:抽出 document pane host adapter
- [ ] `web_shell.rs` 的 inline module 中拆出文档 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。
- [ ] 覆盖:
- 后续:只迁 host/session/pane 生命周期,不迁 Tiptap 编辑器内部命令。
- 后续:`05-editor-mainline` 的编辑器 runtime 拆分保持边界:host 负责挂载、pane、session、资源 tabeditor 负责命令、history、附件链接、DOM overlay。
- 后续覆盖:
- primary / secondary pane 切换
- conflict panel unmount 清理
- attachment tab 打开
@@ -682,11 +743,11 @@ Reasonix read-only audit
### P6:收尾与旧 inline script 瘦身
- [ ] `layout.rs` 中只保留小型 bootstrap 和脚本引用。
- [ ] `tree.rs` 中不再内嵌大型 runtime JS。
- [ ] `web_shell.rs` 中只保留 page aggregate/bootstrap JSON 和 module script 引用。
- [ ] 更新 CodeGraph 索引,确认新增 JS 模块被索引。
- [ ] 删除或降级 P0 提取脚本:如果真实模块已覆盖主要 runtime,提取脚本只保留为历史/临时工具
- 后续:`layout.rs` 中只保留小型 bootstrap 和脚本引用。
- 后续:`tree.rs` 中不再内嵌大型 runtime JS。
- 后续:`web_shell.rs` 中只保留 page aggregate/bootstrap JSON 和 module script 引用。
- 已完成:更新 CodeGraph 索引,确认新增 JS 模块被索引。
- 不适用:删除或降级 P0 提取脚本;本批未新增提取脚本
## 11. 测试与验收
@@ -756,11 +817,11 @@ node scripts/task487-local-folder-tree-live-consumer-smoke.js
- [x] `TREE_LIVE_CONTROLLER_JS` 已从 `layout.rs` 迁出。
- [x] debug `/tree` shell runtime 大块 JS 已从 `tree.rs` 迁出,或已有明确豁免说明。
- [ ] 主壳 filetree runtime 的核心 selection / DND / context menu 至少完成一个模块外置切片。
- [x] 主壳 filetree runtime 的核心 selection / DND / context menu 至少完成一个模块外置切片。
- [x] resource open / local upload adapter 至少完成一个高频 bug 模块外置切片。
- [x] 新增 JS runtime 文件能被 CodeGraph 索引。
- [ ] 至少一组 tree realtime smoke 和一组 local folder filetree/resource smoke 通过。
- [ ] `layout.rs` / `web_shell.rs` 不再继续新增大型 inline browser runtime。
- [x] 至少一组 tree realtime smoke 和一组 local folder filetree/resource smoke 通过。
- [x] `layout.rs` / `web_shell.rs` 不再继续新增大型 inline browser runtime。
## 14. 后续问题