Files
mnote/design/04-tree-domain/done/4-21-local-folder-convex-unified-tree-source-v1.md
T
lix-2026 47e224d419 chore: align sqlite control plane architecture
- replace default Convex control-plane wording with Rust SQLite control-plane across architecture, AGENTS, Reasonix, and design docs

- retire root Convex functions source and deploy script into recycle while keeping explicit cloud/compat/sync-replica boundaries

- add control-plane migration guard/docs and keep CodeGraph refreshed after the SQLite control-plane cutover
2026-05-22 17:45:22 +08:00

17 KiB
Raw Blame History

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-133-3 等后续稿跟踪,本稿不再承载新的 process 项。

1. 结论

支持引入 Local Folder Workspace,但不能因此维护两套文件树操作。

新的长期结构应固定为:

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 命令路由

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.createtree.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.createresourceKind=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。

如果用户想“本地建立每个用户的文件夹”,可以作为部署策略:

/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

  • commitb5c5f2f
  • 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 默认只选中文件名 stemF2 可在 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 workspacelocal 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 渲染,就会变成:

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 或制造第二套树真相。