Files
mnote/design/05-editor-mainline/done/5-33-navigation-page-route-guard-checklist-v1.md
T
lix-2026 1882db7681 收口 MNote P0 P1 P2 审查尾项
- 归档 OnlyOffice live bridge、Page AI、mindmap、design governance 与相关 bug 条目
- 补齐 MinerU OCR 后端 runtime 合同与 smoke/test 基线
- 收口 ChatOnly/Doubao、ObjectIdentity、Page Aggregate compat 与 runtime owner 文档口径

验证:
- cargo test --manifest-path rust/Cargo.toml -p mnote-web local_ocr -- --test-threads=1
- cargo test --manifest-path rust/Cargo.toml -p mnote-web onlyoffice_bridge -- --test-threads=1
- git diff --check
- git diff --cached --check
- codegraph index . --force && codegraph status .
- codegraph sync . && codegraph status .
2026-06-01 09:29:12 +08:00

238 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 5-33 导航页与路由守卫执行清单 v1
> 状态:done
>
> Owner05-editor-mainline / 03-rust-web / control-plane
>
> 创建时间:2026-05-27
>
> 背景:当前本地文件夹入口在没有明确页面时容易回退打开默认页面;未登录、删除页、失效页等路径也缺少统一页面级恢复策略。需要把“导航页作为主页”与“路由守卫”合并设计,避免用户停留在错误页或误打开无关页面。
## 0. 当前落地状态
- [x] Phase A 核心语义已落地:`/` 与 local folder / folder scope 无明确页面时渲染导航页,不再自动打开 recent / first page。
- [x] Phase B recent 闭环已落地:control-plane SQLite recent 表、读写 API、SSR recent 展示、打开 folder/page 写入 recent。
- [x] Phase C 主路径已落地:导航页/星标文件夹进入 scoped 导航页;FileTree 普通文件夹点击只展开;Markdown 普通点击记录 recent 并打开文档页。
- [x] Phase D 验证已落地:补 Rust route/API 测试与 `task500-navigation-page-route-guard-smoke.js` browser smoke,覆盖未登录、删除页回退、recent 分组和导航页首屏无 editor bootstrap。
- [x] 后续增强已拆出:文档页 breadcrumb 文件夹段逐级回到导航页、所有 fetch 404/410 的全局浏览器兜底继续由 `design/05-editor-mainline/process/5-35-navigation-breadcrumb-fetch-guard-followup-v1.md` 跟踪;当前服务端 HTML shell 已覆盖 canonical route guard。
2026-06-01 归档说明:Phase A-D 主路径已有 Rust route/API 测试与 `task500-navigation-page-route-guard-smoke.js` 证据;本文归档到 `done/`,剩余增强不继续阻塞导航页主清单。
## 1. 目标
- 本地文件夹和文件夹 scope 没有明确页面时显示导航页,不再自动打开 recent / first page 作为默认正文。
- 导航页是主编辑区的主页状态;点击 Markdown 页面后,文档页覆盖该主页。
- 点击文件夹后,行为对齐星标文件夹:切到 scoped Explorer,并以该文件夹为导航页上下文。
- 导航页展示最近访问的文件夹和最近访问的页面,两组分开显示,并可直接访问。
- 页面级路由守卫统一处理未登录、无权限、页面不存在、页面已删除等场景。
- 首屏性能不因导航页退化:不构建 Page Aggregate,不启动 tiptap island,不额外扫描完整大目录。
## 2. 非目标
- 不新增第二套树真相;导航页只消费 Rust projection / control-plane recent。
- 不把 recent 写入 Markdown frontmatter 或本地文件夹 `.mnote` 作为长期事实。
- 不把文件夹导航页做成真实 Markdown 页面。
- 不改变 local-first Markdown 正文事实源。
- 不在本阶段实现复杂最近访问排序模型,例如全文索引热度、跨设备文件内容同步或多设备路径重绑定。
## 3. 产品语义
### 3.1 导航页状态
- 工作区导航页:`/` 或默认 local workspace 入口,展示工作区级最近访问、打开本地文件夹入口和当前工作区概览。
- 文件夹导航页:`/?sourceKind=local_folder&rootUri=...&fileTreeScope=docs&treeView=filetree`,展示 `docs/` scope 的子文件夹、Markdown 页面和资源入口。
- 导航页不是错误页;它是没有明确文档目标时的正常主页。
### 3.2 点击行为
- 点击最近文件夹:进入该文件夹 scope,主区域显示文件夹导航页。
- 点击最近页面:打开文档页并覆盖导航页。
- 在导航页或 Explorer 中点击 Markdown:打开文档页并覆盖导航页。
- 在文档页点击 breadcrumb 的文件夹段:回到对应文件夹导航页。
- Ctrl / Meta / 中键点击页面:允许浏览器新标签页打开;普通点击优先走 MNote 当前主编辑区。
### 3.3 失效恢复
- 未登录访问受保护页面:跳转 `/auth?next=<encoded-current-url>`
- 登录后有 `next`:返回原始目标;如果目标已失效,则进入对应导航页。
- Markdown 文件不存在、已删除或 document id 无法解析:回到当前 `rootUri + fileTreeScope` 的导航页,并显示轻量提示。
- 文件夹 scope 不存在或无权限:回到 root 导航页或授权页,不停留在空白错误态。
## 4. 架构判断
- 导航页属于 `05-editor-mainline` 的工作区/文档壳体验,数据入口由 `03-rust-web` 的 route 和 projection 提供。
- Recent 持久化属于 control-plane 用户状态,不属于文件系统事实。
- FileTree / PageTree 内容仍由 Rust projection 生成;前端只负责导航、局部渲染和交互。
- 路由守卫 canonical 逻辑放在 Rust HTML shell route;前端 fetch guard 只做浏览器交互恢复。
## 5. 数据与协议
### 5.1 Recent 记录
- [ ] 在 control-plane 增加用户 recent 记录表或复用已有偏好表能力,字段至少包含:
- `id`
- `user_id`
- `kind`: `folder | page`
- `source_kind`: MVP 先支持 `local_folder`
- `root_uri`
- `relative_path`
- `document_id`
- `title`
- `visited_at`
- `status`
- [ ] Recent folder 使用 `root_uri + relative_path` 定位;root 文件夹 `relative_path=""`
- [ ] Recent page 使用 `root_uri + document_id` 定位,并冗余保存 `relative_path/title` 方便展示。
- [ ] 每个用户每类 recent 保留有限条数,MVP 建议 folder 10 条、page 20 条。
- [ ] list recent 时做授权过滤;无权限、root 不可用、路径不存在的记录不作为可点击主项。
- [ ] localStorage 里已有 recent local roots 只作为迁移/兜底读取,不作为长期 canonical。
### 5.2 API
- [ ] 新增或扩展读取 API:返回当前用户 recent folders / recent pages。
- [ ] 新增写入 API:打开 folder scope 或 Markdown page 后记录 recent。
- [ ] API 写入只记录用户行为,不触碰 Markdown 或 `.mnote` 文件。
- [ ] API 返回项包含可直接用于导航的 URL 参数:`sourceKind/rootUri/fileTreeScope/documentId/workspaceId`
## 6. Rust HTML Shell
### 6.1 导航页渲染
- [ ] 新增导航页 view model,区分 `workspace``folder` 两种 scope。
- [ ] 导航页首屏只加载 shallow file projection 和 recent 列表。
- [ ] 导航页不调用 `build_page_aggregate_snapshot`
- [ ] 导航页不注入 `__MNOTE_PAGE_AGGREGATE__` / `__MNOTE_EDITOR_BOOTSTRAP__`,除非后续明确需要编辑器。
- [ ] 导航页继续使用 `PageLayout` 和现有 workspace sidebar。
- [ ] 文件夹导航页主区域展示:
- 当前文件夹标题和路径面包屑
- 子文件夹列表
- 当前层 Markdown 页面列表
- 当前层资源列表
- 最近访问页面和最近访问文件夹
### 6.2 `/` 入口选择规则
- [ ] `/` 未登录继续跳 `/auth?next=/`
- [ ] `/``pageId` 且无 `fileTreeScope`:显示工作区导航页。
- [ ] `/``sourceKind=local_folder&rootUri=...` 但无 `pageId`:显示对应 root 导航页。
- [ ] `/``fileTreeScope`:显示该 scope 文件夹导航页。
- [ ] `/``pageId`:尝试打开文档页;失败时回退导航页。
- [ ] 移除或限制 `recent_page_id -> first_page` 的自动正文打开行为,避免文件夹入口误开默认页面。
### 6.3 `/documents/{document_id}` 守卫
- [ ] 未登录访问 `/documents/{document_id}``303 /auth?next=<current-url>`
- [ ] local_folder 缺少 `rootUri`:跳 root/workspace 导航页,并保留错误提示参数。
- [ ] `local_markdown_not_found`:跳 `/?sourceKind=local_folder&rootUri=...&fileTreeScope=<parent>&missingPage=<document_id>`
- [ ] `local_workspace_access_denied`:跳授权说明页或 root 导航页提示无权限。
- [ ] 其它不可恢复错误保留错误页,但必须带 trace id。
## 7. Browser Runtime
- [ ] 抽出 `openNavigationPageForFolder(rootUri, relativePath, workspaceId)` helper,供最近文件夹、星标文件夹、breadcrumb 文件夹段复用。
- [ ] 点击文件夹时不只展开侧栏;如果是导航动作,应更新 URL `fileTreeScope` 并渲染文件夹导航页。
- [ ] 点击 Markdown 时记录 recent page,然后打开文档页覆盖导航页。
- [ ] 点击导航页或星标 folder scope 时记录 recent folder,然后显示导航页;FileTree 普通文件夹点击只展开子项。
- [ ] fetch 返回 `401` 且当前是浏览器交互:跳 `/auth?next=<current-url>`
- [ ] fetch 返回 `404/410` 且目标是当前页面:跳当前文件夹导航页。
- [ ] 保留普通业务错误 toast,不把所有 API 错误都变成导航跳转。
## 8. 性能约束
- [ ] 导航页首屏不得扫描完整 workspace root 下所有 Markdown。
- [ ] 文件夹导航页使用 scoped shallow FileTree projection。
- [ ] PageTree scope 可继续给 sidebar 使用,但主导航页首屏不依赖完整 PageTree。
- [ ] Recent list 查询必须限制条数。
- [ ] 大目录 smoke 要验证打开 `design/` scope 不显示 root 兄弟目录,不触发完整 root 渲染。
- [ ] 导航页首屏不启动 tiptap island,避免无文档目标时加载编辑器 bundle。
- [ ] 若需要最近页面标题刷新,只在列表展示时对有限条记录做存在性校验,不批量读取正文。
## 9. 验收标准
- [ ] 登录后访问 `/` 显示导航页,而不是自动打开某个默认页面。
- [ ] 打开本地文件夹 root 后显示该文件夹导航页。
- [ ] 点击文件夹进入 scoped 导航页,Sidebar Explorer 与主区域 scope 一致。
- [ ] 点击 Markdown 后打开文档页并覆盖导航页。
- [ ] 最近文件夹和最近页面分组展示;点击最近文件夹进入导航页,点击最近页面打开文档。
- [ ] 删除当前 Markdown 后刷新旧 `/documents/...`,进入父文件夹导航页并显示提示。
- [ ] 未登录访问 `/documents/...``/auth?next=...`,登录后能回到目标或导航页兜底。
- [ ] 无权限访问本地 folder 不显示空白文档页。
- [ ] 导航页没有注入页面 aggregate 和 editor bootstrap。
- [ ] 大目录 scope 打开不退化为完整 root 扫描。
## 10. 测试计划
### 10.1 Rust 单元 / route 测试
- [ ] `root_entry_without_page_renders_navigation_home`
- [ ] `root_entry_local_folder_scope_renders_folder_navigation`
- [ ] `root_entry_does_not_auto_open_recent_page_for_folder_scope`
- [ ] `document_shell_unauthenticated_redirects_to_auth_with_next`
- [ ] `document_shell_missing_local_markdown_redirects_to_folder_navigation`
- [ ] `recent_navigation_records_are_user_scoped`
- [ ] `recent_navigation_list_filters_inaccessible_local_roots`
### 10.2 Browser smoke
- [ ] 新增 `task5xx-navigation-home-local-folder-smoke.js`
- 登录测试账号
- 打开本地 folder root
- 断言主区域是导航页
- 断言未打开默认 Markdown
- [ ] 新增 `task5xx-navigation-folder-scope-smoke.js`
- 点击文件夹
- 断言 URL 含 `fileTreeScope`
- 断言主区域标题和 Explorer scope 一致
- [ ] 新增 `task5xx-navigation-recent-smoke.js`
- 打开两个文件夹和一个 Markdown
- 回到导航页
- 断言 recent folders / recent pages 分组可见并可点击
- [ ] 新增 `task5xx-route-guard-auth-and-deleted-page-smoke.js`
- 未登录访问文档 URL 跳 auth
- 登录后打开文档
- 删除对应 `.md`
- 刷新旧文档 URL 后回到文件夹导航页
### 10.3 回归测试
- [ ] `cargo test -p mnote-web root_entry -- --nocapture`
- [ ] `cargo test -p mnote-web local_folder -- --nocapture`
- [ ] `node scripts/task492-sidebar-starred-shortcuts-smoke.js`
- [ ] `node scripts/task497-local-page-tree-filetree-open-performance-smoke.js`
- [ ] `git diff --check`
- [ ] 修改代码后运行 `codegraph sync .`,提交前确认 CodeGraph 无 pending。
## 11. 分阶段执行建议
### Phase A:守住语义
- [ ] 新增导航页 SSR 壳和 route 测试。
- [ ]`/` 入口选择规则,不再在 folder scope 下自动打开默认页面。
- [ ] `/documents/{document_id}` 增加未登录和 missing local markdown 路由恢复。
### Phase BRecent 闭环
- [ ] control-plane 增加 recent 记录。
- [ ] 导航页 SSR 展示 recent folders/pages。
- [ ] 打开 folder/page 时写入 recent。
### Phase C:浏览器交互对齐
- [ ] 文件夹点击进入导航页 scope。
- [ ] Markdown 点击覆盖导航页。
- [ ] breadcrumb 文件夹段返回导航页。
- [ ] fetch guard 处理 `401/404/410`
### Phase D:性能与 smoke
- [ ] 确认导航页首屏不构建 editor / aggregate。
- [ ] 大目录 scoped smoke 验证不加载完整 root。
- [ ] 删除页、未登录、recent 点击 smoke 通过。
## 12. 风险与边界
- Recent 写入如果只做前端 localStorage,会和登录用户、授权过滤、SSR 首屏冲突;MVP 应优先 SQLite。
- 文件夹导航页如果复用完整 PageTree 递归扫描,可能在大目录退化;首屏应以 shallow FileTree projection 为主。
- `mnote_recent_page_id` 旧 cookie 不能继续作为文件夹入口自动打开正文的依据;最多用于工作区级“继续编辑”入口展示。
- 路由守卫不能把所有错误都吞成导航页;不可恢复错误仍要保留 trace,方便排障。
- Ctrl / Meta / 中键打开浏览器新标签页不能破坏普通点击覆盖主页的主路径。