16 KiB
16 KiB
4-34 Filetree Trash Dual Browser No Refresh v1
状态:done 创建时间:2026-05-15 所属主线:04-tree-domain / VSCode Explorer 文件树与垃圾箱闭环 来源:
design/10-review/07-vscode-explorer-filetree-trash-gap-review.md阶段 8
1. 目标
补齐文件树与垃圾箱的双浏览器 no-refresh 回归:A 浏览器执行页面、附件、mindmap、table 的 create / archive / restore / purge / empty-trash,B 浏览器不刷新即可在 File Tree 与 Trash 工作台看到变化。
阶段 8 不以“单浏览器本地 optimistic update 成功”作为完成标准;refreshTree() 只能作为 A 浏览器本地兜底,不能作为 B 浏览器同步证据。
2. 验收矩阵
| 对象 | create | archive | restore | purge | empty-trash | 证据要求 |
|---|---|---|---|---|---|---|
| 页面 document | A 新建后 B File Tree 可见 | A 删除后 B File Tree 消失、Trash 可见 | A 恢复后 B File Tree 可见、Trash 消失 | A 彻底删除后 B Trash 消失 | A 清空页面垃圾箱后 B Trash 清空 | 记录 tree delta/resync 或 Convex live 来源 |
| 普通附件 file asset | A 创建/上传或 seed 后 B File Tree 可见 | A 删除后 B File Tree 消失、Trash 可见 | A 恢复后 B File Tree 可见、Trash 消失 | A 彻底删除后 B Trash 消失 | A 清空资源垃圾箱后 B Trash 清空 | 记录 tree.resource.* / compat alias 与实时来源 |
| mindmap | A 新建 mindmap 后 B File Tree 可见 | A 删除后 B File Tree 消失、Trash 可见 | A 恢复后 B File Tree 可见、Trash 消失 | A 彻底删除后 B Trash 消失 | A 清空资源垃圾箱后 B Trash 清空 | 记录 mindmap route 仍为 compat alias |
| table | A 新建/seed table 后 B File Tree 可见 | A 删除后 B File Tree 消失、Trash 可见 | A 恢复后 B File Tree 可见、Trash 消失 | A 彻底删除后 B Trash 消失 | A 清空资源垃圾箱后 B Trash 清空 | 记录 table route 仍为 compat alias |
| local folder Markdown | 外部创建 .md 后 page/file tree 可见 |
✅ task475 API archive(delete 后进入 .mnote/trash,trash-index 记录 local-md:*) |
✅ task475 API restore(.md 回原路径,trash 文件消失,index 清理) |
✅ task475 API purge(源路径与 trash 路径均不存在,index 清理) | ✅ task474 UI empty-trash(清空页面垃圾箱,confirm dialog → refresh(),无导航) | task475 覆盖 loose Markdown API trash lifecycle;task474 覆盖 empty-trash UI;bundled Markdown 由 Rust 单测覆盖 |
| local folder 非 md 资源 | ✅ task435 外部 create/rename/delete 无 reload | ✅ task437 API archive + task464 UI trash | ✅ task437 API restore + task473 UI restore no-refresh + focus/reveal | ✅ task437 API purge + task464 UI purge | ✅ task474 UI empty-trash(清空资源垃圾箱,confirm dialog → refresh(),无导航) | task435/437/464/473 覆盖;restore focus 缺口已关闭,见 task473;task474 覆盖 empty-trash UI |
3. 分批执行
3.1 task432:页面生命周期双浏览器
先覆盖页面 document,因为它是 tree.node.* 主链:
- 使用测试账号或一次性用户登录两个 browser context。
- 使用隔离 workspace / 本轮测试前缀创建父页面与子页面。
- B 打开 3000 主入口文档页与
/trash?workspaceId=...两个 tab,安装 tree event recorder。 - A 执行 create / archive / restore / purge / empty-trash。
- B 每一步不刷新页面,等待 File Tree / Trash DOM 更新,并记录是
tree:delta、tree:resync、EventSource snapshot 还是 Convex live 更新。
通过后才勾选阶段 8 的页面子项。
执行记录:
- 2026-05-15:
task432-filetree-trash-page-dual-browser-no-refresh-smoke已通过。A 浏览器对页面执行 create / archive / restore / purge / empty-trash;B 浏览器文件树与/trash均无需刷新可见更新,且全程无framenavigated。事件来源记录为:create ->tree:resync,archive ->tree:delta remove_document,restore ->tree:delta upsert_document,purge ->tree:resync,empty-trash ->tree:resync。结果文件:tmp/task432-filetree-trash-page-dual-browser-no-refresh-smoke/result.json。 - 2026-05-15:清空页面垃圾箱的初次 smoke 暴露两层根因并已修正:Rust
documents.emptyTrashByWorkspace之前未写tree.trash.documents.emptieddomain event,Convexdocuments.emptyTrashByWorkspacevalidator 也未放行streamDeltaHint/domainEventHint/domainEventPlan,导致/trash必须刷新才清空。两处已补齐并重新部署本地 Convex。
3.2 task433:普通附件生命周期双浏览器
覆盖普通 file asset:
- 优先复用
task428/task427的 API seed 方式,避免依赖上传文件选择器。 - archive / restore / purge 走
/api/media/batch与/api/media/purge兼容 URL,但必须能在 artifact / response 中证明进入tree.resource.archive/restore/purge。 - B 侧分别观察 File Tree 与 Trash 工作台。
执行记录:
- 2026-05-15:
task433-filetree-trash-file-asset-dual-browser-no-refresh-smoke已通过。A 浏览器对普通附件执行 upload / archive / restore / archive+purge / upload+archive+empty-trash;B 浏览器文件树与/trash均无需刷新可见更新,执行阶段无framenavigated。事件来源记录为:upload ->tree:delta upsert_assets,archive ->tree:resync,restore ->tree:resync,purge ->tree:resync,empty-trash ->tree:resync。结果文件:tmp/task433-filetree-trash-file-asset-dual-browser-no-refresh-smoke/result.json。 - 2026-05-15:初次 smoke 暴露
tree.resource.archive/restore/purgelegacy Convex 参数转换覆盖真实 Convex userId,导致mediaAssets.patchByIdmembership 检查失败;已修正mnote-webtransport,使资源 lifecycle 转换优先保留args_json.userId。随后暴露/api/media/empty-trash不写 tree event,导致 B/trash清空必须刷新;已补tree.trash.media.emptieddomain event 与resync_requiredstream delta。 - 2026-05-15:验证
cargo test --manifest-path rust/Cargo.toml -p mnote-web convex_resource_lifecycle_args_keep_effective_user_id -- --nocapture、cargo test --manifest-path rust/Cargo.toml -p mnote-web media_trash_routes_delete_restore_purge_and_empty -- --nocapture均通过。
3.3 task434:mindmap / table 生命周期双浏览器
覆盖仍处 compat alias 的 mindmap / table:
- mindmap 可复用现有 mindmap block / asset seed 方式。
- table 可复用
task427的 table seed 与 purge query 方式。 - 本阶段不把 mindmap / table 宣称为正式
tree.resource.*cutover,只验证双浏览器可见性并保留 compat 边界。
执行记录:
- 2026-05-15:
task434-filetree-trash-mindmap-table-dual-browser-no-refresh-smoke已通过。A 浏览器对 mindmap 执行 create / archive / restore / archive+purge,对 table 执行 create / archive / restore / archive+purge,并对 mindmap + table 执行 create / archive / empty-trash;B 浏览器文件树与/trash均无需刷新可见更新,执行阶段无framenavigated。结果文件:tmp/task434-filetree-trash-mindmap-table-dual-browser-no-refresh-smoke/result.json。 - 2026-05-15:为避免把 table 预置数据误标为 create 实时证据,Rust
mnote-web已新增/api/tables/create兼容入口,创建成功后记录tree.resource.table.createddomain event 与resync_requiredstream delta。mindmap / table 的 delete / restore / purge / empty-trash compat route 也补齐 tree resync artifacts。 - 2026-05-15:事件来源记录为:mindmap create / archive / restore 主要触发
tree:delta resync_required,mindmap purge 触发tree:resync;table create / archive / restore 主要触发tree:delta resync_required,table purge / empty-trash 触发tree:resync。 - 2026-05-15:验证
cargo test --manifest-path rust/Cargo.toml -p mnote-web trash_routes -- --nocapture --test-threads=1与node --check scripts/task434-filetree-trash-mindmap-table-dual-browser-no-refresh-smoke.js均通过。
3.4 task435:empty-trash 双浏览器汇总
在一次性 workspace 中执行页面垃圾箱与资源垃圾箱清空:
- A 清空页面垃圾箱后,B
/trash页面不刷新变为空。 - A 清空资源垃圾箱后,B
/trash页面资源区不刷新变为空。 - Convex query 断言对应对象不可再查询。
3.5 task436:local folder watcher 与本地资源生命周期
本阶段不混入 Convex 双浏览器矩阵,单独验证 local folder source:
- 打开 local folder page tree 与 filetree,记录初始
navigationEvents。 - 外部创建
.md,断言 page tree / filetree 可见,并记录是否由/api/local-folder/events、/api/tree/local-folder-watch、reload 或手动刷新驱动。 - 外部重命名
.md,断言旧 row 消失、新 row 出现;当前打开文档若受影响,应出现刷新或冲突提示。 - 外部删除
.md,断言 page tree / filetree 中 row 消失;若当前打开该文档,应有明确状态提示。 - 外部创建 / 重命名 / 删除至少两类非 md 文件,例如
.txt与.png,断言 filetree 与真实文件系统一致,并记录是否发生window.location.reload()。 - 对本地非 md 资源执行删除 / restore / purge;若 UI 仍禁用,记录为未完成能力,不得用 Markdown trash 代替。
当前只读审计结论:
LocalFolderWatcherRegistry使用notify递归监听 root,但事件发送前经过is_markdown_path,只允许.md/.markdown。/api/local-folder/events订阅该 watcher,主要服务打开的本地 Markdown document session。- 主 tree 的 local folder 模式不接
/api/tree/events,而是轮询/api/tree/local-folder-watch(startLocalFolderSidebarWatch,1200ms 间隔)。revision 变化时调用refreshLocalFolderSidebarSnapshot()重渲染 filetree,不触发window.location.reload()。Rust 单元测试sidebar_tree_runtime_polls_local_folder_without_browser_reload验证了 SSR template 中不含 reload 语句。 - trash workbench 的 local folder restore/purge 使用
refresh()(gateway.rs:1040-1060):fetch+DOMParser替换 workbenchinnerHTML,不触发浏览器导航。但 restore/purge 完成后不调用refreshLocalFolderSidebarSnapshot(),filetree 更新依赖下次轮询触发。 task163-local-folder-unified-tree-browser-smoke.js已覆盖外部新增 Markdown、外部新增 / 删除watcher-asset.txt的可见性;task435-local-folder-watch-no-reload-smoke已覆盖 Markdown 与非 md 外部 create/rename/delete 无 reload;非 md trash/restore/purge 已由 task437(API)和 task464(UI)覆盖;local folder Markdown 自身 trash lifecycle(archive/restore/purge through tree commands)已由 task475 覆盖(API-only:loose Markdown 的 delete → trash 进入/restore 恢复/purge 清理)。task435-local-folder-watch-no-reload-smoke已通过:外部非 md 文件(.txt、.png)的 create / rename / delete 在 filetree 中无刷新可见更新,全程无framenavigated。(结果:tmp/task435-local-folder-watch-no-reload-smoke/result.json)task437-local-folder-asset-trash-lifecycle-smoke已通过(API-only):tree.resource.archive/restore/purge的 canonicalCommand 与文件系统一致性。(结果:tmp/task437-local-folder-asset-trash-lifecycle-smoke/result.json)task464-local-folder-resource-trash-ui-smoke已通过(Browser UI):trash 页中 delete→visible / restore→file restored / purge→file gone 完整 UI 流程。(结果:tmp/task464-local-folder-resource-trash-ui-smoke/result.json)task473-local-folder-trash-restore-no-refresh-focus-gap-smoke新增并已由 Codex + Reasonix Browser Worker 实跑:验证 trash workbench 的 restore 操作为 fetch 型refresh()(restoreNavFree.delta=0,不触发浏览器导航),restore 后 filetree 通过轮询显示恢复行(fileReappearsInFiletree=true)。2026-05-21/22 Codex 修复并复跑后,恢复行出现 focus 证据(data-active="true"),focus gap 已关闭。(结果:tmp/task473-local-folder-trash-restore-no-refresh-focus-gap-smoke/result.json;浏览器证据:tmp/reasonix-batch-a-browser-task473-2026-05-21/)task475-local-folder-markdown-trash-lifecycle-smoke已通过(API-only):验证 loose Markdown 页面在/api/tree/commands的 delete(archive)→ restore → 再次 delete → purge 完整生命周期,文件系统与trash-index.json一致性。当前实现中 loose Markdown 的 delete/restore/purge 响应不含canonicalCommand字段(与trash_local_raw_file不同)。bundled Markdown 未覆盖(bundle 涉及目录整体移动/恢复,由 Rust 单测local_tree_command_manages_loose_markdown_like_regular_file和local_tree_command_delete_page_bundle_directory_uses_page_trash覆盖)。(结果:tmp/task475-local-folder-markdown-trash-lifecycle-smoke/result.json)task474-local-folder-empty-trash-ui-smoke新增:验证 local folder trash workbench 的清空页面垃圾箱(local-empty-documents)和清空资源垃圾箱(local-empty-resources)按钮操作。点击按钮后 accept confirm dialog,refresh()以 fetch+DOMParser 替换 innerHTML,不触发浏览器导航;断言对应 trash row 消失、trash-index.json对应 entry 清理、trash 文件删除。结果写入tmp/task474-local-folder-empty-trash-ui-smoke/result.json。- 2026-05-21:Codex 在最新临时
mnote-webhttp://127.0.0.1:3014上复跑task474/task475均通过;Reasonix Browser Worker 输出tmp/reasonix-batch-a3-browser-task474-empty-trash-2026-05-21/,截图显示清空后页面与资源均为 0,两个清空按钮 disabled。
通过后才允许在 10-review 中把 local folder 阶段 8 子项勾选;如果仍发生 reload,只能标为“reload 型刷新可见”,不能标为 no-refresh。
4. 实时来源记录
每个 smoke 结果必须记录:
- B 浏览器是否接到
/api/tree/events。 - B 浏览器是否接到
tree:snapshot、tree:delta、tree:resync。 - B 侧 DOM 更新是否发生在无 reload / 无 framenavigated 的前提下。
- 是否依赖了 A 浏览器本地
refreshTree();如果依赖,只能作为 A 本地补偿,不计入 B 实时证据。
5. 当前边界
- 3000 常驻进程可能是旧
mnote-web二进制;修改 Rust 后做 smoke 时必须重启 3000,或用新编译的临时 3001 并在结果中明确记录。 - 资源生命周期中,普通附件 file asset 已进入正式
tree.resource.*;mindmap / table 仍是 compat route,阶段 8 只验证 no-refresh,不改变 cutover 口径。 - local folder Markdown watcher 只有部分历史覆盖,且主 tree filetree 当前是 revision polling;非 md 本地资源文件 watcher / trash / restore / purge 已由 task435(外部 create/rename/delete)、task437(API trash lifecycle)、task464(trash UI)、task473(restore no-refresh + focus/reveal)分项覆盖;local folder empty-trash UI(md 页面 + 非 md 资源)已由 task474 覆盖;local folder Markdown loose page trash lifecycle 已由 task475(API)覆盖;bundled Markdown 由 Rust 单测覆盖,尚未编写独立 smoke。
resync_requireddelta 不是数据替换本身,必须伴随后续 snapshot / resync 或客户端主动拉取 snapshot;后续新增资源 route 时必须补事件合同回归。
6. 完成标准
design/10-review/07-vscode-explorer-filetree-trash-gap-review.md阶段 8 的 Convex 页面、普通附件、mindmap、table checklist 全部有对应 smoke 证据。- local folder 子项必须有 task435/437/464/473/474/475 或等价 smoke 证据;若仍发生 reload,保持未完成 no-refresh 状态。restore focus 已由 task473 关闭,不再视为 blocker。
- 每个 smoke 结果写入
tmp/task43*-*/result.json,并在 10-review 中记录命令、对象、实时来源和剩余边界。 - 不再用单浏览器 no-refresh、API purge 成功或 Convex query 成功替代双浏览器 UI no-refresh。