# Harness 会话下拉入口

已确认的交互：Arkme 登录状态下，把 DSH 常驻会话侧栏收进对话标题下拉菜单。标题使用圆角描边按钮，文字与箭头之间有分隔线；菜单直接显示原有会话和工作区，底部同一行显示新会话、视图选项和添加工作区。菜单内不再显示重复的搜索入口，搜索仍可从 Arkme 第二栏使用。空白会话也有入口，点击会话、外部区域或 Escape 收起菜单。未登录和独立 Harness 页面继续使用官方侧栏。

## 官方升级边界

`harness-session-dropdown.ts` 是可撤销的展示适配层。官方 SidebarRoot、WorkspaceBrowser、会话树、右键/更多菜单、工作区目录选择器和所有数据操作继续由当前宿主提供。没有复制官方组件源码，没有重挂载会话内容，没有新增会话数据缓存，也没有移动 React 所拥有的 DOM 节点。

目前官方没有替换标题和把侧栏列宽设为零的公共插槽。因此用已有的 `data-slot`、语义按钮和三列布局结构识别适配位置，仅添加自己的标题按钮和 CSS 标记。菜单是原侧栏的浮层展示，原“新会话”按钮通过 CSS 排序放到底部。原生侧栏折叠时，首次展开菜单通过原按钮请求展开；没有直接改布局存储。

第一列归零；右侧详情列的宽度从官方当前布局读取并跟随变化，其他官方组件和样式继续使用升级后的版本。若升级改变了已识别的侧栏或布局结构，适配自动撤销，恢复官方侧栏，避免失去导航。未知结构需要重新适配；不能保证任意未来版本的私有 DOM 结构都兼容。

模块卸载或账号退出会移除按钮、样式及标记，并恢复原侧栏折叠状态（仅当适配层主动展开过它时）。插件的登录态设置入口规则继续单独使用官方 `sidebar.settings` 插槽。

工具栏紧凑布局仅在识别到官方搜索、视图选项、目录选择插槽及已知操作集合时启用；出现未知操作时保留原工具栏。隐藏的是标题与搜索区域，整个工具栏容器使用 `display: contents`，目录选择插槽继续挂载。视图选项和添加工作区仅通过 CSS 定位到底部，原按钮及其回调、原生菜单和目录选择流程都不变。撤销适配时恢复全部区域。

底部操作行上方不额外绘制分隔线，以现有留白区分会话列表和操作区；新会话按钮自身的边框和三个操作的布局保持不变。

## 第二栏卡片悬停入口

登录后，鼠标在第二栏 DSH 卡片停留 200ms，原生会话菜单出现在卡片右侧；离开卡片和菜单 300ms 后收起，经过两者间隙时允许移入菜单继续操作。原卡片点击入口保留，键盘聚焦卡片后可用向右键进入菜单，Escape 返回卡片。标题下拉与悬停入口共享同一菜单，互斥展开。

`harness-session-hover.ts` 通过同源文档事件请求 `harness-session-dropdown.ts` 改变浮层位置，没有复制列表、重挂载 iframe 或转移 React 节点。私聊、群聊继续显示；仅在菜单及其原生子菜单区域展示和接收 iframe 输入，其他区域仍可操作原聊天。退出浮层时移除临时样式和监听器。点击会话或新会话时，先执行原生操作，再激活 Arkme 的 DSH 页面。

悬停不改变 `data-arkme-visible` / `data-arkme-follow-session`，不会把当前 DSH 会话标为已查看；账号范围不匹配、原生布局未识别、页面切换或 iframe 重载时撤销浮层。触屏不触发悬停，继续使用点击卡片和标题下拉入口。底部原有操作及排序偏好继续归官方管理。

验证：6 个相关测试文件、65 项检查通过，类型检查和完整打包通过。真实页面覆盖 DSH、私聊、群聊的会话选择，悬停保持原聊天与未读状态、跨菜单间隙、二级菜单外部关闭、创建空白会话、标题入口互斥、键盘向右键/Escape 和窄窗口边界；页面无 JavaScript 错误。证据保存在独立 profile 的 `session-hover-verification.json` 与 `session-hover-*.png`。

第二栏所有会话卡片统一使用悬停底色：普通卡片显示主题的浅色悬停背景，选中卡片在原底色上轻微加深；DSH 菜单展开期间通过 `aria-expanded` 保持来源卡片高亮。规则仅覆盖 Arkme 会话树，包含插件贡献入口和虚拟列表中的私聊/群聊/Bot，不影响原生 DSH 菜单或联系人目录。浅深色主题均适配，底色过渡为 120ms，系统减少动态效果时关闭过渡；禁用卡片不增加可点击提示。

第二栏卡片不再设置浏览器原生 `title` 提示，避免“打开：会话名”等文字浮框遮挡菜单。卡片名称、摘要、状态角标、读屏标签、高亮、点击与悬停菜单均保留。

## 隔离验证

只更新 `.codex-artifacts/web-preview-3082` 的独立 Web profile，使用已有线上账号登录。未操作桌面客户端或 3080/3081 服务。

验证覆盖真实宿主 0.1.5-rc.2 的空白页、会话切换、新会话、原有菜单、搜索、Escape/外部关闭、窄窗口、登录/未登录设置入口，以及升级结构不匹配时恢复官方导航。测试脚本和截图保存在该独立 profile 目录。

2026-09-16 验收结果：类型声明构建和完整打包成功，6 个相关测试文件、63 项测试通过。真实 Web 页面在宽/窄窗口中第一列均为 0，对话区使用剩余全部宽度；标题栏仍为 51px，轨迹往返正常。原菜单保留重命名、分叉和归档；新会话、搜索、外部关闭和 Escape 正常。浏览器临时插入长列表夹具验证菜单上限 560px、原列表滚动及底部新会话可见，未向服务端创建夹具会话。登录/未登录设置入口回归通过。

