# 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 自动引用”的实机验收。