Files
mnote/design/04-tree-domain/done/4-33-vscode-explorer-dnd-readonly-conflict-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

5.9 KiB
Raw Blame History

4-33 VSCode Explorer DnD Readonly Conflict v1

状态:done 创建时间:2026-05-15 所属主线:04-tree-domain / VSCode Explorer 文件树与垃圾箱闭环 来源:design/10-review/07-vscode-explorer-filetree-trash-gap-review.md 阶段 7 DnD 剩余项

1. 目标

补齐阶段 7 中尚未闭合的 DnD 剩余项:主 Sidebar Convex filetree 的 readonly 禁止与命名冲突确认。

当前已完成的 DnD 证据是:

  • 自拖自身拒绝。
  • 父拖到子节点 / 后代拒绝。
  • Alt / Ctrl / Meta copy modifier 被识别为 copy。
  • 3000 主入口 /api/tree/filetree/drop-preflight 已返回 Rust bridge tree.filetree.drop.preflight plan。

尚未完成的是:

  • 主 Sidebar Convex filetree 下的 readonly 目标 / readonly source 禁止。
  • 主 Sidebar Convex filetree 下的重名目标冲突确认,而不是直接 move/copy。

2. 当前代码事实

  • bridge-runtimeFileTreeDropPreflightCommandPayload 当前只有:
    • copy
    • targetDocumentId / targetRowId / focusedRowId / activeDocumentId
    • rowIds
    • rows
    • documentParents
  • FileTreeDropPreflightRow 当前只有:
    • rowId / rowKind / documentId / assetId / assetDocumentId / assetType / storagePath
  • 现有 build_filetree_drop_plan 能判断:
    • 目标页面。
    • doc / asset transfer plan。
    • 父拖子 / 自拖非法 move。
    • copy / move action。
  • 现有 payload 不能判断:
    • 目标是否 readonly。
    • source 是否 readonly。
    • target 下是否已有同名页面 / 附件。
    • 冲突应 rename、replace、skip 还是 cancel。

3. 设计增量

3.1 Drop preflight payload 增量

tree.filetree.drop.preflight payload 增加以下可选字段:

{
  "sourceCapabilities": ["read", "write", "move", "copy"],
  "targetCapabilities": ["read", "write", "drop"],
  "rows": [
    {
      "rowId": "doc:...",
      "title": "页面标题",
      "operationProfile": "convex_workspace"
    }
  ],
  "targetChildren": [
    {
      "rowKind": "doc",
      "documentId": "...",
      "assetId": null,
      "title": "页面标题"
    }
  ],
  "conflictPolicy": "prompt"
}

说明:

  • sourceCapabilities / targetCapabilities 用于表达当前 workspace source 与目标 row 的写权限,不在 UI 层猜测。
  • operationProfile 允许 local readonly、remote readonly、shared readonly 等来源进入同一 preflight,而不是散落在前端。
  • titletargetChildren 用于做同级重名检测。
  • conflictPolicy 默认 prompt,后续可扩展 rename / skip / replace,但本阶段只要求“发现冲突并要求确认”。

3.2 Drop preflight plan 增量

FileTreeDropPlan 增加:

{
  "allowed": true,
  "blockedReason": null,
  "requiresConfirmation": false,
  "conflicts": []
}

readonly 场景:

  • allowed=false
  • blockedReason="readonly_target"readonly_source"
  • 不返回可执行 transfer plan,避免 UI 误执行。

命名冲突场景:

  • allowed=true
  • requiresConfirmation=true
  • conflicts 列出冲突对象、目标父级、建议策略。
  • UI 必须展示确认,不允许静默执行。

3.3 3000 主壳行为

  • 内部 drop 触发前继续调用 /api/tree/filetree/drop-preflight
  • allowed=false,显示阻塞原因,不发 /api/tree/commands/api/media/batch
  • requiresConfirmation=true,显示冲突确认;用户取消时不执行。
  • 用户确认后,按 plan 执行 move/copy。

4. 验收标准

  • readonly target
    • 真实或构造的 readonly target drop preflight 返回 400 或 allowed=false
    • 浏览器不发执行请求。
    • UI 有“只读”类可解释反馈。
  • conflict
    • 目标父页面已有同名页面或同名附件时,preflight 返回 requiresConfirmation=trueconflicts
    • 用户取消确认后不执行 move/copy。
    • 用户确认后执行 move/copy,并在结果中记录冲突处理策略。
  • no false positive
    • 非冲突移动不弹冲突确认。
    • copy modifier 仍保留 copy=true
    • 父拖子 / 自拖仍被拒绝。

5. 建议执行顺序

  1. 扩展 bridge-runtime payload / plan 类型,补 readonly 与 conflict 单测。
  2. 扩展 3000 /api/tree/filetree/drop-preflight route 透传新增字段。
  3. 扩展 3000 Rust SSR 主壳内部 drop 逻辑,生成 row title / target children / capability payload。
  4. 新增 scripts/task431-vscode-explorer-dnd-readonly-conflict-smoke.js
    • 一次性用户 / workspace。
    • 构造同名目标,验证 conflict confirmation cancel / confirm。
    • 构造 readonly payload 或 readonly source fixture,验证 blocked。
  5. 更新 design/10-review/07-vscode-explorer-filetree-trash-gap-review.md,仅在 task431 通过后勾选阶段 7 DnD 总项。

6. 当前状态

截至 2026-05-15,本文件已完成阶段 7 DnD readonly / conflict 的执行与验证:

  • bridge-runtimetree.filetree.drop.preflight 已支持 source / target capability、target children、conflict policy、requiresConfirmationconflicts
  • readonly target / source 在 preflight 阶段被拒绝;同名目标返回确认计划;copy modifier 不被 readonly move 规则误拒绝。
  • 主 Sidebar 内部 drop 与 cut-paste move 路径在 requiresConfirmation 时确认,取消后不执行后续 move/copy。
  • 验证命令:
    • cargo test -p bridge-runtime tree_filetree_drop_preflight -- --nocapture
    • pnpm test src/lib/file-tree/shell.test.ts src/lib/file-tree/resource-command-client.test.ts src/components/sidebar/sidebar-paste-preflight-source.test.ts src/components/sidebar/sidebar-dnd-source.test.ts
    • MNOTE_UI_BASE_URL=http://127.0.0.1:3001 MNOTE_AUTH_BASE_URL=http://127.0.0.1:3000 node scripts/task431-vscode-explorer-dnd-readonly-conflict-smoke.js

注意:验证时 3000 常驻进程仍是旧 mnote-web 二进制,因此 task431 对 3000 初次执行复现旧 readonly 放行行为;当前代码在 3001 新编译进程验证通过,3000 需重启后生效。