## 默认单列表

2026-09-16 的后续调整：登录后的首次启用改为单列表。没有重写官方列表或默认状态对象；通过公开插槽注册一个不显示内容的组件，共享官方工作区视图 StoreHandle，仅调用官方 `setGroupBy('flat')`。排序继续沿用官方的最近更新默认值，已有的手动排序选择也保留。

浏览器本地只新增一个“已应用默认分组”的标记，不保存第二份视图偏好。之后分组与排序由官方继续保存，刷新或重新挂载不会强制改回单列表；其记忆范围与官方一致，即当前浏览器和站点。官方改变存储键或增加其他字段时无需复制或覆盖。共享存储仅在两端插槽都为 root、且原官方分组动作存在时启用。

验证：67 项相关检查、类型检查和打包通过。真实页面首次为 flat/updated；手动选择 workspace/manual 后刷新保留；再切回 flat/updated 后刷新同样保留。没有操作桌面客户端。

## 下拉入口和紧凑工具栏

2026-09-16 的后续调整：标题按钮增加圆角边框、独立箭头分区和展开状态；去掉下拉列表的搜索及标题行，把官方视图选项、添加工作区与新会话放在底部同一行。

验证：68 项相关检查、类型检查和完整打包通过。真实宿主宽/窄窗口中，三个底部操作垂直居中且互不遮挡，移除的标题行高度为 0；60 个临时 DOM 行验证列表在 560px 上限内滚动，底部操作始终可见。会话切换、新会话和官方视图选项正常，当前对话标题栏仍为 51px，页面无 JavaScript 错误。

此 Web 运行环境由官方自动选用系统目录选择器，添加工作区点击后已验证其启动原生目录选择进程；关闭测试浏览器时自动撤销，未选择目录或创建工作区。这里只改变按钮位置，没有改成另一套目录选择器。截图和几何验证结果位于独立 profile 的 `session-dropdown-compact-*.png` 和 `session-dropdown-compact-verification.json`。

## 标题下方的模式与对话次数

后续确认的布局：标题独占第一行，模式标签与“n 次对话”并列在第二行。标题取消 360px 固定上限，使用右侧官方操作按钮以外的可用宽度，超长时省略并通过原生 title 提示显示全文。

模式标签仍由官方 `agent-preset` 贡献提供，没有复制名称、图标或交互。只识别原生标题行、标题容器和 `conversation.session.header.actions` 的关系，通过 CSS 纵向排列；原节点及 React 归属不变，卸载、退出账号或未知布局时撤销标记。

次数作为独立条目注册到公开的 `conversation.session.header.actions` 会话级列表插槽，使用框架 `useProjection('sessionStats')` 读取官方整段会话的 `turns`。不统计当前屏幕消息，不复制官方计算逻辑、不缓存第二份次数；分页、折叠、历史压缩及实时推送继续由官方负责。失败、取消及轮次何时增长也沿用宿主统计口径，与官方底部“n 轮”保持一致。数据缺失或格式不兼容时不显示次数，不用 0 代替未知值。文案通过官方 locale 接口提供中英文。

验证：8 个相关文件、59 项检查通过，包含次数更新、工具步骤不重复计数、会话数据切换、缺失值、卸载清理以及原生节点身份保留。类型检查和完整打包通过。真实页面对比顶部次数与官方底部轮次，验证宽/窄窗口、超长标题、会话切换、新会话和轨迹往返。当前两行标题区高度 77px；第二行是实际信息，并非原 tab 留白。测试用长标题仅在浏览器临时 DOM 中模拟，没有修改服务端会话标题。

## 固定标题的任务状态同步

后续调整：下拉列表里每个会话行左侧由官方渲染任务状态（`pendingInteraction` 警告点、运行中的像素跑马灯、运行中的子任务，以及完成/空闲态），但上方固定的标题按钮此前只有文字，用户聚焦标题时看不到当前会话是否还在跑。现在把选中行自己的状态节点镜像到标题按钮里。

镜像而不是重算：只识别 `[role="treeitem"][aria-selected="true"]` 行的第一个子节点槽，取其直接子级的 `[data-state]` 节点整棵克隆。状态集合、颜色、尺寸和跑马灯动画继续由官方 `StateDot` 与其 CSS Module 提供，这里不复制状态判定，也不新增第二份本地化文案。官方对「完成且未标记完成」的行本来就不渲染状态槽，此处同样不显示，标题保持原宽度。

只在签名（克隆节点的 `outerHTML` 加官方读屏文案）变化时替换克隆节点，避免每次无关重渲染都重建节点、把进行中的跑马灯动画打断。读屏文案取官方状态槽内已有的隐藏文本，拼进标题按钮的 `aria-label`，形如「切换会话：标题（进行中）」；克隆节点本身不带文字，`textContent` 仍是纯标题，读屏不会重复播报。

状态可能只改 `data-state` 而不增删节点（例如「已完成」转为「等待批准」），因此观察器的 `attributeFilter` 增加 `data-state`。适配层卸载或结构不匹配时移除克隆节点并复位签名。标题、箭头与状态三者的排布继续由既有 `[data-arkme-conversation-selector] > span:first-child` 规则承载：状态槽放在标题文字所在的第一个 span 内，不改变官方按钮的两个分区结构。

验证：`tests/harness-session-dropdown.test.ts` 覆盖状态镜像、无状态时隐藏且标题保持原文本、仅 `data-state` 变化、状态不变时节点身份保持（动画不重启）、切换选中行后丢弃过期状态、以及卸载清理；并确认去掉 `data-state` 观察项或有条件替换判断时对应用例会失败。类型检查、完整测试与打包通过。
