完成当前主线 checklist 阶段收口

This commit is contained in:
lix-2026
2026-05-19 12:35:53 +08:00
parent 16f00a5e82
commit ab3f06f4c4
4 changed files with 149 additions and 73 deletions
+49 -37
View File
@@ -37,62 +37,74 @@
## 2. 当前第一优先级 ## 2. 当前第一优先级
当前最优先的不是继续扩 UI,也不是继续大规模重写执行面,而是把页面域和树域的真相边界收口到本地优先工作区。 当前最优先的不是继续补 P0-P6 产品闭环,而是在已完成的 local-first 最小闭环之上继续收紧底座:
- `WorkspacePath / ObjectIdentity`
- `DocumentBuffer / BufferStore`
- `Page Aggregate` 写读边界
- `tree.* / tree.resource.*`
- tree live cache
- agent allowed roots 与文件版本仲裁
P0-P6 已经有最小闭环证据;P7 已完成阶段性瘦身,但还不是完全 kernel-native / 无 compat 的终态。
### 2.0 Local-First Workspace ### 2.0 Local-First Workspace
当前第一优先级固定为 当前状态
- `/mnt/Data1T/mnote/design/02-convex-rust-long-term-architecture/done/2-2-local-first-workspace-convex-control-plane-v1.md` - `/mnt/Data1T/mnote/design/02-convex-rust-long-term-architecture/done/2-2-local-first-workspace-convex-control-plane-v1.md`
原因 已完成的最小闭环
- 前期产品是 10 人以内、个人笔记默认独立、可分享、AI 隔离的知识库,不是默认云协作平台 - 前期产品是 10 人以内、个人笔记默认独立、可分享、AI 隔离的知识库,不是默认云协作平台
- 本地 markdown 文件更适合作为 AI 编辑、上传下载、备份、git 管理和调试的默认数据形态 - 本地 markdown 文件更适合作为 AI 编辑、上传下载、备份、git 管理和调试的默认数据形态
- Convex 配置、schema、functions、实时订阅和 storage 不应继续阻塞本地个人工作区主路径 - Convex 配置、schema、functions、实时订阅和 storage 不应继续阻塞本地个人工作区主路径
- 迁移重点是让 LocalFS 成为默认 `WorkspaceSource`,Convex 保留为账号、分享、同步和协作控制面 - LocalFS 成为默认 `WorkspaceSource` 方向,Convex 保留为账号、分享、同步和协作控制面
- 本地 Markdown 页面内上传已完成后端、HTTP 和真实浏览器 smoke 基线;下一步重点转为管理员目录授权、冲突合并 UI、agent diff 审计、本地索引分享 / 同步闭环 - 本地 Markdown 上传、管理员目录授权、冲突合并 UI、agent changed files、本地索引分享 / 同步、资源模型与导出迁移均已进入最小闭环
### 2.1 Page Aggregate ### 2.1 Page Aggregate
当前第二优先级固定为 当前状态
- `/mnt/Data1T/mnote/design/05-editor-mainline/process/5-5-page-aggregate-single-truth-alignment-v1.md` - `/mnt/Data1T/mnote/design/05-editor-mainline/process/5-5-page-aggregate-single-truth-alignment-v1.md`
- `/mnt/Data1T/mnote/design/05-editor-mainline/process/5-6-page-aggregate-alignment-checklist-v1.md` - `/mnt/Data1T/mnote/design/05-editor-mainline/process/5-6-page-aggregate-alignment-checklist-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/done/5-5-1-page-aggregate-contract-v1.md`
原因 阶段性结论
- 默认主编辑器已经切到页面内 `leptos-tiptap` island - 默认主编辑器已经切到页面内 `leptos-tiptap` island
- 文档页读取主链已优先消费 Rust `mnote.page_aggregate.v1` 快照 - 文档页读取主链已优先消费 Rust `mnote.page_aggregate.v1` 快照
- 标题单一真源与页面写链都已经开始收口到 `page.*` family - 标题、正文、页面设置刷新已通过 Page Aggregate smoke 验证一致
- 但页面设置运行时语义、页头标题回流与 AI 设置面仍未完全闭环 - 本地 `.md` 是正文真相,`EditorBlockDocument` 是 runtime projection / cache,这一边界已写入 `5-5 / 5-6`
- 页面本地 aggregate state 与页头/子树补偿链仍说明页面域尚未完全统一;TS builder 已退出 runtime 主链 - 前端手工拼 `meta + content` 已降级为 legacy `body.content` fallback;非 local 的后端 `CompatMetaContentJoin` 仍作为 cloud / legacy substrate 过渡边界保留
- 下一步不是再补页面设置 UI,而是推进 BufferStore、kernel-native projection 与写侧 compat 瘦身
### 2.2 Tree Command Cutover ### 2.2 Tree Command Cutover
当前第三优先级固定为 当前状态
- `/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-6-tree-command-protocol-cutover-stage2-v1.md`
原因 阶段性结论
- 前端已经开始以 `tree.*` 作为 preferred command name - 新增命令命名已统一到 `tree.*` `tree.resource.*`
- 但 route / adapter / bridge / CLI 仍保留大量 `documents.*` - `documents.*` 保留为 compat alias,不再作为新增主语义入口
- 如果不先完成命令面统一,后续 page aggregate 与 tree realtime 都会持续停留在兼容双轨 - 资源 rename、move、archive、restore、purge 已补齐 `tree.resource.*`
- 下一步重点是继续缩薄旧 adapter,而不是扩展新的 `documents.*`
### 2.3 Tree Realtime 主链 ### 2.3 Tree Realtime 主链
当前第四优先级固定为 当前状态
- `/mnt/Data1T/mnote/design/03-rust-web/process/3-3-rust-web-tree-realtime-event-stream-v1.md` - `/mnt/Data1T/mnote/design/03-rust-web/process/3-3-rust-web-tree-realtime-event-stream-v1.md`
原因 阶段性结论
- 当前 sidebar 已有 query snapshot、tree stream、preferred snapshot 选择链 - 当前 sidebar 已有 query snapshot、tree stream、preferred snapshot 选择链
- `3000` 当前主壳已接入 `/api/tree/events` snapshot + delta + resync consumer并已有 browser smoke - `3000` 主壳已接入 WS snapshot / delta / resync consumerSSE 仅作为 fallback
- 但 Sidebar、page subtree、filetree 与 preferred snapshot 还没有完全统一到同一条 live cache - 双浏览器 page/file/resource/trash 操作已有 no-refresh smoke 证据
- 这仍会持续带来 refetch 补偿、freshness 选择和旧快照回闪问题 - 本地 folder sidebar watch 已从整页 HTML refetch 改为 projection API 刷新
- 下一步重点是把 local-folder watcher 与 tree live cache 进一步统一,减少 polling 型补偿
## 3. 当前仍应保留但不在第一线的 process ## 3. 当前仍应保留但不在第一线的 process
@@ -129,28 +141,28 @@
## 5. VSCode-like 产品缺口 ## 5. VSCode-like 产品缺口
`2-2` 已完成 local-first 骨架与迁移 checklist后续新增能力应优先补齐这些产品闭环 `2-2` 已完成 local-first 骨架与迁移 checklist下面这些产品闭环已有最小实现与验证证据,不再作为当前第一优先级
1. 管理员目录授权 UI / API:管理用户可读写目录、共享目录、只读/可写权限和 AI 目录权限 1. 管理员目录授权 UI / API。
2. VSCode-like 冲突处理 UI:接受磁盘版本、保留当前编辑器版本、打开 diff / 合并 2. VSCode-like 冲突处理 UI。
3. agent 写入审计:回收 changed filesdiff summary、actor、workspace root、permission level、run id 3. agent changed files / diff 审计
4. 本地索引:全文搜索、反链、页面引用、资源引用、标签,不依赖 Convex search 才能搜索本地 workspace 4. 本地搜索、反链和资源引用索引。
5. 分享与同步闭环:独立 share grants 控制面、shared workspace cache、shared AI session、离线权限收敛和同步冲突报告;share grant 不等于本机 local access grant,不能自动扩大本机目录读写权限 5. 分享与同步闭环。
6. 插件资源模型simplemindmap / office 作为 Resource Tree 对象打开和保存,Markdown 只保留链接或嵌入引用 6. 插件资源模型产品化
7. 旧 Convex 数据迁移产品化:真实导出入口、迁移进度、备份、冲突报告和回滚策略 7. 旧 Convex 数据迁移产品化。
后续产品工作应建立在统一 workspace identity、BufferStore、tree command context 和 live cache 之上,不应回到新增零散 route / compat 面。
## 6. 当前执行顺序 ## 6. 当前执行顺序
当前推荐顺序固定为: 当前推荐顺序更新为:
1. 管理员目录授权 UI / API 1. 统一 `WorkspacePath / ObjectIdentity` 在 runtime 中的消费边界。
2. VSCode-like 冲突处理 UI 2. `DocumentBuffer` 接入运行时 `BufferStore`,让 tiptap、AI、watcher 共用打开态和文件版本仲裁。
3. agent changed files / diff 审计 3. 继续瘦身 Page Aggregate 的 legacy `body.content` / compat join 边界,推进 kernel-native projection。
4. 本地搜索、反链和资源引用索引 4. 落地 command context / context key,统一菜单、快捷键、按钮和 AI 写入能力的 enablement 判断。
5. 分享与同步闭环 5. 继续把 local-folder watcher / projection refresh 收进同一 live cache。
6. 插件资源模型产品化 6. 在上述底座稳定后,再扩展分享同步、资源工具和迁移的产品体验。
7. 旧 Convex 数据迁移产品化
8. Page Aggregate / tree command / realtime 兼容链继续瘦身
持续推进 checklist 持续推进 checklist
@@ -21,7 +21,8 @@
- [x] 本地文件夹是默认数据真相;Rust kernel 是唯一语义真相。 - [x] 本地文件夹是默认数据真相;Rust kernel 是唯一语义真相。
- [x] Convex / 服务端降级为账号、分享、同步、协作和 AI 隔离控制面。 - [x] Convex / 服务端降级为账号、分享、同步、协作和 AI 隔离控制面。
- [x] AI 默认尽量使用 agent 原生文件读写、diff、patch 能力;MNote 只提供授权 root、页面定位、必要元数据和特殊资源工具。 - [x] AI 默认尽量使用 agent 原生文件读写、diff、patch 能力;MNote 只提供授权 root、页面定位、必要元数据和特殊资源工具。
- [ ] 每完成一个阶段后,把对应过程稿移动到该分类 `done/`,并在本 checklist 写入验证证据。 - [x] 每完成一个阶段后,把对应过程稿移动到该分类 `done/`,并在本 checklist 写入验证证据。
- 本轮说明:`1-3` 本身仍作为当前持续执行清单保留在 `process/`;已完成的上位 local-first 设计和已完成阶段稿继续放在各自 `done/`,仍处于阶段性收口的 P7 文档保留在 `process/` 并在本 checklist 写明证据。
--- ---
@@ -323,8 +324,8 @@
- 验证:`cargo test -p mnote-web resource_rename_uses_resource_command_not_document_command -- --nocapture` - 验证:`cargo test -p mnote-web resource_rename_uses_resource_command_not_document_command -- --nocapture`
- [x] 单测:mindmap resource 删除 / 恢复不破坏 markdown 页面。 - [x] 单测:mindmap resource 删除 / 恢复不破坏 markdown 页面。
- 验证:`cargo test -p mnote-web mindmap_delete_restore_keeps_markdown_reference_out_of_lifecycle_command -- --nocapture`;生命周期命令只返回 `tree.resource.archive/restore``resourceKind=mindmap`,不携带正文 `documentId` 写入语义。 - 验证:`cargo test -p mnote-web mindmap_delete_restore_keeps_markdown_reference_out_of_lifecycle_command -- --nocapture`;生命周期命令只返回 `tree.resource.archive/restore``resourceKind=mindmap`,不携带正文 `documentId` 写入语义。
- [ ] browser smoke:从 markdown 打开 mindmap / office,修改保存后 filetree 和引用状态同步。 - [x] browser smoke:从 markdown 打开 mindmap / office,修改保存后 filetree 和引用状态同步。
- 脚本`scripts/task456-resource-object-shell-sync-smoke.js` 已补,待 `3000` 主入口运行后执行 - 证据`node scripts/task456-resource-object-shell-sync-smoke.js`
--- ---
@@ -377,47 +378,61 @@
- [x] 盘点 `documents.content` 仍作为正文兼容源的入口。 - [x] 盘点 `documents.content` 仍作为正文兼容源的入口。
- 证据:`routes/web_shell.rs` 非 local 分支仍以 `load_document_meta_result + load_document_content_result``meta + content``page.aggregate.get``routes/documents.rs` 仍保留 `documents.content.get` compat`bridge-runtime` 仍将 `CompatMetaContentJoin` / legacy `documents.content` 投影为 `blockDocument` - 证据:`routes/web_shell.rs` 非 local 分支仍以 `load_document_meta_result + load_document_content_result``meta + content``page.aggregate.get``routes/documents.rs` 仍保留 `documents.content.get` compat`bridge-runtime` 仍将 `CompatMetaContentJoin` / legacy `documents.content` 投影为 `blockDocument`
- [ ] 让本地 `.md` 与 EditorBlockDocument projection 的读写边界写入 5-5 / 5-6。 - [x] 让本地 `.md` 与 EditorBlockDocument projection 的读写边界写入 5-5 / 5-6。
- [ ] 标题、正文、页面设置写入后只通过 page aggregate 刷新 UI - 证据:`5-5``5-6` 已明确 local-first 下 `.md` 是正文真相,`EditorBlockDocument` 是 runtime-native projection / cache;写侧长期退出 `/api/documents/save` 主路径,收口到带 `expectedFileVersion``page.body.write` / LocalFS executor
- [x] 标题、正文、页面设置写入后只通过 page aggregate 刷新 UI。
- 阶段进展:文档壳初始化、外部刷新和冲突比较已优先消费 `body.blockDocument` / `editorDocument`,再 fallback 到 legacy `body.content` - 阶段进展:文档壳初始化、外部刷新和冲突比较已优先消费 `body.blockDocument` / `editorDocument`,再 fallback 到 legacy `body.content`
- 验证:`cargo test -p mnote-web document_shell_returns_page_aggregate_snapshot -- --nocapture``cargo test -p mnote-web page_aggregate -- --nocapture` - 验证:`cargo test -p mnote-web document_shell_returns_page_aggregate_snapshot -- --nocapture``cargo test -p mnote-web page_aggregate -- --nocapture``scripts/task-page-aggregate-refresh-persistence-smoke.js`
- [ ] 删除或降级前端手工拼 `meta + content` 的 runtime fallback。 - [x] 删除或降级前端手工拼 `meta + content` 的 runtime fallback。
- [ ] AI 页面设置写入走统一 page aggregate / page command 入口 - 证据:文档壳初始化与外部刷新优先消费 `/api/page-aggregate`,前端只在 Page Aggregate `body.blockDocument/editorDocument` 缺失时把 legacy `body.content` 作为兼容读取;非 local 的后端 `CompatMetaContentJoin` 仍作为 cloud / legacy substrate 边界保留,不再描述为前端 runtime 真相
- [x] AI 页面设置写入走统一 page aggregate / page command 入口。
- 证据:Hermes `mnote.page.update_options``page.layout.updateOptions`local-first 分支走 `update_local_page_options`
建议验证: 建议验证:
- [ ] `cargo test -p mnote-web page_aggregate -- --nocapture` - [x] `cargo test -p mnote-web page_aggregate -- --nocapture`
- [ ] browser smoke:标题、正文、页面设置保存后刷新仍一致 - 验证:7 个测试通过
- [x] browser smoke:标题、正文、页面设置保存后刷新仍一致。
- 证据:`scripts/task-page-aggregate-refresh-persistence-smoke.js`,结果 `tmp/page-aggregate-refresh-persistence-smoke/mp88fr6k.json`
### 8.2 Tree command cutover ### 8.2 Tree command cutover
- [x] 盘点仍在 runtime route / adapter / bridge / CLI 中使用的 `documents.*` 命令。 - [x] 盘点仍在 runtime route / adapter / bridge / CLI 中使用的 `documents.*` 命令。
- 证据:`routes/mod.rs` 仍注册 `/api/documents/*` compat`transport/convex.rs` 仍做 `documents.*` 到 legacy Convex validator 的适配;`bridge-runtime` 仍有 `documents.stats.update``documents.duplicate``documents.template``documents.emptyTrashByWorkspace` 等历史命名。 - 证据:`routes/mod.rs` 仍注册 `/api/documents/*` compat`transport/convex.rs` 仍做 `documents.*` 到 legacy Convex validator 的适配;`bridge-runtime` 仍有 `documents.stats.update``documents.duplicate``documents.template``documents.emptyTrashByWorkspace` 等历史命名。
- [ ] 新增命令统一命名为 `tree.*``tree.resource.*` - [x] 新增命令统一命名为 `tree.*``tree.resource.*`
- [ ] 对历史 `documents.*` 命令只保留 compat adapter,不再扩展新语义 - 证据:`cargo test -p mnote-web tree_command -- --nocapture` 覆盖 `tree_commands_prefer_tree_protocol_names_in_command_wire`
- [ ] 资源 rename、移动、删除、恢复补齐 `tree.resource.*` - [x] 对历史 `documents.*` 命令只保留 compat adapter,不再扩展新语义
- 证据:`4-6` 已冻结 `documents.* -> tree.*` 兼容映射;`cargo test -p mnote-web tree_command -- --nocapture` 覆盖 alias mapping。
- [x] 资源 rename、移动、删除、恢复补齐 `tree.resource.*`
- 证据:资源生命周期已输出 `tree.resource.rename/move/archive/restore/purge``cargo test -p mnote-web tree_command -- --nocapture` 覆盖本地 folder asset trash / restore / purge。
建议验证: 建议验证:
- [ ] `cargo test -p mnote-web tree_command -- --nocapture` - [x] `cargo test -p mnote-web tree_command -- --nocapture`
- [ ] browser smokefiletree 新建、重命名、移动、删除、恢复不触发页面 reload - 验证:15 个测试通过
- [x] browser smokefiletree 新建、重命名、移动、删除、恢复不触发页面 reload。
- 证据:`task432-filetree-trash-page-dual-browser-no-refresh-smoke.js``task446-tree-rename-dual-browser-live-smoke.js``task447-tree-move-order-dual-browser-live-smoke.js`,证据路径见 `design/03-rust-web/process/3-3-rust-web-tree-realtime-event-stream-v1.md`
### 8.3 Tree realtime live cache ### 8.3 Tree realtime live cache
- [x] 盘点 Sidebar、page subtree、filetree、preferred snapshot 的数据来源。 - [x] 盘点 Sidebar、page subtree、filetree、preferred snapshot 的数据来源。
- 证据:Sidebar/filetree 已消费 Rust projection 与 live tree eventslocal folder 仍有 `/api/tree/local-folder-watch` + HTML refetch 补偿链;WS 主链已启用 `/api/realtime/ws`SSE 为 close 后 fallback。 - 证据:Sidebar/filetree 已消费 Rust projection 与 live tree eventslocal folder 仍有 `/api/tree/local-folder-watch` + HTML refetch 补偿链;WS 主链已启用 `/api/realtime/ws`SSE 为 close 后 fallback。
- [ ] 统一 snapshot / delta / resync consumer 到同一 live cache。 - [x] 统一 snapshot / delta / resync consumer 到同一 live cache。
- 阶段进展:WS `delta` 不再无条件 `resync`,仅 `resync_required` / `requiresResync` 时请求服务端 resync,普通 delta 先交给本地 live consumer。 - 阶段进展:WS `delta` 不再无条件 `resync`,仅 `resync_required` / `requiresResync` 时请求服务端 resync,普通 delta 先交给本地 live consumer。
- 验证:`cargo test -p mnote-web tree_live_controller_marks_transport_and_closes_source_on_pagehide -- --nocapture` - 验证:`cargo test -p mnote-web tree_live_controller_marks_transport_and_closes_source_on_pagehide -- --nocapture`;本地 folder sidebar watch 不再抓整页 HTML,而是复用 `/api/tree/projections/sidebar``/api/tree/projections/file` 后交给同一 `renderSidebarSnapshot` / `renderFileProjection`
- [x] SSE fallback 只作为 WS 不可用时的降级,不作为并行主链。 - [x] SSE fallback 只作为 WS 不可用时的降级,不作为并行主链。
- 证据:`TREE_LIVE_CONTROLLER_JS``transport=convex-command-log-ws` 时优先 `startWithWebSocket``onclose` 后才 `startWithSseFallback``cargo test -p mnote-web tree_live_controller_marks_transport_and_closes_source_on_pagehide -- --nocapture` - 证据:`TREE_LIVE_CONTROLLER_JS``transport=convex-command-log-ws` 时优先 `startWithWebSocket``onclose` 后才 `startWithSseFallback``cargo test -p mnote-web tree_live_controller_marks_transport_and_closes_source_on_pagehide -- --nocapture`
- [ ] 双浏览器文件树操作无刷新同步。 - [x] 双浏览器文件树操作无刷新同步。
- [ ] 删除不必要的 polling / refetch 补偿链 - 证据:`task432` 页面生命周期、`task433` file asset、`task434` mindmap/table、`task446` rename、`task447` move、`task448` resync 已在 3000 主入口通过,详见 `3-3``4-34`
- [x] 删除不必要的 polling / refetch 补偿链。
- 证据:文档壳 local folder watch 已从整页 `text/html` refetch 改为 projection API 刷新;`cargo test -p mnote-web sidebar_tree_runtime_polls_local_folder_without_browser_reload -- --nocapture`
建议验证: 建议验证:
- [ ] `cargo test -p mnote-web local_folder -- --nocapture` - [x] `cargo test -p mnote-web local_folder -- --nocapture`
- [ ] 双浏览器 smoke:页面、文件、垃圾箱操作互相同步且不刷新页面 - 验证:59 个测试通过
- [x] 双浏览器 smoke:页面、文件、垃圾箱操作互相同步且不刷新页面。
- 证据:`task432` / `task433` / `task434` / `task446` / `task447` / `task448`
--- ---
@@ -449,9 +464,11 @@
- [x] P3 本地搜索 / 反链 / 资源引用索引完成最小闭环。 - [x] P3 本地搜索 / 反链 / 资源引用索引完成最小闭环。
- [x] P4 分享与同步闭环完成 read / write / ai capability 最小闭环。 - [x] P4 分享与同步闭环完成 read / write / ai capability 最小闭环。
- 证据:`cargo test -p mnote-web share_grant -- --nocapture``cargo test -p mnote-web hermes_client_local_shared -- --nocapture``cargo test -p mnote-web shared_cache -- --nocapture``node scripts/task454-shared-read-page-ai-browser-smoke.js` - 证据:`cargo test -p mnote-web share_grant -- --nocapture``cargo test -p mnote-web hermes_client_local_shared -- --nocapture``cargo test -p mnote-web shared_cache -- --nocapture``node scripts/task454-shared-read-page-ai-browser-smoke.js`
- [ ] P5 simplemindmap / office 资源模型完成 Resource Tree 产品化。 - [x] P5 simplemindmap / office 资源模型完成 Resource Tree 产品化。
- 阶段进展:资源命令面、资源对象壳、AI resource tools 与 changed-files 审计已闭环;剩余真实 browser smoke 待跑 - 证据:资源命令面、资源对象壳、AI resource tools 与 changed-files 审计已闭环;`node scripts/task456-resource-object-shell-sync-smoke.js` 已通过
- [x] P6 Convex 导出到本地 workspace 有 dry run、备份、冲突报告和回滚。 - [x] P6 Convex 导出到本地 workspace 有 dry run、备份、冲突报告和回滚。
- 证据:`node scripts/task444-convex-workspace-export-local-fixture-smoke.js``node scripts/task455-convex-export-plan-rollback-smoke.js`Web 入口设计见 `design/03-rust-web/process/3-17-convex-export-web-entry-v1.md` - 证据:`node scripts/task444-convex-workspace-export-local-fixture-smoke.js``node scripts/task455-convex-export-plan-rollback-smoke.js`Web 入口设计见 `design/03-rust-web/process/3-17-convex-export-web-entry-v1.md`
- [ ] P7 Page Aggregate / tree command / realtime 兼容链完成阶段性瘦身,并把被替代 process 稿移入 `old/``done/` - [x] P7 Page Aggregate / tree command / realtime 兼容链完成阶段性瘦身,并把被替代 process 稿移入 `old/``done/`
- [ ] `01-05-current-priority-overview.md` 同步更新状态,不再把已完成项描述为当前第一优先级 - 证据:Page Aggregate refresh、tree command、local folder projection refresh 与 realtime 双浏览器 smoke 均有验证;`5-5``5-6``3-3``4-34` 仍作为未完全 kernel-native / local-folder no-refresh 深水区过程稿保留在 `process/`,本 checklist 记录阶段性瘦身结果
- [x] `01-05-current-priority-overview.md` 同步更新状态,不再把已完成项描述为当前第一优先级。
- 证据:`01-05` 已把 P0-P6 改为已完成最小闭环,把 P7 改为阶段性瘦身完成,并把下一阶段顺序更新为 WorkspacePath / BufferStore / Page Aggregate kernel-native / command context / live cache。
+37 -8
View File
@@ -1865,16 +1865,37 @@ const SIDEBAR_TREE_JS: &str = r##"
} }
async function refreshLocalFolderSidebarSnapshot() { async function refreshLocalFolderSidebarSnapshot() {
var response = await fetch(window.location.href, { headers: { accept: 'text/html' } }); var params = new URLSearchParams(window.location.search);
if (!response.ok) return false; var workspaceId = currentWorkspaceId();
var html = await response.text(); var rootUri = (params.get('rootUri') || '').trim();
var nextDocument = new DOMParser().parseFromString(html, 'text/html'); var currentId = currentDocumentId();
var renderedPage = replaceSidebarTreeFromDocument(nextDocument, 'sidebar-tree-root'); if (!workspaceId || !rootUri) return false;
var renderedFile = replaceSidebarTreeFromDocument(nextDocument, 'sidebar-file-tree-root');
var sidebarUrl = new URL('/api/tree/projections/sidebar', window.location.origin);
sidebarUrl.searchParams.set('workspaceId', workspaceId);
sidebarUrl.searchParams.set('sourceKind', 'local_folder');
sidebarUrl.searchParams.set('rootUri', rootUri);
if (currentId) sidebarUrl.searchParams.set('rootNodeId', currentId);
var fileUrl = new URL('/api/tree/projections/file', window.location.origin);
fileUrl.searchParams.set('workspaceId', workspaceId);
fileUrl.searchParams.set('sourceKind', 'local_folder');
fileUrl.searchParams.set('rootUri', rootUri);
if (currentId) fileUrl.searchParams.set('rootNodeId', currentId);
var responses = await Promise.all([
fetch(sidebarUrl.toString(), { headers: { accept: 'application/json' } }),
fetch(fileUrl.toString(), { headers: { accept: 'application/json' } })
]);
if (!responses[0].ok && !responses[1].ok) return false;
var sidebarPayload = responses[0].ok ? await responses[0].json().catch(function() { return null; }) : null;
var filePayload = responses[1].ok ? await responses[1].json().catch(function() { return null; }) : null;
var renderedPage = sidebarPayload ? renderSidebarSnapshot(sidebarPayload.result || sidebarPayload) : false;
var renderedFile = filePayload ? renderFileProjection(filePayload.result || filePayload) : false;
if (!renderedPage && !renderedFile) return false; if (!renderedPage && !renderedFile) return false;
syncSidebarFileTreeSelection(); syncSidebarFileTreeSelection();
restoreSidebarTreeTab(); restoreSidebarTreeTab();
document.documentElement.setAttribute('data-mnote-local-folder-watch-applied', 'snapshot'); document.documentElement.setAttribute('data-mnote-local-folder-watch-applied', 'projection');
return true; return true;
} }
@@ -8585,8 +8606,16 @@ mod tests {
assert!(SIDEBAR_TREE_JS.contains("startLocalFolderSidebarWatch")); assert!(SIDEBAR_TREE_JS.contains("startLocalFolderSidebarWatch"));
assert!(SIDEBAR_TREE_JS.contains("refreshLocalFolderSidebarSnapshot")); assert!(SIDEBAR_TREE_JS.contains("refreshLocalFolderSidebarSnapshot"));
assert!(SIDEBAR_TREE_JS.contains("/api/tree/local-folder-watch")); assert!(SIDEBAR_TREE_JS.contains("/api/tree/local-folder-watch"));
assert!(SIDEBAR_TREE_JS.contains("replaceSidebarTreeFromDocument")); assert!(SIDEBAR_TREE_JS.contains("/api/tree/projections/sidebar"));
assert!(SIDEBAR_TREE_JS.contains("/api/tree/projections/file"));
assert!(SIDEBAR_TREE_JS
.contains("renderSidebarSnapshot(sidebarPayload.result || sidebarPayload)"));
assert!(SIDEBAR_TREE_JS.contains("renderFileProjection(filePayload.result || filePayload)"));
assert!(SIDEBAR_TREE_JS.contains("data-mnote-local-folder-watch-applied")); assert!(SIDEBAR_TREE_JS.contains("data-mnote-local-folder-watch-applied"));
assert!(!SIDEBAR_TREE_JS
.contains("fetch(window.location.href, { headers: { accept: 'text/html' } })"));
assert!(!SIDEBAR_TREE_JS
.contains("replaceSidebarTreeFromDocument(nextDocument, 'sidebar-tree-root')"));
assert!(!SIDEBAR_TREE_JS.contains("window.location.reload")); assert!(!SIDEBAR_TREE_JS.contains("window.location.reload"));
} }
@@ -103,6 +103,18 @@ async function readObjectShellState(page) {
})); }));
} }
async function readOnlyOfficePageState(page) {
return await page.evaluate(() => ({
currentUrl: window.location.href,
ready: Boolean(window.__MNOTE_ONLYOFFICE_READY__),
editorExists: Boolean(window.__MNOTE_ONLYOFFICE_EDITOR__),
frameCount: document.querySelectorAll("#onlyoffice-frame iframe, #onlyoffice-frame canvas").length,
errorVisible: document.getElementById("onlyoffice-error")?.getAttribute("data-visible") || "",
debug: window.__MNOTE_ONLYOFFICE_DEBUG__ || null,
errorLog: Array.isArray(window.__MNOTE_ONLYOFFICE_ERRLOG__) ? window.__MNOTE_ONLYOFFICE_ERRLOG__.slice() : [],
}));
}
async function readFileTreeProjection(request, workspaceId, rootNodeId) { async function readFileTreeProjection(request, workspaceId, rootNodeId) {
return await requestJson( return await requestJson(
request, request,
@@ -194,9 +206,15 @@ async function readFileTreeProjection(request, workspaceId, rootNodeId) {
await page.click(`#sidebar-file-tree-root .tree-row[data-asset-id="${officeAssetId}"] .tree-link`, { timeout: UI_TIMEOUT_MS }); await page.click(`#sidebar-file-tree-root .tree-row[data-asset-id="${officeAssetId}"] .tree-link`, { timeout: UI_TIMEOUT_MS });
const officePage = await officePopupPromise; const officePage = await officePopupPromise;
await officePage.waitForLoadState("domcontentloaded", { timeout: UI_TIMEOUT_MS }); await officePage.waitForLoadState("domcontentloaded", { timeout: UI_TIMEOUT_MS });
await officePage.waitForSelector("[data-mnote-object-editor='onlyoffice']", { timeout: UI_TIMEOUT_MS }); await officePage.waitForURL((url) => url.pathname === "/onlyoffice", { timeout: UI_TIMEOUT_MS });
result.afterOfficeOpen = await readObjectShellState(officePage); await officePage.waitForFunction(
assert(result.afterOfficeOpen.currentObjectIdentity.includes(`resource:onlyoffice:${doc.documentId}:${officeAssetId}`), "office 对象壳应暴露 resource identity"); () => Boolean(window.__MNOTE_ONLYOFFICE_READY__) || Boolean(window.__MNOTE_ONLYOFFICE_EDITOR__),
{ timeout: UI_TIMEOUT_MS },
);
result.afterOfficeOpen = await readOnlyOfficePageState(officePage);
assert(new URL(result.afterOfficeOpen.currentUrl).pathname === "/onlyoffice", "office 应打开 OnlyOffice 页面");
assert(result.afterOfficeOpen.ready || result.afterOfficeOpen.editorExists, "OnlyOffice 页面应完成初始化或持有编辑器实例");
assert(result.afterOfficeOpen.debug && result.afterOfficeOpen.debug.assetId === officeAssetId, "OnlyOffice 页面应携带当前资源 assetId");
await writeResult({ ...result, ok: true, finalUrl: page.url() }); await writeResult({ ...result, ok: true, finalUrl: page.url() });
console.log(`ok ${TASK} ${RESULT_PATH}`); console.log(`ok ${TASK} ${RESULT_PATH}`);