Files
mnote/design/04-tree-domain/done/4-28-trash-restore-location-reveal-focus-v1.md
T
lix-2026 47e224d419 chore: align sqlite control plane architecture
- replace default Convex control-plane wording with Rust SQLite control-plane across architecture, AGENTS, Reasonix, and design docs

- retire root Convex functions source and deploy script into recycle while keeping explicit cloud/compat/sync-replica boundaries

- add control-plane migration guard/docs and keep CodeGraph refreshed after the SQLite control-plane cutover
2026-05-22 17:45:22 +08:00

109 lines
7.3 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.
# 4-28 Trash Restore Location Reveal Focus v1
> 状态:done
> 创建时间:2026-05-15
> 所属主线:04-tree-domain / VSCode Explorer 文件树与垃圾箱闭环
> 来源:`design/10-review/07-vscode-explorer-filetree-trash-gap-review.md` 阶段 6
## 1. 目标
页面进入垃圾箱前必须记录可恢复的位置快照;恢复时优先回到删除前父节点与排序位置,并在 UI 中 reveal / focus 恢复对象。若原父节点已不存在或仍在垃圾箱,应显式 fallback 到根目录并提示用户。
## 2. 已落地范围
- `documents` schema 新增 `restore_parent_id``restore_sort_order`
- `documents.softDelete` 对页面子树写入删除前 `parent_id / sort_order` 快照。
- `documents.restore`
- 读取同 workspace 当前文档集合。
- 父节点仍存在或属于本次级联恢复子树时,恢复到原父节点。
- 父节点已不存在或仍在垃圾箱时,恢复到根目录。
- 恢复时回写 `parent_id / sort_order`,并对目标父级兄弟节点重新编号,避免恢复到原排序位时产生重复 `sort_order`
- 清空恢复位置快照。
- 返回 `restore_location.parent_id / sort_order / fallback_reason`
- `/api/tree/commands restore` 读取页面 meta 时允许包含已删除页面,避免垃圾箱页面在进入 restore mutation 前被 `getMeta` 过滤成 404。
- `restoreDocumentCommand` 透传恢复位置结果。
- Sidebar 垃圾箱恢复后刷新数据、展开恢复父链、滚动并 focus 恢复行;发生 fallback 时提示“原父页面已不存在,已恢复到根目录”。
- local folder 非 md 资源 restore 通过 `mnote.pendingLocalFolderRestoreFiletreeRowId` 记录恢复目标,filetree 在加载与轮询刷新时消费该 pending row-id 并聚焦恢复行。
## 3. 设计口径
- 当前记录的是稳定数据位置:`parent_id / sort_order`
`view path / expanded path` 不持久化到 Convex,恢复后由 Sidebar 根据当前父链展开得到。
- 子树恢复只恢复与父节点同一 `deleted_at` 批次的子节点,避免把更早独立删除的子页面误恢复。
- fallback 策略先固定为“恢复到根目录 + 明示提示”,不在本阶段引入位置选择弹窗。
- `sort_order` 会按当前兄弟数量 clamp;如果原位置越界,则放到可解释的最近位置。
## 4. 已验证
```bash
cd /mnt/Data1T/mnote/wolai-frontend
pnpm test src/lib/documents/tree-command-client.test.ts src/lib/documents/restore-location.test.ts src/components/sidebar/tree-shell-host.test.tsx src/components/sidebar/sidebar-sync.test.ts
```
结果:4 个测试文件通过,20 项测试通过。
补充 route 与排序回归:
```bash
cd /mnt/Data1T/mnote/wolai-frontend
pnpm test src/lib/documents/tree-command-client.test.ts src/lib/documents/restore-location.test.ts src/components/sidebar/tree-shell-host.test.tsx src/components/sidebar/sidebar-sync.test.ts src/app/api/tree/commands/route.test.ts
```
结果:5 个测试文件通过,32 项测试通过。
真实浏览器 smoke
```bash
cd /mnt/Data1T/mnote
node scripts/task429-trash-restore-location-reveal-smoke.js
```
结果:通过。脚本使用一次性用户 / workspace,在 3000 主入口验证;历史记录中曾需手工注册 Convex functions,当前默认 auth/session/control-plane 已切到 SQLite,不再要求先启动 Convex
- 删除子页面后通过 `tree.node.restore` 恢复,返回 `restore_location.parent_id` 为原父页面、`sort_order` 为原排序位。
- 原排序位已被兄弟页面占用时,兄弟页面被后移,Convex 中不产生重复 `sort_order`
- 当前浏览器页面不刷新,恢复后的页面行重新出现在侧边栏树中。
- 构造 `restore_parent_id` 指向缺失父节点的历史数据,恢复后返回 `fallback_reason: parent_missing_or_deleted`,并落到根目录。
局部 TypeScript 筛查:
```bash
cd /mnt/Data1T/mnote/wolai-frontend
pnpm exec tsc --noEmit --pretty false 2>&1 | rg "convex/documents.ts\\((160|161|162|163|164|165|166|167|168|169|170|171|172|173|174|175)"
```
结果:阶段 6 修改附近无新增 TypeScript 报错。全量 `tsc` 仍受仓库既有错误影响,未作为本阶段完成条件。
## 5. 2026-05-21 local folder restore focus 收口
### 5.1 Convex 页面(已有部分证据)
- React Sidebar Drawer 的恢复后 DOM focus 仍需在 React 入口可稳定渲染页面树时补浏览器证据;当前 3000 主壳 smoke(`task429`)已覆盖"恢复后无需刷新可见 / reveal"fallback 提示仍由 React Drawer 代码路径负责。
- 双浏览器 no-refresh:A 恢复页面后,B 不刷新即可看到位置变化;该项归入阶段 8 的实时刷新矩阵。
- 已知源码问题:`selectSidebarFileTreeDocument` 存在且工作(create / navigate 路径调用),restore 路径仍未直接调用,但 local folder 已通过 pending row-id 消费实现恢复行 focus。
### 5.2 Local folder 非 md 资源 restore focus(已完成,Worker C 2026-05-21/22 复核)
当前状态:**已完成**。实现通过 pending row-id 传递与 filetree 轮询消费完成,审计证据及源码对照如下:
| 检查项 | 状态 | 证据来源 |
|--------|------|----------|
| `selectSidebarFileTreeDocument` 是否从 restore 路径直接调用 | ❌ 否 | 采用 `pendingLocalFolderRestoreFiletreeRowId` + filetree 轮询消费,而不是 restore handler 直调 |
| restore 后 filetree 行是否有 `data-active` / `data-selected` | ✅ 有 | `task473` 通过结果:`data-active="true"` |
| restore 后是否 `scrollIntoView``focus()` | ✅ 有 | `applyPendingLocalFolderRestoreFocusOnce` 选中并滚动恢复行 |
| trash workbench 的 restore 后 filetree 同步机制 | 轮询 1200ms | `layout.rs:1991-2004` (`startLocalFolderSidebarWatch`) |
| trash workbench 的 restore 后是否调用 `refreshLocalFolderSidebarSnapshot` | ❌ 否,只调 `refresh()`(自身 workbench | `gateway.rs:1064-1065`,但 filetree 轮询可消费 pending focus |
| `selectSidebarFileTreeDocument` 是否支持 `local-file:` row-id | ❌ 否 | 目前 focus 由 `selectSidebarFileTreeRowById``local:asset:` row-id 完成 |
### 5.3 已采用方案(供后续复核)
1. **短路径**trash workbench inline script 的 restore `.then()` 中写入 `mnote.pendingLocalFolderRestoreFiletreeRowId`
2. **filetree 消费**`refreshLocalFolderSidebarSnapshot()` 与初始 render 共享 `schedulePendingLocalFolderRestoreFocus()`,在 filetree 加载/轮询后聚焦恢复行。
3. **smoke 回归**`task473` 已从已知缺口回归改为可检测正向 smoke,最终应观察到 `data-active="true"` 或其他 focus 证据出现。
### 5.4 2026-05-21/22 浏览器复核
- Codex 独立复跑 `node --check scripts/task473-local-folder-trash-restore-no-refresh-focus-gap-smoke.js``MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3012 node scripts/task473-local-folder-trash-restore-no-refresh-focus-gap-smoke.js``ok:true``restoreNavFree.delta=0``fileReappearsInFiletree=true``focusAfterRestore.attrs.data-active="true"`
- Reasonix Browser Worker 输出目录:`tmp/reasonix-batch-a-browser-task473-2026-05-21/`,初始结论为 `PASS_WITH_KNOWN_GAP`;本轮修复后,Codex 本地 smoke 已验证 gap 关闭。
- 复核结论:local folder 非 md 资源 restore 后 no-refresh、行恢复与 focus/reveal 均已完成,可归档到 `done/`