chore: align mvp design governance

- 统一 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
This commit is contained in:
lix-2026
2026-05-21 09:04:13 +08:00
parent fd94792ea6
commit 1956a8a21a
69 changed files with 3390 additions and 111 deletions
@@ -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`
@@ -1,135 +0,0 @@
# 5-15 本地 Markdown 路径身份统一 Checklist v1
## 背景
当前本地 Markdown 主链正在对齐 VS Code 文件模型:普通 `.md` 文件的运行时身份应来自文件资源地址,而不是 frontmatter 或 MNote 内部稳定 ID。
VS Code 参考:
- `design/05-editor-mainline/reference-code/vscode/src/vs/platform/files/common/fileService.ts`
- `createFile(resource)``URI` 创建文件。
- `design/05-editor-mainline/reference-code/vscode/src/vs/workbench/contrib/files/browser/fileCommands.ts`
- 新建文件后 `openEditor({ resource: saveUri })`
- `design/05-editor-mainline/reference-code/vscode/src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts`
- `matches()` 使用 `isEqual(otherInput.resource, this.resource)`
- `design/05-editor-mainline/reference-code/vscode/src/vs/workbench/contrib/files/browser/editors/fileEditorHandler.ts`
- working copy 与 editor 是否同一打开项,比较 `workingCopy.resource``editor.resource`
本阶段目标不是引入新的稳定语义 ID,而是先消除普通本地 Markdown runtime 中的多 ID 竞争。
## 身份原则
- 普通本地 Markdown 的真实身份是 `sourceKind + rootUri + relativePath`
- 对外 `documentId` 继续使用路径型短句柄:`local-md:<encoded relative_path>`
- `mnote_id` 只作为已有 frontmatter 元数据保留,不参与普通本地 Markdown 的 `documentId` 决策。
- `.mnote/page-ids.json` 只允许作为历史兼容/迁移辅助,不参与普通本地 Markdown 的主身份生成。
- 不主动修改、删除或重写用户 Markdown frontmatter。
## 允许修改范围
- `rust/crates/mnote-web/src/routes/local_search_index.rs`
- `rust/crates/mnote-web/src/routes/local_folder_source.rs`
- 必要时更新直接相关测试:
- `rust/crates/mnote-web/src/routes/search.rs`
- `rust/crates/mnote-web/src/routes/tree.rs`
- `rust/crates/mnote-web/src/routes/documents.rs`
不要修改用户数据目录,不要清理仓库中无关脏文件,不要做 git commit。
## P0 Checklist
### 1. 搜索索引 documentId 与树形列表统一
- [ ] `local_search_index.rs``index_markdown_file()` 永远按 `relative_path` 生成 `local-md:<encoded relative_path>`
- [ ] frontmatter `mnote_id` 不再生成 `local-mdid:*`
- [ ] 搜索结果、最近修改、反链接口返回的 Markdown `documentId` 与文件树一致。
- [ ]`mnote_id` 的 Markdown 搜索测试必须断言仍返回路径型 ID,例如 `local-md:docs~2Fchild.md`
验收:
```bash
cargo test --manifest-path rust/Cargo.toml -p mnote-web local_search_index -- --nocapture
cargo test --manifest-path rust/Cargo.toml -p mnote-web search_local_index -- --nocapture
```
### 2. 清理本地 Markdown 身份生成的误导参数
- [ ] `local_markdown_page_id(relative_path, _mnote_id, _metadata)` 不再保留无用参数签名。
- [ ] 普通调用点直接使用 `local_markdown_path_page_id(relative_path)` 或新的清晰命名函数。
- [ ] 如果必须保留 legacy resolver,函数名必须包含 `legacy` / `compat`,并且不能参与普通本地 Markdown 主链。
验收:
```bash
rg -n "local_markdown_page_id\\(|local-mdid|mnote_id|page-ids" rust/crates/mnote-web/src/routes/local_folder_source.rs rust/crates/mnote-web/src/routes/local_search_index.rs
```
期望:
- `local_markdown_page_id(` 不应再出现在普通主链调用中。
- `local_search_index.rs` 不应再出现 `local-mdid` 生成逻辑。
- `mnote_id` 可以继续出现在解析、兼容测试或“保留 frontmatter”测试中,但不能参与普通 `documentId` 生成。
### 3. 不污染用户文件
- [ ] 新建本地 Markdown 不写 `mnote_id` frontmatter。
- [ ] 复制本地 Markdown 不改写 `mnote_id`
- [ ] 保存已有 Markdown 时保留原 frontmatter,但不新增身份字段。
验收:
```bash
cargo test --manifest-path rust/Cargo.toml -p mnote-web local_tree_command_local_folder_lifecycle -- --nocapture
cargo test --manifest-path rust/Cargo.toml -p mnote-web local_page_aggregate_accepts_path_id_when_frontmatter_has_mnote_id -- --nocapture
cargo test --manifest-path rust/Cargo.toml -p mnote-web local_save -- --nocapture
```
## P1 Checklist
### 4. 路径型身份覆盖打开/保存/搜索一致性
- [ ] 同一个含 `mnote_id``.md` 文件,在文件树、页面 aggregate、搜索索引中返回同一个 `local-md:<encoded relative_path>`
- [ ] `documents.content.get` / 页面打开不再需要 `local-mdid:*` 才能命中文件。
- [ ] rename/move 后新路径返回新的路径型 `documentId`,旧路径型 ID 不作为稳定页面 ID 保留。
验收:
```bash
cargo test --manifest-path rust/Cargo.toml -p mnote-web local_markdown_identity_uses_path_even_when_frontmatter_has_mnote_id -- --nocapture
cargo test --manifest-path rust/Cargo.toml -p mnote-web local_page_aggregate_accepts_path_id_when_frontmatter_has_mnote_id -- --nocapture
cargo test --manifest-path rust/Cargo.toml -p mnote-web tree_command_local_folder_lifecycle -- --nocapture
```
## 最终验收
```bash
cargo fmt --check --all --manifest-path rust/Cargo.toml
cargo test --manifest-path rust/Cargo.toml -p mnote-web local_search_index -- --nocapture
cargo test --manifest-path rust/Cargo.toml -p mnote-web search_local_index -- --nocapture
cargo test --manifest-path rust/Cargo.toml -p mnote-web local_markdown_identity_uses_path_even_when_frontmatter_has_mnote_id -- --nocapture
cargo test --manifest-path rust/Cargo.toml -p mnote-web local_page_aggregate_accepts_path_id_when_frontmatter_has_mnote_id -- --nocapture
cargo test --manifest-path rust/Cargo.toml -p mnote-web tree_command_local_folder_lifecycle -- --nocapture
```
若涉及浏览器可见行为,Codex 复核阶段必须使用真实浏览器验证并将截图放在 `tmp/` 下。
## 执行状态(2026-05-19
- [x] P0-1 搜索索引 `documentId` 已统一为 `local-md:<encoded relative_path>`,不再由 frontmatter `mnote_id` 生成 `local-mdid:*`
- [x] P0-2 普通本地 Markdown 主链已移除 `local_markdown_page_id(relative_path, _mnote_id, _metadata)` 误导签名,文件树、page aggregate、find-by-id 改用路径型 ID。
- [x] P0-3 新建、复制、rename/move/restore/purge 普通本地 Markdown 不再写 `.mnote/page-ids.json`,也不再写入或改写 `mnote_id`
- [x] P1-4 rename/move 后返回新路径型 `documentId`,旧 ID 不再作为稳定页面 ID 保留。
- [x] 追加清理:删除 `ParsedLocalMarkdownPage.mnote_id` 字段,避免后续代码重新把 frontmatter `mnote_id` 当身份入口。
已通过验收:
```bash
cargo fmt --check --all --manifest-path rust/Cargo.toml
cargo test --manifest-path rust/Cargo.toml -p mnote-web local_search_index -- --nocapture
cargo test --manifest-path rust/Cargo.toml -p mnote-web search_local_index -- --nocapture
cargo test --manifest-path rust/Cargo.toml -p mnote-web local_markdown_identity_uses_path_even_when_frontmatter_has_mnote_id -- --nocapture
cargo test --manifest-path rust/Cargo.toml -p mnote-web local_page_aggregate_accepts_path_id_when_frontmatter_has_mnote_id -- --nocapture
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
```
@@ -1,53 +0,0 @@
# 5-16 Editor Group 与 Resource Tab 安全收口 Checklist v1
> 状态:process
>
> 日期:2026-05-20
>
> Owner`mnote-web` 主编辑区 runtime / resource tab
## 1. 目标
把当前浏览器内存版 resource tab 收口为轻量 editor group 模型的第一阶段:不引入完整 VSCode 多 group,不改变 Page Aggregate 事实源,只解决打开、激活、关闭和脏资源防护的最小闭环。
## 2. 允许修改范围
- `rust/crates/mnote-web/src/routes/web_shell.rs`
- `rust/crates/mnote-web/src/ssr/pages/document.rs`
- `rust/crates/mnote-web/src/ssr/styles.rs`
- `scripts/task457-main-editor-resource-tab-smoke.js`
- 可新增一个聚焦 smoke,例如 `scripts/task460-resource-tab-close-dirty-smoke.js`
## 3. Checklist
- [ ] 定义轻量 `MainEditorTabEntry` / `mainEditorTabRegistry` 语义,至少包含 `id``kind``title``dirty``saving``hasExternalConflict``lastActiveAt`
- [x] 保持 page tab 为固定 tab,但 resource tab 激活/关闭应走同一套 registry 状态更新。
- [x] `closeResourceTab` 关闭前检查 resource session
- [x] `saving=true` 时禁止直接关闭或等待保存完成。
- [x] `hasExternalConflict=true` 时禁止静默关闭。
- [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,允许用户关闭或重试,不要留下半注册状态。
## 4. 验收
- [x] 打开两个 resource tab,切到第二个并关闭后,应回到第一个,而不是无条件回 page tab。
- [x] 编辑 resource markdown 后立即点关闭,不应静默丢弃未保存内容。
- [x] 保存中关闭不会释放 editor session。
- [ ] 打开失败的 resource tab 有明确错误占位,并可关闭。
- [x] 现有 `task457-main-editor-resource-tab-smoke.js` 继续通过。
- [x] 新增 close dirty / MRU smoke 通过。
## 4.1 本轮执行记录
- 已合入:resource tab MRU、关闭 guard、`aria-selected` / `tabindex` 同步、关闭后回退到上一个 resource tab。
- 已新增:`scripts/task460-resource-tab-close-dirty-smoke.js` 覆盖近期本地输入后的关闭阻止。
- 未完成:正式 `MainEditorTabEntry` 模型、打开失败 placeholder tab。
## 5. 非目标
- 不实现完整 VSCode preview / pinned / sticky / drag reorder。
- 不实现 tab 持久化恢复。
- 不改变 secondary pane 布局。
- 不把 `resourceTabRegistry` 移到后端持久状态。
@@ -1,57 +0,0 @@
# 5-17 Resource Editor Kind 与 Smoke 矩阵 Checklist v1
> 状态:process
>
> 日期:2026-05-20
>
> Owner:资源打开 resolver / Office / Markdown/Text/Code/Image/PDF smoke
## 1. 目标
把资源类型识别和打开策略从分散 if/else 收口为轻量 resolver,并补齐当前缺失的类型 smoke。默认目标是主编辑区 resource tab;右侧边栏和新窗口必须作为显式目标保留。
## 2. 允许修改范围
- `rust/crates/mnote-web/src/routes/web_shell.rs`
- `rust/crates/mnote-web/src/ssr/pages/layout.rs`
- `rust/crates/mnote-web/src/ssr/styles.rs`
- `rust/crates/mnote-web/src/routes/onlyoffice.rs`
- `scripts/task457-main-editor-resource-tab-smoke.js`
- `scripts/task459-local-markdown-attachment-tab-smoke.js`
- 可新增 resource kind smoke
## 3. Checklist
- [ ] 定义轻量 resource editor resolver
- [ ] 输入:`name``mimeType``href``assetId``rootUri``path``openTarget`
- [ ] 输出:`editorKind``badgeKind``defaultOpenMode``editable``viewerUrl`
- [x] `.md` / `.markdown` / `.txt` / 常见 code 文件继续使用 tiptap resource editor。
- [ ] Office 文件默认在主编辑区 tab 内 iframe 打开,显式新窗口仍可打开。
- [x] PDF 使用独立 `pdf` badge kind,不再复用 ppt。
- [x] image 使用 image badge kind 和 img viewer。
- [x] unknown file 有明确 fallback:下载/新窗口/错误占位,不应无响应。
- [ ] `active-tab``side``new-window` 三个 target 在同一 resolver 后执行,不再散落在 attachment capture 与 filetree click 中。
## 4. Smoke 矩阵
- [x] `.md` 上传/文件树点击 -> 主编辑区 tiptap tab,可编辑并写回资源文件。
- [x] `.txt` -> 主编辑区 tiptap tab,可编辑并写回。
- [x] `.json``.ts` -> 主编辑区 tiptap/code-like tab,可编辑并写回。
- [ ] `.docx` -> 主编辑区 Office iframeDocumentServer 可回源。
- [ ] `.pptx` / `.xlsx` -> badge 颜色分别正确。
- [x] `.pdf` -> 主编辑区 iframebadge 为 pdf 独立颜色。
- [x] image -> 主编辑区 img viewer。
- [x] unknown binary -> 明确 fallback,不破坏 page tab 切换。
## 4.1 本轮执行记录
- 已合入:PDF 独立 badge kind、file fallback badge、resource kind smoke 覆盖 markdown/text/code/image/pdf 与未知类型不破坏 tab 回退。
- 已验证:`scripts/task461-resource-kind-smoke.js``uploaded_attachment_rows_use_colored_file_badges` 通过。
- 未完成:正式 resource editor resolver 抽象、Office DocumentServer 回源修复、`active-tab` / `side` / `new-window` 统一 resolver 后执行。
## 5. 非目标
- 不引入 Monaco / CodeMirror。
- 不实现云端资源写回。
- 不重构 OnlyOffice DocumentServer 部署。
- 不改变 `.md` 资源仍用 tiptap 编辑的产品口径。
@@ -1,60 +0,0 @@
# 5-18 Main Editor Tab State 与错误占位 Checklist v1
> 状态:process
>
> 日期:2026-05-20
>
> Owner:主编辑区 tab runtime / `web_shell.rs`
## 1. 目标
补齐 P0-1 的最小轻量 editor group 状态:在不引入完整 VSCode 多 group 的前提下,让 page/resource tab 共享一个明确的浏览器内存状态模型,并补上 resource 打开失败时的错误占位 tab。
## 2. 允许修改范围
- `rust/crates/mnote-web/src/routes/web_shell.rs`
- `rust/crates/mnote-web/src/ssr/pages/document.rs`
- `rust/crates/mnote-web/src/ssr/styles.rs`
- `scripts/task457-main-editor-resource-tab-smoke.js`
- 可新增 `scripts/task462-resource-tab-error-placeholder-smoke.js`
## 3. 禁止事项
- 不引入完整 VSCode DI、多 editor group grid、preview/sticky 持久化。
- 不改变 Page Aggregate 或 local Markdown 事实源。
- 不修改 resource resolver、OnlyOffice、File Tree lifecycle;这些由其它 checklist 处理。
## 4. Checklist
- [x] 定义轻量 main editor tab 状态,至少能表达 `id``kind``title``dirty``saving``hasExternalConflict``lastActiveAt``status`
- [x] page tab 与 resource tab 激活统一通过同一状态更新入口。
- [x] resource tab close guard 读取同一状态入口,避免散落检查。
- [x] 打开 resource 失败时创建或保留 error placeholder tab。
- [x] error placeholder tab 有明确错误 UI,可关闭,不留下半注册 panel/session。
- [x] 更新 Rust 字符串 contract test,确保关键 runtime marker 存在。
## 5. 验收
- [x] `node --check scripts/task462-resource-tab-error-placeholder-smoke.js`
- [x] `node scripts/task462-resource-tab-error-placeholder-smoke.js`
- [x] `node scripts/task457-main-editor-resource-tab-smoke.js`
- [x] `cargo test -p mnote-web web_shell -- --test-threads=1`
## 6. 本轮执行记录
- 已完成:
- `createResourceSession` 已补 `lastActiveAt`resource session 继续持有 `dirty` / `saving` / `hasExternalConflict` / `status` 等关闭保护状态。
- page tab 与 resource tab 继续统一通过 `activateMainEditorTab` 激活,并在激活 resource tab 时更新 MRU 与 `lastActiveAt`
- `closeResourceTab` 继续通过 `resourceTabCloseGuardReason` 读取关闭保护状态。
- `openResourceInActiveTab` 失败时保留 resource tab,并通过 `.mnote-resource-tab-error` 渲染可见错误占位。
- 新增 `scripts/task462-resource-tab-error-placeholder-smoke.js`,已验证失败资源会出现错误占位、可关闭、连续失败不会产生失控 tab。
- `web_shell.rs``styles.rs` 已补关键 runtime / CSS contract。
- 未完成:
- `lastActiveAt` 时间戳尚未用于 tab 排序(当前 MRU 队列 `resourceTabMru` 控制切换顺序)。
- `dirty`/`saving`/`hasExternalConflict` 状态在 resource tab close guard 中读出但无 UI 确认弹窗(仅 console.warn + 禁止关闭)。
- 风险:无
## 7. 非目标
- 不做 tab 持久化恢复。
- 不做 tab 拖拽排序、context menu 或 close others。
@@ -1,62 +0,0 @@
# 5-19 Resource Open Resolver 与 OnlyOffice 回源 Checklist v1
> 状态:process
>
> 日期:2026-05-20
>
> Owner:资源打开 resolver / attachment capture / OnlyOffice URL 配置
## 1. 目标
补齐 P0-3:把资源类型识别、badge、默认打开目标、显式 `active-tab / side / new-window` 目标收口到轻量 resolver,并优先修复本地上传 `.docx` 在主编辑区 tab 内无法回源的问题。
## 2. 允许修改范围
- `rust/crates/mnote-web/src/routes/web_shell.rs`
- `rust/crates/mnote-web/src/ssr/pages/layout.rs`
- `rust/crates/mnote-web/src/routes/onlyoffice.rs`
- `rust/crates/mnote-web/src/ssr/styles.rs`
- `scripts/task457-main-editor-resource-tab-smoke.js`
- `scripts/task461-resource-kind-smoke.js`
- 可新增 OnlyOffice/resolver smoke
## 3. 禁止事项
- 不引入 Monaco / CodeMirror。
- 不重构 OnlyOffice DocumentServer 部署。
- 不改变 `.md` / `.txt` / code 资源继续用 tiptap resource editor 的口径。
- 不处理 File Tree open target 合同;该项由 4-39 处理。
## 4. Checklist
- [x] 定义轻量 resource open resolver,输入包含 `name/mimeType/href/assetId/rootUri/path/openTarget`
- [x] resolver 输出 `editorKind/badgeKind/defaultOpenMode/editable/viewerUrl` 或等价结构。
- [x] `tree.asset.open`、attachment click capture、显式 new-window 复用同一 resolver 判断。
- [x] 默认打开目标为主编辑区 `active-tab``new-window` 显式保留;`side` 不回归。
- [x] `.docx/.pptx/.xlsx` 默认进入主编辑区 Office iframe,显式 new-window 打开 `/onlyoffice`
- [x] OnlyOffice iframe 的 document URL 对 local-folder 文件使用 Rust 可访问的 `/api/local-folder/files/open``/api/onlyoffice/proxy` 回源 URL,避免 DocumentServer 访问浏览器不可达 URL。
- [x] 如果本机 DocumentServer 不可用,smoke 至少断言 iframe config URL / document URL / callback URL 正确,不把外部服务不可用误判为 resolver 失败。
## 5. 验收
- [x] `node --check scripts/task457-main-editor-resource-tab-smoke.js && node --check scripts/task461-resource-kind-smoke.js`
- [x] `node scripts/task457-main-editor-resource-tab-smoke.js`
- [x] `node scripts/task461-resource-kind-smoke.js`
- [x] 新增或更新 Office smoke,覆盖 `.docx` 主编辑区 tab iframe 与显式 new-window。
- [x] `cargo test -p mnote-web onlyoffice -- --test-threads=1`
## 6. 本轮执行记录
- 已完成:
- `resolveResourceOpen` 已集中输出 `editorKind` / `badgeKind` / `defaultOpenMode` / `editable` / `viewerUrl` / `openTarget`
- 本地 Office 资源默认进入主编辑区 iframe,显式 `new-window` 仍打开 `/onlyoffice`
- `buildOnlyOfficeOpenUrl` 会把相对 `fileUrl` 绝对化,local-folder Office iframe 携带 `http://127.0.0.1:3000/api/local-folder/files/open?...` 这类 Rust 可访问回源 URL。
- 新增 `scripts/task463-onlyoffice-resolver-smoke.js`,已覆盖 `.docx/.pptx/.xlsx` badge、主编辑区 iframe、显式 new-window、OnlyOffice 页面脚本结构。
- 未完成:
- 未验证真实外部 DocumentServer 协同编辑会话,只验证 MNote 侧 URL / iframe / proxy / callback 配置。
- 风险:无
## 7. 非目标
- 不修复外部 DocumentServer 服务部署本身。
- 不实现云端资源写回。
@@ -2,6 +2,8 @@
> 更新时间:2026-04-19
>
> 当前状态:`process-reference`。本文只作为 tiptap / Notion-like 行为模型参考,不再作为当前执行 checklist;默认主编辑器已是页面内 `leptos-tiptap` island。
>
> 这份文档已经按当前进展修正:
>
> 现在的问题不再是“这套模板能不能迁进来”,而是:
@@ -1,50 +0,0 @@
# 5-20 Resource Tab Close Confirm Checklist v1
> 状态:process
>
> 日期:2026-05-20
>
> Owner:主编辑区 resource tab close guard / `web_shell.rs`
## 1. 目标
补齐 P0-2 的可见关闭防护:resource tab 已能检测 `dirty/saving/hasExternalConflict`,但当前只是 `console.warn` 并阻止关闭。需要补上用户可见的最小确认/提示 UI,避免“点击无反应”。
## 2. 允许修改范围
- `rust/crates/mnote-web/src/routes/web_shell.rs`
- `rust/crates/mnote-web/src/ssr/styles.rs`
- `scripts/task460-resource-tab-close-dirty-smoke.js`
- 可新增一个聚焦 smoke
## 3. 禁止事项
- 不引入完整 VSCode working copy service。
- 不改变 resource markdown / text / code 的保存事实源。
- 不实现完整 discard/undo 体系;本轮只做可见提示和明确阻止。
## 4. Checklist
- [x] `dirty` 关闭时显示明确提示,说明资源有未保存更改。
- [x] `saving` 关闭时显示明确提示,说明正在保存。
- [x] `hasExternalConflict` 关闭时显示明确提示,说明存在外部冲突。
- [x] tab 上保留 `data-mnote-resource-close-guard` 与可测试状态。
- [x] 提示 UI 可被后续关闭尝试更新,不堆积多个弹层。
- [x] 新增或更新 smoke 覆盖点击关闭后出现提示,而不是静默无响应。
## 5. 验收
- [x] `node --check scripts/task460-resource-tab-close-dirty-smoke.js`
- [x] `node scripts/task460-resource-tab-close-dirty-smoke.js`
- [x] `cargo test -p mnote-web web_shell -- --test-threads=1`
## 5.1 本轮执行记录
- 已合入:`showResourceTabCloseGuardNotice`,对 `dirty` / `saving` / `hasExternalConflict` 输出可见提示。
- 已验证:`task460` 断言可见提示存在且重复点击不堆叠。
- 保留约束:仍只阻止关闭,不实现 discard/undo。
## 6. 非目标
- 不做真正丢弃更改按钮。
- 不做保存队列或 retry 策略。
@@ -1,49 +0,0 @@
# 5-21 Editor Tab URL / Active Row / Canonical Identity Checklist v1
> 状态:process
>
> 日期:2026-05-20
>
> Owner:主编辑区 tab runtime / sidebar active state / resource identity
## 1. 目标
在 P0 清零后推进相邻 P1:resource tab 激活目前主要改 DOM 状态,不稳定同步 URL、sidebar active row 与 canonical identity。先做轻量收口,让用户在 resource tab / page tab 之间切换时有可测试的 active resource 状态。
## 2. 允许修改范围
- `rust/crates/mnote-web/src/routes/web_shell.rs`
- `rust/crates/mnote-web/src/ssr/pages/layout.rs`
- `scripts/task457-main-editor-resource-tab-smoke.js`
- 可新增聚焦 smoke
## 3. 禁止事项
- 不实现 tab 持久化恢复。
- 不改变 Page Aggregate / local Markdown 事实源。
- 不实现完整 side group。
## 4. Checklist
- [x] resource tab 使用统一 canonical identity,优先 `resource:file:<rootUri>:<path>`
- [x] 激活 resource tab 时写入可测试 URL state 或 history state,不触发整页导航。
- [x] 切回 page tab 时清除 active resource state。
- [x] sidebar file tree 对应 resource row 能显示 active 状态或至少暴露稳定 DOM 标记。
- [x] page tab / resource tab 切换后不会卡死在 resource tab`task457` 继续覆盖。
## 5. 验收
- [x] `node --check scripts/task457-main-editor-resource-tab-smoke.js`
- [x] `node scripts/task457-main-editor-resource-tab-smoke.js`
- [x] `cargo test -p mnote-web web_shell -- --test-threads=1`
## 5.1 本轮执行记录
- 已合入:resource tab 激活时写入 `resourceTab=<canonical identity>` URL state,切回 page tab 或关闭全部资源 tab 时清除。
- 已合入:对应 filetree row 已渲染时只设置 `data-active=true`,不污染 `data-selected` 多选状态。
- 已验证:`task457` 覆盖 resource tab、Office tab、new-window、关闭回退和 URL state 清理。
## 6. 非目标
- 不做跨刷新恢复。
- 不做多 editor group。
@@ -1,52 +0,0 @@
# 5-22 Mindmap Main Editor Tab Kind Checklist v1
> 状态:process
>
> 日期:2026-05-20
>
> Ownermain editor resource tab / mindmap editor kind
## 1. 目标
补齐 P1mindmap 仍以独立 object shell 进入主 pane,而不是 main editor group 内的 editor kind。目标是把 File Tree 中的 mindmap 资源默认打开到主编辑区 tab,并保留显式对象页 / 新窗口兼容入口。
## 2. 允许修改范围
- `rust/crates/mnote-web/src/routes/web_shell.rs`
- `rust/crates/mnote-web/src/ssr/pages/layout.rs`
- `scripts/task169-mindmap-realtime-smoke.js`
- 可新增聚焦 smoke`scripts/task47*-mindmap-resource-tab-smoke.js`
## 3. 禁止事项
- 不把 mindmap 数据保存到 `page.body.save`
- 不删除 `/mindmap/:documentId/:mindmapId` object shell 兼容路由。
- 不破坏已存在的 mindmap realtime / long text edit smoke。
- 不让 mindmap tab 和 page tab 使用同一个 DOM identity。
## 4. Checklist
- [x] resource open resolver 识别 `kind=mindmap` / `objectKind=mindmap`
- [x] 点击 File Tree mindmap 资源默认创建 main editor resource tab,而不是整页跳 `/mindmap/...`
- [x] mindmap tab 使用稳定 canonical identity,如 `resource:mindmap:<documentId>:<mindmapId>` 或 local file canonical identity。
- [x] tab panel 内挂载既有 mindmap runtime,并保持 `data-mnote-object-editor="mindmap"` / 可观测标记。
- [x] 显式新窗口 / object shell 入口仍可用。
- [x] smoke 覆盖从 File Tree 打开 mindmap tab、切回 page、再切回 mindmap。
## 5. 验收
- [x] `cargo test -p mnote-web web_shell -- --test-threads=1`
- [x] `node scripts/task169-mindmap-realtime-smoke.js`
- [ ] 如新增 smoke`node scripts/task47*-mindmap-resource-tab-smoke.js`
## 5.1 本轮执行记录
- 已合入:`kind=mindmap` resource tab,使用 `resource:mindmap:<documentId>:<mindmapId>` identity,并复用现有 `/mindmap/...` bootstrap + runtime mount。
- 已保留:旧 object shell 作为 runtime 不可用或显式路径 fallback。
- 本轮更新:`task169` 已从旧 object shell 口径切到 main editor mindmap resource tab 口径,断言 File Tree 打开 mindmap 后进入 `data-mnote-tab-kind="mindmap"` 的 resource tab,并覆盖切回 page tab 后再切回 mindmap tab。
- 已退役:`task169` 内旧 topic/runtime realtime 深测不再作为必过主链,结果中写入 `retiredRealtimeTopicEditing`,避免旧 object shell 时代的交互假设干扰当前 P1 resource tab 验收。
## 6. 非目标
- 不实现 mindmap 多实例协同编辑模型。
- 不实现 tab 持久化恢复。
@@ -1,50 +0,0 @@
# 5-23 Editor Side Target / Secondary Pane Checklist v1
> 状态:process
>
> 日期:2026-05-20
>
> Ownerdocument pane runtime / side target
## 1. 目标
补齐 P1secondary pane 与 main tab 目前是两套概念。目标是先建立轻量 side target 合同,让 `openTarget=side` 能稳定映射到当前 secondary pane,而不是继续分散在“右侧边栏打开”和 resource tab 特例里。
## 2. 允许修改范围
- `rust/crates/mnote-web/src/routes/web_shell.rs`
- `rust/crates/mnote-web/src/ssr/pages/layout.rs`
- `scripts/task457-main-editor-resource-tab-smoke.js`
- 可新增聚焦 smoke`scripts/task47*-side-target-smoke.js`
## 3. 禁止事项
- 不实现完整多 editor group grid。
- 不改变 primary page / resource tab 的默认 active tab 行为。
- 不把 secondary pane 等同为 active resource tab。
- 不删除现有 `secondaryDocumentId` query 兼容。
## 4. Checklist
- [x] `resolveResourceOpen` / document pane runtime 对外暴露 `openTarget=side` 的稳定返回。
- [x] 页面文档的 side target 继续打开 secondary pane,并更新/清理既有 secondary query。
- [x] 资源类 side target 有明确行为:当前阶段可先显示 unsupported/error placeholder,但必须可观测且不误开新窗口。
- [x] 关闭 secondary pane 不影响 active resource tab URL state。
- [x] smoke 覆盖 active tab 与 secondary pane 可同时存在、互不清状态。
## 5. 验收
- [x] `cargo test -p mnote-web web_shell -- --test-threads=1`
- [x] `node scripts/task457-main-editor-resource-tab-smoke.js`
- [x] 如新增 smoke`node scripts/task47*-side-target-smoke.js`
## 5.1 本轮执行记录
- 已合入:`openResourceAsSideTarget`,资源侧栏目标先显示可观测 unsupported placeholder,不再误开新窗口或污染 active resource tab。
- 已合入:`tree.page.open` + `openTarget=side` 的 document side target wrapper,复用现有 `openSecondaryDocument` / `secondaryDocumentId` 合同。
- 已合入:资源类 `openTarget=side` 使用 secondary pane unsupported placeholder 时清理 `secondaryDocumentId` / `secondarySourceKind` / `secondaryRootUri`,保留 active resource tab 的 `resourceTab` URL state。
- 验证记录:`node --check scripts/task472-side-target-secondary-pane-smoke.js``node --check scripts/task457-main-editor-resource-tab-smoke.js``cargo test -p mnote-web web_shell -- --test-threads=1``node scripts/task472-side-target-secondary-pane-smoke.js``node scripts/task457-main-editor-resource-tab-smoke.js`
## 6. 非目标
- 不实现 split editor layout、drag editor between groups、MRU across groups。
@@ -1,51 +0,0 @@
# 5-24 Open Editors Lightweight View Checklist v1
> 状态:process
>
> 日期:2026-05-21
>
> Ownermain editor tab / sidebar open editors parity
## 1. 目标
参考 Sidex / VSCode `OpenEditorsView`,为 MNote 做轻量版“打开的编辑器”视图或可观测模型。当前阶段不要求完整 UI,但至少要让 page/resource tabs 的 active、title、kind、dirty guard 状态可被统一读取和测试。
## 2. 允许修改范围
- `rust/crates/mnote-web/src/routes/web_shell.rs`
- `rust/crates/mnote-web/src/ssr/styles.rs`
- 可新增 `scripts/task47*-open-editors-*.js`
## 3. 禁止事项
- 不引入完整 VSCode editor group service。
- 不实现多 editor group grid。
- 不改变 Page Aggregate / local Markdown 事实源。
- 不破坏现有 resource tab 打开、关闭和 URL state。
## 4. Checklist
- [x] 暴露轻量 open editors snapshotpage tab + resource tabs。
- [x] snapshot 至少包含 `objectIdentity``title``kind``active``dirtyGuard`
- [x] active tab 切换时 snapshot 更新。
- [x] resource tab close / create 时 snapshot 更新。
- [ ] 如做 UI,保持低干扰,不新增复杂 sidebar 分组。
- [x] 补测试或 smoke,验证可读取 active editor 列表。
## 5. 验收
- `cargo test -p mnote-web web_shell -- --test-threads=1`
- 如新增 smoke`node scripts/task47*-open-editors-*.js`
## 6. 非目标
- 不做 drag editor between groups。
- 不做完整 dirty working copy service。
## 7. 执行记录
- 2026-05-21Codex 补齐 `mnote.open_editors_snapshot.v1` 轻量合同,暴露 `window.__mnoteDocumentPaneRuntime.getOpenEditorsSnapshot()`,并同步 `window.__mnoteOpenEditorsSnapshot` / `data-mnote-open-editors-count` / `data-mnote-active-editor`
- 2026-05-21snapshot 覆盖 page tab 与 resource tabsresource tab 条目包含 `objectIdentity``title``kind``badgeKind``active``dirtyGuard``assetId``path`
- 2026-05-21:未新增 sidebar UI,当前阶段先保持为可观测模型。
- 2026-05-21`cargo test -p mnote-web web_shell -- --test-threads=1` 通过。
- Reasonix Worker A 本轮只停留在探索,未产生可合入 diff;最终由 Codex 本地实现。
@@ -1,74 +0,0 @@
# 5-25 Editor Tab Keyboard and MRU Checklist v1
> 状态:process
>
> 日期:2026-05-21
>
> Ownermain editor tab keyboard / MRU behavior
## 1. 目标
参考 Sidex / VSCode `MultiEditorTabsControl`,补 MNote main tab 的基础键盘和 MRU 行为。当前只做轻量交互,不照搬完整 tab control。
## 2. 允许修改范围
- `rust/crates/mnote-web/src/routes/web_shell.rs`
- `rust/crates/mnote-web/src/ssr/styles.rs`
- 可新增聚焦 smoke
## 3. 禁止事项
- 不新增全局快捷键与浏览器默认冲突。
- 不实现完整 tab overflow 复杂布局。
- 不改变 resource tab identity。
## 4. Checklist
- [x] tab strip 支持左右箭头 roving focus。
- [x] `Enter` / `Space` 激活当前 focused tab。
- [x] 关闭 active resource tab 后回到 MRU 或 page tab,行为稳定。
- [x] tab 上保留可测试 DOM 状态。
- [x] 补测试或 smoke。
## 5. 验收
- `cargo test -p mnote-web web_shell -- --test-threads=1`
- 如新增 smoke`node scripts/task47*-editor-tab-keyboard-*.js`
## 6. 执行记录
> 引用 proposal`.codex/reasonix-proposals/2026-05-21-tab-keyboard-mru-proposal.md`
### 已确认
- `resourceTabMru` 数组 + `touchResourceTabMru` / `lastActiveResourceTabKey` / `removeFromResourceTabMru` 逻辑完整,MRU 核心逻辑无需改动。
- `activateMainEditorTab` 已正确维护 roving tabindexactive → `tabindex="0"`, others → `tabindex="-1"`)。
- `createResourceTabDom` 中 resource tab 初始 `tabindex="-1"` 符合预期。
### 已实现
- [x] 新增 `bindMainEditorTabStrip()` 函数(keydown 事件委托 + collectTabs + 箭头/Home/End/Enter/Space)。
- [x] 修改 `closeResourceTab()` 末尾,关闭后 focus 迁移到新激活的 tab。
- [x]`styles.rs` 补充 `:focus-visible` 样式。
- [x] 更新 Rust contract test 断言。
- [x] `cargo test -p mnote-web web_shell -- --test-threads=1` 通过。
### 后续可选
- [ ] 如后续需要浏览器级回归,可新增 `scripts/task475-editor-tab-keyboard-mru-smoke.js`,覆盖多 tab 键盘切换与关闭后 focus。
### 非预期发现
- `closeResourceTab` 在 activate 后未显式调用 `focus()`,导致 keyboard focus 丢失。需修复。
- tab strip 无 `:focus-visible` 样式,键盘用户在箭头导航后看不到 focus 环。
### Codex 实施记录
- 2026-05-21Reasonix Worker B 按要求只输出 proposal,未直接改源码。
- 2026-05-21Codex 按 proposal 的最小安全集实现键盘 roving focus、Enter/Space 激活、关闭后 MRU/page tab focus 迁移。
- 2026-05-21:未新增全局快捷键;事件只绑定在 `[data-mnote-main-tab-strip]` 内。
## 7. 非目标
- 不做拖拽 tab 排序。
- 不做 close others / close saved 复杂菜单。
@@ -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` 的过渡兼容背景,不应再作为默认产品形态解释。
@@ -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 为准。
>