From 1956a8a21a6e8253e68c2db124bc366b3c378068 Mon Sep 17 00:00:00 2001 From: lix-2026 Date: Thu, 21 May 2026 09:04:13 +0800 Subject: [PATCH] chore: align mvp design governance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 统一 local-first MVP 后阶段架构口径,补充 process 执行总序和 Reasonix 协作记录 - 归档已完成的 design checklist,标注参考型 process,更新 AGENTS/REASONIX/架构文档 - 补充文件树/主编辑器下载与上下文菜单相关实现、bug 记录和 smoke 脚本 验证:git diff --check;codegraph sync .;cargo test -p mnote-web;node --check scripts/task476-filetree-editor-context-menu-download-smoke.js --- .codegraph/.gitignore | 16 + .codegraph/config.json | 161 +++++++ ...owser-test-filetree-editor-context-menu.md | 79 ++++ ...-05-21-design-governance-worker-a-01-03.md | 49 ++ ...5-21-design-governance-worker-b-04-tree.md | 45 ++ ...-design-governance-worker-c-05-06-08-09.md | 52 +++ ...-05-21-design-governance-worker-d-07-10.md | 50 ++ .../design-governance-worker-a-01-03.md | 271 +++++++++++ .../design-governance-worker-b-04-tree.md | 230 +++++++++ .../design-governance-worker-c-05-06-08-09.md | 254 ++++++++++ .../design-governance-worker-d-07-10.md | 362 +++++++++++++++ AGENTS.md | 3 +- ARCHITECTURE.md | 8 +- CURRENT_ARCHITECTURE.md | 44 +- REASONIX.md | 22 +- auth-console.txt | 1 + auth-network.txt | 0 ...iletree-editor-context-menu-download-v1.md | 64 +++ design/01-05-current-priority-overview.md | 11 +- ...workspace-root-page-bundle-checklist-v1.md | 4 + ...-first-graph-product-vision-progress-v1.md | 2 +- ...current-priority-execution-checklist-v1.md | 2 +- ...1-8-mvp-post-process-execution-order-v1.md | 413 ++++++++++++++++ ...first-workspace-convex-control-plane-v1.md | 2 +- ...kspace-access-control-productization-v1.md | 6 +- ...-markdown-asset-upload-relative-path-v1.md | 10 +- .../3-17-convex-export-web-entry-v1.md | 4 +- ...7-resource-lifecycle-command-cutover-v1.md | 3 +- ...n-target-resource-identity-checklist-v1.md | 4 +- ...ycle-command-cutover-smoke-checklist-v1.md | 4 +- ...letree-bulk-resource-command-cutover-v1.md | 4 +- ...iletree-open-target-rooturi-contract-v1.md | 4 +- ...-local-folder-sort-order-persistence-v1.md | 4 +- ...l-cloud-explicit-bridge-p3-candidate-v1.md | 2 + ...-path-identity-unification-checklist-v1.md | 5 +- ...-group-resource-tab-safety-checklist-v1.md | 12 +- ...itor-kind-and-smoke-matrix-checklist-v1.md | 4 +- ...tate-and-error-placeholder-checklist-v1.md | 4 +- ...en-resolver-and-onlyoffice-checklist-v1.md | 4 +- ...resource-tab-close-confirm-checklist-v1.md | 4 +- ...or-tab-url-active-identity-checklist-v1.md | 4 +- .../5-22-mindmap-main-editor-tab-kind-v1.md | 4 +- ...23-editor-side-target-secondary-pane-v1.md | 4 +- ...n-editors-lightweight-view-checklist-v1.md | 4 +- ...ditor-tab-keyboard-and-mru-checklist-v1.md | 4 +- ...pce-vscode-reference-adoption-matrix-v1.md | 2 + ...tiptap-notion-like-template-adoption-v1.md | 2 + ...age-aggregate-single-truth-alignment-v1.md | 2 + ...e-main-editor-experience-restoration-v1.md | 1 + ...ndmap-leptos-adapter-reference-notes-v1.md | 2 + ...-block-ai-real-smoke-followup-matrix-v1.md | 6 +- ...30-acp-session-load-resume-checklist-v1.md | 4 +- ...p-permission-decision-loop-checklist-v1.md | 0 ...l-location-and-open-action-checklist-v1.md | 4 +- ...3-acp-session-info-plan-ui-checklist-v1.md | 4 +- ...block-ai-tooling-execution-checklist-v1.md | 2 +- ...-acp-session-convex-sharing-contract-v1.md | 4 +- .../7-28-resource-ai-tool-contract-v1.md | 4 +- .../process/wolai-aline-test-flow-v1.md | 2 + ...yuan-reference-boundary-and-adoption-v1.md | 2 + ...15-design-governance-mvp-post-review-v1.md | 131 ++++++ ...12-sidex-vscode-workbench-gap-review-v1.md | 30 +- .../13-hermes-vscode-plugin-gap-review-v1.md | 12 +- ...-sidex-workbench-gap-followup-review-v1.md | 8 +- design/README.md | 10 +- .../src/routes/local_folder_source.rs | 439 +++++++++++++++++- rust/crates/mnote-web/src/routes/web_shell.rs | 18 +- rust/crates/mnote-web/src/ssr/pages/layout.rs | 261 ++++++++++- ...tree-editor-context-menu-download-smoke.js | 308 ++++++++++++ 69 files changed, 3390 insertions(+), 111 deletions(-) create mode 100644 .codegraph/.gitignore create mode 100644 .codegraph/config.json create mode 100644 .codex/reasonix-tasks/2026-05-21-browser-test-filetree-editor-context-menu.md create mode 100644 .codex/reasonix-tasks/2026-05-21-design-governance-worker-a-01-03.md create mode 100644 .codex/reasonix-tasks/2026-05-21-design-governance-worker-b-04-tree.md create mode 100644 .codex/reasonix-tasks/2026-05-21-design-governance-worker-c-05-06-08-09.md create mode 100644 .codex/reasonix-tasks/2026-05-21-design-governance-worker-d-07-10.md create mode 100644 .codex/reasonix-tasks/results/design-governance-worker-a-01-03.md create mode 100644 .codex/reasonix-tasks/results/design-governance-worker-b-04-tree.md create mode 100644 .codex/reasonix-tasks/results/design-governance-worker-c-05-06-08-09.md create mode 100644 .codex/reasonix-tasks/results/design-governance-worker-d-07-10.md create mode 100644 auth-console.txt create mode 100644 auth-network.txt create mode 100644 bugs/04-tree-domain/done/4-46-filetree-editor-context-menu-download-v1.md rename design/01-tree-first-graph-kernel/{process => done}/1-7-local-workspace-root-page-bundle-checklist-v1.md (97%) create mode 100644 design/01-tree-first-graph-kernel/process/1-8-mvp-post-process-execution-order-v1.md rename design/02-convex-rust-long-term-architecture/{process => done}/2-3-local-workspace-access-control-productization-v1.md (92%) rename design/03-rust-web/{process => done}/3-15-local-markdown-asset-upload-relative-path-v1.md (92%) rename design/04-tree-domain/{process => done}/4-27-resource-lifecycle-command-cutover-v1.md (98%) rename design/04-tree-domain/{process => done}/4-39-filetree-open-target-resource-identity-checklist-v1.md (94%) rename design/04-tree-domain/{process => done}/4-40-resource-lifecycle-command-cutover-smoke-checklist-v1.md (93%) rename design/04-tree-domain/{process => done}/4-41-filetree-bulk-resource-command-cutover-v1.md (93%) rename design/04-tree-domain/{process => done}/4-42-filetree-open-target-rooturi-contract-v1.md (92%) rename design/04-tree-domain/{process => done}/4-43-local-folder-sort-order-persistence-v1.md (95%) rename design/05-editor-mainline/{process => done}/5-15-local-markdown-path-identity-unification-checklist-v1.md (98%) rename design/05-editor-mainline/{process => done}/5-16-editor-group-resource-tab-safety-checklist-v1.md (77%) rename design/05-editor-mainline/{process => done}/5-17-resource-editor-kind-and-smoke-matrix-checklist-v1.md (94%) rename design/05-editor-mainline/{process => done}/5-18-main-editor-tab-state-and-error-placeholder-checklist-v1.md (96%) rename design/05-editor-mainline/{process => done}/5-19-resource-open-resolver-and-onlyoffice-checklist-v1.md (94%) rename design/05-editor-mainline/{process => done}/5-20-resource-tab-close-confirm-checklist-v1.md (94%) rename design/05-editor-mainline/{process => done}/5-21-editor-tab-url-active-identity-checklist-v1.md (94%) rename design/05-editor-mainline/{process => done}/5-22-mindmap-main-editor-tab-kind-v1.md (96%) rename design/05-editor-mainline/{process => done}/5-23-editor-side-target-secondary-pane-v1.md (95%) rename design/05-editor-mainline/{process => done}/5-24-open-editors-lightweight-view-checklist-v1.md (93%) rename design/05-editor-mainline/{process => done}/5-25-editor-tab-keyboard-and-mru-checklist-v1.md (96%) rename design/07-ai/{process => done}/7-16-page-block-ai-real-smoke-followup-matrix-v1.md (97%) rename design/07-ai/{process => done}/7-30-acp-session-load-resume-checklist-v1.md (93%) rename design/07-ai/{process => done}/7-31-acp-permission-decision-loop-checklist-v1.md (100%) rename design/07-ai/{process => done}/7-32-acp-tool-location-and-open-action-checklist-v1.md (94%) rename design/07-ai/{process => done}/7-33-acp-session-info-plan-ui-checklist-v1.md (94%) create mode 100644 design/10-review/done/15-design-governance-mvp-post-review-v1.md create mode 100644 scripts/task476-filetree-editor-context-menu-download-smoke.js diff --git a/.codegraph/.gitignore b/.codegraph/.gitignore new file mode 100644 index 00000000..9de0f169 --- /dev/null +++ b/.codegraph/.gitignore @@ -0,0 +1,16 @@ +# CodeGraph data files +# These are local to each machine and should not be committed + +# Database +*.db +*.db-wal +*.db-shm + +# Cache +cache/ + +# Logs +*.log + +# Hook markers +.dirty diff --git a/.codegraph/config.json b/.codegraph/config.json new file mode 100644 index 00000000..a392e078 --- /dev/null +++ b/.codegraph/config.json @@ -0,0 +1,161 @@ +{ + "version": 1, + "include": [ + "**/*.ts", + "**/*.tsx", + "**/*.js", + "**/*.jsx", + "**/*.py", + "**/*.go", + "**/*.rs", + "**/*.java", + "**/*.c", + "**/*.h", + "**/*.cpp", + "**/*.hpp", + "**/*.cc", + "**/*.cxx", + "**/*.cs", + "**/*.php", + "**/*.rb", + "**/*.swift", + "**/*.kt", + "**/*.kts", + "**/*.dart", + "**/*.svelte", + "**/*.vue", + "**/*.liquid", + "**/*.pas", + "**/*.dpr", + "**/*.dpk", + "**/*.lpr", + "**/*.dfm", + "**/*.fmx", + "**/*.scala", + "**/*.sc" + ], + "exclude": [ + "**/.git/**", + "**/node_modules/**", + "**/vendor/**", + "**/Pods/**", + "**/dist/**", + "**/build/**", + "**/out/**", + "**/bin/**", + "**/obj/**", + "**/target/**", + "**/*.min.js", + "**/*.bundle.js", + "**/.next/**", + "**/.nuxt/**", + "**/.svelte-kit/**", + "**/.output/**", + "**/.turbo/**", + "**/.cache/**", + "**/.parcel-cache/**", + "**/.vite/**", + "**/.astro/**", + "**/.docusaurus/**", + "**/.gatsby/**", + "**/.webpack/**", + "**/.nx/**", + "**/.yarn/cache/**", + "**/.pnpm-store/**", + "**/storybook-static/**", + "**/.expo/**", + "**/web-build/**", + "**/ios/Pods/**", + "**/ios/build/**", + "**/android/build/**", + "**/android/.gradle/**", + "**/__pycache__/**", + "**/.venv/**", + "**/venv/**", + "**/site-packages/**", + "**/dist-packages/**", + "**/.pytest_cache/**", + "**/.mypy_cache/**", + "**/.ruff_cache/**", + "**/.tox/**", + "**/.nox/**", + "**/*.egg-info/**", + "**/.eggs/**", + "**/go/pkg/mod/**", + "**/target/debug/**", + "**/target/release/**", + "**/.gradle/**", + "**/.m2/**", + "**/generated-sources/**", + "**/.kotlin/**", + "**/.dart_tool/**", + "**/.vs/**", + "**/.nuget/**", + "**/artifacts/**", + "**/publish/**", + "**/cmake-build-*/**", + "**/CMakeFiles/**", + "**/bazel-*/**", + "**/vcpkg_installed/**", + "**/.conan/**", + "**/Debug/**", + "**/Release/**", + "**/x64/**", + "**/.pio/**", + "**/release/**", + "**/*.app/**", + "**/*.asar", + "**/DerivedData/**", + "**/.build/**", + "**/.swiftpm/**", + "**/xcuserdata/**", + "**/Carthage/Build/**", + "**/SourcePackages/**", + "**/__history/**", + "**/__recovery/**", + "**/*.dcu", + "**/.composer/**", + "**/storage/framework/**", + "**/bootstrap/cache/**", + "**/.bundle/**", + "**/tmp/cache/**", + "**/public/assets/**", + "**/public/packs/**", + "**/.yardoc/**", + "**/coverage/**", + "**/htmlcov/**", + "**/.nyc_output/**", + "**/test-results/**", + "**/.coverage/**", + "**/.playwright-mcp/**", + "**/.idea/**", + "**/logs/**", + "**/tmp/**", + "**/temp/**", + "**/_build/**", + "**/docs/_build/**", + "**/site/**", + "recycle/**", + "tmp/**", + "test/results/**", + "test-results/**", + "references/**", + "LightRAG/**", + "home/lix/.hermes/skills/note-taking/mnote-block-ai/**", + "home/**", + ".sandbox-home/**", + ".sandbox-tmp/**", + ".convex-tmp/**", + "graphify-out/**", + "design/old/**", + "design/**/reference-code/**", + "reference-code/**", + "rust/spikes/**/generated/**", + "rust/spikes/**/dist/**" + ], + "languages": [], + "frameworks": [], + "maxFileSize": 1048576, + "extractDocstrings": true, + "trackCallSites": true +} diff --git a/.codex/reasonix-tasks/2026-05-21-browser-test-filetree-editor-context-menu.md b/.codex/reasonix-tasks/2026-05-21-browser-test-filetree-editor-context-menu.md new file mode 100644 index 00000000..6bc4c78d --- /dev/null +++ b/.codex/reasonix-tasks/2026-05-21-browser-test-filetree-editor-context-menu.md @@ -0,0 +1,79 @@ +你是浏览器测试员,不是代码修改员。 + +项目根目录:`/mnt/Data1T/mnote` +被测入口:`http://127.0.0.1:3000` +输出目录:`/mnt/Data1T/mnote/tmp/reasonix-filetree-editor-context-menu-2026-05-21` + +## 硬性约束 + +- 禁止修改源码、禁止 git commit、禁止回滚文件。 +- 使用真实浏览器或 Playwright 验证。 +- 使用全新的 isolated browser context / userDataDir;测试开始前清空 cookies、localStorage、sessionStorage、IndexedDB 和 Cache Storage。 +- 所有截图和 `result.json` 写入输出目录。 +- 失败时保留 console/network 摘要。 +- 最终只汇报测试证据,不提出未经验证的代码结论。 + +## 登录 + +- 打开 `/auth`。 +- 优先点击“测试账号快速登录”。 +- 默认测试账号:`mnote.e2e@example.com` / `MnoteE2E123!`。 + +## 测试目标 + +验证当前 MNote 的: + +1. 文件树右键菜单实际显示哪些动作,尤其是文件/asset 行是否有“下载”。 +2. 文件树右键菜单点击后是否有可观测状态变化: + - `data-mnote-filetree-last-action` + - `data-mnote-filetree-last-action-status` + - `data-mnote-filetree-readonly-blocked` + - 是否发生新 tab / 下载 / network 请求。 +3. 主编辑器附件 hover 菜单与更多菜单: + - hover 附件后是否出现 `[data-testid="mnote-attachment-actions"]`。 + - 点击更多菜单后是否出现 `[data-testid="mnote-tree-context-menu"][data-kind="attachment"]`。 + - attachment 菜单是否有“下载 / 弹窗预览 / 弹窗编辑 / 新窗口编辑”等动作。 + +## 推荐操作步骤 + +1. 打开主页面或任意 `/documents/...` 页面,确保侧边栏和文件树可见。 +2. 如果当前没有明显文件/asset 行,可上传或使用页面内已有附件;也可以创建一个临时 `.txt` / `.md` / `.docx` 资源,但必须记录文件名。 +3. 右键文件树中的 page/document 行,截图菜单并记录所有 menu item 文案与 `data-action`。 +4. 右键文件树中的 asset/resource 文件行,截图菜单并记录所有 menu item 文案与 `data-action`;重点确认是否有 `download`。 +5. 如菜单里有“下载”,点击并记录 download 事件或 network;如没有,标记为 `FAIL:filetree_asset_download_action_missing`。 +6. 在主编辑器找到附件块或上传一个小附件,hover 后截图附件 hover actions。 +7. 点击附件 hover actions 的更多按钮,截图 attachment context menu 并记录 actions。 + +## result.json 格式 + +```json +{ + "ok": false, + "url": "", + "assertions": [ + {"name": "filetree_doc_context_menu_visible", "status": "pass|fail|blocked", "details": ""}, + {"name": "filetree_asset_context_menu_visible", "status": "pass|fail|blocked", "details": ""}, + {"name": "filetree_asset_download_action", "status": "pass|fail|blocked", "details": ""}, + {"name": "editor_attachment_hover_actions", "status": "pass|fail|blocked", "details": ""}, + {"name": "editor_attachment_context_menu", "status": "pass|fail|blocked", "details": ""} + ], + "menus": { + "filetreeDocument": [], + "filetreeAsset": [], + "editorAttachment": [] + }, + "screenshots": [], + "consoleErrors": [], + "networkFailures": [], + "downloads": [] +} +``` + +## 最终回复 + +请只汇报: + +- `result.json` 路径 +- 截图路径 +- 每个断言的 pass/fail/blocked +- 关键失败证据 diff --git a/.codex/reasonix-tasks/2026-05-21-design-governance-worker-a-01-03.md b/.codex/reasonix-tasks/2026-05-21-design-governance-worker-a-01-03.md new file mode 100644 index 00000000..ffc018ac --- /dev/null +++ b/.codex/reasonix-tasks/2026-05-21-design-governance-worker-a-01-03.md @@ -0,0 +1,49 @@ +# Reasonix Worker A:design 01/02/03 审查 + +Project root: `/mnt/Data1T/mnote` + +## 目标 + +审查以下范围内的 `process/` 文档,判断哪些应继续执行、哪些应标为参考、哪些应归档到 `done/` 或 `old/process/`: + +- `design/01-tree-first-graph-kernel/process/` +- `design/02-convex-rust-long-term-architecture/process/` +- `design/03-rust-web/process/` + +## 当前统一口径 + +MNote 已进入 local-first MVP 后阶段。本地 workspace folder 是默认数据真相,本地 `.md` 是页面正文真相,Rust kernel / projection / command 持有语义,`mnote-web` 是 3000 主入口,Convex 只是 auth/share/sync/ACP/cloud/compat 控制面。旧 Next / BlockNote / Convex 默认主存储口径只能作为 legacy / compat 背景。 + +## 只读要求 + +- 不要移动、修改或删除 `design/` 文件。 +- 只创建或更新你的报告文件:`.codex/reasonix-tasks/results/design-governance-worker-a-01-03.md`。 +- 不要提交 git,不要回滚任何文件。 + +## 审查输出格式 + +对每个 process 文档给出: + +```text +文件: +建议状态:keep-process | mark-process-reference | archive-done | archive-old-process | needs-human-confirmation +证据: +归档/保留原因: +建议补充说明: +``` + +重点检查: + +- 是否仍用旧 Convex 默认存储 / Next 主入口 / BlockNote 默认编辑器口径。 +- 是否已被 `design/01-05-current-priority-overview.md`、`ARCHITECTURE.md`、`CURRENT_ARCHITECTURE.md` 或同域 done 文档覆盖。 +- 若建议 `archive-done`,必须指出文档内或仓库中可见的完成证据。 +- 若建议 `archive-old-process`,必须指出覆盖它的新文档。 + +## 验证命令 + +可运行只读命令: + +- `find design/01-tree-first-graph-kernel/process design/02-convex-rust-long-term-architecture/process design/03-rust-web/process -maxdepth 1 -type f -name '*.md' | sort` +- `rg -n "Convex|Next|BlockNote|MVP|当前状态|状态|待完成|未完成|已完成|smoke|done|process" ` + +最终回复只说明报告路径和最高风险项。 diff --git a/.codex/reasonix-tasks/2026-05-21-design-governance-worker-b-04-tree.md b/.codex/reasonix-tasks/2026-05-21-design-governance-worker-b-04-tree.md new file mode 100644 index 00000000..f2f03284 --- /dev/null +++ b/.codex/reasonix-tasks/2026-05-21-design-governance-worker-b-04-tree.md @@ -0,0 +1,45 @@ +# Reasonix Worker B:design 04 tree-domain 审查 + +Project root: `/mnt/Data1T/mnote` + +## 目标 + +审查 `design/04-tree-domain/process/` 下所有 process 文档,判断哪些仍需继续执行,哪些已经可归档到 `done/`,哪些应降级到 `old/process/`。 + +## 当前统一口径 + +MNote 已进入 local-first MVP 后阶段。树域长期口径是 Resource Tree -> File Tree -> Page Tree,Rust kernel / projection / command 是语义 owner。新增能力优先走 `tree.*` / `tree.resource.*`,文件树不能重新制造第二份资源真相。旧 Convex 主存储口径只作为 compat/cloud 背景。 + +## 只读要求 + +- 不要移动、修改或删除 `design/` 文件。 +- 只创建或更新你的报告文件:`.codex/reasonix-tasks/results/design-governance-worker-b-04-tree.md`。 +- 不要提交 git,不要回滚任何文件。 + +## 审查输出格式 + +对每个 process 文档给出: + +```text +文件: +建议状态:keep-process | mark-process-reference | archive-done | archive-old-process | needs-human-confirmation +证据: +归档/保留原因: +建议补充说明: +``` + +重点检查: + +- `4-27` 到 `4-45` 是否已被近期 bug done、smoke 或代码改动覆盖。 +- open target、trash、resource lifecycle、bulk delete、download、drop upload、open editors lifecycle 是否仍有未完成项。 +- 若建议 `archive-done`,必须指出对应 done bug/smoke 或文件内完成证据。 +- 若证据只来自当前未提交文件,标明“依赖未提交变更”,不要直接判定已稳定。 + +## 验证命令 + +可运行只读命令: + +- `find design/04-tree-domain/process -maxdepth 1 -type f -name '*.md' | sort` +- `rg -n "状态|待完成|未完成|已完成|smoke|task|bugs/04|tree.resource|download|drop|open target|trash|bulk" design/04-tree-domain/process bugs/04-tree-domain scripts` + +最终回复只说明报告路径和最高风险项。 diff --git a/.codex/reasonix-tasks/2026-05-21-design-governance-worker-c-05-06-08-09.md b/.codex/reasonix-tasks/2026-05-21-design-governance-worker-c-05-06-08-09.md new file mode 100644 index 00000000..1542cb1c --- /dev/null +++ b/.codex/reasonix-tasks/2026-05-21-design-governance-worker-c-05-06-08-09.md @@ -0,0 +1,52 @@ +# Reasonix Worker C:design 05/06/08/09 审查 + +Project root: `/mnt/Data1T/mnote` + +## 目标 + +审查以下范围内的 process 文档: + +- `design/05-editor-mainline/process/` +- `design/06-mindmap/process/` +- `design/08-wolai-aline-test-flow/process/` +- `design/09-siyuan-reference/process/` + +判断哪些仍需继续执行,哪些应归档到 `done/`,哪些应降级到 `old/process/`,哪些只应标为参考流程。 + +## 当前统一口径 + +MNote 已进入 local-first MVP 后阶段。文档页默认主编辑器是 `leptos-tiptap` island,但本地 `.md` 才是正文真相。主编辑器 tab / resource open / OnlyOffice / mindmap 都应服从 Resource Tree 和 Page Aggregate,不恢复 BlockNote 或 Next App Router 主链。 + +## 只读要求 + +- 不要移动、修改或删除 `design/` 文件。 +- 只创建或更新你的报告文件:`.codex/reasonix-tasks/results/design-governance-worker-c-05-06-08-09.md`。 +- 不要提交 git,不要回滚任何文件。 + +## 审查输出格式 + +对每个 process 文档给出: + +```text +文件: +建议状态:keep-process | mark-process-reference | archive-done | archive-old-process | needs-human-confirmation +证据: +归档/保留原因: +建议补充说明: +``` + +重点检查: + +- `5-15` 到 `5-26` 是否仍是当前 tab/resource/OnlyOffice/mindmap 主执行清单,还是已被近期代码和 smoke 覆盖。 +- `5-2 / 5-7 / 5-9` 是否仍应保留为 Wolai/tiptap 体验持续参考,而非当前执行 checklist。 +- `06-mindmap` 是否有已完成项应进入 done,是否仍有内核化/adapter 未完成。 +- `08-wolai` 和 `09-siyuan` 是否应保持 process 作为流程/参考,还是需要改为 reference/done。 + +## 验证命令 + +可运行只读命令: + +- `find design/05-editor-mainline/process design/06-mindmap/process design/08-wolai-aline-test-flow/process design/09-siyuan-reference/process -maxdepth 1 -type f -name '*.md' | sort` +- `rg -n "BlockNote|Next|leptos|tiptap|OnlyOffice|mindmap|tab|resource|状态|待完成|未完成|已完成|smoke|task" design/05-editor-mainline/process design/06-mindmap/process design/08-wolai-aline-test-flow/process design/09-siyuan-reference/process bugs scripts` + +最终回复只说明报告路径和最高风险项。 diff --git a/.codex/reasonix-tasks/2026-05-21-design-governance-worker-d-07-10.md b/.codex/reasonix-tasks/2026-05-21-design-governance-worker-d-07-10.md new file mode 100644 index 00000000..687eecd2 --- /dev/null +++ b/.codex/reasonix-tasks/2026-05-21-design-governance-worker-d-07-10.md @@ -0,0 +1,50 @@ +# Reasonix Worker D:design 07/10 审查 + +Project root: `/mnt/Data1T/mnote` + +## 目标 + +审查以下范围内的 process 文档: + +- `design/07-ai/process/` +- `design/10-review/process/` + +判断哪些仍需继续执行,哪些应归档到 `done/`,哪些应降级到 `old/process/`,哪些只应保留为参考审查。 + +## 当前统一口径 + +MNote 已进入 local-first MVP 后阶段。local-first 普通 Markdown 编辑主路径是 `currentFile + selection + allowedRoots / aiAccessScope + agent 原生 patch/diff + watcher 同步`。`mnote.doc.*` / `mnote.block.*` 是 cloud、remote、compat 或复杂结构辅助。Hermes/Reasonix 是 agent runtime;不要恢复 page-ai fast-path 或 BlockNote AI 主链作为默认方向。 + +## 只读要求 + +- 不要移动、修改或删除 `design/` 文件。 +- 只创建或更新你的报告文件:`.codex/reasonix-tasks/results/design-governance-worker-d-07-10.md`。 +- 不要提交 git,不要回滚任何文件。 + +## 审查输出格式 + +对每个 process 文档给出: + +```text +文件: +建议状态:keep-process | mark-process-reference | archive-done | archive-old-process | needs-human-confirmation +证据: +归档/保留原因: +建议补充说明: +``` + +重点检查: + +- `7-10 / 7-12 / 7-14 / 7-15 / 7-16 / 7-17 / 7-27` 是否仍代表活跃 AI 主线,或已被 local-first 文件编辑与 ACP runtime 完成态覆盖。 +- `7-30` 到 `7-33` 是否是当前 ACP session 后续产品化 checklist。 +- `10-review/process/12-14` 是否已被后续执行/bug 修复覆盖,应归档到 done 或保留为 gap review。 +- 标出任何仍把 `mnote.doc.markdown_edit`、`page-ai/block-edit-workflow`、BlockNote AI 或 Convex session 全文当默认主路径的段落。 + +## 验证命令 + +可运行只读命令: + +- `find design/07-ai/process design/10-review/process -maxdepth 1 -type f -name '*.md' | sort` +- `rg -n "mnote\\.doc|mnote\\.block|page-ai|block-edit-workflow|BlockNote|Hermes|Reasonix|ACP|Convex|状态|待完成|未完成|已完成|smoke|task" design/07-ai/process design/10-review/process bugs scripts` + +最终回复只说明报告路径和最高风险项。 diff --git a/.codex/reasonix-tasks/results/design-governance-worker-a-01-03.md b/.codex/reasonix-tasks/results/design-governance-worker-a-01-03.md new file mode 100644 index 00000000..4db6b248 --- /dev/null +++ b/.codex/reasonix-tasks/results/design-governance-worker-a-01-03.md @@ -0,0 +1,271 @@ +# Design Governance Report — Worker A + +审查范围:`design/01-tree-first-graph-kernel/process/`、`design/02-convex-rust-long-term-architecture/process/`、`design/03-rust-web/process/` + +审查时间:2026-06-17 + +上位依据:`design/01-05-current-priority-overview.md`(2026-05-21 口径) + +--- + +## 总览 + +| 域 | 文件 | 建议状态 | 高风险项 | +|----|------|---------|---------| +| 01 | 1-tree-first-graph-kernel-v1.md | **keep-process** | 无 | +| 01 | 1-1-tree-first-graph-kernel-checklist-v2.md | **keep-process** | 无 | +| 01 | 1-2-tree-first-graph-product-vision-progress-v1.md | **keep-process** | 无 | +| 01 | 1-3-current-priority-execution-checklist-v1.md | **keep-process** | 无 | +| 01 | 1-4-next-phase-execution-roadmap-v1.md | **keep-process** | 无 | +| 01 | 1-5-next-phase-sequential-execution-checklist-v1.md | **keep-process** | ⚠ 多数 items 未勾选 | +| 01 | 1-6-next-phase-gap-closure-checklist-v1.md | **keep-process** | ⚠ browser smokes 未复跑 | +| 01 | 1-7-local-workspace-root-page-bundle-checklist-v1.md | **archive-done** | 无 | +| 02 | 2-3-local-workspace-access-control-productization-v1.md | **archive-done** | 无 | +| 03 | 3-rust-web-long-term-architecture-v1.md | **keep-process** | 无 | +| 03 | 3-1-rust-web-long-term-checklist-v2.md | **keep-process** | 无 | +| 03 | 3-3-rust-web-tree-realtime-event-stream-v1.md | **keep-process** | 无 | +| 03 | 3-13-rust-web-local-markdown-gfm-ast-parser-migration-v1.md | **keep-process** | ⚠ 3 unsub items | +| 03 | 3-15-local-markdown-asset-upload-relative-path-v1.md | **keep-process** | ⚠ browser smoke 待补 | +| 03 | 3-17-convex-export-web-entry-v1.md | **needs-human-confirmation** | ⚡ 设计稿但未落码 | + +--- + +## 逐文件审查 + +--- + +### 1. `01-tree-first-graph-kernel/process/1-tree-first-graph-kernel-v1.md` + +**建议状态:keep-process** + +**证据:** `01-05-current-priority-overview.md` §1 明确列为当前仍然有效的 3 份上位主线之一。文档更新于 2026-04-22,全文固定 "tree-first graph kernel" 长期方向,不使用旧 Convex 默认存储 / Next 主入口 / BlockNote 默认编辑器口径。 + +**归档/保留原因:** 仍是 tree-first kernel 方向的权威定义文档,所有后续设计以此为基础。 + +--- + +### 2. `01-tree-first-graph-kernel/process/1-1-tree-first-graph-kernel-checklist-v2.md` + +**建议状态:keep-process** + +**证据:** `01-05-current-priority-overview.md` §3 明确列为"仍然有效但不在第一线"的 process 文档。文档头部已标注优先级转移说明,使用 `DONE / PARTIAL / NOT_STARTED` 状态体系,反映真实代码状态而非乐观口径。 + +**归档/保留原因:** 仍是 Kernel Phase 全景参考,Phase 0-3 已 DONE,Phase 4-9 进度清晰。 + +--- + +### 3. `01-tree-first-graph-kernel/process/1-2-tree-first-graph-product-vision-progress-v1.md` + +**建议状态:keep-process** + +**证据:** 更新于 2026-05-19,引用 `01-05-current-priority-overview.md` 作为上位依据。使用 local-first MVP 后阶段口径。包含对照 Wolai 帮助中心的详细块类型覆盖率、AI 操作能力缺口分析,以及三条架构收口主线的进度百分比。 + +**归档/保留原因:** 唯一一份产品愿景 vs 实现进展的对照文档,提供 gap analysis(AI 操作思维导图 / Office 文件),对产品规划仍有参考价值。 + +--- + +### 4. `01-tree-first-graph-kernel/process/1-3-current-priority-execution-checklist-v1.md` + +**建议状态:keep-process** + +**证据:** `01-05-current-priority-overview.md` §6 明确引用为"持续推进 checklist"。文档 P0-P6 全部勾选且有验证证据(单测命令 + smoke 脚本),P7 已完成阶段性瘦身。迁移到 done/ 的标准清晰列出并已满足。 + +**归档/保留原因:** 当前最活跃的执行清单,`1-3` 本身作为持续执行清单保留在 process/,已完成的上位设计各自在 done/。 + +--- + +### 5. `01-tree-first-graph-kernel/process/1-4-next-phase-execution-roadmap-v1.md` + +**建议状态:keep-process** + +**证据:** 更新于 2026-05-19,引用 `01-05` 和 `1-3`。全文核心论点是"架构收口为主,产品化闭环为辅",与当前 local-first MVP 后阶段口径完全一致。 + +**归档/保留原因:** 定义了下一阶段路线图(WorkspacePath → BufferStore → Page Aggregate kernel-native → command context → live cache),仍有效。 + +--- + +### 6. `01-tree-first-graph-kernel/process/1-5-next-phase-sequential-execution-checklist-v1.md` + +**建议状态:keep-process** + +**证据:** 更新于 2026-05-19,引用 `1-4` 和 `1-3`。所有执行原则和 Phase A0-A7 定义仍符合当前架构方向。 + +**归档/保留原因:** 提供了顺序执行的详细清单,且多数 items 尚未勾选,说明它是未来的工作计划而非已完成的文档。 + +**⚠ 风险:** 全场 687 行,大多数 checkbox 未勾选。如果团队当前执行顺序已不按此 checklist 推进(例如优先做 BufferStore 而非 smokes),应降级为 `mark-process-reference` 或更新本文件。当前保留但需关注执行偏差。 + +--- + +### 7. `01-tree-first-graph-kernel/process/1-6-next-phase-gap-closure-checklist-v1.md` + +**建议状态:keep-process** + +**证据:** 创建于 2026-05-19,状态 `PARTIALLY_DONE`。引用 `1-5` 和 `1-4`。基于 Reasonix 执行汇总的缺口补齐文档。 + +**归档/保留原因:** 记录了哪些缺口已由 Reasonix 执行填补(BufferStore、CommandContext、export/rollback),哪些仍未完成(browser smokes、conflict UI)。仍有跟踪价值。 + +**⚠ 风险:** §6.2 列出 6 个未复跑的 browser smokes。这些 smokes 如果长期不跑,本文件的剩余项跟踪价值下降。 + +--- + +### 8. `01-tree-first-graph-kernel/process/1-7-local-workspace-root-page-bundle-checklist-v1.md` + +**建议状态:archive-done** + +**证据:** +- 所有 checklist items 全部勾选并附验证命令 +- 8 个 Rust 单测通过(`create_default_local_workspace_uses_managed_data_root`、`local_tree_command_create_page_creates_timestamped_markdown_and_sibling_folder` 等) +- HTTP smoke 通过:`node scripts/task443-local-markdown-asset-upload-smoke.js` +- 浏览器验收完成(截图保存到 `tmp/page-bundle-*`) +- 磁盘结构验证通过:无默认 `pages/`、`assets/`、`mindmaps/` 顶层目录,新建页面带 `HHMMSS` 时间戳 + +**代码证据:** +- `create_default_local_workspace_for_actor_at_base` — `local_folder_source.rs:1494` +- 同名目录作为页面子容器 — 单测 `local_page_tree_uses_sibling_directory_as_page_children_container` +- 文件树不合并 `.md` 和同名目录 — 单测 `local_file_tree_keeps_markdown_and_sibling_folder_as_separate_rows` + +**归档/保留原因:** 所有目标已实现且有验收证据。设计稿的收口方向是"默认工作区只保留用户内容根目录和 `.mnote/` 系统目录"——已落地。 + +--- + +### 9. `02-convex-rust-long-term-architecture/process/2-3-local-workspace-access-control-productization-v1.md` + +**建议状态:archive-done** + +**证据:** +- A1-A5 (P0 管理员控制面 API):全部完成,`GET/POST/DELETE /api/admin/access-policy/*` 已实现并有单测覆盖 +- B1-B5 (P1 权限覆盖审计):全部完成,覆盖 local folder open、page body write、tree command、Hermes/Reasonix、shared AI session +- `01-05-current-priority-overview.md` §5 已将"管理员目录授权 UI/API"列为"已有最小实现与验证证据,不再作为当前第一优先级" + +**代码证据:** +- `rust/crates/mnote-web/src/routes/local_folder_source.rs` — `add_local_access_grant_for_context`、`create_local_access_grant`、`ensure_local_workspace_access_for_actor` +- `rust/crates/mnote-web/src/ssr/pages/admin.rs` — 管理员 UI +- `rust/crates/mnote-web/src/routes/mod.rs` — route registration + +**归档/保留原因:** 所有 checklist items 已勾选并有单测/代码证据。文档中定义的产品化目标(管理员可管理、agent 可复用、全入口不绕过)已实现。后续 access control 扩展可在新文档中推进。 + +--- + +### 10. `03-rust-web/process/3-rust-web-long-term-architecture-v1.md` + +**建议状态:keep-process** + +**证据:** `01-05-current-priority-overview.md` §1 明确列为当前仍然有效的 3 份上位主线之一。更新于 2026-05-09,定义了 Rust Web 分层方案(axum + Leptos Islands + Hermes)。 + +**归档/保留原因:** 仍是 Rust Web 架构的权威定义文档。 + +--- + +### 11. `03-rust-web/process/3-1-rust-web-long-term-checklist-v2.md` + +**建议状态:keep-process** + +**证据:** `01-05-current-priority-overview.md` §3 明确列为"仍然有效但不在第一线"。文档头部已标注优先级转移说明(2026-04-22 + 2026-04-29 复核补充)。Phase 0-8 状态清晰(DONE / PARTIAL / NOT_STARTED),反映真实代码状态。 + +**归档/保留原因:** 仍是 Rust Web 实施全景参考。Phase 1 接近 DONE、Phase 8 接近 DONE、Phase 3/4/5/7 为 PARTIAL。 + +--- + +### 12. `03-rust-web/process/3-3-rust-web-tree-realtime-event-stream-v1.md` + +**建议状态:keep-process** + +**证据:** `01-05-current-priority-overview.md` §2.3 引用为 Tree Realtime 当前状态文档。2026-05-18 补充 local-first 口径,明确 Convex realtime substrate 仅适用于 convex_workspace。有明确 §9.2 "仍未完成" 说明不能移到 done/。 + +**归档/保留原因:** 有明确 §9.2 未完成项(Sidebar/page subtree/filetree 未全部统一到同一条 live stream cache;WS 尚未成为主实时链路)。README 中明确了 done/ 迁移前必须完成的验收标准。无风险。 + +--- + +### 13. `03-rust-web/process/3-13-rust-web-local-markdown-gfm-ast-parser-migration-v1.md` + +**建议状态:keep-process** + +**证据:** 文档自身 §11.9 明确标注 "[ ] 迁移完成后把设计稿状态从 process 移到 done" 未勾选。剩余 3 项未完成: +1. "[ ] 新增 web shell 单测,验证 legacy <-> Tiptap 转换不丢 marks" +2. "[ ] 把手写 parser 标记为过渡实现" +3. "[ ] 移除 web_shell.rs 中只为补丁存在的临时适配分支" + +**归档/保留原因:** 迁移工作未完成。当前读侧 AST 已就位,写侧回 Markdown 仍部分依赖手写规则。无口径冲突。 + +**⚠ 风险:** 3 个未完成项如果长期无人推进,应降级为 `mark-process-reference`。但当前准确。 + +--- + +### 14. `03-rust-web/process/3-15-local-markdown-asset-upload-relative-path-v1.md` + +**建议状态:keep-process** + +**证据:** 文档状态头明确标注"(代码、单测和 HTTP smoke 已完成;真实浏览器 smoke 待补)"。§9 checklist 中除浏览器 smoke 外所有项目已勾选。实现与缺陷闭环在 `bugs/03-rust-web/done/3-16`。 + +**代码证据:** +- `local_markdown_asset_upload_copies_next_to_markdown_with_relative_path` 单测通过 +- HTTP smoke 覆盖 `/api/local-folder/assets/upload` 真实 multipart 上传 + +**归档/保留原因:** 代码实现已完成,仅缺浏览器 smoke 这一最终验收。应该保持 process 直到 browser smoke 通过。 + +**⚠ 风险:** 缺少浏览器 smoke 意味着编辑器上传面板的实际用户体验未验证。低风险。 + +--- + +### 15. `03-rust-web/process/3-17-convex-export-web-entry-v1.md` + +**建议状态:needs-human-confirmation → 候选人:mark-process-reference** + +**证据:** 状态标记为 `process`,但文档是一份 Web 控制台 UI 的设计提案("后续落码顺序"列出 5 步尚未实现的落码步骤)。CLI 工具已存在: +- `scripts/export-convex-workspace-to-local.js` 支持 `--dry-run`、`--manifest`、`--conflict-report`、`--rollback` +- `task444` 和 `task455` smoke 通过 + +`01-05-current-priority-overview.md` §5 已将 P6 (Convex 导出) 列为"已有最小实现与验证证据,不再作为当前第一优先级"。 + +**建议补充说明:** 文档作为 Web 入口设计稿保留在 process/ 会产生"这是当前待办"的误导。建议: + +- **选项 A (推荐):mark-process-reference** — 保持文档但标记为参考设计,不误认为待办。保留 CLI 设计供后续 Web 入口实现时参考。 +- **选项 B:keep-process** — 如果团队明确要在短期内实现 Web 入口。 +- **选项 C:archive-old-process** — 如果决定 CLI 能力已足够,不优先做 Web UI。 + +**⚠ 风险:** 如果标记为 process,用户可能误以为 Web 入口已进入实施状态。实际上 5 个落码步骤均未开始。 + +--- + +## 汇总统计 + +| 建议状态 | 数量 | 文件 | +|---------|------|------| +| keep-process | 11 | 1, 1-1, 1-2, 1-3, 1-4, 1-5, 1-6, 3, 3-1, 3-3, 3-13, 3-15 | +| archive-done | 2 | 1-7, 2-3 | +| needs-human-confirmation | 1 | 3-17 | + +> 注:3-15 虽推荐 keep-process,但其代码实现已完成,仅缺浏览器 smoke;在 browser smoke 通过后应自动进入 archive-done。 + +--- + +## 最高风险项 + +1. **`3-17-convex-export-web-entry-v1.md` (needs-human-confirmation)** — 被标记为 process 但实质是未落码的设计稿,可能误导读者以为 Web 入口在实施中。需人工确认是降级为参考、归档、还是推进落地。 + +2. **`1-5-next-phase-sequential-execution-checklist-v1.md` (keep-process, ⚠)** — 全场 687 行,大多数 checkbox 未勾选。如果执行计划已偏离此 checklist,内容可能过时。 + +3. **`1-6-next-phase-gap-closure-checklist-v1.md` (keep-process, ⚠)** — 6 个 browser smokes 长期未复跑降低剩余项跟踪价值。 + +4. **`3-13-rust-web-local-markdown-gfm-ast-parser-migration-v1.md` (keep-process, ⚠)** — 3 个未完成项如果无人认领,迁移工作将停滞在 partial 状态。 + +--- + +## 验证命令 + +```bash +# 确认文件存在 +find design/01-tree-first-graph-kernel/process design/02-convex-rust-long-term-architecture/process design/03-rust-web/process -maxdepth 1 -type f -name '*.md' | sort +# 确认归档建议的文件已落地代码 +cargo test -p mnote-web create_default_local_workspace_uses_managed_data_root -- --nocapture +cargo test -p mnote-web local_tree_command_create_page_creates_timestamped_markdown_and_sibling_folder -- --nocapture +cargo test -p mnote-web local_workspace_access -- --nocapture +``` + +--- + +## 未完成项 + +- 本报告仅为只读审查,未移动任何 `design/` 文件。 +- 如果审核通过,`1-7` 和 `2-3` 应移至各自域的 `done/`。 +- `3-17` 需人工确认最终状态。 diff --git a/.codex/reasonix-tasks/results/design-governance-worker-b-04-tree.md b/.codex/reasonix-tasks/results/design-governance-worker-b-04-tree.md new file mode 100644 index 00000000..1343eee9 --- /dev/null +++ b/.codex/reasonix-tasks/results/design-governance-worker-b-04-tree.md @@ -0,0 +1,230 @@ +# Design Governance Report — Worker B: 04-tree-domain + +> 生成时间: 2026-05-21 +> 范围: `design/04-tree-domain/process/` 全部 12 个 process 文档 +> 状态: 只读审查,未修改任何 `design/` 文件 + +--- + +## Executive Summary + +审查 12 个 process 文档,建议分布: + +| 状态 | 数量 | 文档 | +|------|------|------| +| archive-done | 6 | 4-27, 4-39, 4-40, 4-41, 4-42, 4-43 | +| keep-process | 5 | 4-28, 4-34, 4-38, 4-44, 4-45 | +| mark-process-reference | 1 | 4-23 | + +**最高风险项**: +1. **4-45 checklist 完全未执行** — 4 项全部未勾选,Worker D 停留在计划阶段未产出任何变更 +2. **4-34 local folder 子项仍无 no-refresh 证据** — Convex 侧已全部通过但 local folder 侧仍标 reload,这是 dual-browser 矩阵的唯一缺口 +3. **4-44 仍有 3 个 browser smoke 未勾选** — Rust 代码改动已合入但 task430/task471 及新增 smoke 未通过 +4. **4-28 React DOM focus 仍缺浏览器证据** — 核心 restore location 已实现但前端焦点行为未验证 + +--- + +## 逐文档审查 + +### 4-23 — Local / Cloud 显式桥接 P3 候选合同 v1 + +**建议状态:mark-process-reference** + +- **证据**: 这是 P3 候选设计合同,定义了 `tree.bridge.import/export/publish/sync` 的 preview/execute 契约。文档无 checklist、无 code change、无 execution record。 +- **归档/保留原因**: 这是一个**候选策略文档**,不是执行 checklist。P3 未排入当前 sprint,文档本身定义了清晰的 done gate(P0/P1/P2 smoke 全绿+preview UI 组件复用+rollback plan+浏览器 smoke)。作为长期架构参考保留比归档更有价值。 +- **补充说明**: 文档质量高,边界定义清晰。建议在文档头部加注 "P3 Candidate — 未进入实现,参考保留"。 + +### 4-27 — Resource Lifecycle Command Cutover v1 + +**建议状态:archive-done** + +- **证据**: + - Section 2 "已落地范围" 列出 7 个已实现条目:`bridge-runtime` 新增 4 个命令计划、`storage-convex-bridge` 映射、`wolai-frontend` `/api/media/batch` 和 `/api/media/purge` 迁移、`mnote-web` Rust 3000 兼容 alias。 + - Section 3 的 "未完成边界"(双浏览器 no-refresh、reveal/focus、projection delta)已由 4-34 和 4-28 覆盖。 + - Section 7 "剩余边界"(rename 仅覆盖附件、empty-trash 仍为批量合同、前端 Sidebar 仍走旧 URL)是有意保留的边界,非未完成任务。 + - Section 8 sub-stage(local_folder file_path resource lifecycle)已全部合入并通过:`tree.resource.archive/restore/purge` 进入 `.mnote/trash`, `trash-index.json`, `task437` smoke 通过。 + - 2026-05-15 验收测试全部:4 项 `cargo test` 通过,2 项 wolai-frontend 测试通过。 +- **归档原因**: 核心命令切换已完成归档。未完成项已明确转移至后续文档(4-34/4-28)或是有意保留的边界(empty-trash 批量合同、rename 范围限制)。 +- **补充说明**: 归档前建议在文件头部加注引用 `4-34`(dual browser)、`4-28`(reveal/focus) 和 `4-37`(trash modal) 作为后续入口。 + +### 4-28 — Trash Restore Location Reveal Focus v1 + +**建议状态:keep-process** + +- **证据**: + - **已落地**:`restore_parent_id`/`restore_sort_order` schema,`documents.softDelete` 快照写入,`documents.restore` 位置恢复与 fallback 逻辑,Sidebar 恢复后展开 focus。 + - **已验证**:4 个测试文件(32 items)通过,`task429` browser smoke 通过覆盖 restore_location/fallback/sort_order 无重复。 + - Section 5 "待补" 仍有 2 项未完成:React Sidebar Drawer 的恢复后 DOM focus 缺浏览器证据;双浏览器 no-refresh 归入阶段 8。 +- **留存原因**: React Sidebar Drawer DOM focus 的浏览器证据未补。当前 3000 主壳 smoke 已覆盖 "恢复后无需刷新可见/reveal",但 "DOM focus" 断言是 React Drawer 代码路径的独立验证。 +- **补充说明**: React DOM focus 是一个较小的缺口,可考虑在下一轮补充一个 focused smoke 后归档。 + +### 4-34 — Filetree Trash Dual Browser No Refresh v1 + +**建议状态:keep-process** + +- **证据**: + - **Convex 侧全部通过**:task432(页面) + task433(附件) + task434(mindmap/table) 双浏览器 no-refresh smoke 全部通过且记录了事件来源。 + - task435(empty-trash 汇总) 作为 3.4 提及但**无独立执行记录**,不过其实已被 task432-434 的 empty-trash 子步骤覆盖。 + - **local folder 侧未完成**:3.5 task436 无执行记录。文档明确标注 "非 md 本地资源文件 watcher / trash / restore / purge 仍单列为待补"。 + - 完成标准要求 local folder 子项也通过 task436 或等价 smoke;当前状态标 reload,不能标 no-refresh。 +- **留存原因**: local folder 双浏览器 no-refresh 仍然是明确缺口。同时 `resync_required` delta 事件合同需在后续新增资源 route 时补回归。 +- **补充说明**: 建议将 Convex 侧已完成的部分在文档内打上标记(如 `[Convex ✓]`),只保留 local folder 暴露项作为未完成。 + +### 4-38 — FileTree Resource Lifecycle 与 Open Target 收口 Checklist v1 + +**建议状态:keep-process** + +- **证据**: + - 已勾选:`local-file:`、`resolve_local_raw_file_id`、`local-file` restore/purge 测试、local mindmap 单独删除入 trash index、local move `sortOrder` 返回 `_unsupportedFields`、Rust `cargo test` 通过。 + - **未勾选**:扩展 File Tree open target、保留 `local:asset:/local:node:` 兼容、前端 delete/bulk delete 优先走 `/api/tree/commands`、browser smoke 覆盖全链、task437 继续通过。 + - 执行记录确认 "未完成:File Tree open target 合同扩展、前端 delete/bulk delete 全面 cutover、浏览器垃圾箱资源行全链 smoke"。 +- **留存原因**: 虽然核心 Rust 变更已合入,但 open target 扩展已有 4-39 承接,前端 cutover 和 browser smoke 尚未完成。需待这些子项完成后归档。 +- **补充说明**: 建议在文档头部加注 "open target 合同由 4-39/4-42 覆盖;bulk delete 由 4-41 覆盖"。本文件的剩余项(前端 cutover + browser smoke)可浓缩后移至更轻量文档。 + +### 4-39 — FileTree Open Target 与 Resource Identity Checklist v1 + +**建议状态:archive-done** + +- **证据**: + - **全部 5 项 checklist 已勾选**:扩展 `FileTreeOpenTarget`(`LocalFile`/`Directory` variant + `resourceKind`)、open target 包含 canonical identity、`parse_open_target_from_row` 兼容旧格式、camelCase 序列化、Rust 单测通过。 + - **验收全部通过**:`cargo test -p mnote-web filetree_runtime`、`runtime_api`、`tree_shell_filetree_mode_embeds_asset_state` 全部通过。 +- **归档原因**: Checklist 全部完成。唯一剩余项 "上游 row dataset 补齐 rootUri" 已由 4-42 承接。 +- **补充说明**: 归档时建议在头部引用 4-42 作为 rootUri 补齐的后续文档。 + +### 4-40 — Resource Lifecycle Command Cutover 与垃圾箱 Smoke Checklist v1 + +**建议状态:archive-done** + +- **证据**: + - 从 7 项 checklist 中,6 项已勾选:旧 route 保留 fallback、command response 保留 artifact、trash workbench 显示 local resource、新增 browser smoke (task437 + task464)。 + - **唯一未勾选项**:"前端 local resource delete/bulk delete 优先调用 `/api/tree/commands` archive/purge/restore" — 这已被 4-41 覆盖。 + - **验收全部通过**:`task437`、`task464` smoke 通过,`cargo test resource_trash` 和 `cargo test tree_command_local_folder` 通过。 +- **归档原因**: 文档定义的 smoke 覆盖率目标已完成。前端 cutover 的剩余项已由 4-41 清晰承接。 + +### 4-41 — FileTree Bulk Resource Command Cutover Checklist v1 + +**建议状态:archive-done** + +- **证据**: + - **全部 6 项 checklist 已勾选**:local file asset/mindmap/table bulk delete 使用 tree command、成功后刷新 snapshot/trash、失败保留错误、smoke 覆盖不同 resource kind。 + - **验收全部通过**:`task464`、`task471` smoke 通过,`cargo test sidebar_filetree_local_table_bulk_delete_uses_tree_command` 和 `tree_command_local_folder` 通过。 +- **归档原因**: Checklist 已全部完成。云端 table 保留旧 fallback 是有意保留的边界(已勾选禁止事项)。 + +### 4-42 — FileTree Open Target RootUri Contract Checklist v1 + +**建议状态:archive-done** + +- **证据**: + - **全部 5 项 checklist 已勾选**:`FileTreeRuntimeEnvironment.rootUri`、`resolve_open_target` 使用 rootUri、SSR state 传入 rootUri、反序列化兼容缺失 rootUri、Rust 单测覆盖。 + - **验收全部通过**:3 项 `cargo test` 全部通过。 +- **归档原因**: 文档定义的 rootUri 合同已全部实现。这是 P0-4 的收尾文档,已经完成其使命。 + +### 4-43 — Local Folder Sort Order Persistence Checklist v1 + +**建议状态:archive-done** + +- **证据**: + - **全部 6 项 checklist 已勾选**:`.mnote/file-order.json` 持久化、move sortOrder 写入、projection 消费排序记录、rename/archive/restore 后清理悬挂项、Rust 合同测试、browser smoke。 + - **验收全部通过**:`cargo test local_tree_command_move_with_sort_order` 和 `tree_command_local_folder` 通过,`task472` smoke 通过。 +- **归档原因**: 完整的端到端实现。`_unsupportedFields.sortOrder` 退役记录清晰。 + +### 4-44 — FileTree Action Layer Paste / Drop / Delete Checklist v1 + +**建议状态:keep-process** + +- **证据**: + - **Rust 代码已合入**:`recordFileTreeAction`/`recordFileTreeActionStatus`、右键 `paste-into` 使用菜单触发 row、`layout.rs` readonly 阻断(data 属性标记 + alert fallback)、paste/drop preflight。 + - **验收 2/5 已勾选**:`cargo test -p mnote-web layout` 和 `tree_command_local_folder` 通过。 + - **3 项验收未勾选**:`node scripts/task430-vscode-explorer-stage7-smoke.js`、`node scripts/task471-local-folder-bulk-resource-trash-smoke.js`、新增 `task47*-filetree-action-layer-smoke.js`。 +- **留存原因**: Rust 侧变更已合入但 browser smoke 验证未通过。task471 的语义断言依赖 environment preparation(4-45 也依赖同一个环境)。无法在不验证 smoke 的情况下确认动作层行为正确。 +- **补充说明**: task430 的 workspace bootstrap 问题(4-45 提及)是前置阻塞项。建议先修复 task430 前置条件,再补全 4-44 的 smoke 验收。 + +### 4-45 — FileTree / Open Editors Lifecycle Link Checklist v1 + +**建议状态:keep-process** + +- **证据**: + - **全部 4 项 checklist 未勾选**: + 1. "跑或修复 task430 / task471 的可复现前置条件" — 未完成 + 2. "明确哪些 smoke 已过、哪些因环境阻塞" — 未完成 + 3. "补充 open editor / filetree active row / lifecycle 状态断言建议" — 未完成 + 4. "更新 4-44 checklist 执行记录" — 未完成 + - 执行记录仅包含 Worker D 的讨论和计划,没有实际变更。 +- **留存原因**: 这是一份非常新的文档(2026-05-21),所有项都未验证。它不是高复杂度文档,但需要解决 task430 workspace bootstrap 前置问题后才能推进。如果 task430 环境问题没有阻碍,可在 1-2 小时内完成所有 4 项。 +- **补充说明**: 这是一份"接通型"文档 — 它的主要目标是让 4-44 的 smoke 验证可复现,以及让 open editor state 可观测。建议优先修 task430 workspace bootstrap,这同时解除 4-44 和 4-45 的阻塞。 + +--- + +## 交叉依赖分析 + +```text +4-23 ──→ P3 候选,不依赖其他文档 +4-27 ──→ 核心 cutover,4-34/4-28 承接其尾项 +4-28 ──→ 依赖 4-27 的 restore 命令,React DOM focus 单列为待补 +4-34 ──→ Convex 侧依赖 4-27 的命令,local folder 侧为独立待补 +4-38 ──→ open target 子项由 4-39/4-42 完成;bulk delete 由 4-41 完成 +4-39 ──→ rootUri 子项由 4-42 完成 [已归档] +4-40 ──→ bulk delete 子项由 4-41 完成 [已归档] +4-41 ──→ 独立完成 [已归档] +4-42 ──→ 独立完成 [已归档] +4-43 ──→ 独立完成 [已归档] +4-44 ──→ 依赖 task430 workspace bootstrap(同 4-45 阻塞项) +4-45 ──→ 依赖 task430 workspace bootstrap +``` + +## 全局风险排序 + +| 风险 | 文档 | 描述 | 影响 | +|------|------|------|------| +| 🔴 高 | 4-45 | 4 项 checklist 全部未勾选,Worker D 无产出 | open editor lifecycle 状态不可观测 | +| 🔴 高 | 4-34 | local folder 双浏览器 no-refresh 未解决 | 唯一未完成的 dual-browser 缺口 | +| 🟡 中 | 4-44 | 3 个 browser smoke 未通过 | action layer 代码合入但未经验证 | +| 🟡 中 | 4-28 | React DOM focus 无浏览器证据 | location restore 已实现但焦点行为未确认 | +| 🟢 低 | 4-38 | 前端 cutover 代码未完成但已有 4-39/4-41 覆盖 | 信息过期风险,非功能缺口 | +| 🟢 低 | 4-23 | P3 候选,从未进入实现 | 无实际风险 | + +--- + +## 建议归档路径(非执行) + +以下仅作为人工决策参考,本次报告**未移动任何文件**: + +| 文档 | 建议目标 | 前提条件 | +|------|----------|----------| +| 4-27 | `done/` | 添加引用 4-34/4-28 的头部注释 | +| 4-39 | `done/` | 添加引用 4-42 的头部注释 | +| 4-40 | `done/` | 无 | +| 4-41 | `done/` | 无 | +| 4-42 | `done/` | 无 | +| 4-43 | `done/` | 无 | +| 4-23 | `process/` 保留 | 添加 "P3 Candidate" 头注 | +| 4-28 | `process/` 保留 | React DOM focus smoke 通过后归档 | +| 4-34 | `process/` 保留 | task436 或等价 local folder no-refresh 证据通过后归档 | +| 4-38 | `process/` 保留 | 剩余前端 cutover + UI smoke 通过后归档 | +| 4-44 | `process/` 保留 | 3 个 smoke 全部通过后归档 | +| 4-45 | `process/` 保留 | 所有 4 项 checklist 完成后归档 | + +--- + +## 报告说明 + +**路径**: `.codex/reasonix-tasks/results/design-governance-worker-b-04-tree.md` + +**只读约束**: 本文只创建了报告文件,未移动、修改或删除任何 `design/` 文件。上述归档/移动建议需由人工或后续 Worker 执行。 + +**验证命令**: +```bash +# 确认报告文件存在 +find .codex/reasonix-tasks/results/ -name '*worker-b*' + +# 确认未修改任何设计文件 +cd /mnt/Data1T/mnote && git diff --stat design/ + +# 复现 Rust 验收(抽样) +cargo test --manifest-path rust/Cargo.toml -p mnote-web filetree_runtime -- --test-threads=1 +cargo test --manifest-path rust/Cargo.toml -p mnote-web tree_command_local_folder -- --test-threads=1 + +# 检查 smoke 脚本语法 +node --check scripts/task430-vscode-explorer-stage7-smoke.js +node --check scripts/task471-local-folder-bulk-resource-trash-smoke.js +``` diff --git a/.codex/reasonix-tasks/results/design-governance-worker-c-05-06-08-09.md b/.codex/reasonix-tasks/results/design-governance-worker-c-05-06-08-09.md new file mode 100644 index 00000000..fc3fb097 --- /dev/null +++ b/.codex/reasonix-tasks/results/design-governance-worker-c-05-06-08-09.md @@ -0,0 +1,254 @@ +# Design Governance Report — Worker C: 05/06/08/09 Review + +> Generated: 2026-05-21 +> Scope: `design/05-editor-mainline/process/`, `design/06-mindmap/process/`, `design/08-wolai-aline-test-flow/process/`, `design/09-siyuan-reference/process/` + +--- + +## Executive Summary + +Of 22 process documents reviewed: + +| Status | Count | Documents | +|--------|-------|-----------| +| archive-done | 10 | 5-15, 5-17, 5-18, 5-19, 5-20, 5-21, 5-22, 5-23, 5-24, 5-25 | +| keep-process | 4 | 5-26, 5-9, 5-6 (partial), 6-mindmap-phase6-leptos-ui-shell-reuse-checklist | +| mark-process-reference | 7 | 5-5, 5-2, 5-7, 5-14, 6-mindmap-leptos-adapter-reference-notes, wolai-aline-test-flow-v1, 9-siyuan-reference | +| needs-human-confirmation | 1 | 5-16 (minor open items, superseded by later docs) | + +**Highest risk**: 5-26 remains unexecuted — convergence plan proposed but never landed. 5-9 Phase F (Page Aggregate items) still shows TODO but the work was done in 5-6 checklists (mild stale reference risk). Mindmap `leptos-mindmap` checklist is large and has visual parity items that haven't been systematically closed. + +--- + +## 1. `design/05-editor-mainline/process/` — 18 files + +### 5-15 — local-markdown-path-identity-unification-checklist-v1 + +**建议状态:archive-done** + +- **证据**: 所有 P0 (3 items) 和 P1 (1 item) checklist 均已勾选完成。文档末尾 `执行状态(2026-05-19)` 列出了所有已完成的验收命令和补充清理(删除 `ParsedLocalMarkdownPage.mnote_id` 字段)。 +- **归档原因**: 这是一个单次执行的 checklist,所有目标均已验证通过。后续身份逻辑若有回归需求,可查阅 `done/`。 +- **补充说明**: 验收命令完整可复现,是存档质量良好的 checklist。 + +### 5-16 — editor-group-resource-tab-safety-checklist-v1 + +**建议状态:needs-human-confirmation** + +- **证据**: 核心项已完成:resource tab MRU、关闭 guard、`aria-selected`/`tabindex`、task460 smoke。剩余未完成项:正式 `MainEditorTabEntry` 模型(后被 5-18 的状态模型覆盖)、打开失败 placeholder tab(后被 5-18 完整实现)。 +- **留存原因**: 未完成的 2 项已被后续 checklists (5-18) 解决,文档本身已无独立未完成工作。 +- **建议**: 若确认 5-18 已完全覆盖 placeholder tab 需求,可归档。需人工确认。 + +### 5-17 — resource-editor-kind-and-smoke-matrix-checklist-v1 + +**建议状态:archive-done** + +- **证据**: 核心项已完成:PDF 独立 badge kind、file fallback badge、smoke 覆盖 markdown/text/code/image/pdf 未知类型。`scripts/task461-resource-kind-smoke.js` 已通过。 +- **归档原因**: 未完成项(正式 resolver 抽象、Office DocumentServer 回源、`active-tab/side/new-window` 统一 resolver)已由 5-19 和 5-26 覆盖。本文件是 5-17→5-19→5-26 递进的第一层,已完成的历史阶段。 + +### 5-18 — main-editor-tab-state-and-error-placeholder-checklist-v1 + +**建议状态:archive-done** + +- **证据**: 所有 checklist 项已勾选。`createResourceSession` 已补 `lastActiveAt`,`openResourceInActiveTab` 失败时保留 error placeholder tab。`task462` smoke 通过。 +- **归档原因**: 未完成项(`lastActiveAt` 未用于排序、dirty guard 无 UI 确认弹窗)属于后续 5-20 的独立主题。本文件闭环了主状态模型和错误占位。 + +### 5-19 — resource-open-resolver-and-onlyoffice-checklist-v1 + +**建议状态:archive-done** + +- **证据**: 所有 checklist 项已执行。`resolveResourceOpen` 已定义,`buildOnlyOfficeOpenUrl` 已修正回源 URL,`task463` smoke 覆盖 docx/pptx/xlsx badge、iframe、new-window。 +- **归档原因**: 唯一未完成项——“未验证真实外部 DocumentServer 协同编辑会话”,这属于外部服务可用性测试而非设计执行,不应阻塞归档。 + +### 5-20 — resource-tab-close-confirm-checklist-v1 + +**建议状态:archive-done** + +- **证据**: 所有 checklist 项已勾选并验证。`showResourceTabCloseGuardNotice` 对 dirty/saving/hasExternalConflict 输出可见提示,`task460` 断言可见提示不堆叠。 +- **归档原因**: 单次执行 checklist,已完成闭环。 + +### 5-21 — editor-tab-url-active-identity-checklist-v1 + +**建议状态:archive-done** + +- **证据**: 所有项已勾选。resource tab 激活写入 `resourceTab=` URL state,filetree row 设 `data-active=true`,`task457` 覆盖全路径。 +- **归档原因**: 单次执行 checklist,已验证。 + +### 5-22 — mindmap-main-editor-tab-kind-v1 + +**建议状态:archive-done** + +- **证据**: 所有项已勾选。`kind=mindmap` resource tab 使用 `resource:mindmap:...` identity,`task169` 从旧 object shell 口径切到 main editor resource tab 口径。 +- **归档原因**: 已执行完成。旧 `task169` realtime 深测被标记为 `retiredRealtimeTopicEditing`,不含未完成工作。 + +### 5-23 — editor-side-target-secondary-pane-v1 + +**建议状态:archive-done** + +- **证据**: 所有项已勾选。`openResourceAsSideTarget`,资源侧栏目标显示 unsupported placeholder,`task472` smoke 通过。 +- **归档原因**: 已完成闭环。 + +### 5-24 — open-editors-lightweight-view-checklist-v1 + +**建议状态:archive-done** + +- **证据**: 所有项已勾选。`mnote.open_editors_snapshot.v1` 已暴露 snapshot 合同。`cargo test -p mnote-web web_shell` 通过。 +- **归档原因**: 执行记录清晰,UI 当前阶段保持为可观测模型(无 sidebar UI 新增)。 + +### 5-25 — editor-tab-keyboard-and-mru-checklist-v1 + +**建议状态:archive-done** + +- **证据**: 所有项已勾选。`bindMainEditorTabStrip()` keyboard roving focus、Enter/Space 激活、关闭后 MRU focus 迁移已实现。`cargo test` 通过。 +- **归档原因**: Reasonix Worker B 输出 proposal,Codex 实现最小安全集。闭环。 + +### 5-26 — resource-open-resolver-convergence-checklist-v1 + +**建议状态:keep-process** + +- **证据**: 执行记录明确记载 "Worker C 进程停留在计划提交阶段,没有生成 final.md"。"审查...重复判断" 和 "给出最小收敛方案" 未完成。Checklist 第 4 节 2 个主项未勾选。 +- **留存原因**: 仍有收敛正文附件与 FileTree 资源打开策略的实际工作待做。但 5-17/5-19 已完成了第一轮 resolver 建设,5-26 的收敛优先级较低。 +- **补充建议**: 可降级为 Low Priority process,或在 next sprint 以 1-2 小时限时任务完成。 + +### 5-5 — page-aggregate-single-truth-alignment-v1 + +**建议状态:mark-process-reference** + +- **证据**: 这是战略设计文档,非 checklist。定义了 Page Aggregate 的 Phase F-J 方向和长期架构目标。文档末尾明确结论 `leptos-tiptap 主编辑区已基本可用,但页面聚合仍未收口`。 +- **保留原因**: 作为长期架构参考仍有效。不应归档为 done(聚合未完全闭环),但也不需要作为 process 继续执行(方向已定)。 +- **建议补充说明**: 建议在文档头部加注 `参考: 当前执行进度见 5-6`。 + +### 5-6 — page-aggregate-alignment-checklist-v1 + +**建议状态:keep-process (partial)** + +- **证据**: 大部分 Phase (F/G/H/I/J) 已基本完成。仍有较小缺口:Phase G 退出标准第 2 项(新增字段不散落 props)未勾选;Phase H 页面设置分类第 2 项(哪些项降级/隐藏)未勾选。 +- **留存原因**: 文档非常大(34KB+),大部分已完成但仍有小缺口。可将这些微小项转到新的浓缩 checklist,或在本文件内更新状态后归档。 +- **建议**: 考虑将已完成的 Phase F/G/H/I/J 子项标记 final,剩余的微量工作移至更轻量文档继续。 + +### 5-2 — tiptap-notion-like-template-adoption-v1 + +**建议状态:mark-process-reference** + +- **证据**: 这不是 checklist,是策略分析文档。结论是 `以后续主编辑器开发继续以 Tiptap 官方能力模型为体验上限...已完成主编辑器接入和 Rust truth 落地`。 +- **保留原因**: 作为 tiptap 编辑器的设计参考和 P0.5 边界定义仍有价值。不应继续视为 process 执行清单。 + +### 5-7 — wolai-page-tree-main-editor-experience-restoration-v1 + +**建议状态:mark-process-reference** + +- **证据**: 文档自身已声明执行口径转交 5-9 checklist 和 `wolai-aline` skill。包含大量 Wolai 参考截图路径和差异矩阵,是重要的取证基线。 +- **保留原因**: 宝贵的 Wolai 对标取证基线记录和交互合同定义,作为 reference 保留。不应继续作为执行 checklist。 + +### 5-9 — wolai-aline-continuous-checklist-v1 + +**建议状态:keep-process** + +- **证据**: 这是活跃的 checklist 文档。Phase A-E 大部分项目已完成(GREEN/PARITY),但存在未完成项: + - **A4** (字体栈与基础 token): 仍为 TODO + - **E18** (Clipboard/Paste): 仍为 TODO + - **F1-F5** (Page Aggregate 相关): 仍为 TODO — 但这些在 5-6 中已大部分完成 + - Phase `已退役` 段落记录了一些退役项 +- **留存原因**: 仍有少量待办项 (A4, E18, F1-F5)。但 F1-F5 的内容实际已在 5-6 中完成,这里存在陈旧风险。 +- **建议**: 审查 F1-F5 是否应追认完成或引用 5-6 的进展;A4 和 E18 可考虑转入低优先级或归档。 + +### 5-14 — zed-lapce-vscode-reference-adoption-matrix-v1 + +**建议状态:mark-process-reference** + +- **证据**: 这是参考采用矩阵设计文档,非 checklist。定义了 Zed/Lapce/VS Code/SideX 各自的参考职责和 P0-P3 实施建议。 +- **保留原因**: 作为代码引用架构的权威参考文档很有价值。不应作为执行 checklist,但推荐作为后续 BufferStore、WorkspacePath 等模块设计时的引用入口。 +- **补充说明**: 文档质量高、边界清晰,建议在 `done/` 也做一份副本或在 05-mainline README 中交叉引用。 + +--- + +## 2. `design/06-mindmap/process/` — 3 files + +### 6-mindmap-kernel-phase6-projection-editor-v1 + +**建议状态:keep-process** + +- **证据**: 这是 Phase 6 mindmap 的主架构设计文档。Phase 6.1 (真实 simple-mind-map runtime cutover) 已于 2026-05-10 完成。但文档定义的长期方向(Rust-native engine 路线)仍是未来可选项。 +- **留存原因**: 定义了三路线定位和 Rust 内核+KMind-like 编辑器的当前主线。仍有 `10.1 现实复核差距` 中记录的 Phase 6.1 尾项(`view_data_change` 持久化、toolbar zoom 绑定、RichText 插件)。 +- **补充说明**: 有 1 份对应的 done checklist (`6-mindmap-phase6-kmind-parity-detail-checklist-v1.md`),建议在 process 文档头部加注引用。 + +### 6-mindmap-leptos-adapter-reference-notes-v1 + +**建议状态:mark-process-reference** + +- **证据**: 这是审计参考笔记,非执行 checklist。详细记录了 lx-doc/mind-map 的 Vue 组件到 kernel command 的映射,以及历史 React UI 壳的能力清单(8 个表格)。 +- **保留原因**: 作为 adapter 实现和命令映射的详细参考,在后续 rust-native UI shell 开发时仍有价值。不应作为执行清单。 + +### 6-mindmap-phase6-leptos-ui-shell-reuse-checklist-v1 + +**建议状态:keep-process** + +- **证据**: 这是活跃的执行 checklist。Task 1-7 和 Task 8 (Context Menu) 大部分已完成。Task 9 (KMind visual parity) 的 9 个子项中有未封闭的项(node style panel、base style panel 等详细面板界面仍有差距)。 +- **留存原因**: 仍有未完成的 visual parity 细节项。KMind 视觉对齐是一个持续的收口过程(对应 `done/` 下的 detail checklist)。 +- **补充说明**: 文档很大(43KB+),建议将已完成 Task 移入 `done/`,只保留未完成的 Task 9+ 视觉差异项。 + +--- + +## 3. `design/08-wolai-aline-test-flow/process/` — 1 file + +### wolai-aline-test-flow-v1 + +**建议状态:mark-process-reference** + +- **证据**: 这是对标测试流程的方法论文档,非 checklist。定义了标准流程(明确小任务→取 Wolai 基线→写 RED smoke→实现→本地验证→subagent 复测→主线程复核)、安全边界、资源路径和 subagent 模板。 +- **保留原因**: 作为 Wolai 对标的标准流程参考仍有效。不应归档(仍在持续使用),也不应作为 process checklist(不是执行清单)。 +- **补充说明**: 文档质量高,应保持为 process/ 中唯一的流程定义文件。 + +--- + +## 4. `design/09-siyuan-reference/process/` — 1 file + +### 9-siyuan-reference-boundary-and-adoption-v1 + +**建议状态:mark-process-reference** + +- **证据**: 这是参考边界与采用策略文档,非 checklist。明确区分了思源值得参考的层级(块引用/属性视图/本地工作区心智/导图插件)和不建议照搬的部分(`.sy` 真相层/SQL 主接口/前端运行时)。 +- **保留原因**: 作为块式笔记能力参考的权威边界稿很有价值。当前无 process 待办项。 +- **补充说明**: 建议在 process 文档头部或设计根目录 README 中交叉引用,让后续设计者知道“思源参考应当落在哪层”。 + +--- + +## 5. Cross-Cutting Observations + +### 5.1 主要风险 + +1. **5-26 计划未落地**: 资源打开 resolver 收敛方案由 Worker C 提出但从未实现。这是本次审查中唯一有"提议但未实施"记录的文件。风险较低(基本功能已由 5-17/5-19 覆盖),但历史记录显示有一轮未闭环的工作流。 + +2. **5-9 F1-F5 陈旧风险**: 5-9 的 F1-F5 (Page Aggregate 相关项) 标记为 TODO,但实际工作在 5-6 中已大部分完成。存在同一工作两处记录不一致的问题。建议审查 5-9 F1-F5 是否应追认或标注 "参见 5-6"。 + +3. **6-mindmap 视觉差距无限延伸**: `6-mindmap-phase6-leptos-ui-shell-reuse-checklist-v1` 最后阶段(Task 9+)的 KMind visual parity 项目可能变成无限尾随工程。建议明确一个 "足够好" 的退出标准。 + +### 5.2 建议立即行动项 + +- 将 5-15/5-17/5-18/5-19/5-20/5-21/5-22/5-23/5-24/5-25 共 10 个文件移至 `done/` +- 将 5-5/5-2/5-7/5-14 标记或移动为 reference/ 类文档(当前 mnote 无统一 reference/ 目录,可另议) +- 为 5-26 决定是否在下一 sprint 收敛或正式废案 +- 解决 5-9 F1-F5 的状态不一致问题 +- 将 6-mindmap 的 3 个文件在 phase 里程碑间做一次已完成/进行中/参考的切分 + +### 5.3 无需变更的文档 + +- `06-mindmap/done/6-mindmap-phase6-kmind-parity-detail-checklist-v1.md` — 已经是正确位置 +- `08-wolai-aline-test-flow/process/wolai-aline-test-flow-v1.md` — 保持在 process/ 作为流程定义 +- `09-siyuan-reference/process/9-siyuan-reference-boundary-and-adoption-v1.md` — 保持在 process/ 作为参考边界稿 + +--- + +## 报告说明 + +**路径**: `.codex/reasonix-tasks/results/design-governance-worker-c-05-06-08-09.md` + +**只读约束**:本文只创建了报告文件,未移动、修改或删除任何 `design/` 文件。上述归档/移动建议需由人工或后续 Worker 执行。 + +**验证命令**: +```bash +# 确认报告文件存在 +find .codex/reasonix-tasks/results/ -name '*worker-c*' + +# 确认未修改任何设计文件 +git diff --stat design/ +``` diff --git a/.codex/reasonix-tasks/results/design-governance-worker-d-07-10.md b/.codex/reasonix-tasks/results/design-governance-worker-d-07-10.md new file mode 100644 index 00000000..eab20ff6 --- /dev/null +++ b/.codex/reasonix-tasks/results/design-governance-worker-d-07-10.md @@ -0,0 +1,362 @@ +# Worker D:design 07/10 审查报告 + +> 执行时间:2026-05-27 +> 范围:`design/07-ai/process/` + `design/10-review/process/` +> 口径:local-first MVP 后阶段统一口径 +> 约束:只读审查,不移动/修改/删除 design 文件 + +--- + +## 综合结论 + +审查的 16 个文档中: + +| 建议状态 | 数量 | 文档 | +|---------|------|------| +| keep-process | 5 | 7-10, 7-12, 7-14, 7-15, 7-27 | +| mark-process-reference | 6 | 7-17, 7-28, 12, 13, 14, 15 | +| archive-done(建议归档) | 5 | 7-16, 7-30, 7-31, 7-32, 7-33 | + +**核心发现**:design 07 中 2026-05-16 之前的文档(7-10/12/14/15)均已叠加 local-first 口径修正,无遗留的「BlockNote 默认主链」或「mnote.doc.markdown_edit 唯一主路径」问题。07 最近的 checklist(7-30~7-33)大部分已完成,仅余次要项。10-review 的四份 doc 均为已消耗的审查参考。 + +--- + +## 逐文档审查 + +### 1. `design/07-ai/process/7-10-page-block-ai-tooling-execution-checklist-v1.md` + +**建议状态**:`keep-process` + +**证据**: +- Phase 1–7(Page Aggregate → block.move_after)全部已勾,有真实 smoke 证据(tmp JSON + 截图 + 测试通过记录)[phase-1-7-complete] +- Phase 8 全部未勾:UI / Review Mode 的 4 项均为未完成状态 [phase-8-pending] +- DONE 条件明确要求 Phase 8 未完成即不能归档 [done-condition-unmet] +- 已包含 2026-05-16 和 2026-05-19 两轮口径修正,对齐 local-first 文件编辑主线 [section-1.0, 1.0.1] + +**归档/保留原因**:Phase 8 UI / Review Mode 仍冻结(Phase C 政策),但 Phase 1–7 的实打实执行证据在此文档中集中记录。如果强行归档到 done/,则 Phase 8 的跟踪上下文丢失。 + +**建议补充说明**:无。 + +--- + +### 2. `design/07-ai/process/7-12-page-ai-hermes-tool-routing-and-review-surface-v1.md` + +**建议状态**:`keep-process` + +**证据**: +- Phase A(设计治理)已勾 [phase-a-done] +- Phase B(Manifest 合同收口)全部未勾 [phase-b-open] +- Phase C(block-edit-workflow 改造)全部未勾 [phase-c-open] +- Phase D(Review session)部分已勾(schema 定义),UI 未勾 [phase-d-partial] +- Phase E(状态统一)全部未勾 [phase-e-open] +- Phase F(验收 smoke)除 selection guard 外全部未勾 [phase-f-open] +- 2026-05-18 已补充 local-first 口径修正 [section-0] + +**归档/保留原因**:定义了当前 AI 编辑的完整架构方向——local-first 文件编辑为主、mnote 兼容工具为辅。Phases B–F 是后续执行 roadmap。 + +**建议补充说明**:Phase C 与 Phase C policy(Phase C 冻结)存在矛盾——本文 Phase C 要求将 block-edit-workflow 改造成 router,但统一口径要求「不要恢复 page-ai fast-path」。建议在本文 Phase C 开头标注「当前仍冻结,待 Phase C 解冻后实施」。 + +--- + +### 3. `design/07-ai/process/7-14-online-local-ai-markdown-editing-convergence-v1.md` + +**建议状态**:`keep-process` + +**证据**: +- 当前产品默认形态已切到 local-first workspace [section-0-update] +- 明确两层模型:agent 原生 patch/diff 为主、mnote.doc.markdown_edit 为 compat fallback [section-1-conclusion] +- Phase A(当前立即)有 5 项,Phase B(Phase A 后)、Phase A/B 后补充、Phase C(远期)均未完成 [section-8-phases] +- CLI Main 参考分析深入,已转化为采纳清单 [section-2.1] + +**归档/保留原因**:本文是最全面定义「两层 AI 编辑模型」的设计文档,是当前本地 vs 在线编辑口径的权威来源。Phase A 的部分实现(scope/detail/markdown 优先)仍待执行。 + +**建议补充说明**:无。 + +--- + +### 4. `design/07-ai/process/7-15-page-ai-acp-agent-runtime-unified-layer-v1.md` + +**建议状态**:`keep-process` + +**证据**: +- 17 步执行计划中 step-1 到 step-7 已被后续 checklist(7-30 到 7-33)覆盖 [section-9-low-friction] +- step-8 到 step-17 仍待实施(Reasonix wrapper、前端运行时选择器、存量迁移、旧代码退役)[steps-in-progress] +- 不包含勾选状态,是架构设计而非 checklist [no-checkboxes] +- 已包含 2026-05-18 local-first 口径补充 [section-1.2] + +**归档/保留原因**:ACP 统一架构的实施蓝图。后续 checklist(7-30~7-33)已覆盖前 7 步,但 step-8 之后仍需此文档指导。 + +**建议补充说明**:建议标注哪些步骤已被 7-30~7-33 覆盖,以便新 reader 快速定位执行状态。 + +--- + +### 5. `design/07-ai/process/7-16-page-block-ai-real-smoke-followup-matrix-v1.md` + +**建议状态**:`archive-done` + +**证据**: +- 3.1 到 3.7 全部已完成,有真实 JSON 和截图证据 [sections-3.1-to-3.7-done] +- 3.8(UI / Review Mode 边界)全部未勾 [section-3.8-open] +- 本文开头声明「不新增 AI 功能面」——3.8 是唯一剩余项,且已被 Phase C 政策冻结 [scope-statement] +- 冻结策略明确写于 7-10 和 7-12:「Phase C 仍冻结,不实施流式 apply 或新的审阅 UI」[phase-c-frozen] + +**归档/保留原因**:本文范围内的所有可执行 smoke 项目已完成。剩余的 3.8(4 项)属于 Phase C 范畴,当前已被统一口径冻结。继续留在 process/ 会产生「还有事要做」的误导,但实际上这些项不能在当前阶段执行。 + +**建议补充说明**:由于本 worker 只读不移动文件,此建议需人工确认后执行 `mv`。若 3.8 解冻,需重新打开此文档或创建新 checklist。 + +--- + +### 6. `design/07-ai/process/7-17-acp-session-convex-sharing-contract-v1.md` + +**建议状态**:`mark-process-reference` + +**证据**: +- 文档自标「状态:PROCESS / FUTURE」[status-future] +- Phase A(约束固化)已完成「ACP 默认主链可用、账号作用域写入 Convex、旧 HTTP 主链进入 recycle」[phase-a] +- Phase B–E 全部标记为项目基本完成后的远期计划 [phases-b-c-d-e] +- 本文冻结 API 草案形状,不要求当前实施 [section-6-api-draft] + +**归档/保留原因**:Phase A 已完成。Phase B–E 是项目基本完成后的产品化功能,不改变当前架构方向。保留在 process/ 作为合同参考。 + +**建议补充说明**:无。 + +--- + +### 7. `design/07-ai/process/7-27-online-markdown-writeback-final-content-truth-v2.md` + +**建议状态**:`keep-process` + +**证据**: +- 本文是针对 7-24/7-25 已知缺陷的修复设计 [bug-references] +- 详细实施计划:6 个步骤(增强 blocks_to_markdown → 实现 parse_final → 实现 build_page_content → 替换写回路径 → 更新 full_content → 清理死代码)[implementation-plan] +- 新增 5 个测试矩阵 P0/P1 级 [test-matrix] +- 没有代码已完成的证据:本文是设计稿,代码尚未实施 [no-code-evidence] + +**归档/保留原因**:是当前活跃的待实施修复。`mnote.doc.markdown_edit` 在线文档路径仍然以「二次推导」而非「最终 markdown」为写回真源,7-27 是改正此问题的方案。 + +**建议补充说明**:高优先级——如果不实施,7-24/7-25 修复后的在线 markdown_edit 在 full_content 和多 operation 场景仍然受限。 + +--- + +### 8. `design/07-ai/process/7-28-resource-ai-tool-contract-v1.md` + +**建议状态**:`mark-process-reference` + +**证据**: +- 定义 mindmap / office 工具合同,无勾选状态 [no-checkboxes] +- 无实施记录或代码证据 [no-code-evidence] +- 不属于当前 AI 编辑主路径(主路径是文件编辑 + ACP session)[outside-main-path] + +**归档/保留原因**:保留在 process/ 作为远期资源工具设计的参考合同。当前不需要实施——mindmap 和 office 的 AI 操作尚未成为产品优先级。 + +**建议补充说明**:无。 + +--- + +### 9. `design/07-ai/process/7-30-acp-session-load-resume-checklist-v1.md` + +**建议状态**:`archive-done` + +**证据**: +- 6/7 项已勾:load_session、ensure_session、ACP run 优先尝试 loaded session、mock 测试覆盖 [checklist-checks] +- 仅 1 项未完成:「明确区分 MNote session id 与 ACP adapter session id;需要持久化/返回时字段名不能混淆」[remaining-item] +- 执行记录:2026-05-20 Reasonix 实施 + Codex 复核通过 [exec-record] +- 验收命令全部通过 [verification-commands] + +**归档/保留原因**:核心功能(session/load → ensure_session 流程)完整实现并通过 Codex 复核。剩余项是 session id 字段全链路持久化问题,不属于功能性缺陷,适合作为后续产品化工作的一部分跟踪。 + +**建议补充说明**:由于本 worker 只读不移动文件,此建议需人工 `mv` 到 `design/07-ai/done/`。 + +--- + +### 10. `design/07-ai/process/7-31-acp-permission-decision-loop-checklist-v1.md` + +**建议状态**:`archive-done` + +**证据**: +- 文档自标「状态:done」[status-done-header] +- 6/7 项已勾:incoming handler、pending map、SSE permission.requested、HTTP endpoint resolve、超时 deny [checklist-checks] +- 仅 1 项未完成:「补 mock ACP 测试:incoming request 等待决策、allow 返回 result、deny 返回 result/error、timeout deny」[remaining-item] +- 执行记录 + Codex 修正记录详细 [exec-record] + +**归档/保留原因**:真实 permission 闭环(前端 allow/deny → 后端 JSON-RPC response)已完整实现。剩余 end-to-end mock 测试是测试覆盖的增强项,不妨碍功能完整性。 + +**建议补充说明**:虽然文档自标 done,但未完成项可能会导致后续重构时缺少回归保护。建议归档时在备注中标注此测试缺口。 + +--- + +### 11. `design/07-ai/process/7-32-acp-tool-location-and-open-action-checklist-v1.md` + +**建议状态**:`archive-done` + +**证据**: +- 6/7 项已勾:ToolLocation 结构、locations SSE、tool card 展示、打开按钮 [checklist-checks] +- 代码层已通过 Codex 复核 [exec-record] +- 仅 1 项未完成:「补测试覆盖 locations parse、SSE 数据、前端字符串断言或 smoke」[remaining-item] + +**归档/保留原因**:ACP tool locations 解析、SSE 桥接、前端 tool card 打开动作完整实现。剩余 browser smoke 是验证性补充,核心功能已就绪。 + +**建议补充说明**:无。 + +--- + +### 12. `design/07-ai/process/7-33-acp-session-info-plan-ui-checklist-v1.md` + +**建议状态**:`archive-done` + +**证据**: +- 5/6 项已勾:session.info.updated SSE、plan.updated SSE、前端 title 更新持久化、plan 面板渲染、bridge 单测 [checklist-checks] +- 仅 1 项未完成:「tool raw output 中包含 todo JSON 时,可以复用 plan 区域或 tool card 状态显示」[remaining-item] +- 执行记录 + Codex 复核通过 [exec-record] + +**归档/保留原因**:ACP session info/plan 的完整桥接+UI 链路已实现。剩余 todo JSON 解析项因格式未确认而暂缓,不影响 session 自动标题和 plan 面板功能。 + +**建议补充说明**:无。 + +--- + +### 13. `design/10-review/process/12-sidex-vscode-workbench-gap-review-v1.md` + +**建议状态**:`mark-process-reference` + +**证据**: +- 审查发现的 P0/P1/P2 缺口已拆分为执行 checklist(5-16、5-17、4-38、5-18、5-19、4-39、4-40、5-20、4-41、4-42、5-21、4-43、5-22、5-23、4-44、4-45)[execution-split] +- 本文本身不再包含待执行项目 [no-remaining-items] +- 「不建议照搬」部分已明确 MNote 的吸收边界 [section-7] + +**归档/保留原因**:已完成审查输出——缺口分析已有对应执行文档跟进。本文保留作为 Sidex 对照原始证据和决策记录。 + +**建议补充说明**:无。 + +--- + +### 14. `design/10-review/process/13-hermes-vscode-plugin-gap-review-v1.md` + +**建议状态**:`mark-process-reference` + +**证据**: +- 审查发现的 P0 缺口已拆分为 7-30、7-31、7-32、7-33(均已实施完成)[execution-split-7-30-33] +- P1/P2 缺口未拆分,但列为后续执行参考 [p1-p2-open] +- 本文本身无待执行勾选 [no-checkboxes] + +**归档/保留原因**:审查产出的 4 份 P0 checklist 均已实施。本文保留作为 Hermes VSCode 插件对照的原始证据和 P1/P2 参考。 + +**建议补充说明**:P1/P2 如果后续拆分执行,建议更新本文关联或创建新 review。 + +--- + +### 15. `design/10-review/process/14-sidex-workbench-gap-followup-review-v1.md` + +**建议状态**:`mark-process-reference` + +**证据**: +- 审查发现的缺口已拆分为执行 checklist(5-24、5-25、5-26、4-45)[execution-split] +- 明确列出「不建议继续照搬」[section-4] +- 本文本身无待执行项 [no-remaining-items] + +**归档/保留原因**:同 12——审查已输出到执行 checklist。 + +**建议补充说明**:无。 + +--- + +### 16. `design/10-review/process/15-design-governance-mvp-post-review-v1.md` + +**建议状态**:`mark-process-reference` + +**证据**: +- 本文是当前的 governance 框架文档,定义了本次审查的范式 [meta-document] +- Workers A/B/C/D 由此散发 [section-3] +- 不包含待执行代码项 [no-code-items] + +**归档/保留原因**:是本次治理审查的元文档和框架定义。如果归档,将失去当前治理周期的 root 文档。 + +**建议补充说明**:建议在所有 Worker 报告完成后,将本文与各 Worker 报告一同作为 governance 周期快照归档到 `design/10-review/done/` 或 `design/10-review/process/` 之内保留。 + +--- + +## 最高风险项 + +按风险从高到低: + +### 🔴 H1:7-27 尚未实施(keep-process) + +`mnote.doc.markdown_edit` 的在线文档写回路径仍然以「二次推导」而非「最终 markdown」为真源。7-24/7-25 已修复拒绝写入的安全问题,但 full_content 和多 operation 场景的正确性仍未达到设计目标。如不尽快实施,已有 bugfix 的保护措施效果有限,且代码与设计文档的差距会随时间扩大。 + +**建议**:优先安排 7-27 的 6 步实施计划。 + +### 🟡 H2:7-10 Phase 8 冻结与后续解冻风险(keep-process) + +Phase 1–7 全部完成,但 Phase 8(UI / Review Mode)因 Phase C 政策冻结。如果后续「冻结」解除,接手者需要从零启动——当前 design 文档不承诺 UI 形状、不包含 mockup、不定义用户交互路径。7-16 3.8 的 4 项未勾项同样属此问题。 + +**建议**:在 Phase C 解冻前标记「Phase 8 实施时需新建设计文档,当前只保留缺口清单」。 + +### 🟡 H3:7-15 执行计划分化(keep-process) + +17 步计划中只有前 7 步被 7-30~7-33 覆盖实施。step-8 之后(Reasonix ACP wrapper、前端运行时选择器、存量迁移、旧代码退役)仍然没有对应的 checklist 或代码证据。若 ACP 架构的目标是「Hermes 与 Reasonix 可互换」,则后续步骤仍需要推动。 + +**建议**:创建 7-34 ~ 7-36 覆盖 Reasonix wrapper、前端选择器和存量迁移。 + +### 🟢 H4:7-30/31/32/33 剩余未完成项(archive-done) + +这些 checklist 的核心功能已完成,但各有 1 项未完成: +- 7-30:session id 全链路持久化 +- 7-31:end-to-end mock 测试缺口 +- 7-32:browser smoke 未补 +- 7-33:todo JSON 格式解析 + +**建议**:归档后将这些剩余项汇总到统一 tracker(如 bugs/07-ai/process/ 或 project board),避免分散丢失。 + +--- + +## 无过期口径声明 + +检查所有文档后确认:**没有文档仍在将 `mnote.doc.markdown_edit` 或 `page-ai/block-edit-workflow` 描述为 local-first 普通 Markdown 编辑的唯一/默认主路径。** 2026-05-16 之后的各轮口径修正已正确覆盖: + +- 7-10 §1.0.1:「local-first 普通正文编辑默认给 Hermes / Reasonix 当前文件引用」 +- 7-12 §0:「local-first 普通 Markdown 编辑默认给 Hermes / Reasonix 授权文件引用」 +- 7-14 §0:「local-first 普通 Markdown 编辑优先给 agent 授权文件引用」 +- 7-15 §1.2:「local-first 普通 Markdown 编辑默认不要求 runtime 调 mnote.doc.markdown_edit」 +- 7-16 §1:「local-first 普通 Markdown 编辑主路径仍是授权文件引用 + agent 原生 patch/diff + watcher 同步」 +- 7-27 §0:「本地 .md 路径的默认 AI 编辑主路径是授权文件引用 + agent 原生 patch/diff + watcher 同步」 +- 7-17、7-28、7-30~33 不涉及编辑主路径定义 + +--- + +## 归档建议汇总(需人工执行) + +以下文件建议由人工移动到 `done/` 目录: + +```bash +# 07-ai 已完成 checklist +mv design/07-ai/process/7-16-page-block-ai-real-smoke-followup-matrix-v1.md design/07-ai/done/ +mv design/07-ai/process/7-30-acp-session-load-resume-checklist-v1.md design/07-ai/done/ +mv design/07-ai/process/7-31-acp-permission-decision-loop-checklist-v1.md design/07-ai/done/ +mv design/07-ai/process/7-32-acp-tool-location-and-open-action-checklist-v1.md design/07-ai/done/ +mv design/07-ai/process/7-33-acp-session-info-plan-ui-checklist-v1.md design/07-ai/done/ +``` + +10-review 的四份文档保留为参考(mark-process-reference),不归档。 + +--- + +## 验证命令执行摘要 + +```bash +# 1. 文件列表验证 +find design/07-ai/process design/10-review/process -maxdepth 1 -type f -name '*.md' | sort +# → 16 files confirmed + +# 2. 高危模式搜索 +rg -n "mnote\\.doc\\.markdown_edit.*主路径|page-ai.*唯一|BlockNote.*默认" \ + design/07-ai/process design/10-review/process +# → 无匹配:所有文档已更新口径 +``` + +--- + +## 文件范围 + +本报告写入 `.codex/reasonix-tasks/results/design-governance-worker-d-07-10.md`。 + +未修改 design/ 下任何文件。未提交 git。未运行浏览器 smoke。 diff --git a/AGENTS.md b/AGENTS.md index 25dbbdd4..09ab97de 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -3,6 +3,7 @@ ## 当前主线 - 当前长期方向以 `tree-first graph kernel` 为准,不以 `BlockNote-first` 或 `Mindmap-first` 为准。 +- 当前已经进入 local-first MVP 后阶段:最小可用闭环已建立,后续重点是底座统一、compat 瘦身和产品化闭环,不再把新增能力建立在旧 Convex / Next / BlockNote 主链上。 - 当前产品形态固定为:`VSCode 简化版工作区 + tiptap 的 Markdown 前端编辑器 + Hermes/Reasonix agent + simplemindmap/office 插件 + Wolai 主题 Web 壳 + 鉴权控制面`。 - 本地文件夹是早期产品默认数据真相;本地 `.md` 是页面正文真相。`Convex` / 服务端降级为 auth、membership、share grants、sync state、AI policy、cloud source、compat 和 sync replica 控制面,不再作为新增能力的默认正文、附件或 AI 会话全文主存储。 - `Rust kernel` 持有树、子树、边、projection、query、command 的语义主导权;新增树规则不要继续散落到前端、Next route 或临时 compat 层。 @@ -12,7 +13,7 @@ - `3000` 当前主壳已接入 Rust Web WebSocket push 主链(`/api/realtime/ws`)+ SSE fallback(`/api/tree/events`)的 snapshot / delta / resync consumer,并已有 browser smoke 验证(2026-05-17 WS 迁移 `57ec8322`);后续收口重点是统一 live cache 与减少补偿链,而不是把它描述成“还没接 live stream”。 - 文档页默认主编辑器已切到页面内 `leptos-tiptap` island;`BlockNote` 已退出文档页默认主路径,只保留为历史参考实现 / 对照材料。 - Page Aggregate 当前已输出 `blockDocument / blockProjectionVersion / projectionSource`(`projectionSource=documents.content`),标题/正文/页面设置写入后 smoke 已通过(`task110`、`task-page-aggregate-body-sync-smoke`、`task-page-aggregate-options-sync-smoke`);页面/块 AI 最小工具链已通过 Rust Hermes tools 读取和写入块投影;但这仍是从 `documents.content` / local markdown content 投影出来的过渡态,不是 EditorBlockDocument 原生落库完成态。客户端 `PageAggregateClientState` reducer 仍在,页面域单一真源未完全闭环。 -- 当前最优先的架构收口不是继续扩编辑器 UI,而是 local-first 工作区产品化、`Page Aggregate` 单一真源收口、`tree command cutover` 收尾、`tree realtime event stream` live cache 统一(WS push 主链 2026-05-17 上线,SSE 降级为 fallback)。AI 侧普通 Markdown 编辑主路径已收口为“当前文件引用 + selection + allowed roots + agent 原生 patch/diff + watcher 同步”;`mnote.doc.fetch`、`mnote.doc.markdown_edit`、`mnote.doc.apply_block_ops`、`mnote.block.*` 只作为 cloud / remote agent / compat fallback 或复杂结构辅助;`review session`/流式 apply 属于 Phase C(设计冻结,当前不实施),不扩新 AI 功能,不把粗粒度 `mnote.page.save` 当成精确块编辑主入口。 +- 当前最优先的架构收口不是继续扩编辑器 UI,而是在 local-first MVP 基线上推进 `WorkspacePath/ObjectIdentity` runtime 消费统一、`DocumentBuffer/BufferStore`、`Page Aggregate` 单一真源收口、`tree command cutover` 收尾、`tree realtime event stream` live cache 统一(WS push 主链 2026-05-17 上线,SSE 降级为 fallback)和 agent diff / 冲突合并 / 本地索引 / 分享同步产品化。AI 侧普通 Markdown 编辑主路径已收口为“当前文件引用 + selection + allowed roots + agent 原生 patch/diff + watcher 同步”;`mnote.doc.fetch`、`mnote.doc.markdown_edit`、`mnote.doc.apply_block_ops`、`mnote.block.*` 只作为 cloud / remote agent / compat fallback 或复杂结构辅助;`review session`/流式 apply 属于 Phase C(设计冻结,当前不实施),不扩新 AI 功能,不把粗粒度 `mnote.page.save` 当成精确块编辑主入口。 ## 组件定位 diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 563dc847..e7689a1c 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -1,10 +1,10 @@ # MNOTE 当前架构梳理 -> 更新时间:2026-05-19 +> 更新时间:2026-05-21 > -> 2026-05-19 口径更新:产品形态已切换为 local-first workspace;本地文件夹是默认数据真相,Convex / 服务端降级为账号、分享、同步、协作和 AI 隔离控制面。相关设计已完成并迁入 `design/02-convex-rust-long-term-architecture/done/2-2-local-first-workspace-convex-control-plane-v1.md`。本地 Markdown 图片与附件上传已新增 `/api/local-folder/assets/upload`,页面内上传默认写入 `{mdBase}.assets/` 并保存相对 Markdown 路径,不再走 Convex media asset。 +> 2026-05-21 口径更新:产品形态已切换为 local-first workspace,且初步 MVP 已建立;本地文件夹是默认数据真相,Convex / 服务端降级为 auth、membership、share grants、sync state、AI policy、cloud source、compat 和 sync replica 控制面。相关设计已完成并迁入 `design/02-convex-rust-long-term-architecture/done/2-2-local-first-workspace-convex-control-plane-v1.md`。本地 Markdown 图片与附件上传已新增 `/api/local-folder/assets/upload`,页面内上传默认写入 `{mdBase}.assets/` 并保存相对 Markdown 路径,不再走 Convex media asset。 > -> **当前阶段:local-first MVP 骨架已达成。** 3000 下文档页、Sidebar/File Tree/Page Tree 三层树模型、tree command(`tree.*` preferred)、tree realtime WS 主链 / SSE fallback、本地 Markdown 上传、AI 会话本地化与 VSCode-like agent 运行口径均已建立。剩余工作集中在管理员目录授权、VSCode-like 冲突合并 UI、agent diff 审计、本地索引 / 分享 / 同步闭环,而不是继续扩张专用 page-ai fast-path 或 Convex 主存储链。 +> **当前阶段:local-first MVP 后阶段。** 3000 下文档页、Sidebar/File Tree/Page Tree 三层树模型、tree command(`tree.*` preferred)、tree realtime WS 主链 / SSE fallback、本地 Markdown 上传、上传下载、AI 会话本地化与 VSCode-like agent 运行口径均已建立。剩余工作集中在 `WorkspacePath/ObjectIdentity` runtime 消费统一、`DocumentBuffer/BufferStore`、Page Aggregate compat 瘦身、tree command context、live cache 统一、VSCode-like 冲突合并 UI、agent diff 审计、本地索引 / 分享 / 同步闭环,而不是继续扩张专用 page-ai fast-path 或 Convex 主存储链。 本文只描述当前仓库中真实成立的主线结构,以及当前最优先的架构收口点。 @@ -46,7 +46,7 @@ - `/mnt/Data1T/mnote/rust/crates/mnote-web/` Rust Web route、kernel projection、documents/search/mindmap shell、Hermes bridge、stream transport、compat,以及显式开启时的 debug shell;它是当前主 Web gateway / shell / transport owner。 - `/mnt/Data1T/mnote/recycle/wolai-frontend/convex/` - 已随 wolai-frontend 移入 recycle。当前活跃 Convex functions 位于 `infra/convex/` 与 Rust bridge 中。 + 已随 wolai-frontend 移入 recycle,不允许作为当前 Convex deploy fallback。当前 active Convex functions 部署源在仓库根 `convex/`(现阶段为 ACP / Hermes runtime session store:`schema.ts` + `aiSessions.ts`);`infra/convex/` 只负责自托管 Convex 基础设施。 - `/mnt/Data1T/mnote/infra/convex/` Convex 自托管部署与基础设施。 - `/mnt/Data1T/mnote/wolai-backend/` diff --git a/CURRENT_ARCHITECTURE.md b/CURRENT_ARCHITECTURE.md index cecd3114..11dd0cc2 100644 --- a/CURRENT_ARCHITECTURE.md +++ b/CURRENT_ARCHITECTURE.md @@ -1,8 +1,8 @@ # 当前完整架构 -> 更新时间:2026-05-19 +> 更新时间:2026-05-21 > -> 范围:`/mnt/Data1T/mnote` 当前可见实现、主线口径、历史退役边界和后续功能缺口。 +> 范围:`/mnt/Data1T/mnote` 当前可见实现、local-first MVP 后阶段主线口径、历史退役边界和后续功能缺口。 ## 1. 当前产品形态 @@ -12,6 +12,7 @@ 这意味着: +- 初步 MVP 已建立,后续不是继续证明 Rust Web、local-first 或 tiptap island 是否成立,而是收紧底座、减少 compat、补齐产品化闭环。 - 本地 workspace folder 是早期产品默认数据真相。 - 本地 `.md` 文件是页面正文真相;Page Aggregate、tiptap state、AI context 都是投影或工作副本。 - Rust kernel / projection / command 持有树、页面、资源和权限语义。 @@ -61,42 +62,63 @@ - `/api/page-ai/block-edit-workflow` 不再作为 local-first 普通正文编辑主路径。 - AI session 全文在 local source 下默认写入 `ai-sessions/private/*.jsonl` 或 `ai-sessions/shared//*.jsonl`;Convex 只保存必要 metadata / audit / sync replica。 -## 3. 已完成收口 +## 3. 已完成 MVP 收口 - Local-first workspace 上位设计和 checklist 已完成,迁入 `design/02-convex-rust-long-term-architecture/done/2-2-*`。 +- 3000 下文档页、Sidebar/File Tree/Page Tree、tree command preferred path、tree realtime WS 主链 / SSE fallback、本地上传下载、AI 会话本地化与 VSCode-like agent 运行口径已建立。 - `cargo fmt --check --all`、`local_folder`、`hermes_tools`、local/shared AI session、local-first Convex guard、Convex export fixture、local asset upload smoke、external-change conflict smoke 均已作为 `2-2` 验收证据记录。 - 本地上传链路已避免 Convex media asset,落盘相对 Markdown 链接。 - 本地 AI 会话已区分 private / shared / cloud,并在 UI 上显示来源。 - Convex guard 已阻止 active 路径重新引入未标注的 Convex documents / media / aiSessions 默认主存储口径。 -## 4. 仍在推进的功能缺口 +## 4. MVP 后阶段功能缺口 -1. **管理员目录授权 UI / API** +1. **WorkspacePath / ObjectIdentity runtime 消费统一** + - 继续把本地路径、资源 identity、页面正文 `.md`、附件、mindmap、office 资源归属统一到 kernel / projection contract。 + - 防止 UI、compat route 或临时文件逻辑重新生成第二套资源真相。 + +2. **DocumentBuffer / BufferStore** + - 让 tiptap、文件 watcher、agent 写入、冲突检测和页面 tab 共用打开态与文件版本仲裁。 + - 避免 dirty editor、资源 tab、文件树点击之间各自维护状态。 + +3. **Page Aggregate compat 瘦身 / kernel-native projection** + - 继续退役 legacy `body.content` / compat join 边界。 + - local-first 正文真相保持为 `.md` 文件,Page Aggregate 只输出稳定投影。 + +4. **tree command context / context key** + - 菜单、快捷键、按钮、AI 写入能力的 enablement 判断应收口到同一套 command context。 + - 文件树和主编辑器右键菜单不能继续各自硬编码。 + +5. **tree live cache 统一** + - 把 local-folder watcher、projection refresh、WS/SSE snapshot/delta/resync 收进同一 live cache。 + - 减少 polling 或重复补偿链。 + +6. **管理员目录授权 UI / API** - 需要把 `access-policy.json` 和当前 auth actor、admin grant、read/write/share 权限做成可管理界面。 - 管理员可授权任意 canonical 目录;普通用户只能访问自己的受管 my-space 或被授权目录。 -2. **VSCode-like 冲突处理 UI** +7. **VSCode-like 冲突处理 UI** - 当前已有 clean/dirty watcher 行为和冲突态 smoke。 - 还需要做可用的“接受磁盘版本 / 保留当前版本 / 打开 diff 合并”交互。 -3. **agent 写入审计** +8. **agent 写入审计** - local-first agent 应回收 changed files、diff summary、tool/run id、actor、workspace root、permission level。 - audit 默认本地落盘,同步开启时再上报控制面。 -4. **本地全文搜索、引用和索引** +9. **本地全文搜索、引用和索引** - 本地 `.md` workspace 需要独立索引:全文搜索、反链、页面引用、资源引用、标签。 - 不能依赖 Convex search 才能搜索本地工作区。 -5. **分享与同步闭环** +10. **分享与同步闭环** - 需要把 share grants、shared workspace cache、shared AI session、只读/可写权限和冲突处理连成产品级闭环。 - share grant 是产品层共享授权,local access grant 是本机目录授权;显式分享不能自动扩大本机 filesystem root 权限。 - 控制面不可用时不得扩大本地缓存权限。 -6. **插件资源模型** +11. **插件资源模型** - simplemindmap / office 应作为 Resource Tree 对象打开和保存。 - Markdown 中只保留链接或嵌入引用,不把复杂对象强塞进普通正文块。 -7. **旧 Convex 数据迁移产品化** +12. **旧 Convex 数据迁移产品化** - 当前已有 fixture/offline 导出脚本。 - 后续需要真实 Convex workspace 导出入口、迁移进度、冲突报告、回滚/备份策略。 diff --git a/REASONIX.md b/REASONIX.md index 0757f2fe..ff73eb7f 100644 --- a/REASONIX.md +++ b/REASONIX.md @@ -1,11 +1,20 @@ # MNOTE — Reasonix working knowledge +## Current Baseline + +- MNote 已进入 **local-first MVP 后阶段**:最小可用闭环已建立,后续重点是底座统一、compat 瘦身和产品化闭环。 +- 产品形态固定为:`VSCode 简化版工作区 + tiptap 的 Markdown 前端编辑器 + Hermes/Reasonix agent + simplemindmap/office 插件 + Wolai 主题 Web 壳 + 鉴权控制面`。 +- 本地 workspace folder 是默认数据真相;本地 `.md` 是页面正文真相;Page Aggregate、tiptap state、AI context 都是投影或工作副本。 +- Rust kernel / projection / command 持有树、页面、资源和权限语义;新增树规则不要散落到前端、Next route 或临时 compat 层。 +- Convex / 服务端不再是默认正文、附件、AI 会话全文主存储,只保留 auth、membership、share grants、sync state、AI policy、cloud source、compat 和 sync replica 控制面。 +- `mnote-web` 是当前 3000 owner;旧 Next / React / wolai-frontend 已移入 `recycle/`,默认不作为当前实现依据。 + ## Stack - **Rust** — workspace of 11 crates, edition 2021, resolver 2, wasm32 target (`rust/rust-toolchain.toml`) - **Axum 0.8** — HTTP/WebSocket server in `mnote-web` crate - **Leptos 0.8.14** — SSR island rendering in `mnote-web` -- **Convex 1.x** — self-hosted document/real-time/storage backend (`infra/convex/docker-compose.yml`) +- **Convex 1.x** — auth/share/sync/ACP/Hermes control plane and optional cloud/compat/sync replica, not the default document/media/session full-text store - **Playwright** — browser smoke tests in `scripts/task-*.js` ## Layout @@ -14,10 +23,10 @@ |------|----------| | `rust/crates/mnote-web/` | Rust web gateway (axum + Leptos SSR, `:3000`) | | `rust/crates/core-protocol/` | Kernel types, projection protocol, tree/graph terms | -| `rust/crates/bridge-runtime/` | Kernel query/command, Convex transport args generation | +| `rust/crates/bridge-runtime/` | Kernel query/command, projection bridge, source normalization | | `rust/crates/mnote-cli/` | CLI tool (`sidebar dataset`, `page get/create/title/save/move`, `block insert/patch`) | -| `rust/crates/storage-convex-bridge/` | Convex storage bridge for Rust | -| `convex/` | Convex schema (`schema.ts`) + functions (`aiSessions.ts`) | +| `rust/crates/storage-convex-bridge/` | Convex compat / control-plane bridge for Rust | +| `convex/` | Active Convex deploy source for ACP / Hermes runtime state (`schema.ts` + `aiSessions.ts`) | | `infra/convex/` | Docker Compose for self-hosted Convex backend | | `infra/onlyoffice/` | Docker Compose for OnlyOffice | | `scripts/` | Node.js dev/build scripts + Playwright smoke tests | @@ -52,9 +61,11 @@ ## Conventions +- **Local-first default**: start from Rust `mnote-web`, LocalFS/local_folder projection, Page Aggregate, File Tree, and Resource Tree. Use Convex paths only for auth/cloud/share/ACP/compat-specific work. - **Smoke test pattern**: standalone Playwright scripts under `scripts/task-*.js` using a shared harness (`ensureAuthenticated`, `createTempDocument`, `cleanupDocuments`). All use `"use strict"` and `require("playwright")` (`scripts/task110-page-title-single-truth-smoke.js:1-4`). - **Rust workspace**: edition 2021, resolver "2", MIT license. All crates inherit workspace version (`rust/Cargo.toml:2-8`). - **Convex boundary**: root `convex/` is the active deploy source for ACP / Hermes runtime state (`schema.ts` + `aiSessions.ts`). Do not deploy from `recycle/wolai-frontend/convex`; old `documents` / `mediaAssets` / `users` / `workspaces` functions must be explicitly migrated to active code, moved to an auxiliary area, or retired. +- **AI editing path**: local-first Markdown editing should prefer `currentFile + selection + allowedRoots / aiAccessScope + agent native patch/diff + watcher sync`. `mnote.doc.*` / `mnote.block.*` are cloud, remote, compat, or complex-structure helpers. - **Bug tracking**: bugs organized by design domain directory (e.g. `bugs/04-tree-domain/process/`), moved to `done/` when fixed (`bugs/README.md`). - **Design docs**: organized by domain in `design/`, with `process/` (in-progress) and `done/` (completed) subdirs (`design/README.md`). @@ -63,6 +74,7 @@ - **write_file path rule**: paths are sandbox-relative; leading `/` is stripped. Use `path: "design/..."` not `/mnt/Data1T/mnote/design/...`. - **`.aionrs/`, `.claw/`, `.codex/`, `.gemini/`** — session data from other AI tools, not project code. Don't read or edit. - **`recycle/`** — retired code, not current implementation. Don't use as evidence for current behavior. -- **Convex must be running** (`infra/convex/`) for full-stack dev — smoke tests depend on a live Convex backend. +- **Convex availability**: local-folder / Rust projection smoke should not require Convex unless the test explicitly covers auth, cloud source, share grants, ACP/Hermes runtime state, or sync replica behavior. +- **MVP 后阶段优先级**: prefer WorkspacePath/ObjectIdentity runtime consumption, DocumentBuffer/BufferStore, Page Aggregate compat slimming, tree command context/context key, live cache unification, agent diff audit, conflict merge, local index, and share/sync productization. - **Rust wasm32 target** required (`rust/rust-toolchain.toml`), needed for `tree-shell-runtime-wasm` crate. - **`rust/target/`** is gitignored and large — `cargo build` runs from scratch if cache is missing. diff --git a/auth-console.txt b/auth-console.txt new file mode 100644 index 00000000..2d4ccc80 --- /dev/null +++ b/auth-console.txt @@ -0,0 +1 @@ +Total messages: 0 (Errors: 0, Warnings: 0) diff --git a/auth-network.txt b/auth-network.txt new file mode 100644 index 00000000..e69de29b diff --git a/bugs/04-tree-domain/done/4-46-filetree-editor-context-menu-download-v1.md b/bugs/04-tree-domain/done/4-46-filetree-editor-context-menu-download-v1.md new file mode 100644 index 00000000..29c9df0c --- /dev/null +++ b/bugs/04-tree-domain/done/4-46-filetree-editor-context-menu-download-v1.md @@ -0,0 +1,64 @@ +# 4-46 FileTree / Editor Context Menu Download v1 + +> 状态:done +> +> 日期:2026-05-21 +> +> Owner:FileTree action layer / editor attachment context menu + +## 1. 问题 + +- 文件树 asset/resource 行右键菜单没有 `下载` 动作,虽然后端已有 `/api/local-folder/files/open?download=true` 下载入口。 +- 主编辑器附件当前主要依赖 hover 后的更多按钮打开附件菜单,真实右键附件链接没有复用同一套菜单。 +- 本地文件下载响应只有 `Content-Disposition: attachment`,缺少 filename,浏览器可能无法使用原文件名保存,中文文件名尤其不稳定。 + +## 2. 目标 + +- 文件树 asset 右键菜单显示 `下载`。 +- 点击文件树 asset 下载时复用现有 attachment 下载链路,并记录 `data-mnote-filetree-last-action=download` / `data-mnote-filetree-last-action-status=requested`。 +- 文件树已选中多个 asset/resource 时,右键任一已选文件执行 `下载` 会批量下载当前选择集中的资源文件。 +- 文件树主 Markdown 文件行和本地文件夹行也应支持下载;文件夹下载为 `.tar` 包。 +- 文件拖到本地文件树文件夹时,应上传到该文件夹路径,不应因缺少 `rootUri` 或 `documentId` 报错。 +- 主编辑器附件右键打开附件 context menu,菜单内容与 hover 更多按钮一致。 +- 本地文件下载响应带 `filename` 与 `filename*`,支持中文文件名。 + +## 3. Checklist + +- [x] 文件树 asset 菜单增加 `download` 动作。 +- [x] `handleTreeContextMenuAction` 处理 filetree asset `download`。 +- [x] `Ctrl/Cmd` 多选和 `Shift` 范围多选后支持文件树 asset 批量下载。 +- [x] 文件树主 Markdown 文件行支持下载原始 `.md` 文件。 +- [x] 文件树本地文件夹行支持下载 `.tar` 包。 +- [x] 文件拖到本地文件树文件夹时使用目标文件夹 `targetRelativePath` 上传。 +- [x] 主编辑器附件链接右键打开 attachment menu。 +- [x] `/api/local-folder/files/open?download=true` 响应补 `Content-Disposition` 文件名。 +- [x] Rust contract / route 测试覆盖新增行为。 +- [x] Reasonix 浏览器截图复核右键菜单与下载入口。 +- [x] Codex post-fix browser smoke 复核文件树 asset 单文件下载与 Ctrl/Cmd、Shift 多选下载。 + +## 4. 验收 + +- `cargo test -p mnote-web sidebar_tree_runtime_renders_context_menu_and_scoped_title_updates -- --test-threads=1` +- `cargo test -p mnote-web local_file_open_download_sets_content_disposition_filename -- --test-threads=1` +- `cargo test -p mnote-web local_folder_open_downloads_directory_as_tar -- --test-threads=1` +- `cargo test -p mnote-web web_shell -- --test-threads=1` +- `node --check scripts/task476-filetree-editor-context-menu-download-smoke.js` +- `node scripts/task476-filetree-editor-context-menu-download-smoke.js` +- Reasonix pre-fix browser result:`tmp/reasonix-filetree-editor-context-menu-2026-05-21/result.json` +- Codex post-fix browser result:`tmp/task476-filetree-editor-context-menu-download-smoke/result.json` + +## 4.1 执行记录 + +- 2026-05-21:Reasonix 浏览器测试确认:文件树 asset 右键菜单缺少 `download`,主编辑器附件 hover/菜单下载可用。 +- 2026-05-21:已补 filetree asset `download` 菜单项和 action handler,下载时复用 attachment 下载链并记录 filetree action 状态。 +- 2026-05-21:已补文件树多选下载,右键已选资源时按当前选择集批量触发浏览器下载;`task476` smoke 覆盖 `Ctrl` 多选 2 个文件与 `Shift` 范围多选 3 个文件。 +- 2026-05-21:已补主 Markdown 文件行下载、文件夹 `.tar` 下载和拖到文件夹上传;`task476` smoke 覆盖 `README.md` 下载、`README.tar` 下载,以及 `README/drag-upload-note.txt` 上传落盘。 +- 2026-05-21:已补主编辑器附件链接真实右键菜单,右键打开与 hover 更多按钮一致的 attachment menu。 +- 2026-05-21:已补 local-folder open 下载文件名:`Content-Disposition` 同时提供 ASCII fallback `filename` 与 UTF-8 `filename*`。 +- 2026-05-21:`task476` post-fix browser smoke 通过;文件树 asset 单文件下载得到浏览器 suggested filename `report-a.pdf`,`Ctrl/Cmd` 多选下载得到 `report-a.pdf` / `report-c.pdf`,`Shift` 范围下载得到 `report-a.pdf` / `report-b.pdf` / `report-c.pdf`。该 smoke 中编辑器附件链接未渲染为可见项,作为本轮文件树多选下载的非阻塞项记录为 `editor_attachment_not_visible`。 + +## 5. 非目标 + +- 不一次性实现 attachment 菜单中所有历史占位动作。 +- 不改变文件树删除语义。 +- 不重构完整 command service。 diff --git a/design/01-05-current-priority-overview.md b/design/01-05-current-priority-overview.md index 40327e82..2a136fc8 100644 --- a/design/01-05-current-priority-overview.md +++ b/design/01-05-current-priority-overview.md @@ -1,6 +1,11 @@ # 01-05 当前主线与优先级总览 -> 更新时间:2026-05-19 +> 更新时间:2026-05-21 +> +> 2026-05-21 口径补充: +> - 当前已进入 local-first MVP 后阶段:最小可用闭环已建立,后续重点是底座统一、compat 瘦身和产品化闭环。 +> - 新设计与 bug 修复默认从 `WorkspacePath/ObjectIdentity`、`DocumentBuffer/BufferStore`、Page Aggregate compat 瘦身、tree command context、live cache 统一、agent diff / 冲突合并 / 本地索引 / 分享同步产品化这些基线出发。 +> - 旧 Convex / Next / BlockNote 主链只作为 legacy / recycle / compat 背景,不再作为新增能力默认方向。 > > 2026-05-18 口径补充: > - 产品形态已调整为 `Local-first workspace`:本地文件夹是默认数据真相,Rust kernel 是唯一语义真相,Convex / 服务端降级为账号、分享、同步、协作和 AI 隔离控制面。 @@ -108,7 +113,7 @@ P0-P6 已经有最小闭环证据;P7 已完成阶段性瘦身,但还不是 ## 3. 当前仍应保留但不在第一线的 process -下面这些文档仍然有效,但当前不应排在第一优先: +下面这些文档仍然有效,但当前不应排在第一优先;引用时必须叠加 2026-05-21 的 local-first MVP 后阶段口径: - `/mnt/Data1T/mnote/design/01-tree-first-graph-kernel/process/1-1-tree-first-graph-kernel-checklist-v2.md` - `/mnt/Data1T/mnote/design/03-rust-web/process/3-1-rust-web-long-term-checklist-v2.md` @@ -167,6 +172,8 @@ P0-P6 已经有最小闭环证据;P7 已完成阶段性瘦身,但还不是 持续推进 checklist: - `/mnt/Data1T/mnote/design/01-tree-first-graph-kernel/process/1-3-current-priority-execution-checklist-v1.md` +- MVP 后阶段 process 执行总序: + `/mnt/Data1T/mnote/design/01-tree-first-graph-kernel/process/1-8-mvp-post-process-execution-order-v1.md` ## 7. 一句话收口 diff --git a/design/01-tree-first-graph-kernel/process/1-7-local-workspace-root-page-bundle-checklist-v1.md b/design/01-tree-first-graph-kernel/done/1-7-local-workspace-root-page-bundle-checklist-v1.md similarity index 97% rename from design/01-tree-first-graph-kernel/process/1-7-local-workspace-root-page-bundle-checklist-v1.md rename to design/01-tree-first-graph-kernel/done/1-7-local-workspace-root-page-bundle-checklist-v1.md index 3e39dcd9..5493ba88 100644 --- a/design/01-tree-first-graph-kernel/process/1-7-local-workspace-root-page-bundle-checklist-v1.md +++ b/design/01-tree-first-graph-kernel/done/1-7-local-workspace-root-page-bundle-checklist-v1.md @@ -1,5 +1,9 @@ # 1-7 Local Workspace Root Page Bundle Checklist v1 +> 状态:done +> +> 归档说明(2026-05-21):默认 local workspace root/page bundle 契约、时间戳新页面、同名目录子页面容器与文件树不合并规则均已由 Rust 单测、HTTP smoke 和浏览器验收覆盖。本文件从 `process/` 归档到 `done/`。 + ## 背景 当前默认本地工作区初始化会创建 `pages/`、`assets/`、`mindmaps/`、`ai-sessions/` 等顶层目录。这更像早期产品内部布局,不像用户真实打开的 VSCode 文件夹,也不利于页面导出。 diff --git a/design/01-tree-first-graph-kernel/process/1-2-tree-first-graph-product-vision-progress-v1.md b/design/01-tree-first-graph-kernel/process/1-2-tree-first-graph-product-vision-progress-v1.md index f48dd32e..3f5ff04a 100644 --- a/design/01-tree-first-graph-kernel/process/1-2-tree-first-graph-product-vision-progress-v1.md +++ b/design/01-tree-first-graph-kernel/process/1-2-tree-first-graph-product-vision-progress-v1.md @@ -93,7 +93,7 @@ | `tree.*` 命令切流(Stage 2)| DONE | `design/04-tree-domain/done/4-6-tree-command-protocol-cutover-stage2-v1.md` | | 文件树资源行与页面命令分离 | DONE | `bugs/04-tree-domain/done/4-46-filetree-resource-row-document-command-leak-v1.md` | | 垃圾箱恢复位置记录 + reveal/focus | DONE | `design/04-tree-domain/process/4-28-trash-restore-location-reveal-focus-v1.md` §2 | -| 资源生命周期命令 `tree.resource.*`(mindmap/table rename 未完成)| PARTIAL | `design/04-tree-domain/process/4-27-resource-lifecycle-command-cutover-v1.md` | +| 资源生命周期命令 `tree.resource.*`(核心 cutover 已完成,局部尾项由后续文档承接)| DONE/PARTIAL tails | `design/04-tree-domain/done/4-27-resource-lifecycle-command-cutover-v1.md`,尾项见 `4-28` / `4-34` / `4-44` / `4-45` | | 双浏览器 no-refresh(页面 + 文件 + 垃圾箱实时同步)| PROCESS | `design/04-tree-domain/process/4-34-filetree-trash-dual-browser-no-refresh-v1.md` | | Local/Cloud 桥接 | P3 CANDIDATE | `design/04-tree-domain/process/4-23-local-cloud-explicit-bridge-p3-candidate-v1.md` | diff --git a/design/01-tree-first-graph-kernel/process/1-3-current-priority-execution-checklist-v1.md b/design/01-tree-first-graph-kernel/process/1-3-current-priority-execution-checklist-v1.md index 5f10e06a..0acff7ad 100644 --- a/design/01-tree-first-graph-kernel/process/1-3-current-priority-execution-checklist-v1.md +++ b/design/01-tree-first-graph-kernel/process/1-3-current-priority-execution-checklist-v1.md @@ -7,7 +7,7 @@ > 上位依据: > - `/mnt/Data1T/mnote/design/01-05-current-priority-overview.md` > - `/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/process/2-3-local-workspace-access-control-productization-v1.md` +> - `/mnt/Data1T/mnote/design/02-convex-rust-long-term-architecture/done/2-3-local-workspace-access-control-productization-v1.md` > - `/mnt/Data1T/mnote/ARCHITECTURE.md` > - `/mnt/Data1T/mnote/CURRENT_ARCHITECTURE.md` > diff --git a/design/01-tree-first-graph-kernel/process/1-8-mvp-post-process-execution-order-v1.md b/design/01-tree-first-graph-kernel/process/1-8-mvp-post-process-execution-order-v1.md new file mode 100644 index 00000000..8f5cabab --- /dev/null +++ b/design/01-tree-first-graph-kernel/process/1-8-mvp-post-process-execution-order-v1.md @@ -0,0 +1,413 @@ +# 1-8 [process] MVP 后阶段 process 执行总序 v1 + +> 创建时间:2026-05-21 +> +> 当前状态:`PROCESS` +> +> 目标:把本轮 design governance 后剩余的主线 `process/` 文档排成可执行顺序,避免后续 worker 在 40 份 process 中自行猜优先级。 +> +> 上位依据: +> - `/mnt/Data1T/mnote/ARCHITECTURE.md` +> - `/mnt/Data1T/mnote/CURRENT_ARCHITECTURE.md` +> - `/mnt/Data1T/mnote/design/README.md` +> - `/mnt/Data1T/mnote/design/01-05-current-priority-overview.md` +> - `/mnt/Data1T/mnote/design/10-review/done/15-design-governance-mvp-post-review-v1.md` + +## 1. 一句话顺序 + +后续执行顺序固定为: + +> **先补阻塞验证和工作区身份底座,再收 Page Aggregate / BufferStore / live cache / command context,随后补资源打开与文件树 action,最后扩 AI、mindmap、Wolai 对标和远期分享同步。** + +不要从 `process/` 中按编号顺序机械执行。编号只代表历史产生顺序,不代表当前优先级。 + +## 2. 状态分类 + +### 2.1 Active Process + +代表仍需继续推进的执行或架构收口文档。 + +判定规则: + +- 有未完成 checklist。 +- 或者是当前主线架构入口,仍会指导后续实现。 +- 或者有明确阻塞项、smoke 缺口、compat 瘦身缺口。 + +### 2.2 Process Reference + +保留在 `process/`,但不是当前直接执行 checklist。 + +典型文档: + +- 参考实现采用矩阵。 +- Wolai / 思源 / Sidex / Hermes 对照证据。 +- P3 / future 候选合同。 +- 已拆成执行 checklist 的 review 原始证据。 + +处理规则: + +- 不迁入 `done/`,因为不是“当前代码已完成”的实现文档。 +- 不当作当前 sprint 的任务源。 +- 需要转实施时,新建更小的执行 checklist。 + +### 2.3 Done / Old + +- 已完成且有真实代码、测试、smoke 或明确验收证据:移动到同域 `done/`。 +- 已被覆盖且口径过时但未完成:移动到 `design/old//process/` 并标 `[recycle]`。 +- 本轮已完成一批 `done/` 归档,详见 `15-design-governance-mvp-post-review-v1.md`。 + +## 3. P0:先清阻塞和验证红灯 + +这一步先处理“会干扰后续判断”的验证缺口,不扩新产品面。 + +### P0.1 FileTree action / open editors smoke 前置 + +入口文档: + +- `design/04-tree-domain/process/4-45-filetree-open-editors-lifecycle-link-checklist-v1.md` +- `design/04-tree-domain/process/4-44-filetree-action-layer-paste-drop-delete-v1.md` + +执行目标: + +1. 修复或明确 `task430-vscode-explorer-stage7-smoke.js` / `task471-local-folder-bulk-resource-trash-smoke.js` 的 workspace bootstrap 前置条件。 +2. 把 4-44 中未通过的 browser smoke 跑绿,或把环境阻塞写成可复现 bug。 +3. 补 open editor / filetree active row / lifecycle 状态断言。 + +验收: + +- `node --check scripts/task430-vscode-explorer-stage7-smoke.js` +- `node --check scripts/task471-local-folder-bulk-resource-trash-smoke.js` +- 对应 smoke 实跑通过,或形成 `bugs/04-tree-domain/process/` 中明确 bug。 + +归档条件: + +- `4-45` 四项 checklist 完成后归档到 `04-tree-domain/done/`。 +- `4-44` browser smoke 全绿后归档到 `04-tree-domain/done/`。 + +### P0.2 local folder no-refresh / restore focus + +入口文档: + +- `design/04-tree-domain/process/4-34-filetree-trash-dual-browser-no-refresh-v1.md` +- `design/04-tree-domain/process/4-28-trash-restore-location-reveal-focus-v1.md` + +执行目标: + +1. 补 local folder 非 md 资源 trash / restore / purge 的双浏览器 no-refresh 证据。 +2. 补 restore 后 DOM focus / reveal 的浏览器证据。 + +验收: + +- 新增或复用 task436 等价 smoke,证明 B 浏览器不刷新能看到 local folder 资源生命周期变化。 +- restore location / reveal / focus smoke 截图和 JSON 证据落盘。 + +归档条件: + +- local folder 子项通过后,`4-34` 归档。 +- React / Rust 主壳 focus 证据完成后,`4-28` 归档。 + +## 4. P1:统一工作区身份、BufferStore 和 Page Aggregate + +这一步是 MVP 后阶段真正的底座收口。 + +### P1.1 WorkspacePath / ObjectIdentity + +入口文档: + +- `design/01-tree-first-graph-kernel/process/1-4-next-phase-execution-roadmap-v1.md` +- `design/01-tree-first-graph-kernel/process/1-5-next-phase-sequential-execution-checklist-v1.md` +- `design/01-tree-first-graph-kernel/process/1-6-next-phase-gap-closure-checklist-v1.md` +- `design/05-editor-mainline/process/5-26-resource-open-resolver-convergence-checklist-v1.md` + +执行目标: + +1. 统一 page `.md`、raw local file、directory、asset folder、mindmap、office 的 canonical identity。 +2. 让 FileTree open target、主编辑器 tab、Open Editors snapshot 和 URL active state 消费同一套 identity。 +3. 解决 `5-26` 中“正文附件 open resolver 与 FileTree open resolver 收敛”未闭环问题。 + +验收: + +- `cargo test -p mnote-web filetree_runtime -- --test-threads=1` +- `cargo test -p mnote-web web_shell -- --test-threads=1` +- 覆盖 markdown / office / mindmap / raw file / directory 的 browser smoke。 + +归档条件: + +- `5-26` checklist 完成后归档。 +- `1-5 / 1-6` 中对应 identity 缺口完成打勾。 + +### P1.2 DocumentBuffer / BufferStore + +入口文档: + +- `design/01-tree-first-graph-kernel/process/1-5-next-phase-sequential-execution-checklist-v1.md` +- `design/01-tree-first-graph-kernel/process/1-6-next-phase-gap-closure-checklist-v1.md` +- `design/05-editor-mainline/process/5-6-page-aggregate-alignment-checklist-v1.md` + +执行目标: + +1. 让 tiptap、resource tab、watcher、agent 写入和冲突 UI 共用打开态与文件版本仲裁。 +2. 避免 dirty editor、资源 tab、文件树点击分别维护状态。 +3. 把已有 BufferStore 基础设施接到更多真实 runtime 消费点。 + +验收: + +- `cargo test -p mnote-web document_buffer -- --test-threads=1` +- `cargo test -p mnote-web local_folder -- --test-threads=1` +- 外部修改、dirty buffer、agent writeback、tab 切换 smoke 全部可解释。 + +归档条件: + +- `1-6` 中 BufferStore 运行时接入、浏览器读回、冲突 UI 相关项完成。 + +### P1.3 Page Aggregate compat 瘦身 + +入口文档: + +- `design/05-editor-mainline/process/5-6-page-aggregate-alignment-checklist-v1.md` +- `design/05-editor-mainline/process/5-5-page-aggregate-single-truth-alignment-v1.md`(reference) +- `design/03-rust-web/process/3-13-rust-web-local-markdown-gfm-ast-parser-migration-v1.md` + +执行目标: + +1. 收敛 `body.content` / compat join / 临时转换分支。 +2. 补 GFM AST parser 写侧尾项:web shell 单测、手写 parser 过渡标记、临时适配分支清理。 +3. 明确 `PageAggregateClientState` 哪些仍是合法 draft state,哪些应退出事实源地位。 + +验收: + +- `cargo test -p mnote-web local_markdown -- --test-threads=1` +- `cargo test -p mnote-web web_shell -- --test-threads=1` +- Page Aggregate body/options/title smoke 不回退。 + +归档条件: + +- `3-13` 三个未完成项完成后归档。 +- `5-6` 剩余 Phase G/H 小尾项完成或拆成更小 checklist 后归档。 + +## 5. P2:tree live cache 与 command context + +### P2.1 Tree realtime live cache + +入口文档: + +- `design/03-rust-web/process/3-3-rust-web-tree-realtime-event-stream-v1.md` +- `design/03-rust-web/process/3-1-rust-web-long-term-checklist-v2.md` + +执行目标: + +1. 把 local-folder watcher、projection refresh、WS/SSE snapshot/delta/resync 收进同一 live cache。 +2. 减少 polling、手动 refresh 和双路径补偿。 +3. 明确 Convex realtime 只服务 cloud / sync replica,不回到 local-first 默认真相层。 + +验收: + +- tree create / rename / move / archive / restore / purge 双浏览器 smoke。 +- local folder watcher 修改 `.md`、resource、directory 后主壳不整页刷新。 + +归档条件: + +- `3-3` 中统一 live cache 和 no-refresh 矩阵完成后归档。 + +### P2.2 Command context / context key + +入口文档: + +- `design/01-tree-first-graph-kernel/process/1-5-next-phase-sequential-execution-checklist-v1.md` +- `design/04-tree-domain/process/4-44-filetree-action-layer-paste-drop-delete-v1.md` + +执行目标: + +1. 菜单、快捷键、按钮、AI 写入能力共享 enablement 判断。 +2. 让 readonly、dirty、permission、resource kind、open target 统一成为 context key。 +3. 降低 filetree / editor / AI 各自硬编码能力判断的概率。 + +验收: + +- Rust 或 JS 单测覆盖 context key `when` 判断。 +- 文件树右键、主编辑器附件菜单、快捷键的 disabled/hidden 行为一致。 + +归档条件: + +- `4-44` action layer smoke 全绿。 +- `1-5` command context 对应项完成。 + +## 6. P3:AI 主线继续收口 + +### P3.1 online markdown final content truth + +入口文档: + +- `design/07-ai/process/7-27-online-markdown-writeback-final-content-truth-v2.md` + +执行目标: + +1. 修正 online / compat `mnote.doc.markdown_edit` 的 final markdown 写回真相。 +2. 避免 full_content 和多 operation 场景仍从旧 blocks 二次推导。 +3. 保持 local-first 默认编辑仍走 agent 原生 patch/diff,不把 `markdown_edit` 升回默认主路径。 + +验收: + +- `cargo test -p mnote-web hermes_tools -- --test-threads=1` +- 覆盖 full_content、多 op、inline content、selection scope 的回归测试。 + +归档条件: + +- 7-27 的 6 步实施计划完成后归档。 + +### P3.2 ACP runtime 后续步骤 + +入口文档: + +- `design/07-ai/process/7-15-page-ai-acp-agent-runtime-unified-layer-v1.md` +- `design/07-ai/process/7-12-page-ai-hermes-tool-routing-and-review-surface-v1.md` + +执行目标: + +1. 继续拆 `7-15` step 8 之后:Reasonix wrapper、前端 runtime selector、存量迁移、旧 HTTP proxy 兼容隔离。 +2. 给 `7-12` 的 manifest / review surface / state event 仍未完成项拆新 checklist。 +3. Phase C / Review Mode 仍冻结,不在未解冻前实现流式 apply。 + +验收: + +- ACP Hermes 与 ACP Reasonix 均能真实回复。 +- session load、permission、tool location、plan UI 不回退。 + +归档条件: + +- `7-15` 后续步骤拆成并完成具体 checklist 后再归档。 +- `7-12` Phase B-F 完成或冻结项移出后再归档。 + +## 7. P4:mindmap、Wolai 对标和参考流程 + +### P4.1 Mindmap 尾项 + +入口文档: + +- `design/06-mindmap/process/6-mindmap-kernel-phase6-projection-editor-v1.md` +- `design/06-mindmap/process/6-mindmap-phase6-leptos-ui-shell-reuse-checklist-v1.md` +- `design/06-mindmap/process/6-mindmap-leptos-adapter-reference-notes-v1.md`(reference) + +执行目标: + +1. 明确 simplemindmap 当前产品化边界。 +2. 给 KMind visual parity 设置可退出标准,避免无限尾项。 +3. 后续 AI 操作 mindmap 时以 Resource Tree 对象能力为入口,不塞入 Markdown 正文真相。 + +验收: + +- mindmap open / save / filetree active / resource tab smoke 继续通过。 +- KMind parity 未完成项要么完成,要么明确降级为参考。 + +### P4.2 Wolai-aline 持续项 + +入口文档: + +- `design/05-editor-mainline/process/5-9-wolai-aline-continuous-checklist-v1.md` +- `design/08-wolai-aline-test-flow/process/wolai-aline-test-flow-v1.md`(workflow reference) +- `design/05-editor-mainline/process/5-7-wolai-page-tree-main-editor-experience-restoration-v1.md`(reference) + +执行目标: + +1. 清理 `5-9` 中已被 `5-6` 覆盖但仍显示 TODO 的 Page Aggregate 项。 +2. 对 A4 字体 token、E18 paste/clipboard 等仍有价值项补 smoke 或降级。 +3. 所有 Wolai 对标任务继续按 `wolai-aline` skill 和 `08` 流程执行。 + +归档条件: + +- `5-9` 剩余项全部完成或降级后归档。 + +## 8. P5:远期 / 候选 / 参考 + +这些文档不进入当前直接执行序列,只有当产品优先级明确时再拆 checklist: + +- `design/03-rust-web/process/3-17-convex-export-web-entry-v1.md` + - CLI 已有迁移闭环;Web 管理入口未排期。 +- `design/04-tree-domain/process/4-23-local-cloud-explicit-bridge-p3-candidate-v1.md` + - P3 候选,不能进入 local-first 默认主链。 +- `design/07-ai/process/7-17-acp-session-convex-sharing-contract-v1.md` + - Future sharing contract。 +- `design/07-ai/process/7-28-resource-ai-tool-contract-v1.md` + - mindmap / office Resource AI tool 草案。 +- `design/09-siyuan-reference/process/9-siyuan-reference-boundary-and-adoption-v1.md` + - 参考边界,不是实施入口。 +- `design/10-review/process/12-*`、`13-*`、`14-*` + - 原始 review 证据;执行项已拆出或归档。 + +## 9. 推荐执行批次 + +### Batch A:清验证阻塞 + +1. `4-45` +2. `4-44` +3. `4-34` +4. `4-28` + +完成后预期:tree/filetree action 和 trash/no-refresh 的判断不再被 smoke 红灯干扰。 + +### Batch B:身份与打开态 + +1. `5-26` +2. `1-5` identity 子项 +3. `1-6` BufferStore / conflict browser 子项 + +完成后预期:页面、资源、tab、filetree active、open editors 可围绕同一 identity 和 buffer state 工作。 + +### Batch C:Page Aggregate / local markdown 写侧 + +1. `3-13` +2. `5-6` +3. `1-5` Page Aggregate 子项 + +完成后预期:正文真相、runtime projection、compat join 的边界更薄。 + +### Batch D:live cache / command context + +1. `3-3` +2. `1-5` command context 子项 +3. `4-44` 若 Batch A 未完成则回补 + +完成后预期:树域实时与 enablement 判断更统一。 + +### Batch E:AI 与资源工具 + +1. `7-27` +2. `7-15` 后续拆分 +3. `7-12` Phase B-F 拆分 +4. `7-28` 仅在 mindmap / office AI 明确排期后进入 + +完成后预期:local-first AI 主路径稳定,cloud/compat 工具不再误导默认路径。 + +### Batch F:体验对标与远期 + +1. `6-mindmap-*` +2. `5-9` +3. `3-17` +4. `4-23` +5. `7-17` + +完成后预期:产品体验和分享同步进入产品化,而不是继续扩底层 compat。 + +## 10. 每轮执行的固定流程 + +每次从本文选一个 batch 后,按以下流程推进: + +1. 先读本文和对应入口文档。 +2. 对该 batch 新建或更新一个小 checklist,不直接修改大而旧的全景文档。 +3. 如果有 2 个以上互不冲突方向,按 Reasonix parallel flow 拆 worker。 +4. worker 只改其 ownership 范围,不提交 git。 +5. Codex 复核 diff、跑 targeted tests / browser smoke。 +6. 完成后: + - 更新 checklist 状态和证据。 + - 能归档的移动到 `done/`。 + - 被覆盖但未完成的移入 `old/` 并标 `[recycle]`。 + - 更新本文对应 batch 状态。 + - 运行 `codegraph sync .`。 + +## 11. 不做事项 + +- 不按文件编号顺序清空 process。 +- 不把 `process-reference` 当作当前实现任务。 +- 不因为文档老就移动到 `done/`;必须有真实完成证据。 +- 不把 Phase C / Review Mode / cloud sync / sharing future 合同提前实现。 +- 不恢复旧 Next / BlockNote / Convex 默认主存储口径。 diff --git a/design/02-convex-rust-long-term-architecture/done/2-2-local-first-workspace-convex-control-plane-v1.md b/design/02-convex-rust-long-term-architecture/done/2-2-local-first-workspace-convex-control-plane-v1.md index bdef9284..bf095ef3 100644 --- a/design/02-convex-rust-long-term-architecture/done/2-2-local-first-workspace-convex-control-plane-v1.md +++ b/design/02-convex-rust-long-term-architecture/done/2-2-local-first-workspace-convex-control-plane-v1.md @@ -13,7 +13,7 @@ > - `/mnt/Data1T/mnote/ARCHITECTURE.md` > - `/mnt/Data1T/mnote/design/01-05-current-priority-overview.md` > - `/mnt/Data1T/mnote/design/02-convex-rust-long-term-architecture/process/2-tree-first-graph-convex-rust-long-term-architecture-v1.md` -> - `/mnt/Data1T/mnote/design/03-rust-web/process/3-15-local-markdown-asset-upload-relative-path-v1.md` +> - `/mnt/Data1T/mnote/design/03-rust-web/done/3-15-local-markdown-asset-upload-relative-path-v1.md` > - `/mnt/Data1T/mnote/design/04-tree-domain/done/4-22-local-folder-convex-unified-tree-execution-checklist-v1.md` > - `/mnt/Data1T/mnote/design/07-ai/process/7-14-online-local-ai-markdown-editing-convergence-v1.md` > - `/mnt/Data1T/mnote/bugs/03-rust-web/done/3-16-local-markdown-upload-uses-convex-media-asset-v1.md` diff --git a/design/02-convex-rust-long-term-architecture/process/2-3-local-workspace-access-control-productization-v1.md b/design/02-convex-rust-long-term-architecture/done/2-3-local-workspace-access-control-productization-v1.md similarity index 92% rename from design/02-convex-rust-long-term-architecture/process/2-3-local-workspace-access-control-productization-v1.md rename to design/02-convex-rust-long-term-architecture/done/2-3-local-workspace-access-control-productization-v1.md index 1edbe595..8977d1ac 100644 --- a/design/02-convex-rust-long-term-architecture/process/2-3-local-workspace-access-control-productization-v1.md +++ b/design/02-convex-rust-long-term-architecture/done/2-3-local-workspace-access-control-productization-v1.md @@ -1,8 +1,10 @@ -# 2-3 [process] Local Workspace Access Control 产品化 checklist v1 +# 2-3 [done] Local Workspace Access Control 产品化 checklist v1 > 创建时间:2026-05-19 > -> 当前状态:`PROCESS` +> 当前状态:`DONE` +> +> 归档说明(2026-05-21):P0 管理员控制面 API 与 P1 权限覆盖审计已完成;管理员 UI/API、local folder open、page body write、tree command、Hermes/Reasonix 与 shared AI session 权限链已有代码与测试证据。后续授权产品化扩展应新开文档。 > > 上位依据: > - `/mnt/Data1T/mnote/design/02-convex-rust-long-term-architecture/done/2-2-local-first-workspace-convex-control-plane-v1.md` diff --git a/design/03-rust-web/process/3-15-local-markdown-asset-upload-relative-path-v1.md b/design/03-rust-web/done/3-15-local-markdown-asset-upload-relative-path-v1.md similarity index 92% rename from design/03-rust-web/process/3-15-local-markdown-asset-upload-relative-path-v1.md rename to design/03-rust-web/done/3-15-local-markdown-asset-upload-relative-path-v1.md index 333a021d..0ca36697 100644 --- a/design/03-rust-web/process/3-15-local-markdown-asset-upload-relative-path-v1.md +++ b/design/03-rust-web/done/3-15-local-markdown-asset-upload-relative-path-v1.md @@ -1,8 +1,10 @@ -# 3-15 [process] 本地 Markdown 图片与附件上传相对路径设计 v1 +# 3-15 [done] 本地 Markdown 图片与附件上传相对路径设计 v1 > 创建时间:2026-05-18 > -> 状态:`[process]`(代码、单测和 HTTP smoke 已完成;真实浏览器 smoke 待补) +> 状态:`[done]` +> +> 归档说明(2026-05-21):代码、单测、HTTP smoke 与真实浏览器 smoke 均已完成;`scripts/task443-local-markdown-asset-upload-smoke.js` 覆盖本地 `.md` 页面上传图片/附件、刷新后恢复与文件树可见。 > > 所属主线:`03-rust-web` / `local_folder` / `local markdown` > @@ -192,7 +194,7 @@ multipart/form-data - [x] 单测覆盖本地上传目标路径和冲突递增。 - [x] 单测覆盖 local markdown 图片/附件 roundtrip。 - [x] HTTP smoke 覆盖 `/api/local-folder/assets/upload` 真实 multipart 上传与落盘。 -- [ ] 浏览器 smoke 覆盖本地 `.md` 页面上传图片/附件、刷新后恢复、文件树可见。 +- [x] 浏览器 smoke 覆盖本地 `.md` 页面上传图片/附件、刷新后恢复、文件树可见(`scripts/task443-local-markdown-asset-upload-smoke.js`)。 ## 10. 验收命令 @@ -205,5 +207,5 @@ cargo test --manifest-path rust/Cargo.toml -p mnote-web local_folder -- --nocapt 浏览器 smoke 后续新增: ```bash -MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task-local-markdown-asset-upload-smoke.js +MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task443-local-markdown-asset-upload-smoke.js ``` diff --git a/design/03-rust-web/process/3-17-convex-export-web-entry-v1.md b/design/03-rust-web/process/3-17-convex-export-web-entry-v1.md index 00a79c24..4b0fa610 100644 --- a/design/03-rust-web/process/3-17-convex-export-web-entry-v1.md +++ b/design/03-rust-web/process/3-17-convex-export-web-entry-v1.md @@ -1,6 +1,8 @@ # 3-17 Convex Export Web Entry v1 -状态:process +状态:process-reference + +归档说明(2026-05-21):CLI 迁移闭环已具备并有 smoke 证据;本文只保留为后续 Web 管理入口设计参考,当前不作为 active implementation checklist。若决定短期落地 Web UI,应新建执行 checklist。 ## 目标 diff --git a/design/04-tree-domain/process/4-27-resource-lifecycle-command-cutover-v1.md b/design/04-tree-domain/done/4-27-resource-lifecycle-command-cutover-v1.md similarity index 98% rename from design/04-tree-domain/process/4-27-resource-lifecycle-command-cutover-v1.md rename to design/04-tree-domain/done/4-27-resource-lifecycle-command-cutover-v1.md index b7e8bb09..6739a800 100644 --- a/design/04-tree-domain/process/4-27-resource-lifecycle-command-cutover-v1.md +++ b/design/04-tree-domain/done/4-27-resource-lifecycle-command-cutover-v1.md @@ -1,6 +1,7 @@ # 4-27 Resource Lifecycle Command Cutover v1 -> 状态:process +> 状态:done +> 归档说明(2026-05-21):核心 `tree.resource.*` 生命周期命令 cutover 已完成;双浏览器 no-refresh、restore reveal/focus 等尾项已由 `4-34`、`4-28` 继续跟踪。 > 更新时间:2026-05-15 > 范围:`tree.resource.archive / restore / purge / rename` 正式命令面,以及 `/api/media/*`、mindmap、table 资源垃圾箱兼容入口的收口边界。 diff --git a/design/04-tree-domain/process/4-39-filetree-open-target-resource-identity-checklist-v1.md b/design/04-tree-domain/done/4-39-filetree-open-target-resource-identity-checklist-v1.md similarity index 94% rename from design/04-tree-domain/process/4-39-filetree-open-target-resource-identity-checklist-v1.md rename to design/04-tree-domain/done/4-39-filetree-open-target-resource-identity-checklist-v1.md index 7e94627b..53c8e9f9 100644 --- a/design/04-tree-domain/process/4-39-filetree-open-target-resource-identity-checklist-v1.md +++ b/design/04-tree-domain/done/4-39-filetree-open-target-resource-identity-checklist-v1.md @@ -1,6 +1,8 @@ # 4-39 FileTree Open Target 与 Resource Identity Checklist v1 -> 状态:process +> 状态:done +> +> 归档说明(2026-05-21):FileTree open target 与 resource identity 合同已完成;`rootUri` 收尾由 `4-42` 覆盖并已归档。 > > 日期:2026-05-20 > diff --git a/design/04-tree-domain/process/4-40-resource-lifecycle-command-cutover-smoke-checklist-v1.md b/design/04-tree-domain/done/4-40-resource-lifecycle-command-cutover-smoke-checklist-v1.md similarity index 93% rename from design/04-tree-domain/process/4-40-resource-lifecycle-command-cutover-smoke-checklist-v1.md rename to design/04-tree-domain/done/4-40-resource-lifecycle-command-cutover-smoke-checklist-v1.md index 1b889d19..d1732ba5 100644 --- a/design/04-tree-domain/process/4-40-resource-lifecycle-command-cutover-smoke-checklist-v1.md +++ b/design/04-tree-domain/done/4-40-resource-lifecycle-command-cutover-smoke-checklist-v1.md @@ -1,6 +1,8 @@ # 4-40 Resource Lifecycle Command Cutover 与垃圾箱 Smoke Checklist v1 -> 状态:process +> 状态:done +> +> 归档说明(2026-05-21):resource lifecycle browser smoke 与 command cutover 验证已完成;bulk delete 剩余项由 `4-41` 覆盖并已归档。 > > 日期:2026-05-20 > diff --git a/design/04-tree-domain/process/4-41-filetree-bulk-resource-command-cutover-v1.md b/design/04-tree-domain/done/4-41-filetree-bulk-resource-command-cutover-v1.md similarity index 93% rename from design/04-tree-domain/process/4-41-filetree-bulk-resource-command-cutover-v1.md rename to design/04-tree-domain/done/4-41-filetree-bulk-resource-command-cutover-v1.md index 8226eda4..be224c38 100644 --- a/design/04-tree-domain/process/4-41-filetree-bulk-resource-command-cutover-v1.md +++ b/design/04-tree-domain/done/4-41-filetree-bulk-resource-command-cutover-v1.md @@ -1,6 +1,8 @@ # 4-41 FileTree Bulk Resource Command Cutover Checklist v1 -> 状态:process +> 状态:done +> +> 归档说明(2026-05-21):FileTree bulk resource command cutover 已完成,云端 table 旧 fallback 为显式保留边界。 > > 日期:2026-05-20 > diff --git a/design/04-tree-domain/process/4-42-filetree-open-target-rooturi-contract-v1.md b/design/04-tree-domain/done/4-42-filetree-open-target-rooturi-contract-v1.md similarity index 92% rename from design/04-tree-domain/process/4-42-filetree-open-target-rooturi-contract-v1.md rename to design/04-tree-domain/done/4-42-filetree-open-target-rooturi-contract-v1.md index 728af84b..bc70bb80 100644 --- a/design/04-tree-domain/process/4-42-filetree-open-target-rooturi-contract-v1.md +++ b/design/04-tree-domain/done/4-42-filetree-open-target-rooturi-contract-v1.md @@ -1,6 +1,8 @@ # 4-42 FileTree Open Target RootUri Contract Checklist v1 -> 状态:process +> 状态:done +> +> 归档说明(2026-05-21):FileTree open target `rootUri` 合同已完成,缺失 `rootUri` 的旧 payload 兼容仍保留。 > > 日期:2026-05-20 > diff --git a/design/04-tree-domain/process/4-43-local-folder-sort-order-persistence-v1.md b/design/04-tree-domain/done/4-43-local-folder-sort-order-persistence-v1.md similarity index 95% rename from design/04-tree-domain/process/4-43-local-folder-sort-order-persistence-v1.md rename to design/04-tree-domain/done/4-43-local-folder-sort-order-persistence-v1.md index 4aca1f38..4d63a914 100644 --- a/design/04-tree-domain/process/4-43-local-folder-sort-order-persistence-v1.md +++ b/design/04-tree-domain/done/4-43-local-folder-sort-order-persistence-v1.md @@ -1,6 +1,8 @@ # 4-43 Local Folder Sort Order Persistence Checklist v1 -> 状态:process +> 状态:done +> +> 归档说明(2026-05-21):local folder sort order 持久化、projection 消费与浏览器 smoke 均已完成。 > > 日期:2026-05-20 > diff --git a/design/04-tree-domain/process/4-23-local-cloud-explicit-bridge-p3-candidate-v1.md b/design/04-tree-domain/process/4-23-local-cloud-explicit-bridge-p3-candidate-v1.md index 74580c97..21d639ea 100644 --- a/design/04-tree-domain/process/4-23-local-cloud-explicit-bridge-p3-candidate-v1.md +++ b/design/04-tree-domain/process/4-23-local-cloud-explicit-bridge-p3-candidate-v1.md @@ -2,6 +2,8 @@ > 更新时间:2026-05-07 > +> 当前状态:`process-reference / P3 candidate`。本文只冻结 Local / Cloud 显式桥接候选合同,未进入当前实施;不得把它当作 local-first 默认读写路径。 +> > 关联: > - `4-21-local-folder-convex-unified-tree-source-v1.md` > - `4-22-local-folder-convex-unified-tree-execution-checklist-v1.md` diff --git a/design/05-editor-mainline/process/5-15-local-markdown-path-identity-unification-checklist-v1.md b/design/05-editor-mainline/done/5-15-local-markdown-path-identity-unification-checklist-v1.md similarity index 98% rename from design/05-editor-mainline/process/5-15-local-markdown-path-identity-unification-checklist-v1.md rename to design/05-editor-mainline/done/5-15-local-markdown-path-identity-unification-checklist-v1.md index 5528d9fe..d8c76754 100644 --- a/design/05-editor-mainline/process/5-15-local-markdown-path-identity-unification-checklist-v1.md +++ b/design/05-editor-mainline/done/5-15-local-markdown-path-identity-unification-checklist-v1.md @@ -1,5 +1,9 @@ # 5-15 本地 Markdown 路径身份统一 Checklist v1 +> 状态:done +> +> 归档说明(2026-05-21):本地 Markdown 路径身份统一 checklist 已完成并从 `process/` 归档到 `done/`。 + ## 背景 当前本地 Markdown 主链正在对齐 VS Code 文件模型:普通 `.md` 文件的运行时身份应来自文件资源地址,而不是 frontmatter 或 MNote 内部稳定 ID。 @@ -132,4 +136,3 @@ cargo test --manifest-path rust/Cargo.toml -p mnote-web local_page_aggregate_acc cargo test --manifest-path rust/Cargo.toml -p mnote-web local_page_id_initialization_returns_path_id_without_writing_page_ids -- --nocapture cargo test --manifest-path rust/Cargo.toml -p mnote-web tree_command_local_folder -- --nocapture ``` - diff --git a/design/05-editor-mainline/process/5-16-editor-group-resource-tab-safety-checklist-v1.md b/design/05-editor-mainline/done/5-16-editor-group-resource-tab-safety-checklist-v1.md similarity index 77% rename from design/05-editor-mainline/process/5-16-editor-group-resource-tab-safety-checklist-v1.md rename to design/05-editor-mainline/done/5-16-editor-group-resource-tab-safety-checklist-v1.md index e0153a22..4d03ce0f 100644 --- a/design/05-editor-mainline/process/5-16-editor-group-resource-tab-safety-checklist-v1.md +++ b/design/05-editor-mainline/done/5-16-editor-group-resource-tab-safety-checklist-v1.md @@ -1,6 +1,8 @@ # 5-16 Editor Group 与 Resource Tab 安全收口 Checklist v1 -> 状态:process +> 状态:done +> +> 归档说明(2026-05-21):本文件核心安全收口已完成;正式 tab state 与错误占位剩余项已由 `5-18` 覆盖并归档。 > > 日期:2026-05-20 > @@ -20,7 +22,7 @@ ## 3. Checklist -- [ ] 定义轻量 `MainEditorTabEntry` / `mainEditorTabRegistry` 语义,至少包含 `id`、`kind`、`title`、`dirty`、`saving`、`hasExternalConflict`、`lastActiveAt`。 +- [x] 定义轻量 `MainEditorTabEntry` / `mainEditorTabRegistry` 语义,至少包含 `id`、`kind`、`title`、`dirty`、`saving`、`hasExternalConflict`、`lastActiveAt`。(由 `5-18` 的主 tab state 收口覆盖) - [x] 保持 page tab 为固定 tab,但 resource tab 激活/关闭应走同一套 registry 状态更新。 - [x] `closeResourceTab` 关闭前检查 resource session: - [x] `saving=true` 时禁止直接关闭或等待保存完成。 @@ -28,14 +30,14 @@ - [x] `dirty=true` 时触发保存或确认流程;不得直接释放 session。 - [x] 关闭 active resource tab 后按 MRU 回到上一个 resource tab;没有可用 resource tab 时才回到 page tab。 - [x] resource tab 激活时更新稳定 `aria-selected`、`tabindex` 和 panel hidden 状态。 -- [ ] 打开失败时保留 error placeholder tab,允许用户关闭或重试,不要留下半注册状态。 +- [x] 打开失败时保留 error placeholder tab,允许用户关闭或重试,不要留下半注册状态。(由 `5-18` 覆盖) ## 4. 验收 - [x] 打开两个 resource tab,切到第二个并关闭后,应回到第一个,而不是无条件回 page tab。 - [x] 编辑 resource markdown 后立即点关闭,不应静默丢弃未保存内容。 - [x] 保存中关闭不会释放 editor session。 -- [ ] 打开失败的 resource tab 有明确错误占位,并可关闭。 +- [x] 打开失败的 resource tab 有明确错误占位,并可关闭。(由 `5-18` 覆盖) - [x] 现有 `task457-main-editor-resource-tab-smoke.js` 继续通过。 - [x] 新增 close dirty / MRU smoke 通过。 @@ -43,7 +45,7 @@ - 已合入:resource tab MRU、关闭 guard、`aria-selected` / `tabindex` 同步、关闭后回退到上一个 resource tab。 - 已新增:`scripts/task460-resource-tab-close-dirty-smoke.js` 覆盖近期本地输入后的关闭阻止。 -- 未完成:正式 `MainEditorTabEntry` 模型、打开失败 placeholder tab。 +- 后续覆盖:正式 `MainEditorTabEntry` 模型与打开失败 placeholder tab 已由 `5-18` 收口。 ## 5. 非目标 diff --git a/design/05-editor-mainline/process/5-17-resource-editor-kind-and-smoke-matrix-checklist-v1.md b/design/05-editor-mainline/done/5-17-resource-editor-kind-and-smoke-matrix-checklist-v1.md similarity index 94% rename from design/05-editor-mainline/process/5-17-resource-editor-kind-and-smoke-matrix-checklist-v1.md rename to design/05-editor-mainline/done/5-17-resource-editor-kind-and-smoke-matrix-checklist-v1.md index 711c4acc..1b757d88 100644 --- a/design/05-editor-mainline/process/5-17-resource-editor-kind-and-smoke-matrix-checklist-v1.md +++ b/design/05-editor-mainline/done/5-17-resource-editor-kind-and-smoke-matrix-checklist-v1.md @@ -1,6 +1,8 @@ # 5-17 Resource Editor Kind 与 Smoke 矩阵 Checklist v1 -> 状态:process +> 状态:done +> +> 归档说明(2026-05-21):资源 editor kind 与 smoke 矩阵已完成;resolver 与 OnlyOffice 回源后续由 `5-19` 覆盖并归档。 > > 日期:2026-05-20 > diff --git a/design/05-editor-mainline/process/5-18-main-editor-tab-state-and-error-placeholder-checklist-v1.md b/design/05-editor-mainline/done/5-18-main-editor-tab-state-and-error-placeholder-checklist-v1.md similarity index 96% rename from design/05-editor-mainline/process/5-18-main-editor-tab-state-and-error-placeholder-checklist-v1.md rename to design/05-editor-mainline/done/5-18-main-editor-tab-state-and-error-placeholder-checklist-v1.md index aec45e2d..4e9c34ae 100644 --- a/design/05-editor-mainline/process/5-18-main-editor-tab-state-and-error-placeholder-checklist-v1.md +++ b/design/05-editor-mainline/done/5-18-main-editor-tab-state-and-error-placeholder-checklist-v1.md @@ -1,6 +1,8 @@ # 5-18 Main Editor Tab State 与错误占位 Checklist v1 -> 状态:process +> 状态:done +> +> 归档说明(2026-05-21):主编辑器 tab state 与错误占位已完成并验证。 > > 日期:2026-05-20 > diff --git a/design/05-editor-mainline/process/5-19-resource-open-resolver-and-onlyoffice-checklist-v1.md b/design/05-editor-mainline/done/5-19-resource-open-resolver-and-onlyoffice-checklist-v1.md similarity index 94% rename from design/05-editor-mainline/process/5-19-resource-open-resolver-and-onlyoffice-checklist-v1.md rename to design/05-editor-mainline/done/5-19-resource-open-resolver-and-onlyoffice-checklist-v1.md index 816fd0cf..71b8cf32 100644 --- a/design/05-editor-mainline/process/5-19-resource-open-resolver-and-onlyoffice-checklist-v1.md +++ b/design/05-editor-mainline/done/5-19-resource-open-resolver-and-onlyoffice-checklist-v1.md @@ -1,6 +1,8 @@ # 5-19 Resource Open Resolver 与 OnlyOffice 回源 Checklist v1 -> 状态:process +> 状态:done +> +> 归档说明(2026-05-21):resource open resolver 与 OnlyOffice 回源已完成;外部 DocumentServer 协同验证不作为本 checklist 阻塞项。 > > 日期:2026-05-20 > diff --git a/design/05-editor-mainline/process/5-20-resource-tab-close-confirm-checklist-v1.md b/design/05-editor-mainline/done/5-20-resource-tab-close-confirm-checklist-v1.md similarity index 94% rename from design/05-editor-mainline/process/5-20-resource-tab-close-confirm-checklist-v1.md rename to design/05-editor-mainline/done/5-20-resource-tab-close-confirm-checklist-v1.md index a3deb1b1..bd75ca2e 100644 --- a/design/05-editor-mainline/process/5-20-resource-tab-close-confirm-checklist-v1.md +++ b/design/05-editor-mainline/done/5-20-resource-tab-close-confirm-checklist-v1.md @@ -1,6 +1,8 @@ # 5-20 Resource Tab Close Confirm Checklist v1 -> 状态:process +> 状态:done +> +> 归档说明(2026-05-21):resource tab close guard 可见提示已完成。 > > 日期:2026-05-20 > diff --git a/design/05-editor-mainline/process/5-21-editor-tab-url-active-identity-checklist-v1.md b/design/05-editor-mainline/done/5-21-editor-tab-url-active-identity-checklist-v1.md similarity index 94% rename from design/05-editor-mainline/process/5-21-editor-tab-url-active-identity-checklist-v1.md rename to design/05-editor-mainline/done/5-21-editor-tab-url-active-identity-checklist-v1.md index 3f423da3..b9f2b5bd 100644 --- a/design/05-editor-mainline/process/5-21-editor-tab-url-active-identity-checklist-v1.md +++ b/design/05-editor-mainline/done/5-21-editor-tab-url-active-identity-checklist-v1.md @@ -1,6 +1,8 @@ # 5-21 Editor Tab URL / Active Row / Canonical Identity Checklist v1 -> 状态:process +> 状态:done +> +> 归档说明(2026-05-21):editor tab URL、active row 与 canonical identity 收口已完成。 > > 日期:2026-05-20 > diff --git a/design/05-editor-mainline/process/5-22-mindmap-main-editor-tab-kind-v1.md b/design/05-editor-mainline/done/5-22-mindmap-main-editor-tab-kind-v1.md similarity index 96% rename from design/05-editor-mainline/process/5-22-mindmap-main-editor-tab-kind-v1.md rename to design/05-editor-mainline/done/5-22-mindmap-main-editor-tab-kind-v1.md index 16c35199..571b97b4 100644 --- a/design/05-editor-mainline/process/5-22-mindmap-main-editor-tab-kind-v1.md +++ b/design/05-editor-mainline/done/5-22-mindmap-main-editor-tab-kind-v1.md @@ -1,6 +1,8 @@ # 5-22 Mindmap Main Editor Tab Kind Checklist v1 -> 状态:process +> 状态:done +> +> 归档说明(2026-05-21):mindmap main editor tab kind 收口已完成。 > > 日期:2026-05-20 > diff --git a/design/05-editor-mainline/process/5-23-editor-side-target-secondary-pane-v1.md b/design/05-editor-mainline/done/5-23-editor-side-target-secondary-pane-v1.md similarity index 95% rename from design/05-editor-mainline/process/5-23-editor-side-target-secondary-pane-v1.md rename to design/05-editor-mainline/done/5-23-editor-side-target-secondary-pane-v1.md index 821714e6..4f587a3c 100644 --- a/design/05-editor-mainline/process/5-23-editor-side-target-secondary-pane-v1.md +++ b/design/05-editor-mainline/done/5-23-editor-side-target-secondary-pane-v1.md @@ -1,6 +1,8 @@ # 5-23 Editor Side Target / Secondary Pane Checklist v1 -> 状态:process +> 状态:done +> +> 归档说明(2026-05-21):`openTarget=side` 到 secondary pane 的轻量合同已完成。 > > 日期:2026-05-20 > diff --git a/design/05-editor-mainline/process/5-24-open-editors-lightweight-view-checklist-v1.md b/design/05-editor-mainline/done/5-24-open-editors-lightweight-view-checklist-v1.md similarity index 93% rename from design/05-editor-mainline/process/5-24-open-editors-lightweight-view-checklist-v1.md rename to design/05-editor-mainline/done/5-24-open-editors-lightweight-view-checklist-v1.md index 08c4c035..d8d40feb 100644 --- a/design/05-editor-mainline/process/5-24-open-editors-lightweight-view-checklist-v1.md +++ b/design/05-editor-mainline/done/5-24-open-editors-lightweight-view-checklist-v1.md @@ -1,6 +1,8 @@ # 5-24 Open Editors Lightweight View Checklist v1 -> 状态:process +> 状态:done +> +> 归档说明(2026-05-21):open editors lightweight snapshot / 可观测模型已完成;完整 sidebar UI 不是本阶段目标。 > > 日期:2026-05-21 > diff --git a/design/05-editor-mainline/process/5-25-editor-tab-keyboard-and-mru-checklist-v1.md b/design/05-editor-mainline/done/5-25-editor-tab-keyboard-and-mru-checklist-v1.md similarity index 96% rename from design/05-editor-mainline/process/5-25-editor-tab-keyboard-and-mru-checklist-v1.md rename to design/05-editor-mainline/done/5-25-editor-tab-keyboard-and-mru-checklist-v1.md index 8e3ee9f7..d78ce162 100644 --- a/design/05-editor-mainline/process/5-25-editor-tab-keyboard-and-mru-checklist-v1.md +++ b/design/05-editor-mainline/done/5-25-editor-tab-keyboard-and-mru-checklist-v1.md @@ -1,6 +1,8 @@ # 5-25 Editor Tab Keyboard and MRU Checklist v1 -> 状态:process +> 状态:done +> +> 归档说明(2026-05-21):editor tab keyboard 与 MRU 行为已完成。 > > 日期:2026-05-21 > diff --git a/design/05-editor-mainline/process/5-14-zed-lapce-vscode-reference-adoption-matrix-v1.md b/design/05-editor-mainline/process/5-14-zed-lapce-vscode-reference-adoption-matrix-v1.md index fe2601cd..6b40e163 100644 --- a/design/05-editor-mainline/process/5-14-zed-lapce-vscode-reference-adoption-matrix-v1.md +++ b/design/05-editor-mainline/process/5-14-zed-lapce-vscode-reference-adoption-matrix-v1.md @@ -2,6 +2,8 @@ > 更新时间:2026-05-19 > +> 当前状态:`process-reference`。本文是参考实现采用矩阵,不是当前执行 checklist;具体实现任务应拆到对应 domain 的 process/done 文档。 +> > 关联背景: > - `/mnt/Data1T/mnote/CURRENT_ARCHITECTURE.md` > - `/mnt/Data1T/mnote/design/05-editor-mainline/process/5-5-page-aggregate-single-truth-alignment-v1.md` diff --git a/design/05-editor-mainline/process/5-2-tiptap-notion-like-template-adoption-v1.md b/design/05-editor-mainline/process/5-2-tiptap-notion-like-template-adoption-v1.md index 25685004..792f293a 100644 --- a/design/05-editor-mainline/process/5-2-tiptap-notion-like-template-adoption-v1.md +++ b/design/05-editor-mainline/process/5-2-tiptap-notion-like-template-adoption-v1.md @@ -2,6 +2,8 @@ > 更新时间:2026-04-19 > +> 当前状态:`process-reference`。本文只作为 tiptap / Notion-like 行为模型参考,不再作为当前执行 checklist;默认主编辑器已是页面内 `leptos-tiptap` island。 +> > 这份文档已经按当前进展修正: > > 现在的问题不再是“这套模板能不能迁进来”,而是: diff --git a/design/05-editor-mainline/process/5-5-page-aggregate-single-truth-alignment-v1.md b/design/05-editor-mainline/process/5-5-page-aggregate-single-truth-alignment-v1.md index ca072cbd..db4c5759 100644 --- a/design/05-editor-mainline/process/5-5-page-aggregate-single-truth-alignment-v1.md +++ b/design/05-editor-mainline/process/5-5-page-aggregate-single-truth-alignment-v1.md @@ -2,6 +2,8 @@ > 更新时间:2026-05-18 > +> 当前状态:`process-reference`。本文作为 Page Aggregate 单一真源长期方案保留;当前执行进度与剩余项以 `5-6` 及 MVP 后阶段 checklist 为准。 +> > 2026-05-18 口径补充: > - 页面聚合的默认 source 已开始向 local-first workspace 收口;页面正文、标题、设置和上传资源不应再默认把 Convex 视作主数据层。 > - 本文中涉及 Convex 的表述只应理解为 local-first 与 Convex-backed 两种 `WorkspaceSource` 的过渡兼容背景,不应再作为默认产品形态解释。 diff --git a/design/05-editor-mainline/process/5-7-wolai-page-tree-main-editor-experience-restoration-v1.md b/design/05-editor-mainline/process/5-7-wolai-page-tree-main-editor-experience-restoration-v1.md index 33bea058..2751a071 100644 --- a/design/05-editor-mainline/process/5-7-wolai-page-tree-main-editor-experience-restoration-v1.md +++ b/design/05-editor-mainline/process/5-7-wolai-page-tree-main-editor-experience-restoration-v1.md @@ -2,6 +2,7 @@ > 更新时间:2026-04-30 > +> 当前状态:`process-reference`。本文只保留 Wolai 体验复刻目标和取证基线;持续执行与安全边界以 `08-wolai-aline-test-flow` 和 `wolai-aline` skill 为准。 > **Wolai-aline 执行口径更新(2026-04-30):** 本文只保留体验复刻任务拆解和产品目标。所有 Wolai 对标测试、浏览器取证、编辑权限、安全边界、subagent 使用、截图复核和 smoke 补齐,统一以 `/home/lix/.codex/skills/wolai-aline` 与 `/mnt/Data1T/mnote/design/08-wolai-aline-test-flow/process/wolai-aline-test-flow-v1.md` 为准。若本文旧段落与该 skill 冲突,以 skill 为准。 > diff --git a/design/06-mindmap/process/6-mindmap-leptos-adapter-reference-notes-v1.md b/design/06-mindmap/process/6-mindmap-leptos-adapter-reference-notes-v1.md index 855ebb3c..3dc21d89 100644 --- a/design/06-mindmap/process/6-mindmap-leptos-adapter-reference-notes-v1.md +++ b/design/06-mindmap/process/6-mindmap-leptos-adapter-reference-notes-v1.md @@ -2,6 +2,8 @@ > 日期:2026-05-10 > +> 当前状态:`process-reference`。本文是 adapter/reference 审计笔记,不作为当前执行 checklist;mindmap 实施状态以 `6-mindmap-phase6-leptos-ui-shell-reuse-checklist-v1` 和 `done/6-mindmap-phase6-kmind-parity-detail-checklist-v1.md` 为准。 +> > 目的:记录 `lx-doc/mind-map` 与 KMind 插件审计结果,为 Phase 6 `leptos-mindmap` 的 simple-mind-map bridge、command bridge 和第一阶段 UI 复刻定界。 ## 1. 参考入口 diff --git a/design/07-ai/process/7-16-page-block-ai-real-smoke-followup-matrix-v1.md b/design/07-ai/done/7-16-page-block-ai-real-smoke-followup-matrix-v1.md similarity index 97% rename from design/07-ai/process/7-16-page-block-ai-real-smoke-followup-matrix-v1.md rename to design/07-ai/done/7-16-page-block-ai-real-smoke-followup-matrix-v1.md index 59eee499..de11c242 100644 --- a/design/07-ai/process/7-16-page-block-ai-real-smoke-followup-matrix-v1.md +++ b/design/07-ai/done/7-16-page-block-ai-real-smoke-followup-matrix-v1.md @@ -1,8 +1,10 @@ -# 7-16 [process] Page Block AI 真实页面 Smoke 后续矩阵 v1 +# 7-16 [done] Page Block AI 真实页面 Smoke 后续矩阵 v1 > 创建时间:2026-05-18 > -> 当前状态:`PROCESS` +> 当前状态:`DONE` +> +> 归档说明(2026-05-21):本文范围内可执行的真实页面 smoke 已完成;剩余 UI / Review Mode 属于 Phase C 冻结范围,解冻时应新建设计稿承接。 > > 来源: > - `/mnt/Data1T/mnote/design/07-ai/process/7-10-page-block-ai-tooling-execution-checklist-v1.md` diff --git a/design/07-ai/process/7-30-acp-session-load-resume-checklist-v1.md b/design/07-ai/done/7-30-acp-session-load-resume-checklist-v1.md similarity index 93% rename from design/07-ai/process/7-30-acp-session-load-resume-checklist-v1.md rename to design/07-ai/done/7-30-acp-session-load-resume-checklist-v1.md index a27265c8..2dde3e93 100644 --- a/design/07-ai/process/7-30-acp-session-load-resume-checklist-v1.md +++ b/design/07-ai/done/7-30-acp-session-load-resume-checklist-v1.md @@ -1,6 +1,8 @@ # 7-30 ACP session/load 恢复 checklist v1 -> 状态:process +> 状态:done +> +> 归档说明(2026-05-21):ACP `session/load` / resume 核心闭环已完成并通过聚焦测试;session id 全链路持久化作为后续产品化项继续跟踪。 > > Owner:ACP session lifecycle > diff --git a/design/07-ai/process/7-31-acp-permission-decision-loop-checklist-v1.md b/design/07-ai/done/7-31-acp-permission-decision-loop-checklist-v1.md similarity index 100% rename from design/07-ai/process/7-31-acp-permission-decision-loop-checklist-v1.md rename to design/07-ai/done/7-31-acp-permission-decision-loop-checklist-v1.md diff --git a/design/07-ai/process/7-32-acp-tool-location-and-open-action-checklist-v1.md b/design/07-ai/done/7-32-acp-tool-location-and-open-action-checklist-v1.md similarity index 94% rename from design/07-ai/process/7-32-acp-tool-location-and-open-action-checklist-v1.md rename to design/07-ai/done/7-32-acp-tool-location-and-open-action-checklist-v1.md index 5a9eee48..932bf6a2 100644 --- a/design/07-ai/process/7-32-acp-tool-location-and-open-action-checklist-v1.md +++ b/design/07-ai/done/7-32-acp-tool-location-and-open-action-checklist-v1.md @@ -1,6 +1,8 @@ # 7-32 ACP tool locations 与打开动作 checklist v1 -> 状态:process +> 状态:done +> +> 归档说明(2026-05-21):ACP tool locations 解析、SSE 桥接与前端打开动作已完成;浏览器 smoke 可作为后续补强。 > > Owner:ACP tool event data + Page AI open action > diff --git a/design/07-ai/process/7-33-acp-session-info-plan-ui-checklist-v1.md b/design/07-ai/done/7-33-acp-session-info-plan-ui-checklist-v1.md similarity index 94% rename from design/07-ai/process/7-33-acp-session-info-plan-ui-checklist-v1.md rename to design/07-ai/done/7-33-acp-session-info-plan-ui-checklist-v1.md index 8178c0bc..979746ab 100644 --- a/design/07-ai/process/7-33-acp-session-info-plan-ui-checklist-v1.md +++ b/design/07-ai/done/7-33-acp-session-info-plan-ui-checklist-v1.md @@ -1,6 +1,8 @@ # 7-33 ACP session info / plan UI checklist v1 -> 状态:process +> 状态:done +> +> 归档说明(2026-05-21):ACP session info / plan SSE 与 Page AI 轻量 UI 已完成;todo JSON 细化显示作为后续格式约定项。 > > Owner:ACP metadata SSE + Page AI lightweight UI > diff --git a/design/07-ai/process/7-10-page-block-ai-tooling-execution-checklist-v1.md b/design/07-ai/process/7-10-page-block-ai-tooling-execution-checklist-v1.md index 17d84bc0..0062f76d 100644 --- a/design/07-ai/process/7-10-page-block-ai-tooling-execution-checklist-v1.md +++ b/design/07-ai/process/7-10-page-block-ai-tooling-execution-checklist-v1.md @@ -79,7 +79,7 @@ 仍未完成,本文继续留在 `process/`: -- 剩余真实页面 smoke 已拆到 `/mnt/Data1T/mnote/design/07-ai/process/7-16-page-block-ai-real-smoke-followup-matrix-v1.md` 持续跟踪;本文只保留总 checklist 和历史证据。 +- 剩余真实页面 smoke 已由 `/mnt/Data1T/mnote/design/07-ai/done/7-16-page-block-ai-real-smoke-followup-matrix-v1.md` 完成并归档;本文只保留总 checklist、历史证据和 Phase 8 / Review Mode 冻结缺口。 - 复杂块移动阻断矩阵已补隔离 route smoke 覆盖标题带子块、列表项、表格、mindmap/resource;真实浏览器页面 smoke 仍未补。 - 持久审阅/preview UI 仍是后续可选项;当前默认 yolo 模式不做写入审批,`scope=selection` 与 `format=page_xml/text` 已进入工具与 PageAIContextBuilder 首版。 - PageAIIntentParser / PageAIOperationPlanner / PageAIOperationValidator / PageAIApplyController 仍需继续建设;当前本地 planner 只覆盖低歧义文本块增删改,不应被视为完整 AI 编辑 runtime。 diff --git a/design/07-ai/process/7-17-acp-session-convex-sharing-contract-v1.md b/design/07-ai/process/7-17-acp-session-convex-sharing-contract-v1.md index 240de7db..27bb2fba 100644 --- a/design/07-ai/process/7-17-acp-session-convex-sharing-contract-v1.md +++ b/design/07-ai/process/7-17-acp-session-convex-sharing-contract-v1.md @@ -2,7 +2,9 @@ > 创建时间:2026-05-18 > -> 当前状态:`PROCESS / FUTURE` +> 当前状态:`process-reference / future` +> +> 归档说明(2026-05-21):本文冻结 ACP session 与控制面分享/账号作用域远期合同;当前只作为 future reference,不作为 MVP 后阶段 active implementation checklist。 > > 本稿目的: > 1. 明确 ACP session 必须绑定当前账号权限与控制面作用域,不能以内存态绕过权限。 diff --git a/design/07-ai/process/7-28-resource-ai-tool-contract-v1.md b/design/07-ai/process/7-28-resource-ai-tool-contract-v1.md index 4603d8c5..7f9881f8 100644 --- a/design/07-ai/process/7-28-resource-ai-tool-contract-v1.md +++ b/design/07-ai/process/7-28-resource-ai-tool-contract-v1.md @@ -1,6 +1,8 @@ # 7-28 Resource AI Tool Contract v1 -状态:process +状态:process-reference + +说明(2026-05-21):本文冻结 mindmap / office 等 Resource AI tool 合同草案;当前不是 local-first 普通 Markdown 编辑主路径,也不是 active implementation checklist。 主线:VSCode 简化版工作区 + tiptap markdown 编辑器 + Hermes / Reasonix agent + simplemindmap / office 插件 + Wolai 主题 web 壳 + 鉴权控制面。 diff --git a/design/08-wolai-aline-test-flow/process/wolai-aline-test-flow-v1.md b/design/08-wolai-aline-test-flow/process/wolai-aline-test-flow-v1.md index 7ae7053e..09f9beb5 100644 --- a/design/08-wolai-aline-test-flow/process/wolai-aline-test-flow-v1.md +++ b/design/08-wolai-aline-test-flow/process/wolai-aline-test-flow-v1.md @@ -1,5 +1,7 @@ # Wolai-aline 对标测试流程 v1 +> 状态:process-reference / active workflow +> > 本流程用于所有以 Wolai 体验复刻、对标、aline/alignment 为目标的任务。目标不是只做出类似文案,而是用真实 Wolai 行为和本地 `3000` 行为做可复现对比,形成可回归的 smoke 与截图证据。 ## 适用范围 diff --git a/design/09-siyuan-reference/process/9-siyuan-reference-boundary-and-adoption-v1.md b/design/09-siyuan-reference/process/9-siyuan-reference-boundary-and-adoption-v1.md index c78dd693..4b5f87eb 100644 --- a/design/09-siyuan-reference/process/9-siyuan-reference-boundary-and-adoption-v1.md +++ b/design/09-siyuan-reference/process/9-siyuan-reference-boundary-and-adoption-v1.md @@ -2,6 +2,8 @@ > 更新时间:2026-05-11 > +> 当前状态:`process-reference`。本文只保留思源参考边界与可借鉴能力,不覆盖 `01-05` 当前优先级,也不作为当前执行 checklist。 +> > 上位依据: > - `/mnt/Data1T/mnote/ARCHITECTURE.md` > - `/mnt/Data1T/mnote/design/01-05-current-priority-overview.md` diff --git a/design/10-review/done/15-design-governance-mvp-post-review-v1.md b/design/10-review/done/15-design-governance-mvp-post-review-v1.md new file mode 100644 index 00000000..5b1cc023 --- /dev/null +++ b/design/10-review/done/15-design-governance-mvp-post-review-v1.md @@ -0,0 +1,131 @@ +# 15 Design Governance MVP 后阶段 Review v1 + +> 状态:done +> +> 归档说明(2026-05-21):四个 Reasonix worker 审查、Codex 复核、done 归档与 process-reference 标注已完成;本文归档为本轮 design governance 事实源。 +> +> 创建时间:2026-05-21 +> +> 目标:在 local-first MVP 已初步完成、架构方向基本稳定后,统一 `design/` 目录口径,归档过时 process 文档,并明确仍需继续执行的 process 文档。 + +## 1. 当前统一口径 + +MNote 当前已进入 local-first MVP 后阶段: + +- 本地 workspace folder 是默认数据真相。 +- 本地 `.md` 是页面正文真相。 +- Rust kernel / projection / command 持有树、页面、资源和权限语义。 +- `mnote-web` 是 3000 主 Web 执行面。 +- Convex / 服务端降级为 auth、membership、share grants、sync state、AI policy、cloud source、compat 和 sync replica 控制面。 +- 旧 Next / React / wolai-frontend、BlockNote 默认主链、Convex 默认正文/附件/AI 会话全文主存储口径均为 legacy / recycle / compat 背景。 + +## 2. 本轮整理原则 + +1. 仍代表长期方向或当前执行入口的文档保留在 `process/`,但必须叠加 MVP 后阶段口径。 +2. 已完成、已有真实代码或 smoke 证据的执行清单移动到同域 `done/`,并补充归档说明。 +3. 被后续文档覆盖、但不代表已完成的旧稿移动到 `design/old//process/`,标题或开头标记 `[recycle]`。 +4. 参考资料、外部产品取证、长期方向说明可以保留在 `process/`,但需要明确“不作为当前执行 checklist”。 +5. 不把旧 Convex / Next / BlockNote 说法改写成当前主线;若保留,必须标注为历史快照或反例。 + +## 3. 并行审查切片 + +- Worker A:`01` / `02` / `03`,重点判断 long-term 架构稿和 Rust Web process 是否仍应继续执行。 +- Worker B:`04-tree-domain`,重点判断 file tree / resource lifecycle / trash / open target 相关 process 是否已由近期 bug/smoke 完成。 +- Worker C:`05-editor-mainline` / `06-mindmap` / `08-wolai` / `09-siyuan`,重点判断编辑器 tab、OnlyOffice、mindmap 和对标流程文档是否仍活跃。 +- Worker D:`07-ai` / `10-review`,重点判断 AI process 与 review process 是否已被后续 Hermes/ACP/local-first 文件编辑口径覆盖。 + +## 4. Codex 归档判定状态 + +本轮使用以下状态: + +- `keep-process`:继续留在 `process/`,后续仍需执行。 +- `mark-process-reference`:留在 `process/`,但只作为长期方向、流程或参考基线,不作为当前直接执行 checklist。 +- `archive-done`:移动到同域 `done/`,因为目标已完成或当前代码已覆盖。 +- `archive-old-process`:移动到 `design/old//process/` 并标 `[recycle]`,因为被后续稿覆盖或口径过时但未必完成。 +- `needs-human-confirmation`:证据不足,暂不移动。 + +## 5. 初步本地发现 + +- `design/01-tree-first-graph-kernel/process/1-1-*` 与 `design/03-rust-web/process/3-1-*` 已自述为全景参考,当前优先级应由 `01-05` 和后续 MVP 后阶段 checklist 驱动。 +- `design/03-rust-web/process/3-15-*` 已写明代码、单测和 HTTP smoke 完成,仅真实浏览器 smoke 待补;需要核对是否已有后续真实浏览器证据。 +- `design/04-tree-domain/process/4-38` 到 `4-45` 是 2026-05-20 之后针对资源生命周期、open target、下载、拖拽上传等 bug 的近期 checklist,可能有部分已经由当前未提交 smoke / bug done 覆盖。 +- `design/05-editor-mainline/process/5-15` 到 `5-26` 初始盘点时大多是近期 main editor tab/resource open/OnlyOffice 收口 checklist;本轮已按真实代码和 smoke 证据逐项判定,`5-15` 到 `5-25` 中除继续执行项外已归档到 `done/`,`5-26` 继续保留为 active process。 +- `design/07-ai/process/7-15` 已包含大量完成记录,但仍有 HTTP proxy compat、session resume、permission loop、tool location 等后续切片,不能仅凭完成项归档。 + +## 6. 本轮不做事项 + +- 不改 `design/11-wolai/` 和 `design/90-reference/` 的参考资料归类。 +- 不删除历史文档。 +- 不把 `old/` 下历史稿移动回主线。 +- 不改代码,不跑浏览器 smoke;本轮只做设计文档治理。 + +## 7. Reasonix 并行审查结果 + +本轮已派发四个只读 Reasonix worker,报告路径如下: + +- Worker A(01/02/03):`.codex/reasonix-tasks/results/design-governance-worker-a-01-03.md` +- Worker B(04 tree-domain):`.codex/reasonix-tasks/results/design-governance-worker-b-04-tree.md` +- Worker C(05/06/08/09):`.codex/reasonix-tasks/results/design-governance-worker-c-05-06-08-09.md` +- Worker D(07/10):`.codex/reasonix-tasks/results/design-governance-worker-d-07-10.md` + +Codex 已复核 worker 输出,并额外核对 `3-15` 的真实浏览器 smoke 证据。 + +## 8. 本轮归档到 done + +以下文档已从 `process/` 移动到对应 `done/`,并在文件头部补充 `done` 状态或归档说明: + +- `design/01-tree-first-graph-kernel/done/1-7-local-workspace-root-page-bundle-checklist-v1.md` +- `design/02-convex-rust-long-term-architecture/done/2-3-local-workspace-access-control-productization-v1.md` +- `design/03-rust-web/done/3-15-local-markdown-asset-upload-relative-path-v1.md` +- `design/04-tree-domain/done/4-27-resource-lifecycle-command-cutover-v1.md` +- `design/04-tree-domain/done/4-39-filetree-open-target-resource-identity-checklist-v1.md` +- `design/04-tree-domain/done/4-40-resource-lifecycle-command-cutover-smoke-checklist-v1.md` +- `design/04-tree-domain/done/4-41-filetree-bulk-resource-command-cutover-v1.md` +- `design/04-tree-domain/done/4-42-filetree-open-target-rooturi-contract-v1.md` +- `design/04-tree-domain/done/4-43-local-folder-sort-order-persistence-v1.md` +- `design/05-editor-mainline/done/5-15-local-markdown-path-identity-unification-checklist-v1.md` +- `design/05-editor-mainline/done/5-16-editor-group-resource-tab-safety-checklist-v1.md` +- `design/05-editor-mainline/done/5-17-resource-editor-kind-and-smoke-matrix-checklist-v1.md` +- `design/05-editor-mainline/done/5-18-main-editor-tab-state-and-error-placeholder-checklist-v1.md` +- `design/05-editor-mainline/done/5-19-resource-open-resolver-and-onlyoffice-checklist-v1.md` +- `design/05-editor-mainline/done/5-20-resource-tab-close-confirm-checklist-v1.md` +- `design/05-editor-mainline/done/5-21-editor-tab-url-active-identity-checklist-v1.md` +- `design/05-editor-mainline/done/5-22-mindmap-main-editor-tab-kind-v1.md` +- `design/05-editor-mainline/done/5-23-editor-side-target-secondary-pane-v1.md` +- `design/05-editor-mainline/done/5-24-open-editors-lightweight-view-checklist-v1.md` +- `design/05-editor-mainline/done/5-25-editor-tab-keyboard-and-mru-checklist-v1.md` +- `design/07-ai/done/7-16-page-block-ai-real-smoke-followup-matrix-v1.md` +- `design/07-ai/done/7-30-acp-session-load-resume-checklist-v1.md` +- `design/07-ai/done/7-31-acp-permission-decision-loop-checklist-v1.md` +- `design/07-ai/done/7-32-acp-tool-location-and-open-action-checklist-v1.md` +- `design/07-ai/done/7-33-acp-session-info-plan-ui-checklist-v1.md` + +## 9. 本轮标记为 process-reference + +以下文档仍保留在 `process/`,但已标注为 `process-reference`,不再作为当前直接执行 checklist: + +- `design/03-rust-web/process/3-17-convex-export-web-entry-v1.md` +- `design/04-tree-domain/process/4-23-local-cloud-explicit-bridge-p3-candidate-v1.md` +- `design/05-editor-mainline/process/5-2-tiptap-notion-like-template-adoption-v1.md` +- `design/05-editor-mainline/process/5-5-page-aggregate-single-truth-alignment-v1.md` +- `design/05-editor-mainline/process/5-7-wolai-page-tree-main-editor-experience-restoration-v1.md` +- `design/05-editor-mainline/process/5-14-zed-lapce-vscode-reference-adoption-matrix-v1.md` +- `design/06-mindmap/process/6-mindmap-leptos-adapter-reference-notes-v1.md` +- `design/07-ai/process/7-17-acp-session-convex-sharing-contract-v1.md` +- `design/07-ai/process/7-28-resource-ai-tool-contract-v1.md` +- `design/08-wolai-aline-test-flow/process/wolai-aline-test-flow-v1.md` +- `design/09-siyuan-reference/process/9-siyuan-reference-boundary-and-adoption-v1.md` +- `design/10-review/process/12-sidex-vscode-workbench-gap-review-v1.md` +- `design/10-review/process/13-hermes-vscode-plugin-gap-review-v1.md` +- `design/10-review/process/14-sidex-workbench-gap-followup-review-v1.md` + +## 10. 仍需继续执行的 process + +本轮保留为 active process 的主要文档与原因: + +- `01`:`1-tree-first-graph-kernel-v1`、`1-1`、`1-2`、`1-3`、`1-4`、`1-5`、`1-6` 仍分别承担长期主线、全景参考、产品愿景、持续执行与下一阶段缺口跟踪。 +- `03`:`3-rust-web-long-term-architecture-v1`、`3-1`、`3-3`、`3-13` 仍有架构或未完成迁移项。 +- `04`:`4-28`、`4-34`、`4-38`、`4-44`、`4-45` 仍有 restore focus、local folder no-refresh、open target 前端 cutover、action-layer smoke 和 open editors lifecycle 验证缺口。 +- `05`:`5-6`、`5-9`、`5-26` 仍分别跟踪 Page Aggregate 小尾项、Wolai-aline 持续项和 resource open resolver 收敛。 +- `06`:`6-mindmap-kernel-phase6-projection-editor-v1` 与 `6-mindmap-phase6-leptos-ui-shell-reuse-checklist-v1` 仍保留 mindmap 内核/视觉尾项。 +- `07`:`7-10`、`7-12`、`7-14`、`7-15`、`7-27` 仍分别承担 Phase 8 冻结缺口、Hermes tool routing、online/local markdown convergence、ACP 统一层和 final markdown truth 后续修复。 diff --git a/design/10-review/process/12-sidex-vscode-workbench-gap-review-v1.md b/design/10-review/process/12-sidex-vscode-workbench-gap-review-v1.md index 572f263c..c652251d 100644 --- a/design/10-review/process/12-sidex-vscode-workbench-gap-review-v1.md +++ b/design/10-review/process/12-sidex-vscode-workbench-gap-review-v1.md @@ -1,6 +1,8 @@ # 12 Sidex / VSCode Workbench 对照审查 v1 -> 状态:process +> 状态:process-reference +> +> 归档说明(2026-05-21):本 review 的 P0/P1/P2 缺口已拆分到 `4-*` / `5-*` 执行 checklist;本文保留为 Sidex / VSCode 对照原始证据,不再作为直接执行清单。 > > 日期:2026-05-20 > @@ -96,27 +98,27 @@ local office 可进入主编辑区 tab,Convex office 仍可能新窗口;mind 本 review 拆分为三份执行 checklist: -- `design/05-editor-mainline/process/5-16-editor-group-resource-tab-safety-checklist-v1.md` +- `design/05-editor-mainline/done/5-16-editor-group-resource-tab-safety-checklist-v1.md` - `design/04-tree-domain/process/4-38-filetree-resource-lifecycle-open-target-checklist-v1.md` -- `design/05-editor-mainline/process/5-17-resource-editor-kind-and-smoke-matrix-checklist-v1.md` +- `design/05-editor-mainline/done/5-17-resource-editor-kind-and-smoke-matrix-checklist-v1.md` 第二轮 P0 收口继续拆为四份独立 checklist: -- `design/05-editor-mainline/process/5-18-main-editor-tab-state-and-error-placeholder-checklist-v1.md` -- `design/05-editor-mainline/process/5-19-resource-open-resolver-and-onlyoffice-checklist-v1.md` -- `design/04-tree-domain/process/4-39-filetree-open-target-resource-identity-checklist-v1.md` -- `design/04-tree-domain/process/4-40-resource-lifecycle-command-cutover-smoke-checklist-v1.md` +- `design/05-editor-mainline/done/5-18-main-editor-tab-state-and-error-placeholder-checklist-v1.md` +- `design/05-editor-mainline/done/5-19-resource-open-resolver-and-onlyoffice-checklist-v1.md` +- `design/04-tree-domain/done/4-39-filetree-open-target-resource-identity-checklist-v1.md` +- `design/04-tree-domain/done/4-40-resource-lifecycle-command-cutover-smoke-checklist-v1.md` 第三轮继续收口剩余 P0,并在 P0 清零后推进相邻 P1: -- `design/05-editor-mainline/process/5-20-resource-tab-close-confirm-checklist-v1.md` -- `design/04-tree-domain/process/4-41-filetree-bulk-resource-command-cutover-v1.md` -- `design/04-tree-domain/process/4-42-filetree-open-target-rooturi-contract-v1.md` -- `design/05-editor-mainline/process/5-21-editor-tab-url-active-identity-checklist-v1.md` +- `design/05-editor-mainline/done/5-20-resource-tab-close-confirm-checklist-v1.md` +- `design/04-tree-domain/done/4-41-filetree-bulk-resource-command-cutover-v1.md` +- `design/04-tree-domain/done/4-42-filetree-open-target-rooturi-contract-v1.md` +- `design/05-editor-mainline/done/5-21-editor-tab-url-active-identity-checklist-v1.md` 第四轮进入 P1,按 editor workbench 语义继续拆分: -- `design/04-tree-domain/process/4-43-local-folder-sort-order-persistence-v1.md` -- `design/05-editor-mainline/process/5-22-mindmap-main-editor-tab-kind-v1.md` -- `design/05-editor-mainline/process/5-23-editor-side-target-secondary-pane-v1.md` +- `design/04-tree-domain/done/4-43-local-folder-sort-order-persistence-v1.md` +- `design/05-editor-mainline/done/5-22-mindmap-main-editor-tab-kind-v1.md` +- `design/05-editor-mainline/done/5-23-editor-side-target-secondary-pane-v1.md` - `design/04-tree-domain/process/4-44-filetree-action-layer-paste-drop-delete-v1.md` diff --git a/design/10-review/process/13-hermes-vscode-plugin-gap-review-v1.md b/design/10-review/process/13-hermes-vscode-plugin-gap-review-v1.md index ba06bee2..79adb4f4 100644 --- a/design/10-review/process/13-hermes-vscode-plugin-gap-review-v1.md +++ b/design/10-review/process/13-hermes-vscode-plugin-gap-review-v1.md @@ -1,6 +1,8 @@ # 13 Hermes VSCode 插件对照审查 v1 -> 状态:process +> 状态:process-reference +> +> 归档说明(2026-05-21):P0 缺口已拆分到 `7-30` 至 `7-33` 并完成归档;本文保留 Hermes VSCode 插件对照证据和 P1/P2 参考项。 > > 日期:2026-05-20 > @@ -87,10 +89,10 @@ MNote 已在 `AcpSessionManager` 解析 `SessionInfoUpdate` 和 `PlanUpdate`, 本 review 先拆四份 P0 checklist,允许并行推进: -- `design/07-ai/process/7-30-acp-session-load-resume-checklist-v1.md` -- `design/07-ai/process/7-31-acp-permission-decision-loop-checklist-v1.md` -- `design/07-ai/process/7-32-acp-tool-location-and-open-action-checklist-v1.md` -- `design/07-ai/process/7-33-acp-session-info-plan-ui-checklist-v1.md` +- `design/07-ai/done/7-30-acp-session-load-resume-checklist-v1.md` +- `design/07-ai/done/7-31-acp-permission-decision-loop-checklist-v1.md` +- `design/07-ai/done/7-32-acp-tool-location-and-open-action-checklist-v1.md` +- `design/07-ai/done/7-33-acp-session-info-plan-ui-checklist-v1.md` P0 收口后再拆 P1: diff --git a/design/10-review/process/14-sidex-workbench-gap-followup-review-v1.md b/design/10-review/process/14-sidex-workbench-gap-followup-review-v1.md index 83952006..07ad577b 100644 --- a/design/10-review/process/14-sidex-workbench-gap-followup-review-v1.md +++ b/design/10-review/process/14-sidex-workbench-gap-followup-review-v1.md @@ -1,6 +1,8 @@ # 14 Sidex / MNote Workbench Gap Follow-up Review v1 -> 状态:process +> 状态:process-reference +> +> 归档说明(2026-05-21):本 follow-up review 已拆分出 `5-24`、`5-25`、`5-26`、`4-45` 等执行项;本文保留为后续 workbench 对照参考,不作为直接执行清单。 > > 日期:2026-05-21 > @@ -51,8 +53,8 @@ VSCode 的 Open Editors / Explorer / working copy / dirty indicator 是一条链 拆为四个方向并行推进: -- `design/05-editor-mainline/process/5-24-open-editors-lightweight-view-checklist-v1.md` -- `design/05-editor-mainline/process/5-25-editor-tab-keyboard-and-mru-checklist-v1.md` +- `design/05-editor-mainline/done/5-24-open-editors-lightweight-view-checklist-v1.md` +- `design/05-editor-mainline/done/5-25-editor-tab-keyboard-and-mru-checklist-v1.md` - `design/05-editor-mainline/process/5-26-resource-open-resolver-convergence-checklist-v1.md` - `design/04-tree-domain/process/4-45-filetree-open-editors-lifecycle-link-checklist-v1.md` diff --git a/design/README.md b/design/README.md index 5570839a..ca68f5c9 100644 --- a/design/README.md +++ b/design/README.md @@ -1,21 +1,26 @@ # design 设计稿索引 -> 更新时间:2026-05-19 +> 更新时间:2026-05-21 > > 当前产品口径:`VSCode 简化版工作区 + tiptap 的 Markdown 前端编辑器 + Hermes/Reasonix agent + simplemindmap/office 插件 + Wolai 主题 Web 壳 + 鉴权控制面`。 > -> 当前上位主线:local-first workspace 是默认数据形态,本地 `.md` 是页面正文真相,Convex / 服务端只作为控制面、cloud source、compat 和 sync replica。入口设计见: +> 当前上位主线:local-first MVP 已初步完成;local-first workspace 是默认数据形态,本地 `.md` 是页面正文真相,Rust kernel / projection / command 持有语义,Convex / 服务端只作为控制面、cloud source、compat 和 sync replica。入口设计见: > `/mnt/Data1T/mnote/design/02-convex-rust-long-term-architecture/done/2-2-local-first-workspace-convex-control-plane-v1.md` > +> 后续新增设计默认以 MVP 后阶段为基线:优先推进 `WorkspacePath/ObjectIdentity`、`DocumentBuffer/BufferStore`、Page Aggregate compat 瘦身、tree command context、live cache 统一、agent diff / 冲突合并 / 本地索引 / 分享同步产品化;不要再把旧 Convex / Next / BlockNote 主链当作新增能力默认方向。 +> > 状态口径以当前仓库真实代码为准: > - `[done]`:对应阶段或收口目标已经在当前主线代码中成立 > - `[process]`:方向已进入主线,但仍在推进中 +> - `[process-reference]`:仍在 `process/` 下保留,但只作为参考流程、候选合同、对照证据或长期设计边界;不代表当前可直接执行的 checklist > - `[recycle]`:已废弃、已被后续稿件替代,统一归档到 `old/` ## 当前优先级入口 - `01-05` 当前有效主线与优先级,请先看: `/mnt/Data1T/mnote/design/01-05-current-priority-overview.md` +- MVP 后阶段剩余 `process/` 的执行顺序,请看: + `/mnt/Data1T/mnote/design/01-tree-first-graph-kernel/process/1-8-mvp-post-process-execution-order-v1.md` ## 主线顺序 @@ -53,6 +58,7 @@ - 主线设计稿完成后,必须从对应大类的 `process/` 移动到 `done/`。 - 仅以当前真实代码为准判断是否完成,不能只按稿件自述判断。 +- 对于未完成但当前不排期的候选设计、参考矩阵、对标流程和 review 原始证据,不移动到 `done/`,必须在文件头部标记 `process-reference` 并说明当前不作为 active implementation checklist。 - 废弃稿统一移动到 `old/` 对应大类下,再按历史成熟度放入 `process/` 或 `done/`。 ## 辅助目录 diff --git a/rust/crates/mnote-web/src/routes/local_folder_source.rs b/rust/crates/mnote-web/src/routes/local_folder_source.rs index cfc2302e..e40da9f5 100644 --- a/rust/crates/mnote-web/src/routes/local_folder_source.rs +++ b/rust/crates/mnote-web/src/routes/local_folder_source.rs @@ -204,6 +204,7 @@ struct LocalAssetUploadFields { file: LocalUploadFile, root_uri: String, document_id: String, + target_relative_path: Option, kind: String, } @@ -2314,12 +2315,21 @@ pub async fn upload_local_markdown_asset( let fields = read_local_asset_upload_multipart(multipart).await?; ensure_local_workspace_access(&context, &fields.root_uri) .map_err(|error| error.with_context(&context))?; - let asset = write_local_markdown_asset( - &fields.root_uri, - &fields.document_id, - &fields.kind, - fields.file, - )?; + let asset = if fields.target_relative_path.is_some() || fields.document_id.trim().is_empty() { + write_local_folder_file_upload( + &fields.root_uri, + fields.target_relative_path.as_deref().unwrap_or(""), + &fields.kind, + fields.file, + )? + } else { + write_local_markdown_asset( + &fields.root_uri, + &fields.document_id, + &fields.kind, + fields.file, + )? + }; Ok((StatusCode::OK, Json(json!({ "ok": true, "asset": asset })))) } @@ -2329,7 +2339,33 @@ pub async fn open_local_file( ) -> Result<(StatusCode, HeaderMap, Vec), WebError> { ensure_local_workspace_read_access(&context, &query.root_uri) .map_err(|error| error.with_context(&context))?; - let target = resolve_local_file_open_path(&query.root_uri, &query.path)?; + let target = resolve_local_open_path(&query.root_uri, &query.path)?; + let mut headers = HeaderMap::new(); + if target.is_dir() { + if !query.download.unwrap_or(false) { + return Err(WebError::bad_request_code( + "local_file_open_is_directory", + "不能直接读取本地目录", + ) + .with_context(&context)); + } + let bytes = build_local_directory_tar_archive(&target).map_err(|error| { + WebError::bad_request_code( + "local_directory_download_failed", + format!("无法打包本地目录 {}: {error}", target.display()), + ) + .with_context(&context) + })?; + headers.insert( + header::CONTENT_TYPE, + HeaderValue::from_static("application/x-tar"), + ); + headers.insert( + header::CONTENT_DISPOSITION, + content_disposition_attachment_for_filename(&local_directory_tar_filename(&target)), + ); + return Ok((StatusCode::OK, headers, bytes)); + } let bytes = fs::read(&target).map_err(|error| { WebError::bad_request_code( "local_file_open_read_failed", @@ -2337,12 +2373,11 @@ pub async fn open_local_file( ) .with_context(&context) })?; - let mut headers = HeaderMap::new(); headers.insert(header::CONTENT_TYPE, content_type_for_path(&target)); if query.download.unwrap_or(false) { headers.insert( header::CONTENT_DISPOSITION, - HeaderValue::from_static("attachment"), + content_disposition_attachment_for_path(&target), ); } Ok((StatusCode::OK, headers, bytes)) @@ -2485,7 +2520,7 @@ pub async fn write_local_resource( )) } -fn resolve_local_file_open_path(root_uri: &str, relative_path: &str) -> Result { +fn resolve_local_open_path(root_uri: &str, relative_path: &str) -> Result { let root_path = parse_file_root_uri(root_uri)?; let canonical_root = root_path.canonicalize().map_err(|error| { WebError::bad_request_code( @@ -2519,6 +2554,11 @@ fn resolve_local_file_open_path(root_uri: &str, relative_path: &str) -> Result

