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.
26 KiB
26 KiB
[recycle] 4-22 [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-21-local-folder-convex-unified-tree-source-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状态说明:本清单对应的 Local Folder / Convex 统一树源主链已进入已完成基线;后续 Markdown parser、Page Aggregate Rust-first、tree live stream 与设计状态漂移由后续稿继续跟踪。
0. 执行原则
本清单按顺序执行,不跳阶段。
总目标:
只维护一套 tree projection、tree command、preflight、runtime state 和 UI;Local Folder 与 Convex 只作为不同
WorkspaceSourceexecutor 存在。
硬约束:
- 不让本地文件夹先导入 Convex 再渲染。
- 不在 UI 层用本地 path 或 Convex record 拼第二套树。
- 不新增 local 专用文件树 UI。
- 不绕过 Rust kernel 写页面树、文件树、排序或权限。
- 不把
expanded/selected/focus/drag/drop/contextMenu写进 projection。 - 所有写操作先 preflight,再 execute,再 projection resync。
Done 条件:
- 本清单对应实现中,Local 和 Convex 都通过同一 command envelope 进入 Rust command router。
file_tree/page_tree/page_aggregate的 UI 消费路径不因 source 分叉。- 本地文件夹与 Convex 的差异只通过 capability / preflight / executor 表达。
1. Phase A:冻结 Source 与协议边界
A1. 定义 WorkspaceSource 合同
- 在 Rust 侧定义
WorkspaceSourcetrait / interface 草案。 - 字段至少覆盖
sourceKind、rootUri、workspaceId、capabilities。 - 方法至少覆盖
loadSnapshot、watch、preflightCommand、executeCommand、resolvePageAggregate。 - 明确
local_folder和convex_workspace两个 source kind。 - 写 contract test,验证 source kind 和 root uri 会进入 command envelope。
Done 条件:
- Rust 代码中存在统一
WorkspaceSource抽象。 - 至少有 mock source 能输出
file_tree/page_treesnapshot。 - 前端没有新增 source-specific tree component。
A2. 扩展 projection source metadata
- 为 projection item 补 source metadata 方案。
- 字段候选固定为
sourceKind、sourceUri、relativePath、storageIdentity、operationProfile。 - 优先放入
resourceMeta.extra,除非需要正式扩展core-protocol。 - 更新 TS 类型映射。
- 补 fixture:local folder item、convex item 同时存在。
Done 条件:
file_treeitem 可以区分来自 local 还是 convex。- UI 仍只读 projection 字段,不直接访问 source adapter。
- fixture 覆盖
directory/md file/normal file/document/index/asset。
A3. 统一 command envelope
- 定义
TreeCommandEnvelope的 source 字段。 - envelope 至少包含
sourceKind、workspaceId、rootUri、targetNodeId、targetResourceMeta、selection、operation。 - 现有
tree.node.*、tree.subtree.*、tree.resource.*进入统一 router。 - router 按 source kind 分发到 executor。
- 对 unknown source / capability missing 返回可解释错误。
Done 条件:
- 同一个 UI action 可以构造同一种 envelope。
- local / convex 的 executor 可以在测试中分别接收 envelope。
- 没有新增
/api/local-filetree/*这类平行命令入口。
2. Phase B:Local Folder 只读主链
B1. 打开本地文件夹入口
- 增加选择本地文件夹的入口。
- 生成
SpaceRoot=file://...。 - 记录最近打开的 local root。
- 禁止 root 外路径访问。
- 对无权限目录返回明确错误。
Done 条件:
- 用户能选择一个本地文件夹并进入 local workspace。
- 3000
desktop:hot主入口不能跳到/treedebug shell,也不能落入legacy_next_compat_disabled。 - Convex workspace 入口不受影响。
- smoke 能断言当前 source kind 为
local_folder。
B2. LocalFolderSource 只读扫描
- 扫描目录。
- 识别 directory。
- 识别
.md文件。 - 识别普通文件。
- 默认忽略
.git、node_modules、.mnote/trash等高噪音目录。 - 处理 symlink:P0 只显示,不默认跟随写入。
Done 条件:
- 本地文件夹能输出 read-only snapshot。
- snapshot 不越过 root。
- 扫描失败不会让整个 app 崩溃。
B3. 输出 local file_tree
- directory 映射为 folder row。
.md映射为 markdown file row。- 普通文件映射为 asset row。
- iconHint 覆盖 markdown/image/video/audio/pdf/book/table/unknown。
- childCount、depth、parentNodeId、position 由 kernel 生成。
- 排序规则先固定为 VSCode default:folder first,name sort。
Done 条件:
kernel.project_view(file_tree)可以返回 local folder projection。- UI 使用同一 Explorer surface 渲染 local projection。
- 浏览器 smoke 可看到真实目录、
.md、普通文件。
B4. 输出 md-only page_tree
- 只把
.md文件纳入 page tree。 - 目录只作为页面树分组/父级,不作为正文页面打开。
- 页面标题优先级固定为 frontmatter
title> 第一行 H1 > 文件名。 - parent/child 来自目录层级。
- 同名 md 文件必须通过 path / page id 区分。
Done 条件:
- 同一个 local root 下,Explorer 显示所有文件,页面树只显示
.md。 - 点击页面树 md 节点能定位到同一个
.mdresource。 - 不因过滤普通文件影响 page tree 层级。
B5. .md 只读 Page Aggregate
- 读取
.md文件。 - 解析 frontmatter。
- 解析标题。
- 生成只读
page_aggregate。 - 文档页用现有
DocumentShell / DocumentContent消费。 - 文件读取失败显示错误页,不清空 Explorer。
Done 条件:
- 点击本地
.md文件能打开文档阅读态。 - 文档页不关心内容来自 local 还是 Convex。
page_aggregate字段满足5-5-1最小契约。
3. Phase C:页面身份与本地元数据
C1. 本地 page id 策略
- 支持 frontmatter
mnote_id。 - 支持
.mnote/page-ids.json。 - 支持 path-derived 临时 id,但仅用于未初始化只读模式。
- page id 生成必须稳定且不与 Convex document id 冲突。
- 写测试覆盖 rename/move 后 id 保持。
Done 条件:
- 重命名或移动
.md后,page id 不变。 - 没有 id 的文件能被安全初始化。
- 同名不同路径
.md不冲突。
C2. 本地 .mnote 元数据目录
- 定义
.mnote/page-ids.json。 - 定义
.mnote/page-options.json。 - 定义
.mnote/trash或系统 trash 策略。 .mnote不出现在默认 Explorer 展示中,除非开启 debug。- 元数据写入必须原子化或有失败回滚策略。
Done 条件:
.mnote元数据能支持 page id 和页面设置。- 默认用户视图不被
.mnote噪音污染。 - 元数据损坏时有可解释错误和恢复建议。
4. Phase D:统一 Explorer P0 交互
D1. Active / Selection / Focus 分离
- 统一 row state:
activeRowId、selectedRowIds、focusedRowId。 - active 表示右侧打开资源。
- selection 表示 Explorer 操作集合。
- focus 表示键盘焦点。
- local 和 convex 共用 reducer。
- 多选时 selection 视觉优先于 active。
Done 条件:
- 截图中的
index.mdactive/focus/selection 歧义被测试覆盖。 - local
.md和 Convexindex.md使用同一视觉规则。 - selection 不回写 projection。
D2. 键盘导航
ArrowUp/ArrowDown移动 focus。ArrowLeft/ArrowRight折叠 / 展开 / 父子导航。Home/End跳首尾。Enter打开 focused row。F2进入 inline rename。Delete/Backspace触发删除 preflight。Ctrl/Cmd+Ccopy。Ctrl/Cmd+Xcut。Ctrl/Cmd+Vpaste。Esc取消 rename / menu / drag feedback。
Done 条件:
- 键盘能力在 runtime contract 中测试,不只散落在 React handler。
- local 和 convex 使用同一组按键浏览器 smoke。
cut不再缺失。
D3. 选择与右键
- 单击单选并设置 anchor/focus。
Ctrl/Cmd + Click切换选中。Shift + Click范围选。- 右键已选区内 row 保留选区。
- 右键未选 row 切换到该 row。
- 空白区点击清空 selection/focus。
Done 条件:
- 多选 context menu 不会被右键误清空。
- local/convex 的 selection reducer 共用。
- 过滤可见行变化后 selection normalize 正确。
- 已选区内右键、未选项右键、空白区点击三条路径都必须在真实浏览器中验证。
D4. Row grid 与 root toolbar
- 固定 indent / twistie / icon / label / decoration / actions 六列。
- section header 使用
EXPLORER。 - root row 明确显示 local folder 或 cloud workspace。
- toolbar 至少有新建、上传/导入、刷新、全部折叠、更多。
- toolbar action 由 capability 控制可用性。
Done 条件:
- hover action 不改变行高和缩进。
- local root 与 Convex root 在同一 Explorer 框架下展示。
- toolbar 不创建 source-specific UI 分支。
- hover / focus 显示 actions 时,row height、indent、twistie/icon/label 列位置必须有浏览器截图或 DOM 断言。
5. Phase E:统一 Preflight 与 Context Menu
E1. Context menu profile
- root 菜单:新建页面、新建文件夹、上传/导入、刷新、全部折叠。
- md/page 菜单:打开、重命名、复制、剪切、粘贴、移动到、删除、Reveal。
- folder 菜单:新建页面、新建文件夹、粘贴、重命名、删除。
- asset 菜单:打开、重命名、复制、剪切、删除、下载、移动到。
- 多选菜单:复制、剪切、删除、移动到。
- share/publish 只在 Convex capability 存在时出现。
Done 条件:
- 菜单项由 rowKind/resourceKind/capability/selection 共同裁剪。
- 不支持的能力有禁用原因或隐藏规则。
- mindmap/table 的“不支持”状态被 capability 表达,而不是散落提示。
E2. Preflight result UI
- delete preflight 展示影响集合。
- move preflight 展示目标和冲突。
- paste preflight 展示命名冲突策略。
- upload/drop preflight 展示目标是否可写。
- readonly / permission denied / root escape 返回统一错误结构。
Done 条件:
- local 和 convex 的 preflight 使用同一 UI 组件展示。
- 失败时不发 execute。
- 危险动作不会静默执行。
6. Phase F:Local 写操作 P1
F1. 新建
tree.node.create在 local source 创建.md。tree.folder.create或等价命令创建目录。- 新建后进入 inline rename。
- 命名冲突走 incremental naming 或 preflight 提示。
- Convex 对
New Folder按 capability 禁用,除非已有 folder resource。
Done 条件:
- 同一个新建入口可在 local 创建文件,在 Convex 创建 document。
- Convex 不支持 folder 时不会显示假入口。
- 新建结果通过 projection resync 出现。
F2. Inline rename
- 本地文件 / 目录 rename 写入文件系统。
.mdrename 保持 page id。- Convex document rename 继续走
tree.node.rename。 - asset rename 收口到
tree.resource.rename或等价正式命令。 Enter提交,Esc取消。- 文件型资源默认选中文件名 stem。
Done 条件:
- 不再使用
prompt做重命名。 - local 和 convex 的 rename UI 完全一致。
- rename 失败不破坏 selection/focus。
F3. Move / Copy / Cut / Paste
- local move/copy 使用文件系统操作。
- Convex move/copy 使用 existing tree/resource command。
- cut 状态有 decoration。
- paste 成功后清空 cut。
- 多选时过滤父子重复选择。
- 禁止 move 到自身或后代。
Done 条件:
- local/convex 都通过同一 clipboard payload。
- cut/copy/paste 浏览器 smoke 覆盖两个 source。
- projection resync 后 selection/focus 合理保留。
F4. Delete / Restore / Purge
- local delete 进入
.mnote/trash或系统 trash,策略需冻结。 - local restore 从 trash 恢复。
- local purge 永久删除。
- Convex delete/restore/purge 继续走
tree.node.*/tree.resource.*。 - asset delete/restore 从 legacy
/api/media/batch收口。
Done 条件:
- 删除不是直接
rm静默执行。 - restore 在 local 和 Convex 都有统一菜单语义。
- purge 有更强确认。
F5. Drop 外部文件
- local drop 文件复制进目标目录。
- Convex drop 文件上传对象存储。
- drop 到 readonly 目标被拒绝。
- drop 到 md 文件时目标归属规则明确。
- before/inside/after indicator 覆盖本地目录和 Convex document/asset-folder。
Done 条件:
- 外部拖入在 local 和 convex 使用同一 drop preflight UI。
- 非法目标不执行写入。
- 文件写入后 projection 自动刷新。
- 外部文件 drop、内部拖拽、before/inside/after indicator、hover auto expand 都必须在真实浏览器中验收,不能只靠 contract test 或 jsdom。
- 至少覆盖自拖自身、拖到后代、跨 workspace、只读目标四类非法拖拽场景。
验收记录:
- local 外部 drop:
scripts/task163-local-folder-unified-tree-browser-smoke.js使用真实 ChromiumDataTransfer/File,覆盖取消 preflight 不写入、确认后复制进目录、drop 到.md时写入父目录、readonly 目标拒绝且不发 execute。 - Convex 外部 drop:同一 smoke 通过 Rust DOM host 监听
tree.filetree.external-drop,覆盖 Convex document 与 asset-folder 两类目标;宿主侧继续由tree-shell-dom-host.tsx -> Sidebar.handleResourcePaneDropFiles -> uploadFileTreeResourceAsset -> /api/media/upload上传对象存储。历史tree-shell-iframe-host.tsx已于 2026-05-16 移入recycle/,不再作为 active 路径。 - 对象存储链路:
resource-command-client.test.ts覆盖uploadFileTreeResourceAsset,app/api/media/upload/route.test.ts覆盖tree.resource.uploadplan、Convex upload URL、mediaAssets:createWithStorage与资源 delta artifact。 - indicator:同一 smoke 覆盖本地目录 before/inside/after、hover auto expand,以及 Convex document before/inside/after 与 asset-folder inside。
7. Phase G:Watcher / Realtime / Refresh
G1. Local file watcher
- 监听 root 下文件新增、删除、重命名、修改。
- 忽略
.git/node_modules/.mnote/trash等目录。 - 防抖合并事件。
- source 输出 tree delta 或触发 snapshot refresh。
- 写操作自身产生的 watcher 事件不造成重复刷新风暴。
Done 条件:
- 外部编辑器新增
.md后 page_tree 自动更新。 - 外部删除文件后 Explorer 自动更新。
- watcher 不越过 root。
验收记录:
- Rust 侧新增
/api/tree/local-folder-watch,由LocalFolderSource计算 root revision,只在可见文件/目录变化时变更 revision,并复用 root escape 与 ignore 规则。 - 浏览器端 local source 不再固定 2.5 秒 reload,而是轮询 revision,变化后防抖触发 snapshot refresh。
cargo test -p mnote-web local_ -- --nocapture覆盖.mnote噪音不改变 revision、可见文件内容变更会改变 revision、root escape 防护。scripts/task163-local-folder-unified-tree-browser-smoke.js覆盖外部新增.md后 page_tree 自动更新,以及外部新增/删除普通文件后 Explorer 自动更新。
G2. Convex realtime parity
- Convex 继续使用现有 tree stream。
- preferred snapshot 选择不因 local source 破坏。
- local source 和 Convex source 都能进入统一 tree event consumer。
- source 切换时清理上一 source 的 watcher / stream。
Done 条件:
- local 和 convex 的实时刷新状态都可观测。
- 切换 workspace 不出现旧 source 事件污染新 workspace。
- 当前 Convex smoke 不回退。
验收记录:
- Convex 侧继续由
useSidebarTreeStream/ tree stream server 处理 realtime,pnpm vitest run src/lib/tree-stream/use-sidebar-tree-stream.test.tsx src/components/sidebar/use-preferred-sidebar-snapshot.test.tsx src/lib/tree-stream/server.test.ts通过。 - local 侧通过同一 tree shell source metadata 和 revision watcher 触发 snapshot refresh,不引入第二套 UI。
- 真实浏览器 smoke 覆盖 local -> Convex source 切换,断言旧 local watcher 内容和 selection 不污染 Convex file tree。
8. Phase H:Markdown 编辑与写回 P2
H1. Markdown AST 到编辑器文档
- Markdown parse 成 editor document。
- frontmatter 保留。
- 基础块覆盖 paragraph / heading / list / quote / code / divider。
- 附件链接解析为 resource reference。
- 不支持块有降级显示。
Done 条件:
- 本地
.md可进入可编辑态。 - 基础 Markdown 往返不丢主体内容。
- 不支持格式不会导致保存破坏原文件。
验收记录:
LocalFolderSource的 Markdown 读取侧已覆盖 heading 1-6、paragraph、bullet / numbered list、quote、fenced code、divider、相对附件链接mediareference;未知/不支持语法降级为 paragraph。cargo test -p mnote-web local_markdown -- --nocapture覆盖基础块解析、附件链接解析、unsupported table 降级显示。- 真实浏览器 smoke 打开
docs/blocks.md,断言 frontmatter title、列表、quote、code 与附件链接文本均可在文档页读出。
H2. 编辑器文档写回 Markdown
- 保存正文写回
.md。 - 标题编辑规则冻结:文件名 / H1 / frontmatter 如何同步。
- 页面设置写入 frontmatter 或
.mnote/page-options.json。 - 保存失败保留编辑器状态并显示错误。
- watcher 与保存事件去重。
Done 条件:
- 修改本地
.md后刷新仍能看到新内容。 - 文件名、H1、frontmatter title 不互相打架。
- Convex 编辑链路不受本地写回影响。
验收记录:
- 正文保存通过
/api/documents/save写回.md,标题编辑通过/api/documents/title只写 frontmattertitle,文件名变更仍由 Explorer rename 负责;三者职责分离。 - 页面设置写入
.mnote/page-options.json,不混入正文。 cargo test -p mnote-web local_ -- --nocapture覆盖本地正文、标题和页面设置写回;local_markdown_save_preserves_frontmatter_and_writes_basic_blocks覆盖 frontmatter 保留与基础块 Markdown 输出。- 真实浏览器 smoke 通过 document APIs 保存本地 Markdown,刷新后仍能看到新标题和正文;revision watcher 只在本地 root revision 变化后防抖刷新,避免固定刷新风暴。
9. Phase I:Local / Cloud 显式桥接 P3
I1. Import / Export
- local folder import 到 cloud workspace。
- cloud workspace export 到 local folder。
- 冲突策略明确。
- page id 映射保留。
- 附件路径和对象存储映射保留。
Done 条件:
- import/export 是显式动作,不是 local 打开时自动发生。
- 用户可预览影响集合。
- 失败可重试或回滚。
验收记录:
- P3 候选合同已冻结在
4-23-local-cloud-explicit-bridge-p3-candidate-v1.md。 - import/export 均被定义为
preview -> execute显式动作,不能在打开 local folder、保存 local.md、watcher 或普通树操作中隐式发生。 - 冲突策略固定为 preview 输出冲突集合,execute 默认 incremental naming,不静默覆盖。
- page id 映射分别通过
mnote_id/.mnote/page-ids.json、cloud_document_id、.mnote/cloud-export-map.json保留。 - 附件路径和对象存储 id 必须进入 export/import map;execute 失败必须返回 retry cursor 和 rollback plan。
I2. Publish / Sync 候选
- local page publish 到 Convex public page。
- 手动 sync 候选设计。
- 不在 P0/P1 引入隐式双向同步。
- 权限模型不混淆:local OS 权限不同于 Convex ACL。
Done 条件:
- 本地 workspace 不依赖 Convex 才能工作。
- Cloud 功能通过显式桥接启用。
- 没有自动双真相。
验收记录:
4-23固定 publish 为tree.bridge.publish.preview/execute,只发布显式选择的单页或子树。- sync 只进入
tree.bridge.sync.preview候选,不提供默认sync.execute,避免形成自动双真相。 - local OS 权限与 Convex ACL 在 preview 中必须单独展示,不互相推导。
- 当前实现中的 local folder 读写、watcher、Markdown 保存均不依赖 Convex;Convex 能力只通过显式 bridge 候选启用。
10. 全局验收矩阵
10.1 Contract tests
WorkspaceSourcemock local / mock convex。- command envelope source routing。
- projection source metadata。
- capability-driven menu profile。
- preflight result schema。
- active / selection / focus reducer。
- cut / copy / paste reducer。
Done 条件:
- 不启动浏览器也能验证核心协议。
- local/convex 至少各有一套同命令不同 executor 的测试。
- Contract tests 只负责协议、schema、reducer、payload 与 executor 分发,不替代拖拽、右键、多选、外部 drop、source 切换的真实浏览器验收。
10.2 Rust tests
- local folder scan。
- md-only page_tree。
- file_tree folder first sorting。
- page id persistence。
- root escape 防护。
- local write executor。
- Convex executor regression。
Done 条件:
- Rust 测试覆盖 source adapter 主路径。
- 安全边界有失败用例。
- Convex 旧路径没有被本地 source 破坏。
10.3 Browser smoke
- 打开 local folder。
- 3000
desktop:hot主入口覆盖打开本地文件夹:不触发原生 prompt,不跳/tree,不落入legacy_next_compat_disabled。 - Explorer 显示目录、md、普通文件。
- 页面树只显示 md。
- 打开 md 到文档页。
- 同一套右键菜单在 local / convex 按 capability 裁剪。
- 已选区内右键保留多选,未选项右键切换 selection 后开菜单,空白区点击清空 selection / focus。
Ctrl/Cmd+Click、Shift+Click、Ctrl/Cmd+A多选链路。- 3000 主 Sidebar file tree 的
Shift+Click范围多选由scripts/task164-desktop-hot-local-folder-main-entry-smoke.js验收。 - inline rename。
Ctrl/Cmd+C/X/V与菜单 Copy / Cut / Paste 都覆盖,cut decoration 出现,paste 成功后消失。- 内部拖拽覆盖单选、多选、move / copy 修饰键切换。
- 内部拖拽覆盖 before / inside / after 三态 indicator。
- 内部拖拽覆盖 hover auto expand。
- 内部拖拽覆盖自拖自身、拖到后代、跨 workspace、只读目标四类非法场景,并断言不会发 execute。
- 外部文件 drop 使用真实
DataTransfer/File或等价 Playwright 文件拖入,覆盖 local folder、Convex document/asset-folder、readonly 目标、md 文件目标。 - 外部文件 drop 覆盖 preflight UI、写入/上传结果、projection refresh。
- watcher 外部修改刷新。
- 切回 Convex workspace 后原有页面树正常。
- source 切换后旧 watcher / stream 已清理,selection / focus / active 不被旧 source 污染。
- local 与 Convex 上同一动作保持同一 UI 和同一命令名,但 executor 行为可不同。
- 组合场景覆盖多同名
新页面、每页下index.md、多选、右键、F2、Ctrl/Cmd+X/V、外部文件拖入、切回 Convex workspace。
Done 条件:
- smoke 覆盖 source 切换。
- smoke 覆盖 3000 主入口本地文件夹链路;不能只用
/treedebug shell 作为完成证据。 - smoke 覆盖至少一个同 UI、同命令、不同 executor 的动作。
- 截图中 Explorer root / active / selection / focus 可解释。
- 浏览器 smoke 必须是真实浏览器自动化,不接受纯 jsdom、DOM 单测或只跑 reducer 的结果。
- 拖拽、外部 drop、上下文菜单、hover 和键盘导航必须在浏览器里触发真实事件链,不接受手工只看截图的验收。
11. 最终 done 条件
只有同时满足以下条件,4-20/4-21/4-22 才能整体移动到 done/:
WorkspaceSource抽象落地。LocalFolderSource支持本地只读file_tree/page_tree/page_aggregate。ConvexWorkspaceSource继续通过同一 projection/command 消费路径工作。- Explorer P0 交互完成:active/selection/focus、keyboard、context menu、inline rename、cut/copy/paste、drag/drop。
- 至少一组写操作在 local 和 convex 上共用 UI 和 command envelope。
- 本地文件夹不经过 Convex 中介。
- page id 不因本地 rename/move 断裂。
- UI 没有 source-specific tree fork。
- 所有危险写操作都有 preflight。
- 浏览器 smoke 和 Rust/TS contract tests 通过。
- 3000
desktop:hot主入口 smoke 通过,覆盖本地文件夹入口、legacy fallback 拒绝和主 SidebarShift+Click多选。 - 拖拽、外部 drop、右键、多选、键盘、source 切换的关键路径在真实浏览器中逐项验收通过;这些 hard gate 未通过时,即使 contract/Rust 测试通过也不得移动到
done/。
12. 明确非目标
- 不在 P0 实现本地和云端自动同步。
- 不在 P0 承诺 Markdown 与编辑器完全无损互转。
- 不用本地 per-user 文件夹替代 Convex 协作权限。
- 不把 VSCode 源码作为直接复制对象;只参考 Explorer 行为模型。
- 不把 Convex 虚拟空间强行伪装成真实磁盘目录。
- 不把 Local Folder 模式做成必须登录才能使用。