Files
mnote/design/04-tree-domain/done/4-24-resource-tree-filetree-pagetree-source-contract-checklist-v1.md
T
lix-2026 384da4e44c feat(tree): checkpoint resource lifecycle work
提交当前顶层 mnote Git 工作区,范围集中在 04-tree-domain 的 resource/trash/filetree 生命周期、mnote-web resource_trash 路由、Convex/Next 兼容接口、sidebar/file-tree 客户端适配、smoke 脚本与对应设计/bug 记录。

不包含被 ignore 的 design/05-editor-mainline/reference-code/leptos-tiptap 嵌套仓库改动。新增 smoke 的测试密码改为运行时读取 MNOTE_E2E_PASSWORD,避免提交明文 credential assignment。
2026-05-16 07:38:45 +08:00

238 lines
15 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-24 [done] Resource Tree / File Tree / Page Tree 真源合同与执行清单 v1
> 更新时间:2026-05-13
>
> 2026-05-15 口径更新:
> - 本文件完成时的 `index.md` 可见 UI 模型已被 `/mnt/Data1T/mnote/design/04-tree-domain/process/4-35-convex-filetree-title-md-source-alignment-v1.md` 覆盖。
> - 后续 Convex File Tree 默认页面正文行显示为 `{title}.md`rowId 为 `doc:<documentId>`;本文中的 `index.md` 仅表示历史正文 object identity / 兼容语义,不再作为默认可见子行继续扩展。
>
> 上游依据:
> - `/mnt/Data1T/mnote/design/10-review/05-tree.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-20-vscode-explorer-file-tree-alignment-v1.md`
> - `/mnt/Data1T/mnote/design/05-editor-mainline/process/5-5-page-aggregate-single-truth-alignment-v1.md`
> - `/mnt/Data1T/mnote/design/10-review/README.md`
## 1. 目标
本清单用于把 `design/10-review/05-tree.md` 中的判断落到树域主线:
- `Resource Tree` 是长期对象组织真源,由 Rust kernel 持有语义。
- `File Tree` 是 Resource Tree 的主组织投影,展示页面、`index.md`、附件、mindmap、OnlyOffice、代码附件等资源。
- `Page Tree` 是页面导航投影 / 快捷视图,不拥有排序、父子、附件归属或资源归属的最终真相。
- Sidebar、文件树 UI、页面树 UI 都只能消费 projection 和发 command,不能重新拼第二套结构真相。
## 2. 术语冻结
- `Resource Tree`workspace 内对象与资源的 canonical hierarchy。它不是 UI 文件树组件,而是 kernel 层的资源组织语义。
- `File Tree`Resource Tree 面向 VS Code Explorer 体验的 projection。
- `Page Tree`Resource Tree 中页面对象的导航 projection,可做快捷显示,但不拥有资源归属。
- `ObjectIdentity`:打开、保存、草稿、tab、command 使用的对象身份。
- `BlockAssetRelation`:页面正文 block 与资源对象之间的稳定关联,例如 `documentId + blockId + assetId + assetKind`
## 3. 非目标
- 不在本阶段重写 Convex 底层存储。
- 不在本阶段一次性删除所有 compat/fallback。
- 不把文件树 UI 组件提升为真源。
- 不让 Page Tree 接管附件、mindmap 或 OnlyOffice 的归属。
## 4. 前置顺序与执行门
本清单是 `5-12` 的前置合同。执行顺序固定为:
1. 先完成本文件的 `Resource Tree / File Tree / Page Tree` 真源合同。
2. 再执行 `design/05-editor-mainline/done/5-12-main-editor-object-tab-resource-alignment-checklist-v1.md` 中的主编辑区 object tab、草稿隔离和打开路径调整。
不允许绕过本文件直接在编辑区里新增第二套 asset / page 归属判断。编辑区可以先做 mindmap 止血,但止血代码必须只消费本文件定义的 object identity / file tree intent,不得把临时前端判断提升为长期合同。
当前代码基线:
- `rust/crates/core-protocol/src/kernel.rs` 已有 `KernelProjectionResourceKind``KernelProjectionAssetKind``KernelProjectionResourceMeta``KernelProjectionItem`
- `rust/crates/bridge-runtime/src/lib.rs` 已有 `build_file_tree_projection_result``make_projection_resource_meta``normalize_file_tree_asset` 等 file tree projection 构造逻辑。
- `rust/crates/mnote-web/src/routes/tree.rs``rust/crates/mnote-web/src/ssr/pages/layout.rs` 已消费 `resourceMeta` 并发出 `tree.asset.open`
- `rust/crates/mnote-web/src/ssr/pages/layout.rs` 当前仍把 mindmap asset 主路径导航到 `/mindmap/{documentId}/{assetId}`,这是 `5-10` bug 的直接落点,但长期合同归属仍应先在本文件冻结。
阶段门:
- G1:协议能表达 `index.md`、mindmap、OnlyOffice、普通附件、代码附件的不同 object identity。
- G2`file_tree` projection row 能稳定输出 object identity 或等价 `resourceMeta.extra.objectIdentity`
- G3`page_tree` projection 被约束为页面导航投影,不承载 asset 归属。
- G4`tree.asset.open` 只表达打开资源对象的 intent,不决定“把资源当正文打开”。
- G5:完成 G1-G4 后,`5-12` 才能把主编辑区打开路径、草稿 key、保存命令接入 object tab / object editor 规则。
## 5. 执行清单
### 5.1 协议模型
- [x]`rust/crates/core-protocol/` 增加或扩展资源对象协议。
- 优先落点:`rust/crates/core-protocol/src/kernel.rs`,必要时拆出 `rust/crates/core-protocol/src/resource.rs` 并在 `lib.rs` re-export。
- 当前已有 `KernelProjectionResourceKind`,本阶段应补齐或映射为长期 `ResourceKind` 语义。
- 最小资源种类:`page``index``mindmap``attachment``onlyoffice``code`
- `ObjectIdentity` 最小字段:`objectKind``documentId``blockId``assetId`
- `BlockAssetRelation` 最小字段:`documentId``blockId``assetId``assetKind`
- 命名规则:Rust 类型使用 `KernelObjectIdentity` / `KernelBlockAssetRelation` 或等价项目内前缀;序列化字段使用 camelCase。
- [x] 补协议单测。
- 文件建议:`rust/crates/core-protocol/tests/resource_tree_contract.rs`
- 断言:mindmap、OnlyOffice、普通附件都能表达为 resource object。
- 断言:`index.md` 的 object identity 与 mindmap asset 的 object identity 不相等。
- 断言:`BlockAssetRelation` 能表达 `documentId + blockId + assetId + assetKind`,且不会被反序列化为页面正文 identity。
- 运行命令:
- `cd /mnt/Data1T/mnote/rust && cargo test -p core-protocol --test resource_tree_contract -- --nocapture`
- 预期:新增协议测试通过,失败时不得继续进入 `5-12` 的 P1/P2。
### 5.2 Projection 合同
- [x] 扩展 `file_tree` projection 的 row contract。
- 每个 row 必须带稳定 `row_id``node_id``projection_kind``resource_meta`
- `resource_meta` 至少包含 `resourceKind``documentId``assetId``assetKind``objectIdentity`
- 优先修改点:`rust/crates/core-protocol/src/kernel.rs``KernelProjectionResourceMeta`,以及 `rust/crates/bridge-runtime/src/lib.rs``make_projection_resource_meta`
- 兼容策略:已有 `resourceKind/documentId/assetId/assetKind` 保持不破坏;新增字段可先放入 `resourceMeta.extra.objectIdentity`,待前后端消费稳定后再提升为强类型字段。
- [x] 固定 `page_tree` projection 的降级边界。
- `page_tree` 只输出页面导航关系。
- `page_tree` 不输出附件归属、mindmap 归属、OnlyOffice 归属的最终判断。
- 如需显示资源状态,只能引用 `resource_meta` 或 resource projection,不得在 page tree renderer 内拼装。
- 优先检查点:`rust/crates/bridge-runtime/src/lib.rs``page_tree` projection 构造逻辑、`wolai-frontend/src/lib/tree-projection.ts``wolai-frontend/src/lib/documents/page-subtree.ts`
- [x] 补 Rust projection 测试。
- 文件优先看:`rust/crates/bridge-runtime/src/lib.rs``rust/crates/mnote-web/src/tree_shell/`
- 测试要求:同一页面下的 `index.md` 与 mindmap asset 生成不同 row,但共享同一 `documentId`,并通过 `BlockAssetRelation` 建立关系。
- 建议新增或扩展测试:
- `kernel_project_view_query_executes_into_file_tree_projection_contract`
- `kernel_file_tree_projection_query_supports_extended_resources_and_max_results`
- 新增 `kernel_file_tree_projection_separates_index_and_mindmap_object_identity`
- 运行命令:
- `cd /mnt/Data1T/mnote/rust && cargo test -p bridge-runtime file_tree_projection -- --nocapture`
- `cd /mnt/Data1T/mnote/rust && cargo test -p mnote-web file_tree_projection -- --nocapture`
### 5.3 File Tree 派生规则
- [x] 页面节点下固定派生 `index.md` row。
- `index.md` row 只能打开 Page Aggregate body。
- `index.md` row 不得根据页面内第一个 block 类型改变打开目标。
- 优先检查点:`rust/crates/bridge-runtime/src/lib.rs``build_file_tree_projection_result`,当前已固定创建 `index:{documentId}` node。
- 前端消费检查点:`rust/crates/mnote-web/src/ssr/pages/layout.rs` 的 file tree row click 分支。
- [x] 页面内资源固定派生为 child resource row。
- mindmap row 打开 mindmap object editor。
- OnlyOffice row 打开 OnlyOffice object editor。
- 代码附件 row 打开代码/预览编辑器。
- 普通附件 row 打开签名资源或预览。
- 优先检查点:`rust/crates/bridge-runtime/src/lib.rs``normalize_file_tree_asset``infer_file_tree_asset_shape``classify_asset_resource_kind`
- 必须保留 `documentId``assetId`,供 `5-12` 的 object tab identity 使用。
- [x] 文件树 row 不得从前端多份数据临时拼真相。
- 允许前端显示、筛选、选中、展开。
- 不允许前端决定资源归属、排序真相或 block-asset relation 真相。
- 前端允许做的本地状态:`expanded``selected``hover``focus``dragging``drop target`
- 前端禁止新增长期字段:`assetParentDocumentId``mindmapOwnerPageId``pageTreeAssetChildren` 这类与 kernel projection 重复的归属真相。
### 5.4 Page Tree 派生规则
- [x] Page Tree 只消费页面导航投影。
- 可显示页面标题、层级、当前页、快捷入口。
- 不负责附件、mindmap、OnlyOffice、代码附件的生命周期。
- [x] Page Tree command 只发页面导航相关 command。
- 页面新建、重命名、移动、归档继续走 `tree.node.*` 或正式 page/tree command。
- 资源 attach/detach/rename/move 不进入 page tree 私有命令。
- 负向检查:Page Tree renderer / host 中不得新增 mindmap、OnlyOffice、附件归属推导分支。
### 5.5 Command 边界
- [x] 新增或确认资源关系 command family。
- `tree.asset.attach`
- `tree.asset.detach`
- `tree.resource.rename`
- `tree.resource.move`
- [x] 明确不同对象写入 owner。
- 页面正文:`page.body.save`
- 页面标题:`page.head.updateTitle`
- 页面设置:`page.layout.updateOptions`
- mindmap 内容:`mindmap.command.apply`
- OnlyOffice 内容:OnlyOffice callback / forcesave 写回链
- 资源归属:`tree.asset.*` / `tree.resource.*`
- 优先检查点:
- `rust/crates/bridge-runtime/src/lib.rs` command plan
- `rust/crates/mnote-web/src/transport/convex.rs` command adapter
- `wolai-frontend/src/lib/documents/rust-runtime.test.ts`
### 5.6 Smoke 与验收
- [x] 增加 Resource Tree / File Tree projection smoke。
- 断言文件树同页下可见 `index.md`、mindmap、附件。
- 断言点击 `index.md` 只打开 Page Aggregate body。
- 断言点击 mindmap 不进入 Page Aggregate body 保存链。
- 可复用脚本:`scripts/task112-tree-rust-family-regression-smoke.js`
- 建议新增断言脚本:`scripts/task170-resource-tree-filetree-source-smoke.js`,或在 task112 中增加独立 case。
- 运行命令:
- `node scripts/task112-tree-rust-family-regression-smoke.js`
- 若新增脚本:`node scripts/task170-resource-tree-filetree-source-smoke.js`
- [x] 增加 Page Tree 负向 smoke。
- 断言 Page Tree 不显示资源归属为自己的结构真相。
- 断言 Page Tree 操作不会改变 mindmap/附件归属。
- 可复用脚本:`scripts/task112-tree-rust-family-regression-smoke.js`
- 负向检查输出需记录:操作 Page Tree 后,同一 `assetId``documentId` / `objectIdentity` 未变化。
## 6. 建议提交切片
本文件只定义设计顺序,不要求一次提交完成全部实现。后续执行时建议按以下切片推进:
1. 协议切片:`core-protocol` 增补 object identity / relation 类型与测试。
2. Projection 切片:`bridge-runtime``mnote-web` file tree projection 输出 object identity。
3. UI 消费切片:file tree host 只转发 object open intent,不拼归属真相。
4. Page Tree 负向切片:补 page tree 不持有资源归属的单测或 smoke。
5. 验收切片:跑 Rust 测试与真实浏览器 smoke,把证据补回对应 bug / design 文档。
## 7. 验收命令
最低验收命令:
```bash
cd /mnt/Data1T/mnote/rust && cargo test -p core-protocol --test resource_tree_contract -- --nocapture
cd /mnt/Data1T/mnote/rust && cargo test -p bridge-runtime file_tree_projection -- --nocapture
cd /mnt/Data1T/mnote/rust && cargo test -p mnote-web file_tree_projection -- --nocapture
node scripts/task112-tree-rust-family-regression-smoke.js
```
如执行环境没有启动 `3000`,先运行仓库常用入口:
```bash
npm run desktop:hot
```
真实浏览器 smoke 必须使用默认测试账号 `mnote.e2e@example.com` / `MnoteE2E123!`,不能用 `devFallback` 代替登录态。
## 8. Done Gate
本文移动到 `done/` 前必须满足:
- [x] `Resource Tree``File Tree``Page Tree` 三个术语在协议和文档中不再混用。
- [x] `file_tree` row 能表达 `index.md`、mindmap、OnlyOffice、附件、代码附件的不同 object identity。
- [x] `page_tree` 被验证为页面导航 projection,不拥有资源归属真相。
- [x] mindmap 与 `index.md` 的打开、保存、草稿身份互相隔离。
- [x] 至少一条真实浏览器 smoke 覆盖 `index.md + mindmap asset + 切页 + 再打开` 的完整链路。
## 9. 完成证据
- 协议:`rust/crates/core-protocol/src/kernel.rs` 增加 `KernelObjectIdentity``KernelObjectKind``KernelBlockAssetRelation`,并由 `rust/crates/core-protocol/tests/resource_tree_contract.rs` 覆盖。
- Projection`rust/crates/bridge-runtime/src/lib.rs` 的 file tree projection 输出 `resourceMeta.objectIdentity` / `blockAssetRelation`,并覆盖 index、mindmap、OnlyOffice、代码附件、普通附件。
- UI intent`rust/crates/mnote-web/src/ssr/pages/layout.rs``rust/crates/mnote-web/src/routes/tree.rs``rust/crates/mnote-web/src/tree_shell/filetree_renderer.rs``objectIdentity` 传入 file tree DOM 与 `tree.asset.open`
- Smoke`node scripts/task112-tree-rust-family-regression-smoke.js` 通过,page tree host / file tree host 主链为 `dom`debug tree shell 关闭项按当前主线记录为 skipped。
- Mindmap 链路:`node scripts/task169-mindmap-realtime-smoke.js` 通过,覆盖 mindmap asset 打开、长中文编辑、保存、切页、回 `index.md`、再打开 mindmap。
## 10. 交接给 5-12 的输入
完成本文件后,`5-12` 可以依赖以下输入推进主编辑区:
- 文件树 `index.md` row 的 `objectIdentity``page:index:{documentId}`
- 文件树 mindmap row 的 `objectIdentity``resource:mindmap:{documentId}:{assetId}`
- 文件树 OnlyOffice row 的 `objectIdentity``resource:onlyoffice:{documentId}:{assetId}`
- 文件树普通附件 row 的 `objectIdentity``resource:attachment:{documentId}:{assetId}`
- 文件树代码附件 row 的 `objectIdentity``resource:code:{documentId}:{assetId}`
- Page Tree 不再作为资源归属输入;它只提供页面导航选择。