# Workspace Explorer 树顺序、独立隐藏与路径引用修复方案

## 1. 状态与范围

- 状态：已完成方案自审，**可执行，开始实施**。
- 目标：修复对话树顺序；增加按选项卡独立的隐藏选择模式；增加目录/资源文件复制路径和拖拽引用。
- 不改变：顶层工作区固定根、DSH 会话、原生归档、磁盘文件；不删除或移动用户文件。

## 2. 自审结论

### 2.1 本轮资源隐藏交互补充方案（2026-10-03）

用户确认：不增加独立“全选”按钮；资源树中的父文件夹选择圆点承担相对子文件夹的全选语义。点击标题眼睛进入资源选择态时，暂时显示此前隐藏的资源项；再次点击提交隐藏。资源选择态中允许选择文件夹或文件，父文件夹选择覆盖其后代，但子文件夹仍可点击取消覆盖：取消后退出全选语义，仅保留用户明确留下的选择。再次点击父文件夹的全选圆点，切换为“仅隐藏子文件夹”集合，父文件夹本身保留且可呈现为空文件夹。提交只发送实际选择的子文件夹路径，不隐藏工作区根。

本次澄清后的两态模型：点击小眼睛进入资源隐藏编辑时，完整资源树（当前已加载范围）显示；已在 resourceHiddenEntries 覆盖范围内的条目为“选中”，其余为“未选中”。再次点击小眼睛提交当前两态集合，选中项隐藏、未选中项显示。圆圈没有 covered/partial 第三态：父目录选中时后代仅视觉继承选中；点击已继承选中的子文件夹，将父级展开为后代显式集合，但该子文件夹及其后代改为未选中，其余后代保持选中；点击已选父目录再次操作则保留父目录为未选中、其全部后代为选中，可形成空父目录。提交必须把两态转换成 Host hidden paths，并保留空父目录语义。

自审边界：当前目录列表是分页、懒加载的，客户端不能声称未加载目录已被全选。第一阶段对已加载条目提供上述可逆交互，并在选择态禁用资源隐藏过滤；保留分页“加载更多”。若验收要求跨未展开目录的真正全量全选，需新增 Host 端递归枚举接口，不能用客户端猜测补齐。


### 可直接实现

1. **对话树顺序**属于客户端纯树模型/UI问题。当前 `ConversationNode` 已分别渲染 `children` 和 `node.sessions`，但顺序是先子文件夹后会话；将顺序改为：当前文件夹行 → 当前文件夹会话 → 当前文件夹子文件夹。子文件夹展开后递归同一顺序。不能用 CSS order 解决，因为会话和目录的 DOM 层级、展开状态和键盘顺序也必须一致。
2. **独立隐藏状态**可复用 Host 的持久化配置和 `/hidden` 路由，但现有单一 `hiddenDirectories` 不足以区分“对话隐藏”和“资源隐藏”。将配置增加 `conversationHiddenDirectories` 与 `resourceHiddenEntries`（目录或文件绝对路径），旧 `hiddenDirectories` 迁移为 `conversationHiddenDirectories`，保证旧用户隐藏状态不丢失。
3. 选择模式完全在客户端短暂存在：`selectionMode` 为 `conversations`、`resources` 或 null；`selectedPaths` 是当前树的折叠路径集合。眼睛按钮切换模式/提交隐藏，正常浏览不出现圆点。
4. 父目录选择只影响提交时生成的隐藏路径，不改变资源加载；选中父目录时 UI 显示后代已覆盖，提交只保存父路径。若用户逐一选完子目录，父目录仍保持未选中；提交后父目录仍显示为空文件夹，不能自动推导并隐藏父目录。资源区同样适用，但资源区文件和目录都可选。
5. 复制路径使用浏览器 `navigator.clipboard.writeText`，失败时用已存在的 notice 机制报告；不新增 Host 文件写入接口。

### 必须保守处理的边界

- 本轮实时 Inspect 未找到可依赖的 `sidebarRight` 或 composer drop Service 合同；因此拖拽使用标准 DOM `DragEvent`/`DataTransfer`：提供 `text/plain` 绝对路径、`text/uri-list` 的 `file:///` 地址和插件自定义 `application/x-dsh-workspace-path`。这只是向拖放目标提供标准字符串载荷；是否被 DSH 对话框接收、是否转为原生引用，必须在目标版本实测，不能声称会自动创建附件/引用。
- 只在文件和资源目录行设置 `draggable=true`；对话行、会话行、顶层工作区根不提供拖拽引用，避免把会话或根误当文件资源。
- 若拖拽目标不接受 drop，源行仍不改变任何文件；不阻止 DSH 原生拖拽，只在 dragstart 写入数据。不能通过未确认的私有 Service 模拟发送消息。
- 剪贴板 API 可能因页面权限拒绝；必须有明确失败提示和可选的文本复制降级，不静默成功。
- 对话隐藏只作用于侧栏显示，并将其下会话一并隐藏；会话不能单独选择或隐藏。资源隐藏独立于对话隐藏：对话隐藏 A 不影响资源区 A；资源隐藏文件不影响对话区。
- 根节点不出现空心圆，也不能通过隐藏根绕过固定工作区语义。若目标路径是根本身，隐藏操作拒绝并提示。

## 3. 数据与协议

配置 v3（兼容 v2）：

```json
{
  "version": 3,
  "conversationHiddenDirectories": ["D:/project/private"],
  "resourceHiddenEntries": ["D:/project/private/secret.txt", "D:/project/build"],
  "hiddenDirectories": []
}
```

