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

153 lines
5.9 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-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-runtime``FileTreeDropPreflightCommandPayload` 当前只有:
- `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 增加以下可选字段:
```json
{
"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,而不是散落在前端。
- `title``targetChildren` 用于做同级重名检测。
- `conflictPolicy` 默认 `prompt`,后续可扩展 `rename` / `skip` / `replace`,但本阶段只要求“发现冲突并要求确认”。
### 3.2 Drop preflight plan 增量
`FileTreeDropPlan` 增加:
```json
{
"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=true``conflicts`
- 用户取消确认后不执行 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-runtime``tree.filetree.drop.preflight` 已支持 source / target capability、target children、conflict policy、`requiresConfirmation``conflicts`
- 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 需重启后生效。