# 诊断交互层：插件点地图与取舍

这份文件记录「为什么是这几个面」与「还有哪些点可以考虑」。实现清单只看
[README.md](README.md)；这里放判断依据。

## 读者与场景

- 读者：现场工程师与技术支持，白天连续处理多单；一只眼看日志，一只眼看着对话。
- 任务循环：收集材料 → 判断 → 给结论 → 回报 / 沉淀。
- 一次中断的代价：切到别的界面、找到那一单、再切回来。三分钟以上的诊断会被切走十几次。

## 六条交互原则

1. **不打断**：读状态与常见写入都在对话内完成。
2. **渐进披露**：默认一行；展开才给细节；偏好在下次打开时保留。
3. **下一步只有一个**：主按钮永远指向当前最该做的事，其余进「更多操作」。
4. **写入可核对**：写前给出对象与字段，写后给结果；并发冲突给原因与刷新入口。
5. **一处定义、多处一致**：三个面共用同一份数据、同一套动作措辞与同一份偏好。
6. **降级可读**：没数据、没权限、写不了时给出原因与下一步，不拿别处的数据顶上。

## 旅程 × 插件点

「已做」是本包已实现的面；「评估过」是看过扩展点后没做的，附理由。

| 时刻 | 摩擦 | 插件点 | 状态 |
|---|---|---|---|
| 开单 | 不记得从哪开始 | `conversation.hero.options`（新会话行加「诊断」入口） | 评估过：`/diagnose` 已在空会话首屏够用，加一个按钮收益低 |
| 诊断中 | 不知道到哪步、在等什么 | `conversation.input.dock` 状态条 | 已做 |
| 诊断中 | 信息块挡消息 | 状态条收起 + 偏好持久化 | 已做 |
| 诊断中 | 要回报 / 收尾却要开面板 | 状态条「更多操作」+ 总览写入 | 已做（回报轴仅生成指令，见 README） |
| 诊断中 | 回报结果该写什么 | 回报表单（结果 / 来源 / 备注 + 指令预览） | 已做 |
| 诊断中 | 想查 case 上游有没有新结论 | 「查 case 上游状态」生成指令 | 已做：菜单先显示 gh 就绪状态（与 doctor 同一件事），prompt 让 agent 走 `verify_upstream_outcomes.py` 幂等落账（同一结局不重复累加） |
| 诊断中 | 想把结论发给同事 | 一键复制摘要（markdown 纯文本） | 已做 |
| 诊断中 | 想跳到该诊断的会话 | `sessions` / `uiWorkspace` 的打开会话方法 | 已做，仅轨迹写了 `harness_session` 时出现 |
| 回合收尾 | 这回合产出了什么、要不要沉淀 | `conversation.chat.turnTail` 每回合一行 | 评估过：需要「本回合」的 trace 增量；当前投影只有整单状态，做出来会和状态条重复 |
| 单条回复 | 复制这一条结论 / 沉淀这条 | `conversation.chat.assistant-actions`（按 `messageId`） | 评估过：要按消息取正文，`ownerProps` 只给 messageId，成本高于收益 |
| 全库巡检 | 想看多单但不想开整页 | `shell.overlay` 诊断总览 + 键盘 | 已做 |
| 全库巡检 | 输入区就在手边 | `conversation.input.right` 工具条入口（带待回报角标） | 已做 |
| 全库巡检 | 顶栏也要一个入口 | `conversation.session.header.utilities` 小胶囊 | 评估过：与工具条入口重复；再放一个会增加视觉噪声 |
| 沉淀 | 不知道该写什么 | 「沉淀为 case」生成指令、复制升格指令 | 已做 |
| 沉淀 | trace 里没标候选的单找不到沉淀入口 | 入口只看「还没沉淀、有内容」，无候选时按 trace 起草 | 已做 |
| 沉淀 | 多单都要沉淀，一单一单点太慢 | 诊断总览「可沉淀」筛选下的批次条（范围 = 当前筛选，生成一条批量指令） | 已做 |
| 沉淀 | 改沉淀状态 | `trace_transition.py --field sedimented` | 已做 |
| 收尾 | 不知道还差什么 | 总览「收尾清单」（报告 / 回报 / 沉淀 / 交接） | 已做 |
| 收尾 | 不敢点写状态 | 写入历史 + 撤销上次收尾 | 已做 |
| 诊断中 | 上游有没有新结论、上次查证何时 | 「上游查证」块（锚点 / last_verified / 依据 / 就绪） | 已做 |
| 诊断中 | 证据与沉淀线索散在 trace 里 | 「证据与沉淀」块（路径复制 / 候选指令 / 报告内看） | 已做 |
| 诊断中 | 想快速打开总览 | `⌘⇧D` / `Ctrl+Shift+D`，提示文案按平台显示（macOS 用 ⌘，其他平台用 Ctrl） | 已做 |
| 诊断中 | 在等什么、还缺什么材料 | 「在等材料」清单 + 复制 / 续接指令 | 已做 |
| 续接 | 事件太多、要逐条回看 | 「步骤时间线」按需加载 | 已做 |
| 收尾 | 要把结论发给别人 | 「复制为 issue 文本」纯文本模板 | 已做 |
| 导航 | 单多、找一单 | 搜索框 + `⌘K` 命令面板 | 已做 |
| 通知 | 切走后欠回报看不见 | 页面标题前缀计数 | 已做 |
| 工作区 | 别的项目也出现状态条 | cwd 不在 ascend-sleuth 就隐藏（含 cwd 缺失） | 已做 |
| 会话 | 续接后不知道在哪、切不回去 | 原始 / 续接会话归属 + 切到会话 + 缺会话号补写 | 已做 |
| 配置 | 想调轮询 / 默认折叠 | `settings.section` 或插件 `Config` | 评估过：当前只有两个常量，先不加设置页 |

