- 为 Rust Web 主入口补齐本地文件夹/云空间切换、最近目录与路径回填体验\n- 对齐 local markdown media 与 inline marks 的 Rust shell / TipTap converter 语义\n- 让 documents/page 优先消费 Rust page aggregate snapshot,并保留 TS fallback\n- 补强 tree live、local markdown 与主入口 smoke,并同步设计稿状态
18 KiB
4-20 [done] 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调 Rustkernel.project_view(file_tree)附加kernel_file_tree_projection。 - 实时链路
/api/mnote-web/streamsnapshot 也会附加同一份 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:
expandedselectedfocusedhoverededitingNameclipboarddraggingdropTargetcontextMenuOpenkeyboardAnchorrevealPending
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这类多语义链路不能用 VSCodecompact 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
当前稳定行类型继续保留:
docindexasset-folderasset
后续如果需要真实目录或资源组,必须先扩展 Rust projection 的 rowKind/resourceKind,不能在 UI 层把某类 asset 临时伪装成目录。
5.3 Root
Explorer 顶部应有明确 root:
- 单工作区:显示 workspace root,root 下再进入页面/资源树。
- 多工作区:允许多个 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 不覆盖 selection,selection 背景优先。
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 menuRename进入 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 固定当前最小规则:
- workspace root
- doc row
- doc 的
index.md - doc 的 direct assets / asset folders
- 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。 - 补
cutclipboard 状态和 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/pastepayload 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 可解释。 - 右键已选区保留多选。
F2inline 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 行为歧义能通过测试说明和验证。
- 新增能力没有制造第二套树真相。