Files
mnote/design/old/04-tree-domain/done/4-21-local-folder-convex-unified-tree-source-v1.md
T
Agent Board b798f628ee chore: land tree view-state, vault, Pi module split, and repo hygiene
Persist PageTree expand state via control-plane view-state and align
chevron/DOM with restored expansion; keep Sidex-style shallow page-tree
scan and drop the unused recursive scanner that only added cargo noise.

Add password vault workbench routes/runtime/skill/CLI, split page_ai_pi
into a module package, and retire Hermes/ACP/OpenHub recycle + root
harness evidence from the index while gitignoring recycle and local
diag dumps.

Archive superseded design/bugs docs under old/, point architecture at
ARCHITECTURE.md, and refresh smokes for Pi S1–S7, vault, and editor
regressions so the working tree can stay clean.
2026-07-21 05:13:05 +08:00

589 lines
17 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.
# [recycle] 4-21 [done] Local Folder / Convex 统一树源架构 v1
> 更新时间:2026-05-06
>
> 关联:
> - `/mnt/Data1T/mnote/design/04-tree-domain/done/4-20-vscode-explorer-file-tree-alignment-v1.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/05-editor-mainline/done/5-5-1-page-aggregate-contract-v1.md`
> - `/mnt/Data1T/mnote/design/05-editor-mainline/reference-code/vscode`
>
> 状态说明:当前 `3000` 主入口、本地文件夹打开、统一 `file_tree / page_tree / page_aggregate` 消费链已进入已完成基线;剩余收口继续由 `3-13`、`3-3` 等后续稿跟踪,本稿不再承载新的 process 项。
## 1. 结论
支持引入 `Local Folder Workspace`,但不能因此维护两套文件树操作。
新的长期结构应固定为:
```text
LocalFolderSource -> Tree Kernel -> file_tree / page_tree / page_aggregate -> UI
ConvexWorkspaceSource -> Tree Kernel -> file_tree / page_tree / page_aggregate -> UI
```
也就是说:
- `Convex` 不再是所有 workspace 的必经中介。
- `Local Folder` 不经过 Convex,直接由 Rust source adapter 读取和写入本地文件系统。
- `Convex` 仅作为显式 cloud / compat workspace 的 source adapter 或 sync replica;同步、协作、权限、分享默认控制面已由 Rust SQLite control-plane 承接。
- 两者共用同一套 projection、command、preflight、runtime state 和 UI。
- 差异只允许出现在 `WorkspaceSource` 的 capability、preflight plan 和 executor 中。
核心原则:
> **维护一套树操作语义,不维护两套文件树产品。**
## 2. 为什么需要这个方案
当前 mnote 的 Explorer 视图仍然基于 Convex 虚拟用户空间,这让 VSCode Explorer 对标天然别扭:
- 没有真实文件夹 root。
- `New File / New Folder / Rename / Move / Cut / Paste / Drop` 都需要模拟文件系统语义。
- `index.md`、页面、附件、mindmap asset-folder 混成复合投影后,无法完全照搬 VSCode 文件夹操作。
如果允许用户打开一个本地文件夹,就能获得真实文件系统语义:
- 文件夹就是文件夹。
- `.md` 文件就是页面内容的本体。
- 普通文件就是资源。
- 拖入、拖动、剪切、复制、粘贴、重命名、删除都可以映射到文件系统操作。
- Git、外部编辑器、脚本、ripgrep 等外部工具自然成立。
但这不表示 Convex 失去意义。Convex 的定位应从“所有树的底座”调整为:
> **云工作区 source:负责多端、协作、权限、分享、发布、异步处理和远程资源。**
## 3. 术语
### 3.1 `WorkspaceSource`
`WorkspaceSource` 是树域的存储源适配层。
最低接口:
- `sourceKind`
- `rootUri`
- `workspaceId`
- `capabilities`
- `loadSnapshot`
- `watch`
- `preflightCommand`
- `executeCommand`
- `resolvePageAggregate`
当前建议先支持两类:
- `local_folder`
- `convex_workspace`
### 3.2 `SpaceRoot`
`SpaceRoot` 是用户在 UI 里选择的工作区入口。
示例:
- `file:///home/lix/Documents/mnote-vault`
- `convex://workspace/ws_123`
`SpaceRoot` 可以出现在同一个 Explorer 列表里,但每个 root 只能由一个 source adapter 持有。
### 3.3 `Source Capability`
capability 描述当前 source 对某项操作的真实支持程度。
示例:
- `create-file`
- `create-folder`
- `rename`
- `move`
- `copy`
- `trash`
- `restore`
- `purge`
- `upload`
- `watch`
- `share`
- `publish`
- `collaborate`
capability 不改变 UI 命令名,只控制菜单显隐、禁用原因和 preflight 结果。
## 4. 真源分层
### 4.1 存储真源
存储真源由 source 持有:
| source | 存储真源 |
| --- | --- |
| `local_folder` | 本地文件系统 |
| `convex_workspace` | Convex 数据库 + 对象存储 |
本地模式不得先导入 Convex 再渲染。那会破坏本地文件夹模式的价值。
### 4.2 语义真源
树、页面和资源语义由 Rust kernel 统一生成:
- `KernelNode`
- `KernelEdge`
- `file_tree`
- `page_tree`
- `page_aggregate`
- `tree.*` command result
source adapter 只负责读写存储,不允许 UI 直接从本地路径或 Convex record 拼树语义。
### 4.3 UI 本地态
继续服从 `4-7`
- `expanded`
- `selected`
- `focused`
- `hovered`
- `editingName`
- `clipboard`
- `dragging`
- `dropTarget`
- `contextMenuOpen`
这些不进入 source,也不进入 projection。
## 5. Local Folder Workspace
### 5.1 P0 范围
P0 只支持:
- 打开一个本地文件夹作为 workspace root。
- `file_tree` 显示该文件夹下的目录、`.md` 文件和普通文件。
- `page_tree` 只显示 `.md` 文件。
- 打开 `.md` 文件时进入文档页。
- 普通文件作为 asset 打开或预览。
- 对本地文件和目录执行 VSCode-like 操作。
暂不支持:
- 多用户本地 ACL。
- 云同步。
- 多端协作。
- 非 Markdown 正文格式的页面编辑。
- 本地文件夹自动发布到 Convex。
### 5.2 本地页面身份
不能只用文件路径作为长期 page id。
建议 P0 采用三层策略:
1. `.md` frontmatter 中存在 `mnote_id` 时使用它。
2. 不存在时在 `.mnote/page-ids.json` 记录 `relativePath -> pageId`
3. 临时未初始化文件夹可用 path-derived id,但只作为只读过渡。
原因:
- 重命名文件时页面引用不能断。
- 移动文件时历史、链接、backlink 和 AI 索引需要能追踪。
- 同名文件在不同目录下必须稳定区分。
### 5.3 Markdown 到 Page Aggregate
本地 `.md` 文件映射到 `page_aggregate`
| Page Aggregate 字段 | 本地来源 |
| --- | --- |
| `page_identity` | `mnote_id` / `.mnote/page-ids.json` |
| `page_head.title` | frontmatter `title` / 第一行 H1 / 文件名 |
| `page_body` | Markdown AST / Tiptap document adapter |
| `page_tree` | 文件夹层级派生的 md-only projection |
| `page_layout` | frontmatter / `.mnote/page-options.json` |
写回时:
- 正文保存写回 `.md`
- 标题重命名优先改文件名,必要时同步 H1/frontmatter。
- 页面设置写入 frontmatter 或 `.mnote/page-options.json`,具体由后续编辑器设计冻结。
### 5.4 本地资源
`.md` 文件作为 resource
- image / video / audio
- pdf / book
- table-like 文件
- unknown file
资源链接优先使用相对路径。
附件从页面正文引用时,应解析成 `resourceMeta`,但不能把正文引用本身变成树结构真相。
## 6. Convex Workspace
Convex 继续存在,但定位更明确:
- 云工作区。
- 权限 / 成员 / 分享。
- 评论、协作状态、公开页面。
- 远程附件存储。
- OCR、AI、索引、异步任务。
- 多设备访问。
Convex source adapter 输出同一套 kernel 输入:
- document record -> page/document node
- media asset -> asset/resource node
- mindmap/table/book/pdf -> resource node
- share/public/template 状态 -> resource meta / decoration meta
Convex 不再被要求模拟真实本地文件系统;它只需要实现同一组 tree command 语义。
## 7. 统一 Projection
### 7.1 `file_tree`
`file_tree` 表达 source 的资源管理结构。
本地 source
- root folder
- directory
- markdown file
- normal file
Convex source
- workspace root
- page/document
- `index.md` view
- asset-folder
- asset
共同字段继续使用 `4-4`
- `rowId`
- `nodeId`
- `parentNodeId`
- `nodeType`
- `projectionKind`
- `title`
- `depth`
- `position`
- `childCount`
- `expandable`
- `capabilities`
- `resourceMeta`
- `iconHint`
新增建议:
- `sourceKind`
- `sourceUri`
- `relativePath`
- `storageIdentity`
- `operationProfile`
这些应进入 `resourceMeta.extra` 或后续协议扩展,不能散落在 UI。
### 7.2 `page_tree`
`page_tree` 表达可作为知识页面打开的页面集合。
本地 source
- 只显示 `.md` 文件。
- 默认层级来自目录结构。
- 排序可先使用文件系统排序,后续支持 frontmatter `order`
Convex source
- 继续显示 document/page 层级。
- 不显示普通 asset。
关键判断:
> Wolai 页面树不是文件树的替代,而是 markdown/page resource 的语义视图。
### 7.3 `page_aggregate`
无论 source 是本地还是 Convex,文档页只消费 `page_aggregate`
前端不应该关心正文来自:
- 本地 `.md`
- Convex document content
- 未来同步副本
差异由 page aggregate loader / source adapter 吸收。
## 8. 统一 Command
UI 只发一套命令。
### 8.1 命令路由
```text
UI action
-> tree command envelope
-> Rust command router
-> source adapter preflight
-> source adapter execute
-> kernel event / projection resync
```
命令 envelope 必须携带:
- `sourceKind`
- `workspaceId`
- `rootUri`
- `targetNodeId`
- `targetResourceMeta`
- `selection`
- `operation`
### 8.2 命令语义
| UI 操作 | 统一命令 | Local executor | Convex executor |
| --- | --- | --- | --- |
| 新建页面 | `tree.node.create` | 创建 `.md` | 创建 document |
| 新建文件夹 | `tree.folder.create``tree.resource.createFolder` | 创建目录 | 按 capability 决定是否支持 |
| 重命名页面 | `tree.node.rename` | rename `.md` / 更新 title | rename document |
| 移动页面 | `tree.subtree.move` | move file / folder | move document subtree |
| 复制页面 | `tree.subtree.copy` | copy file / folder | copy document subtree |
| 删除页面 | `tree.node.archive` | 移到 `.mnote/trash` 或系统 trash | 标记 archived |
| 永久删除 | `tree.node.purge` | 删除文件 | purge document |
| 上传 / 拖入文件 | `tree.resource.upload` | copy into folder | upload object storage |
| 移动资源 | `tree.resource.move` | move file | update asset parent/document |
| 复制资源 | `tree.resource.copy` | copy file | copy asset |
| 重命名资源 | `tree.resource.rename` | rename file | rename asset |
| 删除资源 | `tree.resource.archive` | trash file | archive asset |
| 恢复资源 | `tree.resource.restore` | restore from trash | restore asset |
说明:
- `tree.folder.create` 是否新增,需要单独冻结命名。若不新增,也可以把 folder 视作 `tree.resource.create``resourceKind=folder`
- 命名必须在后续 `4-6` 的 command protocol 补稿里冻结。
## 9. 统一操作但不同 capability
用户担心“两套文件树操作”是合理的。解决方式不是把底层做成一样,而是统一 capability 解释。
### 9.1 统一 UI
以下 UI 行为必须完全共用:
- row model
- active / selection / focus
- keyboard
- inline rename
- context menu
- clipboard
- drag/drop
- reveal
- filter/search
- preflight result display
### 9.2 不同 source capability
差异只表现为:
- 菜单项隐藏。
- 菜单项禁用并显示原因。
- preflight plan 返回不同风险。
- execute adapter 调用不同存储 API。
示例:
- 本地文件夹支持 `New Folder`
- Convex 虚拟空间默认不支持真实 `New Folder`,除非引入 folder resource。
- Convex 支持 `Share / Publish`
- 本地文件夹默认不支持 `Share / Publish`,除非执行显式 publish 到 cloud。
## 10. 权限模型
### 10.1 本地权限
本地权限来自:
- 操作系统文件权限。
- 用户选择文件夹的显式授权。
- workspace trust。
- mnote 自己的安全限制,例如禁止越过 root。
如果用户想“本地建立每个用户的文件夹”,可以作为部署策略:
```text
/data/mnote-users/alice/
/data/mnote-users/bob/
```
但这只是本地 source 的 root 管理方式,不等价于 Convex 的协作权限模型。
### 10.2 Convex 权限
Convex 权限来自:
- workspace membership
- document ACL
- public/share state
- group permission
- server-side policy
这类能力本地文件夹不能自动获得。
### 10.3 不混淆
不要让本地文件夹伪装成云协作空间。
正确表达:
- local:你有这个文件夹的本机读写权。
- cloud:你有这个 workspace 的协作权限。
## 11. VSCode 源码参考
本轮已把 VSCode 源码浅克隆到:
`/mnt/Data1T/mnote/design/05-editor-mainline/reference-code/vscode`
当前 checkout
- commit`b5c5f2f`
- sparse paths
- `src/vs/workbench/contrib/files`
- `src/vs/platform/files`
- `src/vs/base/browser/ui/tree`
- `src/vs/base/browser/ui/list`
- `src/vs/workbench/browser/parts/views`
- command / action / context key 相关基础目录
### 11.1 值得参考的点
VSCode Explorer 有几个重要设计值得直接吸收:
- `IExplorerService` 同时管理 roots、context、editable、copy/cut、refresh、select/reveal 和 bulk edit。
- context key 明确区分 folder、readonly、root、cut、focus、find provider 等状态。
- `ExplorerItem` 是资源模型,持有 `resource/name/isDirectory/isReadonly/parent/root/children`
- renderer 把 cut 状态变成 row class,并把 inline rename 做在 row 内。
- inline rename 默认只选中文件名 stem`F2` 可在 stem/all/extension 三种选择之间切换。
- DnD 会区分 native drop、Explorer 内部 drop、root reorder、readonly、self/descendant invalid、copy vs move、before/after/over indicator 和 auto expand。
- sorter 支持 default/mixed/filesFirst/type/modified/foldersNestsFiles,但排序逻辑仍在模型层,不在 UI 临时判断。
### 11.2 不能照搬的点
不能照搬:
- 真实磁盘路径作为唯一页面身份。
- compact folders 到 page/resource 复合链。
- OS 级菜单动作作为默认能力。
- VSCode 的 editor tab preview/pin 语义。
- 文件 nesting 规则到 mnote 的页面引用或附件关系。
## 12. 推荐实施顺序
### P0:统一 Source 抽象和本地只读投影
- 增加 `WorkspaceSource` 设计与 Rust trait。
- 增加 `LocalFolderSource` 只读扫描。
- 本地 source 输出 `file_tree`
- 本地 source 输出 md-only `page_tree`
- 打开 `.md` 生成只读 `page_aggregate`
- UI 可从入口选择 `convex workspace``local folder`
验收:
- 打开一个本地文件夹后,Explorer 显示真实目录和 `.md`
- 页面树只显示 `.md`
- 点击 `.md` 能进入文档页阅读态。
- Convex 现有 workspace 不受影响。
### P1:本地写操作与统一 command
- 本地新建 `.md`
- 本地新建目录。
- inline rename。
- move / copy / cut / paste。
- delete 到 `.mnote/trash` 或系统 trash。
- drop 外部文件。
- 文件 watcher 刷新 projection。
验收:
- 同一套 UI 对 local 和 Convex 发同一类 command envelope。
- source adapter 分别执行。
- preflight UI 共用。
### P2:本地 Markdown Page Aggregate 写回
- Markdown AST 与 Tiptap document 双向转换。
- 标题编辑写回文件名 / H1 / frontmatter 的规则冻结。
- page id 管理。
- backlink / reference scan。
- 本地全文索引。
### P3Local 与 Cloud 的显式桥接
- Import local folder to cloud。
- Publish local page to cloud。
- Cloud workspace export to folder。
- 可选 sync,不作为 P0/P1。
## 13. 风险
### 13.1 最大风险:双真相
如果本地文件夹先导入 Convex,再从 Convex 渲染,就会变成:
```text
file system -> Convex mirror -> projection -> UI
```
这会制造双真相,也会让 VSCode-like 操作变慢、变怪。
必须避免。
### 13.2 页面身份风险
如果 page id 只依赖路径,移动/重命名会断引用。
P0 可以先只读,P1 写操作前必须完成 page id 方案。
### 13.3 Markdown 往返风险
Tiptap 文档和 Markdown 不是无损双向转换。
P0 可以先阅读态或有限编辑;P2 再处理复杂块、附件、引用、frontmatter。
### 13.4 安全风险
本地 source 必须限制在用户选择的 root 下:
- 禁止 `../` 越界。
- symlink 默认只显示,不默认跟随写入。
- 删除 / 覆盖必须有 preflight。
- watcher 不监听 root 外路径。
## 14. 非目标
- 不让 Convex 成为本地文件夹模式的中介。
- 不维护 local file tree 和 convex file tree 两套 UI。
- 不在前端用路径和 Convex record 临时拼第二套树。
- 不在 P0 做本地/云自动同步。
- 不在 P0 承诺 Markdown 与 Tiptap 完全无损互转。
- 不用本地 per-user 文件夹替代 Convex 协作权限。
## 15. 完成判定
本文进入 done 的条件:
- `WorkspaceSource` 抽象落地。
- local 和 convex 都能通过同一 command envelope 接入 tree runtime。
- `file_tree/page_tree/page_aggregate` 对 UI 的消费路径不分叉。
- 本地文件夹 P0 只读投影和 `.md` 页面打开可验证。
- 至少一组写操作在 local 和 Convex 上共用 UI、共用命令名、只分 source executor。
- 新增能力没有绕开 Rust kernel 或制造第二套树真相。