## 三个面为什么这样分工

- **状态条**：常驻、离输入框最近，负责「现在」与「下一步」。默认收起成一个居中的窄胶囊，点开才是完整两行；收起选择记住。
- **工具条入口**：状态条收起时仍在手边；角标把「有几单欠回报」变成环境信息，不打断。
- **总览**：全库横向比较与批量动作的主场。浮层而不是新页面，关掉就回到对话；键盘操作减少鼠标往返。

没有做第四个面（顶栏胶囊）：同一个入口放三处，读者要记「哪里能点」；两处已经覆盖
「在输入区」与「不在输入区」。

## 展开态怎么收起

- 原先只有右上角一个箭头能收，目标小、离视线远；展开容易、收起要瞄准角落，操作成本不匹配。
- 点状态条外收起是浮层的通用约定，点击目标覆盖状态条以外的整片区域。点进输入框不算：收起会让输入区上移，
  正在定位光标的点击会被带偏。
- 不想被收起的人用「展开后常驻」：按钮在右上角，选择与其他偏好一起存在本地。
- 不做静置自动收起：诊断进行中有人靠它盯状态，自动收起比多点一下更糟。
- 判断「这次点击是不是在状态条里」用**手势起点**（`mousedown` 时记）与 `composedPath()`，不看点击瞬间的
  `contains(target)`：菜单项、选择器行点击会把自己从 DOM 换掉，到 `document` 时 target 已 detached，
  只看 `contains` 会把这类点击当成「点外部」收起整条，刚展开的表单跟着消失。
- 静置淡出只改透明度。`prefers-reduced-motion: reduce` 只把过渡去掉（2.8 秒后瞬间变淡），不取消变淡：
  Windows 关闭「动画效果」时 Chromium 报 reduce，若连变淡一起停掉，读者看到的是「这个效果没了」。
  透明度变化不属于位移或缩放类动效，保留它与无障碍设置的意图不冲突。
- 「更多操作」与选择器浮层优先向上展开；上方可用空间不足时向下并限高。量的是状态条上方可用的视口高度，
  以及最近一个会裁内容（`overflow` 为 auto / scroll / hidden）的祖先顶边，避免浮层被裁掉或被顶栏盖住。

## 第二行放不下时收哪一段

- 平台、框架来自 trace 的自由文本，`detected_platform` 能写几十字；性质是中断 / 精度 / 性能三类枚举。
- 放不下时有换行、溢出、截断三种处理。
- 换行让状态条高度随数据变，输入区跟着上移。
- 溢出把文字顶出状态条：实测 760px 宽的状态条里，最后一段身份标签被顶到条外；窄窗口下还越过窗口右边界。
- 选截断：平台 / 框架 / 性质与「在等什么」按省略号收。两段同时按比例收，没有谁先收。
- 身份标签保留完整宽度：它是离散的状态标签，逐段裁会变成半截标签。极端宽度下由第二行容器裁断，同样没有省略号。
- 截断不丢信息：两段都带 `title`，鼠标悬停看全文。完整值在诊断总览里一步可见，键盘能打开总览。
- 不在 JS 里按字数裁剪：字数与像素不等价，中文、ASCII、字号都会让同一字数占不同宽度。
  裁剪规则放 CSS，换主题或改字号时自动跟着走。

