# Agent Note: Web 对话 UI 视觉优化

Status: implemented

[English](2026-07-28-web-conversation-polish-sweep.md) | 中文

## 问题

一次针对 web GUI 对话界面的设计评审发现了一批视觉呈现缺陷：portal 菜单在重新定位前会先在错误位置绘制一帧（打开时可见跳动）；只要某条步骤消息只携带工具调用头，聊天列就会把一次工具运行拆成好几组；工具行摘要打印以工作区为根的绝对路径，占掉行内大部分空间；运行中行的扫光效果用 alpha 遮罩实现，把整行都压暗；hero 区的工作区 chip 会从会话 cwd 里复现已删除工作区的文件夹名；标题栏还在 13px 的标题旁显示一个没人需要的轮次计数。

## 决策

本次修复仅改动展示层；不会有任何内容进入会话日志。

- **Portal 菜单先隐藏预渲染，绘制前完成测量。**菜单列表以 `visibility: hidden` 挂载在 (0,0)，在 `useLayoutEffect` 中测量，显示时已处于最终位置。菜单与视口保持 12px 间距并支持内部滚动；工作区创建操作固定在不滚动的页脚区。
- **聊天流跳过不渲染任何内容的助手节点。**已定稿的助手节点若其块仅含工具调用头和空白的文本／推理（reasoning）内容，就会从流推导中剔除，于是连续的工具结果合并为一组。被中断的节点始终渲染（它们携带「已停止」标记）。
- **工具行摘要把以工作区为根的路径转为相对路径。**会话 cwd 经由 toolview 插槽契约（`ToolRowOwnerProps.cwd`）逐层传递，`toolRowModel` 从以其开头的摘要中剥去该前缀；工作区之外的路径保持原样。这只影响显示：工具参数与日志均不受影响。
- **运行中的扫光效果改为高光带叠加层。**一条固定宽度的 `::after` 渐变光带横向扫过整行（即 deepsuite 的 ShimmerText 模式），取代先前的 `mask-image` 方案，ToolRow 与 Bash toolview 两处均已替换。
- **hero 区的工作区 chip 是选择器，而非回显。**没有有效选中项时（冷启动，或列表稳定后工作区被删除），它显示「Choose workspace」占位文案；由 cwd 推导的名称只用于衔接列表的首次加载，尚待确认的选择所对应的工作区从已就绪的列表中消失时，该陈旧选择会被清除。
- **统一为 16px 的纵向节奏。**聊天列间距与分组内工具行间距统一为 16px，取代原先「分组内 10px 间距加跨分组负外边距」的做法。
- **标题文字改为 14/20，标题栏去掉轮次计数**；StateDot 的进行中状态与轮次尾部采用逐格推进的像素追逐式加载视觉语言；`body` 启用灰度抗锯齿（`-webkit-font-smoothing` 及 Firefox 在 macOS 上的等价设置）。

## 考虑过的替代方案

- **挂载前根据锚点矩形同步定位菜单。**不予采纳：列表自身尺寸在布局完成前无从得知，向视口内收拢仍然需要布局后测量；对已挂载的隐藏节点做测量正是 React 与 Floating UI 文档记载的模式。
- **在宿主侧过滤空的助手消息。**不予采纳：该节点是真实的模型输出，Trajectory 与回放都必须保留它；只有聊天展示应当跳过它，且按契约 web 层只负责呈现。
- **在每个工具各自的 presenter 中做路径相对化。**不予采纳：这种冗余是所有输出路径摘要的工具共有的；在 `toolRowModel` 里做一次仅影响显示的处理即可覆盖全部工具，非聊天消费方仍拿到绝对路径。
- **保留基于遮罩的扫光。**不予采纳：遮罩会把包括状态圆点在内的整行内容压暗，其退出过渡还与悬停图标的交叉淡入淡出相互冲突；叠加光带在内容之上合成，完全不触碰内容的 alpha。
- **让 chip 继续显示已删除工作区的名称。**不予采纳：chip 是为*下一个*会话服务的选择器；用户刚删掉某个工作区，还回显它的 cwd，就是在错误呈现当前的选择。

## 后果

聊天渲染出的流条目数少于快照中的节点数：凡是拿渲染出的块与节点数对账的人，都必须把被跳过的「不渲染任何内容」的助手节点计算在内（chat-view 规格测试固定了这一点）。路径相对化只是针对会话 cwd 的前缀检查，因此会话中途重命名工作区后，摘要在重新推导前会显示绝对路径，这被接受为仅影响显示的陈旧状态。统一的 16px 节奏淘汰了原先更紧凑的 10px 工具运行外观；将来若要更紧凑的布局，应当有意识地重新引入第二个常量。菜单预渲染让每次打开多一次隐藏布局计算，在菜单的尺寸量级下开销可忽略。

## 测试

`chat-view.spec.tsx` 锁定了「不渲染任何内容」节点的分组行为（含被中断节点这一例外）；`chat-tool-row.spec.tsx` 锁定了工作区内、工作区外以及 cwd 为空时的 cwd 相对化行为；`atoms.spec.tsx` 与 `workspace-picker.spec.tsx` 覆盖菜单与 chip 的各种状态；ui-conversation、ui-primitives 与 ui-workspace 的全量测试套件通过。