Result { + let target = resolve_local_open_path(root_uri, relative_path)?; if target.is_dir() { return Err(WebError::bad_request_code( "local_file_open_is_directory", @@ -2534,6 +2574,7 @@ async fn read_local_asset_upload_multipart( let mut file: Option = None; let mut root_uri = String::new(); let mut document_id = String::new(); + let mut target_relative_path: Option = None; let mut kind = String::new(); while let Some(field) = multipart.next_field().await.map_err(|error| { @@ -2583,6 +2624,9 @@ async fn read_local_asset_upload_multipart( match name.as_str() { "rootUri" => root_uri = value.trim().to_string(), "documentId" => document_id = value.trim().to_string(), + "targetRelativePath" | "targetDirectoryPath" => { + target_relative_path = Some(value.trim().to_string()) + } "kind" => kind = value.trim().to_string(), _ => {} } @@ -2591,7 +2635,10 @@ async fn read_local_asset_upload_multipart( let file = file.ok_or_else(|| { WebError::bad_request_code("local_asset_upload_file_missing", "缺少 file") })?; - if file.bytes.is_empty() || root_uri.is_empty() || document_id.is_empty() { + if file.bytes.is_empty() + || root_uri.is_empty() + || (document_id.is_empty() && target_relative_path.is_none()) + { return Err(WebError::bad_request_code( "local_asset_upload_required_missing", "缺少必要参数", @@ -2601,10 +2648,85 @@ async fn read_local_asset_upload_multipart( file, root_uri, document_id, + target_relative_path, kind, }) } +pub(crate) fn write_local_folder_file_upload( + root_uri: &str, + target_relative_path: &str, + kind: &str, + file: LocalUploadFile, +) -> Result { + let root_path = parse_file_root_uri(root_uri)?; + let canonical_root = root_path.canonicalize().map_err(|error| { + WebError::bad_request_code( + "local_folder_unavailable", + format!("无法访问本地文件夹: {error}"), + ) + })?; + if !canonical_root.is_dir() { + return Err(WebError::bad_request_code( + "local_folder_not_directory", + "本地 rootUri 必须指向目录", + )); + } + let target_relative_path = target_relative_path.trim(); + let requested = if target_relative_path.is_empty() { + Path::new(".") + } else { + Path::new(target_relative_path) + }; + if requested.is_absolute() + || requested + .components() + .any(|component| matches!(component, std::path::Component::ParentDir)) + { + return Err(WebError::bad_request_code( + "local_folder_upload_root_escape", + "上传目标目录不能越过 root", + )); + } + let target_dir = canonical_root + .join(requested) + .canonicalize() + .map_err(|error| { + WebError::bad_request_code( + "local_folder_upload_target_missing", + format!("找不到上传目标目录: {error}"), + ) + })?; + if !target_dir.starts_with(&canonical_root) || !target_dir.is_dir() { + return Err(WebError::bad_request_code( + "local_folder_upload_target_invalid", + "上传目标必须是本地 root 内的目录", + )); + } + let sanitized_name = sanitize_file_name(&file.name, "附件"); + let target = next_available_raw_path(&target_dir, &sanitized_name); + fs::write(&target, &file.bytes).map_err(|error| { + WebError::bad_request_code( + "local_folder_upload_write_failed", + format!("无法写入本地文件 {}: {error}", target.display()), + ) + })?; + let root_relative_path = normalize_relative_path(&canonical_root, &target)?; + let asset_type = local_upload_asset_type(kind, &file.content_type); + Ok(json!({ + "id": format!("local-file:{root_relative_path}"), + "asset_type": asset_type, + "file_name": target.file_name().and_then(|value| value.to_str()).unwrap_or(&sanitized_name), + "mime_type": file.content_type, + "file_size": file.bytes.len(), + "file_url": root_relative_path, + "sourcePath": root_relative_path, + "sourceKind": "local_folder", + "rootUri": file_uri_for_path(&canonical_root), + "rootRelativePath": root_relative_path, + })) +} + pub(crate) fn write_local_markdown_asset( root_uri: &str, document_id: &str, @@ -6532,6 +6654,217 @@ fn content_type_for_path(path: &Path) -> HeaderValue { HeaderValue::from_static(content_type) } +fn percent_encode_content_disposition_filename(value: &str) -> String { + let mut encoded = String::new(); + for byte in value.as_bytes() { + match *byte { + b'A'..=b'Z' + | b'a'..=b'z' + | b'0'..=b'9' + | b'!' + | b'#' + | b'$' + | b'&' + | b'+' + | b'-' + | b'.' + | b'^' + | b'_' + | b'`' + | b'|' + | b'~' => encoded.push(*byte as char), + _ => encoded.push_str(&format!("%{byte:02X}")), + } + } + encoded +} + +fn content_disposition_attachment_for_path(path: &Path) -> HeaderValue { + let filename = path + .file_name() + .and_then(|value| value.to_str()) + .map(str::trim) + .filter(|value| !value.is_empty()) + .unwrap_or("download"); + content_disposition_attachment_for_filename(filename) +} + +fn content_disposition_attachment_for_filename(filename: &str) -> HeaderValue { + let filename = filename.trim(); + let filename = if filename.is_empty() { + "download" + } else { + filename + }; + let ascii_fallback = filename + .chars() + .map(|ch| { + if ch.is_ascii_alphanumeric() || matches!(ch, '.' | '-' | '_' | ' ') { + ch + } else { + '_' + } + }) + .collect::(); + let ascii_fallback = if ascii_fallback.trim().is_empty() { + "download".to_string() + } else { + ascii_fallback + }; + let quoted = ascii_fallback.replace('\\', "\\\\").replace('"', "\\\""); + let encoded = percent_encode_content_disposition_filename(filename); + HeaderValue::from_str(&format!( + "attachment; filename=\"{quoted}\"; filename*=UTF-8''{encoded}" + )) + .unwrap_or_else(|_| HeaderValue::from_static("attachment")) +} + +fn local_directory_tar_filename(path: &Path) -> String { + let name = path + .file_name() + .and_then(|value| value.to_str()) + .map(str::trim) + .filter(|value| !value.is_empty()) + .unwrap_or("folder"); + format!("{name}.tar") +} + +fn tar_octal(value: u64, width: usize) -> Vec { + let mut encoded = format!("{value:o}").into_bytes(); + let max_digits = width.saturating_sub(1); + if encoded.len() > max_digits { + encoded = vec![b'7'; max_digits]; + } + let mut out = vec![b'0'; max_digits.saturating_sub(encoded.len())]; + out.extend(encoded); + out.push(0); + out +} + +fn split_tar_path(path: &str) -> Result<(&str, &str), std::io::Error> { + let bytes = path.as_bytes(); + if bytes.len() <= 100 { + return Ok(("", path)); + } + let mut best: Option = None; + for (index, ch) in path.char_indices() { + if ch == '/' { + let prefix_len = path[..index].as_bytes().len(); + let name_len = path[index + 1..].as_bytes().len(); + if prefix_len <= 155 && name_len <= 100 { + best = Some(index); + } + } + } + if let Some(index) = best { + return Ok((&path[..index], &path[index + 1..])); + } + Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "tar entry path is too long", + )) +} + +fn append_tar_header( + out: &mut Vec, + path: &str, + size: u64, + typeflag: u8, + mode: u64, + mtime: u64, +) -> Result<(), std::io::Error> { + let normalized = path.trim_start_matches('/').replace('\\', "/"); + let (prefix, name) = split_tar_path(&normalized)?; + let mut header = [0u8; 512]; + header[0..name.as_bytes().len()].copy_from_slice(name.as_bytes()); + let mode = tar_octal(mode, 8); + header[100..108].copy_from_slice(&mode); + header[108..116].copy_from_slice(&tar_octal(0, 8)); + header[116..124].copy_from_slice(&tar_octal(0, 8)); + header[124..136].copy_from_slice(&tar_octal(size, 12)); + header[136..148].copy_from_slice(&tar_octal(mtime, 12)); + header[148..156].fill(b' '); + header[156] = typeflag; + header[257..263].copy_from_slice(b"ustar\0"); + header[263..265].copy_from_slice(b"00"); + if !prefix.is_empty() { + header[345..345 + prefix.as_bytes().len()].copy_from_slice(prefix.as_bytes()); + } + let checksum = header.iter().map(|byte| u32::from(*byte)).sum::(); + let checksum_bytes = format!("{checksum:06o}\0 ").into_bytes(); + header[148..156].copy_from_slice(&checksum_bytes); + out.extend_from_slice(&header); + Ok(()) +} + +fn append_tar_padding(out: &mut Vec, size: usize) { + let remainder = size % 512; + if remainder > 0 { + out.extend(std::iter::repeat(0).take(512 - remainder)); + } +} + +fn append_local_directory_tar_entries( + out: &mut Vec, + root: &Path, + current: &Path, + base_name: &str, +) -> Result<(), std::io::Error> { + let metadata = fs::symlink_metadata(current)?; + if metadata.file_type().is_symlink() { + return Ok(()); + } + let relative = current.strip_prefix(root).unwrap_or(current); + let mut entry_name = if relative.as_os_str().is_empty() { + base_name.to_string() + } else { + format!( + "{base_name}/{}", + relative.to_string_lossy().replace('\\', "/") + ) + }; + let mtime = metadata + .modified() + .ok() + .and_then(|time| time.duration_since(UNIX_EPOCH).ok()) + .map(|duration| duration.as_secs()) + .unwrap_or(0); + if metadata.is_dir() { + if !entry_name.ends_with('/') { + entry_name.push('/'); + } + append_tar_header(out, &entry_name, 0, b'5', 0o755, mtime)?; + let mut children = fs::read_dir(current)?.collect::, _>>()?; + children.sort_by_key(|entry| entry.file_name()); + for child in children { + append_local_directory_tar_entries(out, root, &child.path(), base_name)?; + } + return Ok(()); + } + if metadata.is_file() { + append_tar_header(out, &entry_name, metadata.len(), b'0', 0o644, mtime)?; + let bytes = fs::read(current)?; + out.extend_from_slice(&bytes); + append_tar_padding(out, bytes.len()); + } + Ok(()) +} + +fn build_local_directory_tar_archive(directory: &Path) -> Result, std::io::Error> { + let canonical = directory.canonicalize()?; + let base_name = canonical + .file_name() + .and_then(|value| value.to_str()) + .map(str::trim) + .filter(|value| !value.is_empty()) + .unwrap_or("folder") + .to_string(); + let mut out = Vec::new(); + append_local_directory_tar_entries(&mut out, &canonical, &canonical, &base_name)?; + out.extend_from_slice(&[0u8; 1024]); + Ok(out) +} + fn editor_block_table_to_markdown(block: &Value) -> String { let table = block .get("props") @@ -8873,6 +9206,90 @@ fn main() {} let _ = std::fs::remove_dir_all(&root); } + #[tokio::test] + async fn local_file_open_download_sets_content_disposition_filename() { + let root = temp_root("mnote-local-file-open-download-filename"); + init_workspace(&root); + std::fs::write(root.join("报告 2026.docx"), b"docx").expect("write docx"); + let root_uri = format!("file://{}", root.display()); + let mut headers = HeaderMap::new(); + headers.insert("x-mnote-actor-id", "user_test".parse().unwrap()); + headers.insert("x-mnote-actor-type", "user".parse().unwrap()); + let context = RequestContext::from_http_parts( + &Method::GET, + &"/api/local-folder/files/open".parse().expect("uri"), + &headers, + ); + + let (_, response_headers, _) = open_local_file( + Extension(context), + Query(LocalFileOpenQuery { + root_uri, + path: "报告 2026.docx".into(), + download: Some(true), + }), + ) + .await + .expect("download local file"); + + let disposition = response_headers + .get(axum::http::header::CONTENT_DISPOSITION) + .and_then(|value| value.to_str().ok()) + .expect("content disposition"); + assert!(disposition.starts_with("attachment;")); + assert!(disposition.contains("filename=\"__ 2026.docx\"")); + assert!( + disposition.contains("filename*=UTF-8''%E6%8A%A5%E5%91%8A%202026.docx"), + "{disposition}" + ); + + let _ = std::fs::remove_dir_all(&root); + } + + #[tokio::test] + async fn local_folder_open_downloads_directory_as_tar() { + let root = temp_root("mnote-local-folder-open-download-directory"); + init_workspace(&root); + let reports = root.join("Reports"); + std::fs::create_dir_all(&reports).expect("create reports"); + std::fs::write(reports.join("summary.txt"), b"summary").expect("write summary"); + let root_uri = format!("file://{}", root.display()); + let mut headers = HeaderMap::new(); + headers.insert("x-mnote-actor-id", "user_test".parse().unwrap()); + headers.insert("x-mnote-actor-type", "user".parse().unwrap()); + let context = RequestContext::from_http_parts( + &Method::GET, + &"/api/local-folder/files/open".parse().expect("uri"), + &headers, + ); + + let (_, response_headers, bytes) = open_local_file( + Extension(context), + Query(LocalFileOpenQuery { + root_uri, + path: "Reports".into(), + download: Some(true), + }), + ) + .await + .expect("download local directory"); + + assert_eq!( + response_headers + .get(axum::http::header::CONTENT_TYPE) + .and_then(|value| value.to_str().ok()), + Some("application/x-tar") + ); + let disposition = response_headers + .get(axum::http::header::CONTENT_DISPOSITION) + .and_then(|value| value.to_str().ok()) + .expect("content disposition"); + assert!(disposition.contains("filename=\"Reports.tar\"")); + assert!(bytes.windows(5).any(|window| window == b"ustar")); + + let _ = std::fs::remove_dir_all(&root); + } + #[test] fn local_file_tree_classifies_mindmap_and_office_assets() { let root = temp_root("mnote-local-filetree-resource-kinds"); diff --git a/rust/crates/mnote-web/src/routes/web_shell.rs b/rust/crates/mnote-web/src/routes/web_shell.rs index 84f816e7..adf8afb0 100644 --- a/rust/crates/mnote-web/src/routes/web_shell.rs +++ b/rust/crates/mnote-web/src/routes/web_shell.rs @@ -799,7 +799,7 @@ pub(crate) fn render_editor_island_adapter_script() -> &'static str { if (typeof runtime.default !== 'function' || typeof runtime.mount !== 'function' || typeof runtime.unmount !== 'function') { throw new Error('island runtime 导出不完整'); } - await runtime.default(wasmUrl); + await runtime.default({ module_or_path: wasmUrl }); if (typeof runtime.mount_mindmap_shell === 'function' && typeof runtime.unmount_mindmap_shell === 'function') { window.__MNOTE_MINDMAP_RUST_SHELL__ = { mount: runtime.mount_mindmap_shell, @@ -3260,6 +3260,14 @@ pub(crate) fn render_editor_island_adapter_script() -> &'static str { } }; + const currentWebShellDocumentId = () => { + const fromBody = document.body?.dataset?.documentId || ''; + if (fromBody) return String(fromBody).trim(); + const pageTab = document.querySelector('[data-mnote-main-tab="page"]'); + if (pageTab instanceof HTMLElement) return String(pageTab.getAttribute('data-document-id') || '').trim(); + return ''; + }; + const normalizeResourceTabKind = (input) => { const kind = String(input?.kind || '').trim().toLowerCase(); const title = String(input?.title || input?.fileName || input?.path || '').trim().toLowerCase(); @@ -3365,7 +3373,7 @@ pub(crate) fn render_editor_island_adapter_script() -> &'static str { : '页面'; const pageEntry = { objectIdentity: 'page', - documentId: String(nodes.pageTab?.getAttribute?.('data-document-id') || currentDocumentId() || '').trim(), + documentId: String(nodes.pageTab?.getAttribute?.('data-document-id') || currentWebShellDocumentId() || '').trim(), workspaceId: String(nodes.pageTab?.getAttribute?.('data-workspace-id') || currentWebShellWorkspaceId() || '').trim(), title: pageTitle || '页面', kind: 'page', @@ -3772,7 +3780,7 @@ pub(crate) fn render_editor_island_adapter_script() -> &'static str { }; const openMindmapResourceTab = async (entry, input) => { - const documentId = String(input.documentId || currentDocumentId() || '').trim(); + const documentId = String(input.documentId || currentWebShellDocumentId() || '').trim(); const mindmapId = String(input.mindmapId || input.assetId || '').trim(); if (!documentId || !mindmapId) throw new Error('mindmap_resource_identity_missing'); const targetUrl = new URL(`/mindmap/${encodeURIComponent(documentId)}/${encodeURIComponent(mindmapId)}`, window.location.origin); @@ -4645,6 +4653,10 @@ mod tests { assert!(html.contains("getOpenEditorsSnapshot")); assert!(html.contains("bindMainEditorTabStrip")); assert!(html.contains("data-mnote-tab-strip-bound")); + assert!(html.contains("currentWebShellDocumentId")); + assert!(!html + .contains("nodes.pageTab?.getAttribute?.('data-document-id') || currentDocumentId()")); + assert!(html.contains("runtime.default({ module_or_path: wasmUrl })")); assert!(html.contains("positionSlashMenuForRoot")); assert!(html.contains("menu.style.position = 'fixed';")); assert!(html.contains("installGlobalSlashMenuPositioning();")); diff --git a/rust/crates/mnote-web/src/ssr/pages/layout.rs b/rust/crates/mnote-web/src/ssr/pages/layout.rs index bac80f50..cab44cbc 100644 --- a/rust/crates/mnote-web/src/ssr/pages/layout.rs +++ b/rust/crates/mnote-web/src/ssr/pages/layout.rs @@ -1764,6 +1764,18 @@ const SIDEBAR_TREE_JS: &str = r##" return String(item && item.title || '无标题').trim() || '无标题'; } + function fileWorkspaceRelativePath(item) { + var meta = item && item.resourceMeta && typeof item.resourceMeta === 'object' ? item.resourceMeta : {}; + var workspacePath = meta.workspacePath && typeof meta.workspacePath === 'object' ? meta.workspacePath : {}; + var fromWorkspacePath = String(workspacePath.relativePath || '').trim(); + if (fromWorkspacePath) return fromWorkspacePath; + var extra = meta.extra && typeof meta.extra === 'object' ? meta.extra : {}; + var source = extra.source && typeof extra.source === 'object' ? extra.source : {}; + var fromSource = String(source.relativePath || '').trim(); + if (fromSource) return fromSource; + return String(item && (item.relativePath || item.rootRelativePath) || '').trim(); + } + function groupRowsByParent(rows) { var ids = new Set(rows.map(nodeIdOf).filter(Boolean)); var grouped = new Map(); @@ -1890,6 +1902,7 @@ const SIDEBAR_TREE_JS: &str = r##" var parent = parentIdOf(item); var documentId = fileDocumentId(item); var assetId = fileAssetId(item); + var relativePath = fileWorkspaceRelativePath(item); var objectIdentity = fileObjectIdentity(item); var iconKind = iconKindOf(item); var title = isFileTreeProjectionPageRow(rowKind, assetId) @@ -1909,7 +1922,7 @@ const SIDEBAR_TREE_JS: &str = r##" var childHtml = expandable ? '

    ' + renderFileRows(nodeId, grouped, activeId, activeRowId) + '
' : ''; - return '
  • ' + toggle + '
    ' + createAction + '
    ' + childHtml + '
  • '; + return '
  • ' + toggle + '
    ' + createAction + '
    ' + childHtml + '
  • '; }).join(''); } @@ -2581,6 +2594,15 @@ const SIDEBAR_TREE_JS: &str = r##" function fallbackFileTreeUploadTarget(detail) { var workspaceId = String(detail && detail.workspaceId || resolveWorkspaceId(document.body) || '').trim(); var documentId = String(detail && detail.documentId || currentDocumentId() || '').trim(); + if (detail && Object.prototype.hasOwnProperty.call(detail, 'targetRelativePath')) { + return { + workspaceId: workspaceId, + targetDocumentId: documentId, + targetMindmapId: null, + targetSubPath: null, + targetRelativePath: String(detail.targetRelativePath || '') + }; + } if (!workspaceId || !documentId) { throw new Error('请选择一个目标页面后再拖入文件'); } @@ -2593,8 +2615,15 @@ const SIDEBAR_TREE_JS: &str = r##" } async function resolveFileTreeUploadTarget(detail) { + if (detail && Object.prototype.hasOwnProperty.call(detail, 'targetRelativePath')) { + return fallbackFileTreeUploadTarget(detail || {}); + } try { - return await preflightFileTreeUploadTarget(detail || {}); + var plan = await preflightFileTreeUploadTarget(detail || {}); + if (detail && Object.prototype.hasOwnProperty.call(detail, 'targetRelativePath')) { + plan.targetRelativePath = String(detail.targetRelativePath || ''); + } + return plan; } catch (error) { console.warn('[mnote upload] upload target preflight fallback', error); return fallbackFileTreeUploadTarget(detail || {}); @@ -2869,6 +2898,16 @@ const SIDEBAR_TREE_JS: &str = r##" var detail = detailFromEditorAttachmentLink(link); var assetId = String(detail && detail.assetId || '').trim(); if (!assetId) return; + var localFilePath = localFilePathFromAssetId(assetId); + if (localFilePath) { + var localMeta = { + assetId: assetId, + fileSize: String(detail && detail.fileSize || '').trim() + }; + attachmentMetaCache[assetId] = localMeta; + applyEditorAttachmentMeta(link, localMeta); + return; + } if (attachmentMetaCache[assetId]) { applyEditorAttachmentMeta(link, attachmentMetaCache[assetId]); return; @@ -3258,13 +3297,15 @@ const SIDEBAR_TREE_JS: &str = r##" if (currentSourceKind() === 'local_folder') { var rootUri = (new URLSearchParams(window.location.search).get('rootUri') || '').trim(); var documentId = String(plan && plan.targetDocumentId || currentDocumentId() || '').trim(); - if (!rootUri || !documentId) { + var hasFolderTarget = plan && Object.prototype.hasOwnProperty.call(plan, 'targetRelativePath'); + if (!rootUri || (!documentId && !hasFolderTarget)) { throw new Error('本地 Markdown 上传缺少 rootUri 或 documentId'); } var localForm = new FormData(); localForm.append('file', file); localForm.append('rootUri', rootUri); - localForm.append('documentId', documentId); + if (documentId) localForm.append('documentId', documentId); + if (hasFolderTarget) localForm.append('targetRelativePath', String(plan.targetRelativePath || '')); localForm.append('kind', file && String(file.type || '').indexOf('image/') === 0 ? 'image' : 'attachment'); var localResponse = await fetch('/api/local-folder/assets/upload', { method: 'POST', @@ -3546,6 +3587,28 @@ const SIDEBAR_TREE_JS: &str = r##" return Promise.resolve(); } + function triggerBrowserDownload(url) { + if (!url) return false; + var link = document.createElement('a'); + link.href = url; + link.target = '_blank'; + link.rel = 'noopener noreferrer'; + link.download = ''; + link.style.position = 'fixed'; + link.style.left = '-9999px'; + link.style.top = '0'; + document.body.appendChild(link); + try { + link.click(); + } catch (_) { + if (typeof window.open === 'function') window.open(url, '_blank', 'noopener,noreferrer'); + } + window.setTimeout(function() { + if (link.parentElement) link.parentElement.removeChild(link); + }, 1000); + return true; + } + function recordFileTreeAction(action, detail) { var normalized = String(action || '').trim() || 'unknown'; var rowId = String(detail && detail.rowId || '').trim(); @@ -3698,6 +3761,10 @@ const SIDEBAR_TREE_JS: &str = r##" var workspaceId = detail.workspaceId || resolveWorkspaceId(trigger || document.body); var title = detail.title || '无标题'; var isAsset = detail.contextKind === 'filetree' && detail.assetId && detail.rowKind !== 'document' && detail.rowKind !== 'index'; + if (detail.contextKind === 'filetree' && action === 'download') { + downloadSelectedFileTreeAssetRows(detail, trigger); + return; + } if (isAsset && action === 'new-window') { recordFileTreeAction('new-window', detail); void openConvexAssetFromFileTree({ ...detail, openTarget: 'new-window' }); @@ -3903,6 +3970,7 @@ const SIDEBAR_TREE_JS: &str = r##" menu.setAttribute('data-kind', kind); var isAttachment = kind === 'attachment'; var isAsset = kind === 'filetree' && detail.assetId && detail.rowKind !== 'document' && detail.rowKind !== 'index'; + var isFileTreeDownload = kind === 'filetree' && detail.downloadable; var items = isAttachment ? [ { action: 'duplicate', icon: 'file_copy', label: '拷贝副本', shortcut: 'Ctrl + D' }, { action: 'delete-trash', icon: 'delete', label: '删除', shortcut: 'Del', danger: true }, @@ -3926,6 +3994,7 @@ const SIDEBAR_TREE_JS: &str = r##" ] : isAsset ? [ { action: 'open-edit-mode', icon: 'edit_note', label: '使用编辑模式打开' }, { action: 'new-window', icon: 'open_in_new', label: '在新窗口打开' }, + { action: 'download', icon: 'download', label: Number(detail.selectedDownloadCount || detail.selectedAssetCount || 0) > 1 ? '下载 ' + Number(detail.selectedDownloadCount || detail.selectedAssetCount || 0) + ' 个项目' : '下载' }, { action: 'rename', icon: 'edit', label: '重命名', shortcut: 'F2' }, { action: 'copy-id', icon: 'tag', label: '复制资源 ID' }, { action: 'delete-trash', icon: 'delete', label: '删除', shortcut: 'Del', danger: true }, @@ -3935,6 +4004,7 @@ const SIDEBAR_TREE_JS: &str = r##" { action: 'reveal', icon: 'my_location', label: 'Reveal' } ] : kind === 'filetree' ? [ { action: 'open-right', icon: 'right_panel_open', label: '在右侧边栏打开', shortcut: 'Alt+' }, + { action: 'download', icon: 'download', label: Number(detail.selectedDownloadCount || 0) > 1 ? '下载 ' + Number(detail.selectedDownloadCount || 0) + ' 个项目' : '下载', disabled: !isFileTreeDownload, title: isFileTreeDownload ? '下载当前本地文件或文件夹' : '当前项目没有可下载的本地路径' }, { separator: true }, { action: 'copy-link', icon: 'link', label: '复制访问链接' }, { action: 'copy-reference-inline', icon: 'content_copy', label: '复制页面引用链接' }, @@ -3987,19 +4057,31 @@ const SIDEBAR_TREE_JS: &str = r##" function openFileTreeContextMenu(row, x, y, trigger) { if (!(row instanceof HTMLElement)) return; var documentId = row.getAttribute('data-document-id') || row.getAttribute('data-doc-id') || ''; + var selectedDownloadRows = selectedSidebarFileTreeRowsForDownload(row); + var selectedAssetRows = selectedDownloadRows.filter(isFileTreeDownloadableAssetRow); openTreeContextMenu('filetree', { documentId: documentId, rowId: row.getAttribute('data-row-id') || '', rowKind: row.getAttribute('data-row-kind') || '', assetId: row.getAttribute('data-asset-id') || '', + localRelativePath: fileTreeRowLocalRelativePath(row), title: rowTitle(row), - workspaceId: resolveWorkspaceId(row) + workspaceId: resolveWorkspaceId(row), + downloadable: isFileTreeDownloadableRow(row), + selectedDownloadCount: selectedDownloadRows.length, + selectedDownloadRowIds: selectedDownloadRows.map(function(downloadRow) { return downloadRow.getAttribute('data-row-id') || ''; }).filter(Boolean), + selectedAssetCount: selectedAssetRows.length, + selectedAssetRowIds: selectedAssetRows.map(function(assetRow) { return assetRow.getAttribute('data-row-id') || ''; }).filter(Boolean) }, x, y, trigger || row); } function visibleFileTreeRows() { return Array.from(document.querySelectorAll('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"]')) - .filter(function(row) { return row instanceof HTMLElement && row.offsetParent !== null; }); + .filter(function(row) { + if (!(row instanceof HTMLElement)) return false; + if (row.closest('.tree-children--collapsed')) return false; + return row.offsetParent !== null || row.getClientRects().length > 0; + }); } function syncSidebarFileTreeSelection() { @@ -4116,11 +4198,130 @@ const SIDEBAR_TREE_JS: &str = r##" return ''; } + function decodeLocalEncodedPath(value) { + var path = String(value || '').trim().replace(/~2F/g, '/'); + if (!path) return ''; + try { + return decodeURIComponent(path); + } catch (_) { + return path; + } + } + + function fileTreeRowLocalRelativePath(row) { + if (!(row instanceof HTMLElement)) return ''; + var direct = String(row.getAttribute('data-local-relative-path') || '').trim(); + if (direct) return direct; + var assetPath = localFilePathFromAssetId(fileTreeRowAssetId(row)); + if (assetPath) return assetPath; + var rowId = String(row.getAttribute('data-row-id') || '').trim(); + var nodeId = String(row.getAttribute('data-node-id') || '').trim(); + var documentId = fileTreeRowDocumentId(row); + var kind = fileTreeRowKind(row); + if (rowId.indexOf('local:asset:') === 0) return rowId.slice('local:asset:'.length); + if (rowId.indexOf('local:markdown:') === 0) return rowId.slice('local:markdown:'.length); + if (rowId.indexOf('local:folder:') === 0) return rowId.slice('local:folder:'.length); + if (rowId.indexOf('local:node:') === 0) return rowId.slice('local:node:'.length); + if (nodeId.indexOf('local:node:') === 0) return nodeId.slice('local:node:'.length); + if ((kind === 'document' || kind === 'doc' || kind === 'markdown') && documentId.indexOf('local-md:') === 0) { + return decodeLocalEncodedPath(documentId.slice('local-md:'.length)); + } + if ((kind === 'folder' || kind === 'directory' || kind === 'index') && documentId.indexOf('local-dir:') === 0) { + return decodeLocalEncodedPath(documentId.slice('local-dir:'.length)); + } + return ''; + } + function fileTreeRowKind(row) { if (!(row instanceof HTMLElement)) return ''; return String(row.getAttribute('data-row-kind') || '').trim(); } + function isFileTreeDownloadableAssetRow(row) { + if (!(row instanceof HTMLElement)) return false; + var kind = fileTreeRowKind(row); + if (kind === 'document' || kind === 'doc' || kind === 'index' || kind === 'markdown' || kind === 'folder' || kind === 'directory') return false; + return Boolean(fileTreeRowAssetId(row)); + } + + function isFileTreeDownloadableRow(row) { + if (!(row instanceof HTMLElement)) return false; + if (currentSourceKind() !== 'local_folder') return isFileTreeDownloadableAssetRow(row); + return Boolean(fileTreeRowLocalRelativePath(row) || isFileTreeDownloadableAssetRow(row)); + } + + function fileTreeAssetDownloadDetail(row) { + if (!isFileTreeDownloadableRow(row)) return null; + var title = rowTitle(row); + var relativePath = fileTreeRowLocalRelativePath(row); + var assetId = fileTreeRowAssetId(row) || (relativePath ? 'local-file:' + relativePath : ''); + return { + contextKind: 'filetree', + documentId: fileTreeRowDocumentId(row), + rowId: row.getAttribute('data-row-id') || '', + rowKind: fileTreeRowKind(row), + assetId: assetId, + localRelativePath: relativePath, + title: title, + fileName: title, + workspaceId: resolveWorkspaceId(row) + }; + } + + function selectedSidebarFileTreeRowsForDownload(contextRow) { + var contextRowId = contextRow instanceof HTMLElement ? contextRow.getAttribute('data-row-id') || '' : ''; + var selectedRows = selectedSidebarFileTreeRows().filter(isFileTreeDownloadableRow); + if (contextRowId && sidebarFileTreeSelection.selectedRowIds.has(contextRowId) && selectedRows.length > 0) return selectedRows; + return isFileTreeDownloadableRow(contextRow) ? [contextRow] : selectedRows; + } + + function selectedSidebarFileTreeAssetRowsForDownload(contextRow) { + return selectedSidebarFileTreeRowsForDownload(contextRow).filter(isFileTreeDownloadableAssetRow); + } + + function downloadSelectedFileTreeAssetRows(detail, trigger) { + var contextRow = trigger && trigger.closest ? trigger.closest('.tree-row[data-shell-mode="filetree"]') : trigger; + if (!(contextRow instanceof HTMLElement) && detail && detail.rowId) { + contextRow = document.querySelector('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-row-id="' + cssEscape(detail.rowId) + '"]'); + } + var rows = selectedSidebarFileTreeRowsForDownload(contextRow); + var seen = new Set(); + var downloads = []; + rows.forEach(function(row) { + var item = fileTreeAssetDownloadDetail(row); + var key = item && (item.localRelativePath || item.assetId || item.rowId); + if (!item || !key || seen.has(key)) return; + seen.add(key); + downloads.push(item); + }); + if (downloads.length === 0 && detail && detail.assetId) { + downloads.push(Object.assign({}, detail, { fileName: detail.fileName || detail.title || '附件' })); + } else if (downloads.length === 0 && detail && detail.localRelativePath) { + downloads.push(Object.assign({}, detail, { + assetId: detail.assetId || 'local-file:' + detail.localRelativePath, + fileName: detail.fileName || detail.title || '附件' + })); + } + if (downloads.length === 0) { + recordFileTreeActionStatus('blocked', Object.assign({}, detail || {}, { reason: 'no-downloadable-assets' })); + return false; + } + var primary = Object.assign({}, downloads[0], { + count: downloads.length, + assetIds: downloads.map(function(item) { return item.assetId || ''; }).filter(Boolean), + localRelativePaths: downloads.map(function(item) { return item.localRelativePath || ''; }).filter(Boolean) + }); + recordFileTreeAction(downloads.length > 1 ? 'bulk-download' : 'download', primary); + recordFileTreeActionStatus('requested', primary); + document.documentElement.setAttribute('data-mnote-filetree-download-count', String(downloads.length)); + document.documentElement.setAttribute('data-mnote-filetree-download-asset-ids', downloads.map(function(item) { return item.assetId || ''; }).filter(Boolean).join(',')); + document.documentElement.setAttribute('data-mnote-filetree-download-paths', downloads.map(function(item) { return item.localRelativePath || ''; }).filter(Boolean).join(',')); + downloads.forEach(function(item) { + void openEditorAttachmentDownload(item); + }); + return true; + } + function hasSelectedDocumentAncestor(row, selectedDocRowIds) { var node = row instanceof HTMLElement ? row.closest('.tree-node') : null; while (node && node.parentElement) { @@ -8010,7 +8211,7 @@ const SIDEBAR_TREE_JS: &str = r##" if (localFilePath) { var localDownloadUrl = buildLocalFileOpenUrl(localFilePath, true); if (localDownloadUrl) { - window.open(localDownloadUrl, '_blank', 'noopener,noreferrer'); + triggerBrowserDownload(localDownloadUrl); return; } } @@ -8028,15 +8229,25 @@ const SIDEBAR_TREE_JS: &str = r##" } } catch (_) {} } + var localRelativePath = String(detail.localRelativePath || '').trim(); + if (localRelativePath) { + var localPathDownloadUrl = buildLocalFileOpenUrl(localRelativePath, true); + if (localPathDownloadUrl) { + triggerBrowserDownload(localPathDownloadUrl); + return; + } + } var target = detail.fileUrl || detail.href; if (!target) return; window.open(target, '_blank', 'noopener,noreferrer'); } - function openEditorAttachmentMenu(link, trigger) { + function openEditorAttachmentMenu(link, trigger, point) { var detail = detailFromEditorAttachmentLink(link); var rect = trigger && trigger.getBoundingClientRect ? trigger.getBoundingClientRect() : link.getBoundingClientRect(); - openTreeContextMenu('attachment', detail, rect.right, rect.bottom + 4, trigger || link); + var x = point && typeof point.x === 'number' ? point.x : rect.right; + var y = point && typeof point.y === 'number' ? point.y : rect.bottom + 4; + openTreeContextMenu('attachment', detail, x, y, trigger || link); } function openEditorAttachmentLink(link) { @@ -8485,7 +8696,9 @@ const SIDEBAR_TREE_JS: &str = r##" } if (fileAction === 'menu') { e.preventDefault(); - selectSidebarFileTreeRow(fileRow, { ctrlKey: false, metaKey: false, shiftKey: false }); + if (rowId && !sidebarFileTreeSelection.selectedRowIds.has(rowId)) { + selectSidebarFileTreeRow(fileRow, { ctrlKey: false, metaKey: false, shiftKey: false }); + } var point = rowCenter(fileBtn || fileRow); dispatchSidebarEvent('tree.filetree.context-menu', { rowId: rowId, rowKind: rowKind, documentId: documentId || null, assetId: assetId || null }); openFileTreeContextMenu(fileRow, point.x, point.y, fileBtn || fileRow); @@ -8559,6 +8772,14 @@ const SIDEBAR_TREE_JS: &str = r##" }); document.addEventListener('contextmenu', function(event) { + var editorAttachmentLink = closestAction(event.target, '.editor-surface .ProseMirror a[data-mnote-attachment-link="true"], .editor-surface .ProseMirror a.mnote-uploaded-attachment-row'); + if (editorAttachmentLink instanceof HTMLAnchorElement) { + event.preventDefault(); + event.stopPropagation(); + enhanceEditorAttachmentLink(editorAttachmentLink); + openEditorAttachmentMenu(editorAttachmentLink, editorAttachmentLink, { x: event.clientX, y: event.clientY }); + return; + } var fileRow = closestAction(event.target, '.tree-row[data-shell-mode="filetree"]'); if (fileRow) { event.preventDefault(); @@ -8904,7 +9125,10 @@ const SIDEBAR_TREE_JS: &str = r##" targetRowId: targetRow ? targetRow.getAttribute('data-row-id') : null, targetRowKind: targetRow ? targetRow.getAttribute('data-row-kind') : 'root', documentId: targetRow ? targetRow.getAttribute('data-document-id') || targetRow.getAttribute('data-doc-id') : null, - assetId: targetRow ? targetRow.getAttribute('data-asset-id') : null + assetId: targetRow ? targetRow.getAttribute('data-asset-id') : null, + targetRelativePath: targetRow && (targetRow.getAttribute('data-row-kind') === 'folder' || targetRow.getAttribute('data-row-kind') === 'directory') + ? fileTreeRowLocalRelativePath(targetRow) + : null }; if (activeFileTreeDropRow instanceof HTMLElement) activeFileTreeDropRow.setAttribute('data-drop-target', 'false'); activeFileTreeDropRow = null; @@ -9718,6 +9942,21 @@ mod tests { assert!(SIDEBAR_TREE_JS.contains("function fileTreeMenuTargetParentId")); assert!(SIDEBAR_TREE_JS.contains("if (action === 'new-folder')")); assert!(SIDEBAR_TREE_JS.contains("fileTreeCopyPath(detail, trigger)")); + assert!(SIDEBAR_TREE_JS + .contains("if (detail.contextKind === 'filetree' && action === 'download')")); + assert!(SIDEBAR_TREE_JS.contains("downloadSelectedFileTreeAssetRows(detail, trigger)")); + assert!(SIDEBAR_TREE_JS.contains("function selectedSidebarFileTreeRowsForDownload")); + assert!(SIDEBAR_TREE_JS.contains("function selectedSidebarFileTreeAssetRowsForDownload")); + assert!(SIDEBAR_TREE_JS.contains("data-local-relative-path")); + assert!(SIDEBAR_TREE_JS.contains("targetRelativePath")); + assert!(SIDEBAR_TREE_JS.contains( + "recordFileTreeAction(downloads.length > 1 ? 'bulk-download' : 'download', primary)" + )); + assert!(SIDEBAR_TREE_JS.contains("data-mnote-filetree-download-count")); + assert!(SIDEBAR_TREE_JS.contains("function triggerBrowserDownload")); + assert!(SIDEBAR_TREE_JS.contains("openEditorAttachmentMenu(editorAttachmentLink, editorAttachmentLink, { x: event.clientX, y: event.clientY });")); + assert!(SIDEBAR_TREE_JS.contains("var localFilePath = localFilePathFromAssetId(assetId);")); + assert!(SIDEBAR_TREE_JS.contains("attachmentMetaCache[assetId] = localMeta;")); assert!(SIDEBAR_TREE_JS.contains( "var pageRow = closestAction(e.target, '.tree-row[data-shell-mode=\"page\"]');" )); diff --git a/scripts/task476-filetree-editor-context-menu-download-smoke.js b/scripts/task476-filetree-editor-context-menu-download-smoke.js new file mode 100644 index 00000000..ad2ea47d --- /dev/null +++ b/scripts/task476-filetree-editor-context-menu-download-smoke.js @@ -0,0 +1,308 @@ +#!/usr/bin/env node +"use strict"; + +const assert = require("node:assert"); +const fs = require("node:fs"); +const os = require("node:os"); +const path = require("node:path"); +const { chromium } = require("playwright"); + +const BASE_URL = (process.env.MNOTE_WEB_SMOKE_BASE_URL || "http://127.0.0.1:3000").replace(/\/+$/, ""); +const UI_TIMEOUT_MS = Number(process.env.MNOTE_SMOKE_UI_TIMEOUT_MS || 30_000); +const OUTPUT_DIR = process.env.MNOTE_CONTEXT_MENU_DOWNLOAD_OUTPUT_DIR + || "/mnt/Data1T/mnote/tmp/task476-filetree-editor-context-menu-download-smoke"; +const CHROMIUM_EXECUTABLE_PATH = process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH + || ["/usr/bin/google-chrome-stable", "/usr/bin/google-chrome", "/snap/bin/chromium"] + .find((candidate) => fs.existsSync(candidate)); + +function fileUrl(localPath) { + return `file://${localPath}`; +} + +function localMdDocumentId(relativePath) { + return `local-md:${relativePath.replaceAll("/", "~2F")}`; +} + +function documentUrl(root, relativePath) { + const url = new URL(`${BASE_URL}/documents/${encodeURIComponent(localMdDocumentId(relativePath))}`); + url.searchParams.set("sourceKind", "local_folder"); + url.searchParams.set("rootUri", fileUrl(root)); + url.searchParams.set("treeView", "filetree"); + return url.toString(); +} + +function writeWorkspaceManifest(root, ownerId) { + const metadataDir = path.join(root, ".mnote"); + fs.mkdirSync(metadataDir, { recursive: true }); + fs.writeFileSync( + path.join(metadataDir, "workspace.json"), + `${JSON.stringify({ + workspaceId: `local-ws:${ownerId}:task476`, + ownerId, + createdAt: new Date().toISOString(), + capabilities: ["local_files", "markdown_edit", "asset_upload"], + }, null, 2)}\n`, + "utf8", + ); +} + +async function quickLogin(page) { + await page.goto(`${BASE_URL}/auth`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS }); + const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" }); + if (await quickLoginButton.count()) { + await quickLoginButton.click({ timeout: UI_TIMEOUT_MS }); + await page.waitForURL((url) => url.pathname === "/", { timeout: UI_TIMEOUT_MS }).catch(() => undefined); + } +} + +async function menuActions(page, kind) { + return await page.evaluate((targetKind) => { + const menu = document.querySelector(`[data-testid="mnote-tree-context-menu"][data-kind="${targetKind}"]`); + return Array.from(menu?.querySelectorAll("[data-action]") || []).map((button) => ({ + action: button.getAttribute("data-action") || "", + label: button.textContent?.trim() || "", + })); + }, kind); +} + +async function waitForDownloadCount(downloads, expectedCount, timeoutMs) { + const startedAt = Date.now(); + while (downloads.length < expectedCount && Date.now() - startedAt < timeoutMs) { + await new Promise((resolve) => setTimeout(resolve, 100)); + } + assert( + downloads.length >= expectedCount, + `等待下载数量达到 ${expectedCount} 失败,当前 ${downloads.length}`, + ); +} + +async function waitForFileExists(filePath, timeoutMs) { + const startedAt = Date.now(); + while (!fs.existsSync(filePath) && Date.now() - startedAt < timeoutMs) { + await new Promise((resolve) => setTimeout(resolve, 100)); + } + assert(fs.existsSync(filePath), `等待文件出现失败: ${filePath}`); +} + +function suggestedFilenames(downloads, startIndex) { + return downloads.slice(startIndex).map((download) => download.suggestedFilename()).sort(); +} + +async function main() { + fs.mkdirSync(OUTPUT_DIR, { recursive: true }); + const root = fs.mkdtempSync(path.join(os.tmpdir(), "mnote-task476-context-download-")); + const relativePath = "README.md"; + const resourceDir = path.join(root, "README"); + const assetNames = ["report-a.pdf", "report-b.pdf", "report-c.pdf"]; + fs.mkdirSync(resourceDir, { recursive: true }); + writeWorkspaceManifest(root, "user_real"); + fs.writeFileSync( + path.join(root, relativePath), + [ + "---", + "title: Context Menu Download", + "---", + "", + "# Context Menu Download", + "", + "[report-a.pdf](README/report-a.pdf)", + "", + ].join("\n"), + "utf8", + ); + for (const fileName of assetNames) { + fs.writeFileSync(path.join(resourceDir, fileName), Buffer.from(`%PDF-1.4\n% task476 ${fileName}\n`, "utf8")); + } + + const browser = await chromium.launch({ + headless: process.env.HEADFUL !== "1", + executablePath: CHROMIUM_EXECUTABLE_PATH, + }); + const context = await browser.newContext({ + viewport: { width: 1360, height: 900 }, + acceptDownloads: true, + extraHTTPHeaders: { + "x-mnote-actor-id": "user_real", + "x-mnote-actor-type": "user", + }, + }); + const page = await context.newPage(); + const consoleErrors = []; + const capturedDownloads = []; + page.on("console", (message) => { + if (message.type() === "error") consoleErrors.push(message.text()); + }); + page.on("download", (download) => { + capturedDownloads.push(download); + }); + try { + await quickLogin(page); + await page.goto(documentUrl(root, relativePath), { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS }); + const editorSurface = page.locator(".document-pane[data-pane-role=\"primary\"] .editor-surface .ProseMirror").first(); + await editorSurface.waitFor({ + state: "attached", + timeout: UI_TIMEOUT_MS, + }).catch(() => undefined); + const editorReady = await editorSurface.isVisible().catch(() => false); + + const assetRow = page.locator('[data-testid="filetree-asset-row"]', { hasText: "report-a.pdf" }).first(); + await assetRow.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); + await assetRow.click({ button: "right", timeout: UI_TIMEOUT_MS }); + const filetreeMenu = page.locator('[data-testid="mnote-tree-context-menu"][data-kind="filetree"]').first(); + await filetreeMenu.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); + const filetreeActions = await menuActions(page, "filetree"); + assert(filetreeActions.some((item) => item.action === "download"), `文件树 asset 菜单缺少下载: ${JSON.stringify(filetreeActions)}`); + await page.screenshot({ path: path.join(OUTPUT_DIR, "01-filetree-asset-menu-with-download.png"), fullPage: false }); + const singleDownloadStart = capturedDownloads.length; + const [filetreeDownload] = await Promise.all([ + page.waitForEvent("download", { timeout: UI_TIMEOUT_MS }), + filetreeMenu.locator('[data-action="download"]').click({ timeout: UI_TIMEOUT_MS }), + ]); + assert.equal(filetreeDownload.suggestedFilename(), "report-a.pdf"); + await filetreeDownload.saveAs(path.join(OUTPUT_DIR, filetreeDownload.suggestedFilename())); + await waitForDownloadCount(capturedDownloads, singleDownloadStart + 1, UI_TIMEOUT_MS); + + const reportCRow = page.locator('[data-testid="filetree-asset-row"]', { hasText: "report-c.pdf" }).first(); + await reportCRow.click({ modifiers: ["Control"], timeout: UI_TIMEOUT_MS }); + const selectedAfterCtrl = await page.evaluate(() => Array.from(document.querySelectorAll('#sidebar-file-tree-root .tree-row[data-selected="true"]')).map((row) => ({ + title: row.textContent?.trim() || "", + rowId: row.getAttribute("data-row-id") || "", + }))); + await reportCRow.click({ button: "right", timeout: UI_TIMEOUT_MS }); + const ctrlMenu = page.locator('[data-testid="mnote-tree-context-menu"][data-kind="filetree"]').first(); + await ctrlMenu.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); + const ctrlActions = await menuActions(page, "filetree"); + assert( + ctrlActions.some((item) => item.action === "download" && item.label.includes("2")), + `Ctrl 多选菜单没有显示 2 个文件下载: actions=${JSON.stringify(ctrlActions)} selected=${JSON.stringify(selectedAfterCtrl)}`, + ); + await page.screenshot({ path: path.join(OUTPUT_DIR, "03-filetree-ctrl-multi-download-menu.png"), fullPage: false }); + const ctrlDownloadStart = capturedDownloads.length; + await ctrlMenu.locator('[data-action="download"]').click({ timeout: UI_TIMEOUT_MS }); + await waitForDownloadCount(capturedDownloads, ctrlDownloadStart + 2, UI_TIMEOUT_MS); + const ctrlDownloadFiles = suggestedFilenames(capturedDownloads, ctrlDownloadStart); + assert.deepStrictEqual(ctrlDownloadFiles, ["report-a.pdf", "report-c.pdf"]); + + await assetRow.click({ timeout: UI_TIMEOUT_MS }); + await reportCRow.click({ modifiers: ["Shift"], timeout: UI_TIMEOUT_MS }); + const selectedAfterShift = await page.evaluate(() => Array.from(document.querySelectorAll('#sidebar-file-tree-root .tree-row[data-selected="true"]')).map((row) => ({ + title: row.textContent?.trim() || "", + rowId: row.getAttribute("data-row-id") || "", + }))); + await reportCRow.click({ button: "right", timeout: UI_TIMEOUT_MS }); + const shiftMenu = page.locator('[data-testid="mnote-tree-context-menu"][data-kind="filetree"]').first(); + await shiftMenu.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); + const shiftActions = await menuActions(page, "filetree"); + assert( + shiftActions.some((item) => item.action === "download" && item.label.includes("3")), + `Shift 范围多选菜单没有显示 3 个文件下载: actions=${JSON.stringify(shiftActions)} selected=${JSON.stringify(selectedAfterShift)}`, + ); + await page.screenshot({ path: path.join(OUTPUT_DIR, "04-filetree-shift-multi-download-menu.png"), fullPage: false }); + const shiftDownloadStart = capturedDownloads.length; + await shiftMenu.locator('[data-action="download"]').click({ timeout: UI_TIMEOUT_MS }); + await waitForDownloadCount(capturedDownloads, shiftDownloadStart + 3, UI_TIMEOUT_MS); + const shiftDownloadFiles = suggestedFilenames(capturedDownloads, shiftDownloadStart); + assert.deepStrictEqual(shiftDownloadFiles, ["report-a.pdf", "report-b.pdf", "report-c.pdf"]); + + const markdownRow = page.locator('#sidebar-file-tree-root .tree-row[data-row-kind="markdown"]', { hasText: "README.md" }).first(); + await markdownRow.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); + await markdownRow.click({ button: "right", timeout: UI_TIMEOUT_MS }); + const markdownMenu = page.locator('[data-testid="mnote-tree-context-menu"][data-kind="filetree"]').first(); + await markdownMenu.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); + const markdownActions = await menuActions(page, "filetree"); + assert(markdownActions.some((item) => item.action === "download"), `Markdown 行菜单缺少下载: ${JSON.stringify(markdownActions)}`); + const [markdownDownload] = await Promise.all([ + page.waitForEvent("download", { timeout: UI_TIMEOUT_MS }), + markdownMenu.locator('[data-action="download"]').click({ timeout: UI_TIMEOUT_MS }), + ]); + assert.equal(markdownDownload.suggestedFilename(), "README.md"); + + const folderRow = page.locator('#sidebar-file-tree-root .tree-row[data-row-kind="folder"]', { hasText: "README" }).first(); + await folderRow.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); + await folderRow.click({ button: "right", timeout: UI_TIMEOUT_MS }); + const folderMenu = page.locator('[data-testid="mnote-tree-context-menu"][data-kind="filetree"]').first(); + await folderMenu.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); + const folderActions = await menuActions(page, "filetree"); + assert(folderActions.some((item) => item.action === "download"), `文件夹行菜单缺少下载: ${JSON.stringify(folderActions)}`); + await page.screenshot({ path: path.join(OUTPUT_DIR, "05-filetree-folder-download-menu.png"), fullPage: false }); + const [folderDownload] = await Promise.all([ + page.waitForEvent("download", { timeout: UI_TIMEOUT_MS }), + folderMenu.locator('[data-action="download"]').click({ timeout: UI_TIMEOUT_MS }), + ]); + assert.equal(folderDownload.suggestedFilename(), "README.tar"); + await folderDownload.saveAs(path.join(OUTPUT_DIR, folderDownload.suggestedFilename())); + + const droppedFileName = "drag-upload-note.txt"; + const droppedPath = path.join(resourceDir, droppedFileName); + await folderRow.dispatchEvent("dragover", { + dataTransfer: await page.evaluateHandle(({ fileName }) => { + const dataTransfer = new DataTransfer(); + dataTransfer.items.add(new File(["drag upload smoke"], fileName, { type: "text/plain" })); + return dataTransfer; + }, { fileName: droppedFileName }), + }); + await folderRow.dispatchEvent("drop", { + dataTransfer: await page.evaluateHandle(({ fileName }) => { + const dataTransfer = new DataTransfer(); + dataTransfer.items.add(new File(["drag upload smoke"], fileName, { type: "text/plain" })); + return dataTransfer; + }, { fileName: droppedFileName }), + }); + await waitForFileExists(droppedPath, UI_TIMEOUT_MS); + + let attachmentActions = []; + let attachmentDownload = null; + const attachmentLink = page.locator('.editor-surface .ProseMirror a[data-mnote-attachment-link="true"]', { hasText: "report-a.pdf" }).first(); + const attachmentReady = editorReady && await attachmentLink.isVisible().catch(() => false); + if (attachmentReady) { + await attachmentLink.click({ button: "right", timeout: UI_TIMEOUT_MS }); + const attachmentMenu = page.locator('[data-testid="mnote-tree-context-menu"][data-kind="attachment"]').first(); + await attachmentMenu.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); + attachmentActions = await menuActions(page, "attachment"); + assert(attachmentActions.some((item) => item.action === "download"), `附件右键菜单缺少下载: ${JSON.stringify(attachmentActions)}`); + await page.screenshot({ path: path.join(OUTPUT_DIR, "02-editor-attachment-context-menu.png"), fullPage: false }); + [attachmentDownload] = await Promise.all([ + page.waitForEvent("download", { timeout: UI_TIMEOUT_MS }), + attachmentMenu.locator('[data-action="download"]').click({ timeout: UI_TIMEOUT_MS }), + ]); + assert.equal(attachmentDownload.suggestedFilename(), "report-a.pdf"); + } + + const result = { + ok: true, + root, + url: page.url(), + filetreeActions, + attachmentActions, + downloads: [ + { action: "filetree_asset_download", suggestedFilename: filetreeDownload.suggestedFilename() }, + { action: "filetree_ctrl_multi_download", suggestedFilenames: ctrlDownloadFiles }, + { action: "filetree_shift_multi_download", suggestedFilenames: shiftDownloadFiles }, + { action: "filetree_markdown_download", suggestedFilename: markdownDownload.suggestedFilename() }, + { action: "filetree_folder_download", suggestedFilename: folderDownload.suggestedFilename() }, + { action: "filetree_folder_drop_upload", targetPath: droppedPath, exists: fs.existsSync(droppedPath) }, + attachmentReady + ? { action: "editor_attachment_context_menu_download", suggestedFilename: attachmentDownload.suggestedFilename() } + : { action: "editor_attachment_context_menu_download", status: "blocked", reason: attachmentReady ? "" : "editor_attachment_not_visible" }, + ], + consoleErrors, + screenshots: [ + path.join(OUTPUT_DIR, "01-filetree-asset-menu-with-download.png"), + path.join(OUTPUT_DIR, "03-filetree-ctrl-multi-download-menu.png"), + path.join(OUTPUT_DIR, "04-filetree-shift-multi-download-menu.png"), + path.join(OUTPUT_DIR, "05-filetree-folder-download-menu.png"), + ...(attachmentReady ? [path.join(OUTPUT_DIR, "02-editor-attachment-context-menu.png")] : []), + ], + }; + fs.writeFileSync(path.join(OUTPUT_DIR, "result.json"), `${JSON.stringify(result, null, 2)}\n`, "utf8"); + console.log(JSON.stringify(result, null, 2)); + } finally { + await context.close().catch(() => undefined); + await browser.close().catch(() => undefined); + } +} + +main().catch((error) => { + console.error(error && error.stack ? error.stack : error); + process.exit(1); +});