## 收尾表单只对进行中的单给

`trace_transition.py` 对终态单把「`--to resolved`」解释成 correct-ending：界面若照常给「已定位」，
点一下会成功写入一条 `resolved → resolved` 转移，重写收尾方式，并用表单默认值覆盖 severity / fix_emitted。
这两个字段分别是「等上游」的判据与指标来源，写错不报错。所以收尾三项只对进行中的单出现。

终态单另给两个明确入口，动作与目标都来自 `_trace_state.py` 的 `actions`：`correct-ending` 按目标渲染
「改收尾方式：已定位 / 已转交 / 不再跟进」（换状态时脚本先写一条重新打开再收尾），`backfill-ending`
只补当前状态的收尾方式。界面不重算状态机；投影没给 `actions` 时不显示这两项。

收尾表单另补两处：`summary` 是 close 动作的 fillable，缺它脚本直接拒（表单预填 trace 现值，可改）；
`fixEmitted` 初值取 trace 的 `fix_emitted`，没有记录才回落 true，避免把 false 写成 true。
所有下拉框的选项都优先取投影里的词表（`trace-status.yaml` 是唯一来源），没有该段时才用常量兜底。

## 数据新鲜度与「选中对单」

- 首屏成功后轮询失败不再静默：保留旧数据并标「数据可能过期」，折叠态胶囊、展开态标签与总览顶部都会提示；
  下次成功读取时清掉标记。取数从没成功过（缺 Python、坏检出）时，总览给原因与重试，不停在骨架屏。
- 总览选中项按 `sessionId` 记住。10 秒刷新会让列表按 `updatedAt` 重排，只按下标存的话右侧详情与动作条
  会静默换到另一条单上，读者按着 A 单的按钮改的是 B 单。
- 表单打开时冻结内容指纹（CAS 令牌），菜单开着期间外部改了 trace 会报冲突并提示刷新；用提交那一刻的
  新指纹会让屏幕上的旧值直接覆盖外部改动。

## 沉淀入口按状态判，不按候选判

`needSed`（trace 里有 case 候选）是 agent 的建议，不是入口的前提：没有候选的单调不出「沉淀为 case」，
读者就只剩「沉淀状态…」这个改状态的入口，真正要起草时无处可点。所以菜单、收尾清单与命令面板的沉淀入口
只看三件事：还没沉淀、这单不是空骨架、命中的 case 不在库里。候选只在文案上区分（无候选时注明按 trace 起草），
以及「可沉淀」筛选与批量条里继续表示「agent 已标候选」。

## 沉淀视图（v1）的四条判断

1. **队列放总览，不新开页面。** 诊断与沉淀分别回答「这一单」与「这一批」，但入口多一个就要多记一处；
   总览里加「诊断 / 沉淀」切换，键盘与动作模式复用。
2. **合入状态是「已提 PR」上的属性，不是第六步。** 队列只有五步；`state` 带 `checked_at` 与
   `source`，查不到写「未确认」，不由分支是否存在推断。多一个阶段会让状态机与文案一起变复杂。
3. **提 PR 指令默认短。** 流程在 `/knowledge-groom` 里，界面只补 agent 看不到的批次、决定、
   落点与边界；命令细节折叠，避免出现第二份流程说明。
4. **远端目标只问一次，只列事实。** 目标存在主检出的 `.local/settings.json`（旧台账的 `target` 段仍读，
   用于迁移）；选择器列 host、平台、HEAD 分支、适配器/token 状态，不预设哪个是团队仓、哪个是上游。
   场景没收敛前，默认留空。

扩展面：PR 平台是 `scripts/platforms/` 下的适配器；界面只读 `{state, checked_at, source}` 与平台显示名，
新增平台不改界面与核心脚本。doctor 从适配器声明里查 token/命令，不写平台特判，也不代装。

## 沉淀队列跟到 PR 合入（v0.16）

1. **队列的对象是事项，不是 inbox 草稿。** 草稿进了 `.done/` 只是「本地收尾完成」，不等于事情做完；
   只要 PR 没合入，就该有一行可看。`.done/` 的标题、close-log 的决定、台账的 PR 三者合并成一行，
   合并只在 `sediment_state.py` 做。
