Files
mnote/design/04-tree-domain/done/4-20-vscode-explorer-file-tree-alignment-v1.md
T

481 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 4-20 [process] VSCode Explorer 文件树对标设计 v1
> 更新时间:2026-05-06
>
> 关联:
> - `/mnt/Data1T/mnote/tmp/image copy 47.png`
> - `/mnt/Data1T/mnote/ARCHITECTURE.md`
> - `/mnt/Data1T/mnote/design/01-05-current-priority-overview.md`
> - `/mnt/Data1T/mnote/design/04-tree-domain/done/4-2-sidebar-pagetree-filetree-product-interaction-contract-v1.md`
> - `/mnt/Data1T/mnote/design/04-tree-domain/done/4-4-tree-projection-protocol-contract-v1.md`
> - `/mnt/Data1T/mnote/design/04-tree-domain/done/4-6-tree-command-protocol-cutover-stage2-v1.md`
> - `/mnt/Data1T/mnote/design/04-tree-domain/done/4-7-tree-shell-ui-state-boundary-v1.md`
> - `/mnt/Data1T/mnote/design/04-tree-domain/done/4-14-task-005-file-tree-final-renderer-capability-checklist-v1.md`
> - `/mnt/Data1T/mnote/design/05-editor-mainline/done/5-5-1-page-aggregate-contract-v1.md`
> - `/mnt/Data1T/mnote/design/90-reference/90-1-filetree.md`
## 1. 目的
这份文档只处理 `filesystem / Explorer` 文件树下一阶段怎么对标 VSCode Explorer。
它不重新定义树域协议。已有结论继续成立:
- `tree-first graph kernel` 是长期对象真相层。
- `Convex` 继续作为本地自托管存储、实时、文件协作底座。
- `mnote-web` 是 Rust Web 主执行面,负责 projection、command、transport 与 realtime stream。
- 前端和 tree shell 只消费稳定 projection,并持有本地交互态。
本设计补的是此前文档没有写细的 Explorer 级显示和操作合同:行模型、选择/焦点、打开、右键、键盘、拖放、剪贴板、新建、重命名、删除、上传、资源移动和验收清单。
## 2. 当前截图判断
`/mnt/Data1T/mnote/tmp/image copy 47.png` 显示当前文件树已经有 Explorer 入口,但仍不像 VSCode Explorer
- `我的页面` 下直接混排页面节点和 `index.md`,但没有清晰的 workspace root / resource root 语义。
- 页面行使用小菱形 / 折叠箭头,文件行使用蓝色文档图标,信息密度接近,但行列网格不稳定。
- 当前选中态落在 `index.md` 文件行,和右侧文档页 `新页面` 的 active / focus / selection 关系不够清楚。
- `Explorer` 标题像 tab 文案,不像 VSCode Explorer 的 section header + toolbar。
- hover 操作只看到 `+`,缺少新建文件、新建文件夹、刷新、全部折叠、更多等容器级动作。
- 长列表中大量重复 `新页面/index.md`,缺少资源装饰、路径 disambiguation、命名冲突处理和 reveal 规则。
结论:当前不是“样式再修一点”的问题,而是 Explorer 行为合同还没有写到足够细。
## 3. 唯一真源
### 3.1 结构显示真源
文件树显示的唯一真源固定为:
`kernel.project_view(file_tree)` 产出的 `KernelProjectionResult`
当前代码证据:
- 首屏 query 会从 Convex `sidebar.datasetList` 取数据,再通过 `resolveKernelFileTreeProjection` 调 Rust `kernel.project_view(file_tree)` 附加 `kernel_file_tree_projection`
- 实时链路 `/api/mnote-web/stream` snapshot 也会附加同一份 file tree projection。
- Rust 侧生成点是 `bridge-runtime``build_file_tree_projection_result`
- projection item 的核心字段已经在 `core-protocol/src/kernel.rs` 与前端 `tree-protocol.ts` 固定。
因此,Explorer 不能在 UI 层重新推导层级、排序、资源类型、child count、capabilities 或合法性。UI 只能把 projection item materialize 成 row。
### 3.2 持久化操作真源
持久化动作的唯一正式命令面固定为:
- 页面:`tree.node.*``tree.subtree.*`
- 资源:`tree.resource.*`
- 文件树预检:`tree.filetree.*.preflight`
当前已经成立:
- 页面新建、重命名、移动、归档、恢复、永久删除、嵌入、复制子树已经接到 `tree.*`
- 文件树 `drop/delete/paste/upload-target` 已有 Rust preflight。
- 资源 `copy/move/upload` 已有 `tree.resource.copy/move/upload`
当前仍未完成:
- 资源 `rename/delete/restore` 仍经过 legacy `/api/media/batch` 直接 patch / restore。
- `cut` 还没有成为剪贴板动作。
- 资源“移动到...”仍有 `prompt("输入目标页面 ID")` 的临时 UX。
### 3.3 本地交互态
这些状态只能属于 tree shell / runtime 本地态,不得写进 projection
- `expanded`
- `selected`
- `focused`
- `hovered`
- `editingName`
- `clipboard`
- `dragging`
- `dropTarget`
- `contextMenuOpen`
- `keyboardAnchor`
- `revealPending`
`expandedByDefault` 只是 projection 给初始展开的建议,不是展开态真相。
## 4. VSCode Explorer 对标边界
### 4.1 必须对齐
文件树必须对齐 VSCode Explorer 的资源管理器模型:
- 一行就是一个资源命中目标。
- `twistie` 只负责展开/折叠,打开资源是另一种动作。
- 图标来自 `rowKind/resourceMeta/iconHint/assetKind`,不是装饰。
- `selection``focus``hover``active` 分离。
- 右键点击已选区内资源时保留选区,右键点击未选资源时切换到该资源。
- 键盘能完整操作可见行。
- 内部拖拽、外部文件拖入、剪贴板和 context menu 都基于同一 selection/focus 状态。
- 所有危险或跨资源类型动作先走 preflight plan,再落正式 command。
### 4.2 不能照搬
文件树不能照搬 VSCode 的真实文件系统语义:
- mnote 的文件树是页面、`index.md`、附件、mindmap 文件夹、导图子资源、table/book/pdf 等复合资源投影,不是磁盘目录树。
- `page -> index.md -> asset-folder -> asset` 这类多语义链路不能用 VSCode `compact folders` 强行压成一行。
- “在终端打开”“Reveal in Finder/Explorer”“文件比较”等 OS 动作不进入默认合同,除非资源模型已有明确承载。
- VSCode 的单击预览 / 双击固定标签页节奏不能直接照搬。mnote 的打开必须服从当前文档页、Page Aggregate、附件打开链路和权限边界。
- Explorer 是 sidebar 中的创新视图,不是整个产品的唯一主导航。`section` 页面树仍保留 Wolai / Notion 方向。
## 5. 显示合同
### 5.1 Row Grid
每个 file tree row 固定为以下视觉列:
| 列 | 说明 |
| --- | --- |
| indent | 只由 `depth` 决定,步长固定 |
| twistie | 仅 `expandable=true` 时可见并可点击 |
| icon | 由 `iconHint/assetKind/resourceKind` 决定 |
| label | 主名称,单行截断 |
| decoration | 条件性 badge、状态色、资源提示 |
| actions | hover / focus 时出现的轻量动作 |
不得让 label 长度、hover 按钮或选中态改变行高和缩进。
### 5.2 Row Kind
当前稳定行类型继续保留:
- `doc`
- `index`
- `asset-folder`
- `asset`
后续如果需要真实目录或资源组,必须先扩展 Rust projection 的 `rowKind/resourceKind`,不能在 UI 层把某类 asset 临时伪装成目录。
### 5.3 Root
Explorer 顶部应有明确 root
- 单工作区:显示 workspace rootroot 下再进入页面/资源树。
- 多工作区:允许多个 workspace root。
- root 行是否可折叠由 projection capability 决定。
当前截图中 `我的页面 -> Explorer` 的组合不够清晰,后续应改为:
- section header`EXPLORER`
- root row:当前 workspace 名称
- toolbar:新建页面 / 新建资源 / 刷新 / 全部折叠 / 更多
### 5.4 Icon 与 Decoration
图标语义至少覆盖:
- workspace / folder / page / index
- asset
- image / video / audio
- pdf / book
- table
- mindmap
- unknown
Decoration 只表达轻量状态,例如:
- stale / indexing / uploading
- read-only / shared / public
- conflict / invalid target
- cut clipboard visual
Decoration 不得变成第二行副标题。
### 5.5 Active、Selection、Focus
三者必须分离:
- `active`:右侧主区域正在打开的 document / asset。
- `selection`:Explorer 当前选中集合,支持多选。
- `focus`:键盘焦点所在 row。
截图中 `index.md` 选中与右侧 `新页面` active 的关系不清晰。目标规则:
- 打开文档时,`doc``index` 可共享 active 文档语义,但只能有一个 focused row。
- 如果 active document 映射到 `index.md`,应显式定义 active 高亮落在 `index` 还是 `doc`。建议:active 落 `index.md`doc 行只作为目录/页面容器。
- 多选时 active 不覆盖 selectionselection 背景优先。
## 6. 操作合同
### 6.1 打开
P0
- `Enter` 打开 focused row。
- 双击 row 打开资源。
- 单击只负责 selection/focus,不自动打开重资源。
- `index.md` 打开对应文档页。
- asset 打开对应资源 viewer / editor。
P1
- 支持“Reveal active resource in Explorer”。
- 页面切换后自动展开祖先链并定位 active row。
- 搜索过滤后保留 active/reveal 的路径上下文。
说明:当前代码已明确与 VSCode 单击打开不同,这是合理差异,但必须写成正式合同,避免后续反复摇摆。
### 6.2 展开 / 折叠
P0
- 点击 twistie 只展开/折叠,不打开。
- `ArrowRight`:可展开行未展开则展开;已展开或叶子则可进入打开动作,具体由 runtime 定义。
- `ArrowLeft`:已展开则折叠;未展开则焦点移动到父节点。
- `Home/End` 跳到第一/最后可见行。
P1
- `Collapse All` 容器动作。
- 外部拖拽悬停到可展开目标一段时间后自动展开。
### 6.3 选择与右键
P0
- 单击:单选并设置 anchor/focus。
- `Ctrl/Cmd + Click`:切换选中。
- `Shift + Click`:从 anchor 到目标做范围选。
- 右键已选区内 row:保留选区并打开菜单。
- 右键未选 row:切换 selection 到该 row,再打开菜单。
- 空白区点击:清空 selection/focus。
P1
- `Ctrl/Cmd + A` 选中当前 Explorer 可见资源。
- 过滤态下范围选只覆盖可见 row。
### 6.4 键盘
P0 最小按键:
| 按键 | 动作 |
| --- | --- |
| `ArrowUp/ArrowDown` | 上下移动 focus |
| `ArrowLeft/ArrowRight` | 折叠 / 展开 / 父子导航 |
| `Home/End` | 首尾行 |
| `Enter` | 打开 focused row |
| `F2` | 重命名 |
| `Delete/Backspace` | 删除 / 归档 |
| `Ctrl/Cmd+C` | copy |
| `Ctrl/Cmd+X` | cut |
| `Ctrl/Cmd+V` | paste |
| `Esc` | 取消 inline rename / 关闭菜单 / 清理 drag feedback |
当前缺口:已有 copy/paste,但缺 `cut`;键盘能力需要以 runtime contract 覆盖 file tree,而不是散在 `sidebar.tsx`
### 6.5 新建
P0
- `New Page`:映射 `tree.node.create`,在目标 doc/root 下创建页面,并进入 inline rename。
- `New File`:只允许创建 mnote 资源模型内真实存在的资源类型;如果当前没有通用文件创建命令,入口应隐藏或置灰。
- `New Folder`:当前没有真实目录语义,不提供默认入口;mindmap asset-folder 不是通用文件夹。
P1
- 支持上传文件作为 `New File` 的可用替代路径,走 `tree.filetree.upload-target.preflight -> tree.resource.upload`
- 如果未来引入通用资源目录,先扩展 kernel projection / command,再开启 `New Folder`
### 6.6 重命名
P0
- `F2` 和 context menu `Rename` 进入 inline rename,不再使用 `prompt`
- doc / index 重命名最终落 `tree.node.rename` 或 Page Aggregate title command 的正式命名面。
- asset 重命名必须从 legacy `/api/media/batch` 收口到 `tree.resource.rename` 或等价正式命令。
- `Enter` 提交,`Esc` 取消。
- 空名、重名、非法字符、只读资源必须由 preflight / command 返回可解释错误。
P1
- 文件型 asset 默认只选中文件名 stem,不误选扩展名。
- rename 期间保留 row 高度和缩进,不触发布局跳动。
### 6.7 删除 / 恢复 / 永久删除
P0
- doc 删除走 `tree.node.archive`
- doc 恢复走 `tree.node.restore`
- doc 永久删除走 `tree.node.purge`,必须是更强确认路径。
- asset 删除 / 恢复必须补正式 `tree.resource.archive/restore` 或等价命令,不能长期停留在 legacy `/api/media/batch`
- 多选删除先走 `tree.filetree.delete.preflight`,展示将影响的 doc/asset 集合。
P1
- 支持删除后 undo / toast action,命令结果仍以 kernel event 和 projection resync 为准。
### 6.8 剪贴板
P0
- `copy`:复制选中 doc subtree / asset resource。
- `cut`:进入 cut clipboard 状态,源 row 降噪显示;paste 成功后清空 cut 状态。
- `paste`:目标由 focused row / context target / active doc 推导,必须先走 `tree.filetree.paste.preflight`
- 多选 paste 保持原有可见顺序,并过滤父子重复选择。
P1
- 系统剪贴板只保存安全 payload,不暴露敏感内部 token。
- 跨窗口 paste 需要校验 workspace 和权限。
### 6.9 拖放
P0
- 内部拖放支持 move/copy,修饰键控制 copy。
- 外部文件拖入支持 upload target preflight。
- 非法目标即时反馈,不发持久化命令。
- doc 拖到自身或后代节点必须拒绝。
- asset-folder / asset 的可投放能力由 `capabilities` 和 preflight 决定。
P1
- 拖到可展开目标 hover 延迟后自动展开。
- 显示 before / inside / after 三种 drop indicator,而不是只有整行背景。
- 多选拖放过滤已被父节点包含的子节点。
### 6.10 Context Menu
菜单按 `rowKind/resourceKind/selection` 裁剪:
| 目标 | P0 动作 |
| --- | --- |
| root | 新建页面、上传文件、刷新、全部折叠 |
| doc | 打开、新建子页面、重命名、复制、剪切、粘贴、移动到、归档、复制链接 |
| index | 打开、复制、剪切、粘贴、Reveal 页面 |
| asset-folder | 打开、复制、剪切、粘贴、重命名、删除、移动到 |
| asset | 打开、复制、剪切、重命名、删除、下载、移动到 |
| 多选 | 打开禁用、复制、剪切、删除、移动到 |
当前缺口:
- mindmap rename 提示不支持。
- table move/download 提示不支持。
- asset 移动仍靠输入页面 ID。
这些必须进入 P0/P1 差异清单,不得被样式修复掩盖。
## 7. 资源排序与混排规则
P0 固定当前最小规则:
1. workspace root
2. doc row
3. doc 的 `index.md`
4. doc 的 direct assets / asset folders
5. child documents
排序由 Rust projection 的 `position` 与 preorder 决定。UI 只渲染。
需要新增到 projection meta 的候选规则:
- `ordering: "kernel_file_tree_preorder"`
- `folderPolicy: "mnote_composite_resource"`
- `indexPlacement: "first_child"`
- `assetPlacement: "after_index_before_child_documents"`
这样可以避免 UI 通过 hardcode 解释为什么 `index.md` 总是出现在页面下第一行。
## 8. Search / Filter / Reveal
P0
- Explorer filter 只改变可见 row,不改变 projection truth。
- filter 命中子节点时必须补全祖先链。
- active row 在 filter 后不可见时,保留 active 状态但不强制显示。
- `Reveal in Explorer` 必须展开祖先链、滚动到目标 row、设置 focus,不破坏 selection,除非调用者明确要求 select。
P1
- 支持 `maxResults` 下的 ancestor completion。
- 支持 indexing visibility,展示 stale / refreshing 状态。
当前 `KernelFileTreeProjection.meta.search.indexingVisibility` 已有雏形,后续应把它用于 UI 状态,而不是在 Explorer 自行发明刷新状态。
## 9. 分阶段任务
### P0:让当前 Explorer 行为不再含糊
- 固定 active / selection / focus 的视觉优先级。
- 补齐 file tree keyboard runtime`Arrow/Home/End/Enter/F2/Delete/Cut/Copy/Paste/Esc`
-`cut` clipboard 状态和 cut row decoration。
- 把 doc / index / asset / asset-folder 的 context menu 动作裁剪成合同。
- 移除资源移动的 `prompt("输入目标页面 ID")`,改用 tree picker / move target picker。
- asset rename/delete/restore 制定 `tree.resource.*` 正式命令或等价 bridge 命名,并保留 legacy alias。
- 写 smoke 固化当前截图中重复页面 + `index.md` 场景的 active/selection/reveal 行为。
### P1:让资源管理器达到 VSCode 常用操作预期
- inline rename 全面替代 prompt。
- root toolbar 对齐 Explorer:新建页面、上传文件、刷新、全部折叠、更多。
- 拖放支持 before / inside / after indicator 与 hover auto expand。
- 上传 target preflight 失败时在 drop target 上显示明确反馈。
- 多选 delete / move / copy 的 preflight plan 显示影响集合。
- mindmap/table 的 rename/move/download 能力从“不支持”变为可解释菜单状态或正式实现。
### P2:高级 Explorer 能力
- compact folders 只用于未来真实目录链,不用于页面/附件复合链。
- git-like / sync-like decorations,如果未来有对应数据源再加入。
- 多 workspace root。
- 大树虚拟化和增量加载。
- 跨窗口剪贴板。
## 10. 验收
### 10.1 单元 / 合同测试
必须覆盖:
- projection item 到 row model 的稳定映射。
- active / selection / focus 分离。
- `Ctrl/Cmd` 多选、`Shift` 范围选、右键保留选区。
- 可见 row 变化后的 selection normalize。
- `cut/copy/paste` payload encode/decode 与目标推导。
- drop preflight payload 构造。
- illegal drop:自身、后代、跨 workspace、只读资源。
- inline rename submit/cancel。
### 10.2 Rust 测试
必须覆盖:
- `kernel.project_view(file_tree)` 输出 `doc/index/asset-folder/asset`
- `resourceMeta``capabilities``iconHint` 稳定。
- `tree.filetree.*.preflight` 对多选和非法目标返回 plan。
- 新增 `tree.resource.rename/archive/restore` 或等价命令后,覆盖 command event 与 projection resync。
### 10.3 浏览器 smoke
必须覆盖:
- 当前截图同类数据:多个同名 `新页面`,每个页面下有 `index.md`
- 点击 `index.md` 后右侧打开对应页面,Explorer active/focus/selection 可解释。
- 右键已选区保留多选。
- `F2` inline rename`Esc` 取消,`Enter` 提交。
- `Ctrl/Cmd+X` 后 cut decoration 出现,paste 成功后消失。
- 外部文件拖入 doc / index / asset-folder 的目标差异。
- 资源移动不再弹 `prompt`
## 11. 非目标
- 不把文件树改成真实磁盘文件系统。
- 不把 `Explorer` 替代 `section` 页面树作为唯一导航。
- 不在 UI 层重新拼排序、层级、资源类型或权限。
- 不为了像 VSCode 而绕过 Page Aggregate、tree projection 或 `tree.*` 命令。
- 不把 `90-1-filetree.md` 的 Rust 生态调研当作实现模板;它只作为参考资料。
## 12. 完成判定
这份设计进入 done 的条件:
- P0 项全部有代码和 smoke 覆盖。
- file tree 的 active / selection / focus / keyboard / context menu / clipboard / DnD 有稳定 runtime contract。
- 资源 rename/delete/restore 不再只依赖 legacy `/api/media/batch` 主路径。
- 当前截图暴露的 Explorer 行为歧义能通过测试说明和验证。
- 新增能力没有制造第二套树真相。