- `conversationHiddenDirectories` 只允许已登记根内的目录路径，Host 持久化时 canonical/fold 去重。
- `resourceHiddenEntries` 只允许已登记根内的目录或文件路径；Host 不要求该条目当前存在，以便移动/暂时不可读时仍可恢复显示，但提交和读取仍做 root lexical/realpath 边界校验。
- 兼容读取 v2：`hiddenDirectories` 迁移到 `conversationHiddenDirectories`；旧字段保留一次备份后原子写 v3。未知/损坏版本保持只读，绝不覆盖。
- 推荐新增路由：`/visibility` GET/POST，或扩展现有 `/hidden` 使其带 `scope: 'conversations'|'resources'`。优先扩展现有协议，减少路由面：
  - `POST /hidden { scope, path, hidden }`：单路径增删。
  - `POST /hidden { scope, paths, hidden: true }`：提交选择模式的多个路径，Host 在一个 store update 中原子处理。
  - 返回 `conversationHiddenDirectories`、`resourceHiddenEntries` 和 state。
- Host 拒绝 scope 错误、根路径、超出授权根、对话 scope 中的文件路径；资源 scope 允许文件和目录。逐路径失败采用全量事务失败，不提交部分结果。

## 4. 对话树顺序与隐藏选择

### 4.1 渲染顺序

对每个目录节点：

```text
目录 A
├── 对话 1
├── 对话 2
├── 子目录 a
│   ├── 对话 3
│   └── 子目录 aa
│       └── 对话 4
└── 子目录 b
```

`ConversationNode` 的展开内容顺序固定为：

1. `node.sessions`，按现有更新时间排序；
2. `node.directories`，按现有自然名称排序；
3. 加载状态/空状态提示。

不改变树节点自身的缩进计算：当前目录深度为 `depth`，会话和子目录均使用 `depth + 1`。这样不会出现“文件夹下会话与子文件夹错位”。

### 4.2 眼睛按钮

- 在“工作区”标题右侧增加眼睛 IconButton，仅在对话/资源树可用时启用。
- 对话页点击：进入 `selectionMode='conversations'`，所有非根目录左侧渲染空心圆；再次点击眼睛提交选中目录并退出模式。
- 资源页点击：进入 `selectionMode='resources'`，所有非根目录及文件左侧渲染空心圆；再次点击提交。
- 选择圆点击必须 `stopPropagation`，不展开、不打开、不弹出目录菜单。
- 选择父目录时只在 UI 中递归显示后代“覆盖选择”；内部仍保留用户实际点击路径，不能把子项自动转换为父项。提交后只写用户实际选择路径集合。
- 对话选择节点时，只允许目录节点，不在会话行渲染圆点；父目录隐藏后所有会话由树过滤掉。
- 资源选择节点包括文件，资源树过滤条件是“隐藏路径自身或位于任一隐藏目录下”；对话过滤只看 conversation scope。
- 设置页分别列出两种隐藏列表，并提供逐项恢复；不再把资源隐藏项误显示成对话隐藏目录。

## 5. 复制路径与拖拽引用

### 5.1 菜单

目录和资源文件右键菜单增加“复制路径”：

- 对话页的目录节点不显示复制路径（需求明确“对话除外”）；对话页仍可“开启新会话”。
- 资源页目录、文件显示“复制路径”；根节点也属于工作区目录资源时可复制，但不允许在对话区操作。
- 复制的是 Host 返回的 canonical display path，不复制会话资源 URI，不复制 `file://` 编码。
- 成功显示短暂成功通知；失败保留路径并提示用户使用系统资源管理器。

### 5.2 拖拽

- 资源页目录/文件：`draggable=true`；对话页不拖拽会话或目录。
- `dragstart` 写入：
  - `text/plain`: 原始绝对路径；
  - `text/uri-list`: `file:///` URL；
  - `application/x-dsh-workspace-path`: JSON `{ path, type, rootId }`。
- 设置 `effectAllowed='copy'`，不执行移动。
- `dragend` 清理临时拖拽状态；不能在源端删除、重命名或修改文件。
- 如果将来 Inspect 确认 Composer 的官方 drop 接口，再增加适配器；当前不伪造发送消息，不在没有契约时直接调用未知 Service。

## 6. 实施顺序

1. Host v3 schema、迁移、scope 校验和原子 visibility 路由。
2. 客户端状态、眼睛按钮、圆点选择、两套过滤与恢复入口。
3. 对话树顺序和目录/会话同级缩进。
4. 目录/资源文件复制路径。
5. 标准 DataTransfer 拖拽引用。
6. 纯逻辑测试、Host 测试、客户端树测试；最后执行完整测试。

## 7. 验收标准

- 对话树每个目录先列直属会话，再列直属子目录；展开任何子目录后仍遵循同一规则。
- 对话眼睛模式不显示根圆点；选择父目录可隐藏全部后代会话；逐一选完子目录不会自动隐藏父目录。
- 资源眼睛模式可分别隐藏目录和单个文件；对话隐藏与资源隐藏互不影响。
- 刷新、重启插件、配置迁移后两套隐藏状态保留；根外目录、越界路径和根本身均拒绝。
- 目录/资源文件右键复制 canonical 路径成功或明确失败；对话区没有该项。
- 拖拽不会移动或删除文件；标准 drop target 能收到绝对路径和 file URI；不支持 drop 时无副作用。
- 运行原有 Host 110 项、native bridge 15+1、tree 25 项，并新增本需求测试全部通过。

## 8. 实机验证边界

必须在目标 DSH GUI 检查眼睛按钮位置、选择圆键盘可达性、树缩进、资源/对话隔离、系统剪贴板权限和拖放到真实对话框的实际表现。由于当前没有已确认的 composer drop Service，源码测试只能验证 DataTransfer 载荷与无副作用，不能代替“拖入后 DSH 自动引用”的实机验收。
