Files
mnote/design/04-tree-domain/process/4-34-filetree-trash-dual-browser-no-refresh-v1.md
T
lix-2026 384da4e44c feat(tree): checkpoint resource lifecycle work
提交当前顶层 mnote Git 工作区,范围集中在 04-tree-domain 的 resource/trash/filetree 生命周期、mnote-web resource_trash 路由、Convex/Next 兼容接口、sidebar/file-tree 客户端适配、smoke 脚本与对应设计/bug 记录。

不包含被 ignore 的 design/05-editor-mainline/reference-code/leptos-tiptap 嵌套仓库改动。新增 smoke 的测试密码改为运行时读取 MNOTE_E2E_PASSWORD,避免提交明文 credential assignment。
2026-05-16 07:38:45 +08:00

11 KiB
Raw Blame History

4-34 Filetree Trash Dual Browser No Refresh v1

状态:process 创建时间: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-trashB 浏览器不刷新即可在 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 可见 本地删除进入 .mnote/trash restore 回原路径 purge 后原文件与 trash 均不存在 可选本地 trash 清空 必须记录是否发生 reload,不能把 reload 型刷新标为 no-refresh
local folder 非 md 资源 外部创建 .txt/.png/.pdf/.xlsx 后 filetree 可见 删除语义待定 restore 待定 purge 待定 待定 当前不走 Markdown watcher SSE;需记录 polling / reload / delta 来源

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:deltatree:resync、EventSource snapshot 还是 Convex live 更新。

通过后才勾选阶段 8 的页面子项。

执行记录:

  • 2026-05-15task432-filetree-trash-page-dual-browser-no-refresh-smoke 已通过。A 浏览器对页面执行 create / archive / restore / purge / empty-trashB 浏览器文件树与 /trash 均无需刷新可见更新,且全程无 framenavigated。事件来源记录为:create -> tree:resyncarchive -> tree:delta remove_documentrestore -> tree:delta upsert_documentpurge -> tree:resyncempty-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.emptied domain eventConvex documents.emptyTrashByWorkspace validator 也未放行 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-15task433-filetree-trash-file-asset-dual-browser-no-refresh-smoke 已通过。A 浏览器对普通附件执行 upload / archive / restore / archive+purge / upload+archive+empty-trashB 浏览器文件树与 /trash 均无需刷新可见更新,执行阶段无 framenavigated。事件来源记录为:upload -> tree:delta upsert_assetsarchive -> tree:resyncrestore -> tree:resyncpurge -> tree:resyncempty-trash -> tree:resync。结果文件:tmp/task433-filetree-trash-file-asset-dual-browser-no-refresh-smoke/result.json
  • 2026-05-15:初次 smoke 暴露 tree.resource.archive/restore/purge legacy Convex 参数转换覆盖真实 Convex userId,导致 mediaAssets.patchById membership 检查失败;已修正 mnote-web transport,使资源 lifecycle 转换优先保留 args_json.userId。随后暴露 /api/media/empty-trash 不写 tree event,导致 B /trash 清空必须刷新;已补 tree.trash.media.emptied domain event 与 resync_required stream delta。
  • 2026-05-15:验证 cargo test --manifest-path rust/Cargo.toml -p mnote-web convex_resource_lifecycle_args_keep_effective_user_id -- --nocapturecargo test --manifest-path rust/Cargo.toml -p mnote-web media_trash_routes_delete_restore_purge_and_empty -- --nocapture 均通过。

3.3 task434mindmap / table 生命周期双浏览器

覆盖仍处 compat alias 的 mindmap / table

  • mindmap 可复用现有 mindmap block / asset seed 方式。
  • table 可复用 task427 的 table seed 与 purge query 方式。
  • 本阶段不把 mindmap / table 宣称为正式 tree.resource.* cutover,只验证双浏览器可见性并保留 compat 边界。

执行记录:

  • 2026-05-15task434-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-trashB 浏览器文件树与 /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.created domain event 与 resync_required stream delta。mindmap / table 的 delete / restore / purge / empty-trash compat route 也补齐 tree resync artifacts。
  • 2026-05-15:事件来源记录为:mindmap create / archive / restore 主要触发 tree:delta resync_requiredmindmap purge 触发 tree:resynctable create / archive / restore 主要触发 tree:delta resync_requiredtable purge / empty-trash 触发 tree:resync
  • 2026-05-15:验证 cargo test --manifest-path rust/Cargo.toml -p mnote-web trash_routes -- --nocapture --test-threads=1node --check scripts/task434-filetree-trash-mindmap-table-dual-browser-no-refresh-smoke.js 均通过。

3.4 task435empty-trash 双浏览器汇总

在一次性 workspace 中执行页面垃圾箱与资源垃圾箱清空:

  • A 清空页面垃圾箱后,B /trash 页面不刷新变为空。
  • A 清空资源垃圾箱后,B /trash 页面资源区不刷新变为空。
  • Convex query 断言对应对象不可再查询。

3.5 task436local 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;revision 扫描可见条目,非 md 文件变化会改变 revision,但当前变化后触发 window.location.reload()
  • task163-local-folder-unified-tree-browser-smoke.js 已覆盖外部新增 Markdown、外部新增 / 删除 watcher-asset.txt 的可见性;尚未覆盖外部 Markdown 重命名 / 删除、非 md rename、非 md trash / restore / purge。

通过后才允许在 10-review 中把 local folder 阶段 8 子项勾选;如果仍发生 reload,只能标为“reload 型刷新可见”,不能标为 no-refresh。

4. 实时来源记录

每个 smoke 结果必须记录:

  • B 浏览器是否接到 /api/tree/events
  • B 浏览器是否接到 tree:snapshottree:deltatree: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 + reload;非 md 本地资源文件 watcher / trash / restore / purge 仍单列为待补,不应混入 Convex 双浏览器矩阵。
  • resync_required delta 不是数据替换本身,必须伴随后续 snapshot / resync 或客户端主动拉取 snapshot;后续新增资源 route 时必须补事件合同回归。

6. 完成标准

  • design/10-review/07-vscode-explorer-filetree-trash-gap-review.md 阶段 8 的 Convex 页面、普通附件、mindmap、table checklist 全部有对应 smoke 证据。
  • local folder 子项必须有 task436 或等价 smoke 证据;若仍发生 reload,保持未完成 no-refresh 状态。
  • 每个 smoke 结果写入 tmp/task43*-*/result.json,并在 10-review 中记录命令、对象、实时来源和剩余边界。
  • 不再用单浏览器 no-refresh、API purge 成功或 Convex query 成功替代双浏览器 UI no-refresh。