2. **出队等合入。** 合入前收尾会先删掉人的决定与评估证据，PR 被拒时只能重评；trace 也会提前写
   `knowledge`。`groom_close_draft.py` 在 PR 不是 merged（含 open / unknown / closed）时拒绝执行，`--allow-open-pr` 才越过。
3. **查合入就地做。** 一次查询是台账写动作，不是对话动作。批次条「查全部合入状态」跑
   `pr_status.py --check-all`；详情里的「查合入状态」跑单条，同一个动作只留一个入口。
   成功与失败都回提示：单条给 PR 号与新状态，整批给更新条数，失败给原因。
   没有写权限时单条与批次都回落到生成查证指令；没有适配器或 token 时在行内给原因。
   状态只认平台查询或人工确认，不从分支推断。
4. **关闭的 PR 给恢复命令，不自动搬文件。** `.done/<key>` 移回 inbox 会改变队列，交给人点一次；
   移动命令按平铺 / 目录两种草稿形态生成。
5. **过期压过待合入。** PR 开着但评估已经过期时，队列先让人重跑 groom；PR 记录留在详情里，
   不让人按「查合入」去做一件已经不该做的事。
6. **平台一直查不到时有人工兜底。** 详情里的「手动改状态」按人确认写 PR 台账，来源记为「手动确认」。
   四档与 `pr_status.py --mark` 的 `open / merged / closed / unknown` 一致，下一次查合入仍以平台结果为准。
   没选推送目标不等于没有 PR：这句提示只在要开 PR 的行出现。

## 总览面板的排版与控件

- 字号取八档、字重取四档，值写在 `.scp-pal` 的 `--scp-fs-*` / `--scp-fw-*` 上；命令面板、表单控件与浮层提示同样从这两组 token 取值；状态条共用的 `.scp-menu-note` / `.scp-case` 仍是固定值。
- 诊断视图的筛选从标题栏移到左列搜索框下方，与沉淀视图一致。两个视图都在这处筛选，切换视图后位置不变；标题栏只留标题、视图切换与关闭。
- 详情按「识别行 → 问题背景 → 区块」排列，不再把案例首句放大成标题。真机 trace 的 `summary` 常见一两百到七八百字；
  16.5px / 650 会把首句渲染成七八行粗体（离线夹具的 `summary` 是一句话，看不出来）。识别行一行给出「这是哪一单、做到哪一步」。
- 沉淀视图右列的草稿详情用同一套识别行与正文背景样式，两处不再各写一种。
- 问题背景按正文 13px / 1.75 排四行，超出时给「展开全文」，同一段不在底部重复。展开入口先读实际溢出
  （`scrollHeight` 与 `clientHeight` 比较）；替身渲染拿不到布局尺寸时，退回 110 字近似。
- 摘要里的路径、错误码、函数调用与 `XxxError` 走等宽字加浅底（`inlineTokens`：只按形状识别，不改文本）；
  展开状态按单号记，切到别单回到收起。
- 区块标题从 12px / 600 深色降到 11.5px / 600 次要色：它只负责分节，不需要比正文更显眼；卡片边框降到 72% 透明度，
  圆角 12 收成 10；字段标签用 11px 次要色；「在等」从色块标题加正文两行收成一条窄行。
- 底部动作收成一个主操作、一个复制摘要和「更多操作」，常驻在详情底部。其余动作收进菜单，窄宽度下不再堆叠按钮。
- 沉淀进度轨的类名从 `.scp-step` 改为 `.scp-sed-step`（`-dot` / `-label` / `-note` 一并改名）。原类名与诊断详情里的「第 N/6 步」共用，进度轨画连线的 `::after` 因此落到了那行文字上，显示成一条盖住文字的灰线。

## 写入安全

- 能力位来自当前会话沙箱档位；只读档位下写入项禁用并显示原因。
- 每次写入带内容指纹；冲突返回 `E_STATE_CHANGED`，界面提示刷新。
- 破坏性收尾（不再跟进）在菜单里单独一组；已定位与转交需要表单字段，不靠一次点击。
- 回报轴当前没有脚本写入口，界面只生成指令，不写 `feedback`。

## 验证边界

渲染冒烟与 Host 冒烟覆盖：状态分支、折叠偏好、点外部 / Esc 收起与常驻偏好、总览键盘、沉淀队列 / 指令 / 目标选择、写入参数、
能力位、CAS、真实脚本写入（在系统临时目录的 trace 副本上做，仓库不被改）。

未覆盖：真实页面排版、鼠标点击、深浅色主题下的观感。这三项只能人工确认。
