- 统一 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
263 lines
11 KiB
Markdown
263 lines
11 KiB
Markdown
# 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 文件夹,也不利于页面导出。
|
||
|
||
本轮收口方向:
|
||
|
||
- 默认工作区只保留用户内容根目录和 `.mnote/` 系统目录。
|
||
- 页面树允许把 `页面.md + 页面/` 解释成一个页面节点与其子页面容器。
|
||
- 文件树必须真实展示文件系统,不合并、不隐藏同名 `.md` 和目录。
|
||
- 新建页面使用 `新页面HHMMSS` 命名,避免重复创建同名 `新页面.md`。
|
||
- 新建页面时同时创建 `新页面HHMMSS.md` 和 `新页面HHMMSS/`。
|
||
- 附件、思维导图等页面资源默认进入同名目录下,而不是顶层 `assets/` / `mindmaps/`。
|
||
|
||
## 参考代码位置
|
||
|
||
- 当前默认工作区初始化:`rust/crates/mnote-web/src/routes/local_folder_source.rs`
|
||
- `create_default_local_workspace_for_actor_at_base`
|
||
- `ensure_default_workspace_directories`
|
||
- `ensure_default_workspace_home_page`
|
||
- 当前本地页面创建:`rust/crates/mnote-web/src/routes/local_folder_source.rs`
|
||
- `execute_local_tree_command`
|
||
- `create_local_markdown_page`
|
||
- 当前本地资源上传:`rust/crates/mnote-web/src/routes/local_folder_source.rs`
|
||
- `write_local_markdown_asset`
|
||
- 当前文件树真实扫描:`rust/crates/mnote-web/src/routes/local_folder_source.rs`
|
||
- `load_local_folder_file_tree_snapshot`
|
||
- `scan_directory`
|
||
- 当前页面树扫描:`rust/crates/mnote-web/src/routes/local_folder_source.rs`
|
||
- `load_local_folder_page_tree_snapshot`
|
||
- `scan_markdown_page_tree`
|
||
- 当前根入口默认初始化测试:`rust/crates/mnote-web/src/routes/gateway.rs`
|
||
- `root_entry_initializes_default_local_workspace_page`
|
||
- 当前上传 smoke:`scripts/task443-local-markdown-asset-upload-smoke.js`
|
||
|
||
## 目标目录契约
|
||
|
||
### 默认初始化
|
||
|
||
默认工作区初始化后应该是:
|
||
|
||
```text
|
||
my-space/
|
||
.mnote/
|
||
workspace.json
|
||
trash/
|
||
初始化的新页面.md
|
||
```
|
||
|
||
不再默认创建:
|
||
|
||
```text
|
||
pages/
|
||
assets/
|
||
mindmaps/
|
||
ai-sessions/
|
||
```
|
||
|
||
`ai-sessions` 后续如果要落本地,应放入 `.mnote/ai-sessions/`,本轮只禁止默认创建顶层 `ai-sessions/`。
|
||
|
||
### 新建页面
|
||
|
||
在父目录下新建页面时,默认标题若为 `新页面`,真实落盘应为:
|
||
|
||
```text
|
||
新页面HHMMSS.md
|
||
新页面HHMMSS/
|
||
```
|
||
|
||
例如:
|
||
|
||
```text
|
||
新页面061240.md
|
||
新页面061240/
|
||
```
|
||
|
||
要求:
|
||
|
||
- `HHMMSS` 使用本地时间或现有时间工具生成,精确到秒。
|
||
- 即使有时间戳,也必须做磁盘冲突检查;同秒冲突时使用 `-2`、`-3` 追加。
|
||
- `documentId` 仍使用路径型 `local-md:<relative_path>`。
|
||
- `.md` 正文标题应使用最终安全标题,例如 `# 新页面061240`。
|
||
|
||
### 页面树和文件树边界
|
||
|
||
- 页面树:允许把 `X.md` 与同目录 `X/` 解释为一个页面 `X`,并把 `X/` 内 Markdown 作为页面 `X` 的子页面。
|
||
- 文件树:必须真实展示 `X.md` 和 `X/` 两个磁盘条目。不得把二者合并为一个节点。
|
||
|
||
### 页面资源
|
||
|
||
对 `X.md` 上传附件或图片时,默认写入:
|
||
|
||
```text
|
||
X/assets/<file>
|
||
```
|
||
|
||
对 mindmap,后续默认写入:
|
||
|
||
```text
|
||
X/mindmaps/<file>
|
||
```
|
||
|
||
本轮先完成 `write_local_markdown_asset` 的附件/图片路径迁移。正文链接相对 `X.md` 应为:
|
||
|
||
```markdown
|
||

|
||
[spec.pdf](X/assets/spec.pdf)
|
||
```
|
||
|
||
## 顺序执行 Checklist
|
||
|
||
### A0 基线保护
|
||
|
||
- [x] 记录当前 `git status --short`,不要清理与本任务无关的脏改动。
|
||
- [x] 运行当前相关定向测试,确认改前基线:
|
||
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web create_default_local_workspace_uses_managed_data_root -- --nocapture`
|
||
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web local_markdown_asset_upload_copies_next_to_markdown_with_relative_path -- --nocapture`
|
||
|
||
验收:
|
||
|
||
- [x] 基线通过。两项测试均 ok。
|
||
|
||
### A1 默认工作区目录最小化
|
||
|
||
- [x] 修改 `ensure_default_workspace_directories`:默认只创建 `.mnote/trash`。
|
||
- [x] 修改 `ensure_default_workspace_home_page`:初始化页改为 `root/初始化的新页面.md`,不再放入 `pages/`。
|
||
- [x] 更新 `create_default_local_workspace_uses_managed_data_root`:
|
||
- 断言 `初始化的新页面.md` 位于 root 下。
|
||
- 断言 `pages/`、`assets/`、`mindmaps/`、顶层 `ai-sessions/` 不存在。
|
||
- 保留 `.mnote/workspace.json` 与 `.mnote/trash` 断言。
|
||
- [x] 更新 `root_entry_initializes_default_local_workspace_page`:
|
||
- 断言 root 下存在 `初始化的新页面.md`。
|
||
- 不再断言 `pages/初始化的新页面.md`。
|
||
|
||
验收:
|
||
|
||
- [x] `cargo test --manifest-path rust/Cargo.toml -p mnote-web create_default_local_workspace_uses_managed_data_root -- --nocapture` — ok
|
||
- [x] `cargo test --manifest-path rust/Cargo.toml -p mnote-web root_entry_initializes_default_local_workspace_page -- --nocapture` — ok
|
||
|
||
### A2 新建页面创建 `.md + 同名目录`
|
||
|
||
- [x] 新增 helper `determine_page_stem`:
|
||
- 输入默认标题 `新页面` 输出 `新页面HHMMSS`(本地时间 HHMMSS,失败时回退 UTC)。
|
||
- 对非默认标题,仍使用用户标题的安全文件名。
|
||
- 对冲突同时检查 `<name>.md` 与 `<name>/`,追加 `-2`、`-3`。
|
||
- [x] 修改 `create_local_markdown_page`:
|
||
- 创建 `<name>.md`。
|
||
- 同步创建 `<name>/`。
|
||
- 返回的 `relativePath` 仍是 `<name>.md`。
|
||
- 返回的 `documentId` 仍是 `local-md:<relative_path>`。
|
||
- [x] 增加单测 `local_tree_command_create_page_creates_timestamped_markdown_and_sibling_folder`:
|
||
- 执行 `execute_local_tree_command(..., "create", ..., Some("新页面"))`。
|
||
- 断言产生 `新页面\d{6}.md` 和同名目录。
|
||
- 断言返回 `documentId` 指向 `.md`。
|
||
|
||
验收:
|
||
|
||
- [x] `cargo test --manifest-path rust/Cargo.toml -p mnote-web local_tree_command_create_page_creates_timestamped_markdown_and_sibling_folder -- --nocapture` — ok
|
||
|
||
### A3 页面树使用同名目录作为页面子容器
|
||
|
||
- [x] 修改 `scan_markdown_page_tree`:
|
||
- 如果同目录存在 `X.md` 和 `X/`,页面树只显示 `X.md` 对应的页面节点。
|
||
- `X/` 内的 Markdown 节点挂在 `X.md` 页面节点下。
|
||
- 没有同名 `.md` 的普通目录,如果包含 Markdown,继续作为 page group 显示。
|
||
- [x] 增加单测:
|
||
- 构造 `Root.md`、`Root/Child.md`。
|
||
- 页面树断言 `Child.md` 的 `parent_node_id` 是 `Root.md` 的 `documentId`。
|
||
- 页面树断言没有单独的 `local:page-group:Root` 行。
|
||
|
||
验收:
|
||
|
||
- [x] `cargo test --manifest-path rust/Cargo.toml -p mnote-web local_page_tree_uses_sibling_directory_as_page_children_container -- --nocapture` — ok
|
||
|
||
### A4 文件树保持真实
|
||
|
||
- [x] 确认 `scan_directory` 不做任何同名 `.md + folder` 合并。
|
||
- [x] 增加单测:
|
||
- 构造 `Root.md`、`Root/Child.md`。
|
||
- 文件树断言同时存在 `Root.md` markdown 行和 `Root` folder 行。
|
||
|
||
验收:
|
||
|
||
- [x] `cargo test --manifest-path rust/Cargo.toml -p mnote-web local_file_tree_keeps_markdown_and_sibling_folder_as_separate_rows -- --nocapture` — ok
|
||
|
||
### A5 附件上传进入页面同名目录
|
||
|
||
- [x] 修改 `write_local_markdown_asset`:
|
||
- `README.md` 的附件目录从 `README.assets/` 改为 `README/assets/`。
|
||
- 返回的 `sourcePath` / `file_url` 相对 Markdown 文件目录,例如 `README/assets/photo.png`。
|
||
- 保留重复文件名 `photo-1.png` 逻辑。
|
||
- [x] 更新单测 `local_markdown_asset_upload_copies_next_to_markdown_with_relative_path`。
|
||
- [x] 更新 `scripts/task443-local-markdown-asset-upload-smoke.js` 中对 `README.assets/...` 的断言为 `README/assets/...`。
|
||
- [x] 只改本地 local_folder 上传路径,不改 cloud / Convex media 路径。
|
||
|
||
验收:
|
||
|
||
- [x] `cargo test --manifest-path rust/Cargo.toml -p mnote-web local_markdown_asset_upload_copies_next_to_markdown_with_relative_path -- --nocapture` — ok
|
||
- [x] `node scripts/task443-local-markdown-asset-upload-smoke.js` — ok,证据:`tmp/task443-local-markdown-asset-upload-smoke/result.json`
|
||
|
||
### A6 重命名 bundle 规则最小收口
|
||
|
||
- [x] 检查 `rename_local_entry`:
|
||
- 当重命名 `X.md` 且同目录存在 `X/` 时,同步重命名目录为新标题目录。
|
||
- 如果目标目录已存在,使用与文件一致的冲突策略或返回可解释错误,禁止覆盖。
|
||
- [x] 增加单测:
|
||
- `Old.md + Old/Child.md` 重命名为 `New`。
|
||
- 断言 `New.md` 和 `New/Child.md` 存在。
|
||
- 页面树中 `Child.md` 仍挂在 `New.md` 下。
|
||
|
||
验收:
|
||
|
||
- [x] `cargo test --manifest-path rust/Cargo.toml -p mnote-web local_rename_markdown_page_renames_sibling_page_folder -- --nocapture` — ok
|
||
|
||
### A7 文档同步
|
||
|
||
- [x] 更新本 checklist 状态。
|
||
- [x] 本 checklist 已明确覆盖旧的默认顶层 `pages/assets/mindmaps/ai-sessions` 初始化结构;未大范围改旧 done 文档。
|
||
|
||
验收:
|
||
|
||
- [x] checklist 中已记录实际完成情况。
|
||
|
||
### A8 最终验证
|
||
|
||
- [x] `cargo fmt --check --all --manifest-path rust/Cargo.toml` — ok
|
||
- [x] `cargo check --manifest-path rust/Cargo.toml -p mnote-web` — ok
|
||
- [x] `cargo test --manifest-path rust/Cargo.toml -p mnote-web create_default_local_workspace_uses_managed_data_root -- --nocapture` — ok
|
||
- [x] `cargo test --manifest-path rust/Cargo.toml -p mnote-web local_tree_command_create_page_creates_timestamped_markdown_and_sibling_folder -- --nocapture` — ok
|
||
- [x] `cargo test --manifest-path rust/Cargo.toml -p mnote-web local_page_tree_uses_sibling_directory_as_page_children_container -- --nocapture` — ok
|
||
- [x] `cargo test --manifest-path rust/Cargo.toml -p mnote-web local_file_tree_keeps_markdown_and_sibling_folder_as_separate_rows -- --nocapture` — ok
|
||
- [x] `cargo test --manifest-path rust/Cargo.toml -p mnote-web local_markdown_asset_upload_copies_next_to_markdown_with_relative_path -- --nocapture` — ok
|
||
- [x] `cargo test --manifest-path rust/Cargo.toml -p mnote-web local_rename_markdown_page_renames_sibling_page_folder -- --nocapture` — ok
|
||
- [x] `node scripts/task443-local-markdown-asset-upload-smoke.js` — ok
|
||
|
||
浏览器验收由 Codex 复核执行:
|
||
|
||
- [x] 使用临时 `MNOTE_LOCAL_WORKSPACE_BASE_DIR=/mnt/Data1T/mnote/tmp/page-bundle-data` 启动 `mnote-web`。
|
||
- [x] 登录/注入测试 actor 后打开首页。
|
||
- [x] 截图保存到 `tmp/`:
|
||
- `tmp/page-bundle-home-3003.png`
|
||
- `tmp/page-bundle-after-create-3003.png`
|
||
- `tmp/page-bundle-filetree-3003.png`
|
||
- [x] 检查磁盘:
|
||
- root 下有 `初始化的新页面.md`。
|
||
- root 下没有默认 `pages/`、`assets/`、`mindmaps/`、顶层 `ai-sessions/`。
|
||
- 新建页面后有 `新页面HHMMSS.md` 和 `新页面HHMMSS/`。
|
||
- 文件树能看到 `.md` 与同名目录两个真实条目。
|
||
- 页面树把同名目录内子页面挂到 `.md` 页面下。
|
||
|
||
## 非目标
|
||
|
||
- 不迁移用户历史数据。
|
||
- 不删除正式 `/mnt/Data1T/Mnote_data/users` 下旧测试目录。
|
||
- 不实现完整 mindmap 新路径落盘;本轮只定义默认目标并完成附件/图片上传路径迁移。
|
||
- 不把文件树改成页面语义树。
|
||
- 不重新引入 `mnote_id` frontmatter。
|