重构前的聊天页实际上是一个"按钮收纳柜":
- 输入框上方:一行
代码 / 计划tabs(segmented control,按钮形态) - 输入框上方:另一行
默认权限 / 完全访问(按钮形态) - 输入框上方:
设计 Agent(按钮形态) - 输入框内部 footer:附加文件按钮 + 斜杠按钮 + CLI 工具按钮 + 模型选择 + Effort + 提交(六个按钮一字排开)
- 顶部一行 RunCockpit:
⚡ Claude Code · 🔌 Anthropic / claude-sonnet-4 · 📌 Pinned · ● 健康(每段都是 button 跳到不同设置页) - 底部右下角:上下文圆环图标 + HoverCard
每个组件单独看都"合理",但拼起来用户每次发消息前要不要挨个确认这些按钮?这种"工具集合"心智反而让 chat 变成"配置页 + 输入框",不像"把任务交给 Agent 去执行"。
用户的原话:"状态、模式、能力、命令都被平铺成按钮了,所以页面看起来干净,但心智上反而有点吵。"
不是说功能要砍,而是把"用户每次发消息前不需要决定的事"从视觉决定区移走:
- 不要让用户主动选:模式 / 权限 / 模型几乎从来不动 → 默认态视觉权重压到最低(隐形 select,hover 才显形)
- 不要让用户拼:本次会用什么 Runtime / 什么 Provider / 什么权限 → 合一个面板一次说清,不要散成多个 chip 让用户脑内拼装
- 不要重复说:底部已经显示当前 Runtime → 模型选择器顶部就别再写一遍 Runtime 名
"出问题才提示,没出问题就不要显示" 是这一轮反复打磨出的核心规则。
最初的设计错误:底部 RunCockpit 常态显示 ⚡ Claude Code · Auto · ● 健康 三段,每段都用 Button 形态。三段都没问题的时候,"三个按钮"反而成了视觉噪声——用户被吸引去关注三个其实"一切正常"的状态指示。
正确的分级:
| 类别 | 视觉 |
|---|---|
| P0 阻塞 / 严重故障 | banner 醒目提示并阻断发送 |
| 温和警告(非阻塞) | composer 右槽显示一个 status chip,点击跳 Health 页 |
| 常态 OK | 完全不渲染 |
具体到这一轮:
- "已固定" 是用户主动选过的状态,不是错误,所以默认态也是浅灰文字(不是 chip)
- "Auto" 是默认态,根本不显示
- 健康度绿色 dot 在常态完全消失(之前一直绿着,给用户造成"健康度是不是个我得管理的指标"的负担)
- Runtime 标签常驻(这是真信息,告诉你"现在用什么引擎在跑")
- 出问题才上色:runtime fallback / pinned-invalid / no compatible provider / health warn 上 status-pair chip
Codex review 提出的 Run 状态聚合面板是这轮的关键改造。底层洞察:
- 散落在 4 个独立 chip 里的状态信息,用户必须自己拼装 "这次会怎么跑"
- 每个 chip 跳到不同设置页 → 用户得来回切换才能改/查
- 解决方法:把 5 个状态维度(引擎 / 模型 / 默认 / 权限 / 上下文)汇总到一个 popover,面板只负责解释,不允许在面板里改——所有"设置 / 切换 / 修改"都跳到对应设置页
为什么面板不允许直接改?两个原因:
- 把它做成"能改"的 popover 就变成又一个设置弹层,跟主路径下的 ModeIndicator / PermissionSelector / 模型 dropdown 重复
- 解释和修改的视觉权重不一样——解释面板要密、要全、要静;修改要分散到操作触手可及的位置(toolbar / dropdown)
Codex review 建议给模型选择器加搜索框 + 顶部置顶 "当前默认 / 推荐 / 最近使用" 三栏。
我们最终的决定:不加搜索框,但加"最近使用"置顶(最多 3 项)。
理由:
- 模型选择器不是"模型市场",不应该鼓励用户搜索海量模型
- 我们的用户主要是切回刚用过的 1-2 个模型,最近使用列表覆盖 80% 场景
- 搜索框加进来后必然要做高亮 / 模糊匹配 / 键盘导航 / 占位文案,引入设计复杂度
- 如果用户真的要找某个特定模型,可以滚——20 个模型滚动找不是大问题
设计初期我把 RunCockpit 的健康 dot(绿/黄/红)做成常驻显示。用户当场反馈:"这个设计目前感觉有点尴尬。健康度应该取决于长期的规划,如果你以后打算依托健康度做其他功能,那目前如果不健康或者出现警示,会给用户造成很大的心理负担。"
最终决定:常态不显示健康度(绿色 dot 完全消失);只在 severity=warn/error 时上 status chip。
延伸到所有"状态显示":默认极简,异常才扩展。这跟操作系统状态栏的设计哲学一致——平时只显示真正需要 user 关心的信息。
原始设计:Pinned chip 用 bg-primary text-primary-foreground 实色(项目主色)。
用户反馈过 3 轮,最终落到:"已固定" 跟其它工具栏组件 同色(muted-foreground),靠图钉图标 + 文字内容传达状态,不靠色彩。
教训:在低对比环境(浅灰打底的工具栏)里,任何"再轻量的色彩 chip" 都会成为视觉焦点。用户会下意识被吸引去关注那个非默认色的元素。
→ 沉淀为 memory 里的 "三层视觉规则"。
中途用户当场指出:"我他妈看不见组件名,用户也看不见组件名。" 这位用户是产品/品牌策略方向,不是程序员,读不懂 RunCockpit.tsx / text-primary / bg-status-error-muted 这种符号。
所有跟 UI 相关的沟通都改成"屏幕上看到的文字 + 位置 + 感知颜色":
- ✅ "已固定 这个标签"、"输入框右下角的百分比图标"
- ❌ "RunCockpit 的 Pinned chip"、"text-primary"
→ 沉淀为 memory feedback_describe_in_user_terms.md。
用户进入聊天页,眼睛被这些元素同时吸引:
- 顶部 4 段独立 chip,3 种不同的色彩(运行中绿色、警告黄色、固定蓝色)
- 输入框 footer 里 6 个按钮一字排开
- 输入框上方 3 行操作(代码/计划 + 默认权限 + 设计 Agent + 上下文圆环)
视觉重量分布:每个元素都"在喊"。心智负担:发消息前要不要挨个确认?
用户进入聊天页,第一眼看到的是:
- 一个紧凑输入框(柔光阴影、24px 圆角)
- 输入框下方两侧:左侧
代码默认权限(浅灰文字,几乎隐形);右侧Claude Code · 16%(一行状态文本) - 异常时左/右某处变成醒目 chip + 警告色
视觉重量集中在:输入框本身(用户当下要做的事)。其它一切是"待命"——我在那里、需要时点开就有。
这一轮中后期引入 Codex(外部 AI)做整体 review 是关键校准点。Codex 一次性提了 10 条建议,其中:
- 5 条是低风险高价值的小修补(占位文字、tooltip、aria-label、∞% 修复、模型下拉文案)→ 直接做
- 3 条需要决策方向(搜索框、运行状态合面板、权限弱化的边界)→ 跟用户对齐再做
- 1 条是大改动(context chips)→ 留下一阶段
- 1 条是不冲突的小修补(菜单项加副标题)→ 直接做
外部 review 的最大价值是 防止过拟合:当我们已经在这个面板上来回打磨 8 轮,会陷入"再调一下应该就好了"的局部最优;Codex 没经历过那 8 轮,看到的是新用户视角,能直接指出"这个面板功能对了,但右侧链接默认 40% opacity 在浅色背景下看起来像 disabled"。
→ 沉淀为后续 review 节奏:每打磨完一段视觉收敛后做一次外部 review,避免在同一处过度打磨。
经过两轮迭代(April 2026 视觉收敛 + Context chips Phase 1),用户明确这五条作为后续 Chat 页所有改动的判断基线。新增任何 UI 元素、交互、状态展示前,按这五条交叉检验。
聊天页的核心动作是对 Agent 说话。一切让用户在发送前需要"决定要不要先点一下这个按钮"的元素都是噪声。新增控件前先问:
- 99% 的会话里这个按钮会被用户动到吗?不会的话不要常驻
- 这个状态用户能从其它地方推断出来吗?能的话不要单独显示
- 这个能力是用户主动触发的吗?是的话只在意图触发时出现,不要常驻按钮化
反例:早期版本输入框上方有"代码 / 计划 / 默认权限 / 设计 Agent" 四个 outlined button 一字排开。每个单独"合理",拼起来像 settings 页。用户每次发送前都要扫一眼这些按钮的状态,心智负担超过收益。
权限 / 模型 / 上下文 / 发送 这四件事是用户对系统的硬性指令——不该被 AI 推断、不该模糊化、必须可见可改。
- 权限:决定 Agent 是否经过用户批准就能动文件 / 跑命令 — 不能隐藏
- 模型:决定哪个 LLM 真正在响应 — 不能隐藏
- 上下文:决定 Agent 看到了什么信息 — 必须可见可移除
- 发送:用户对消息边界的决定权 — 永远显式(按 Enter 或点提交按钮,不是 AI 自动发)
这四件事在 Chat 页都有明确的 UI surface:底部工具栏左侧是权限 / 模型 toggle,输入框上方是 context chips,输入框右下角是发送按钮。它们的视觉权重可以低(隐形 select / 浅灰文字),但不能消失。
跟上一条相反:Agent / MCP / CLI 工具 / Skills 这类"AI 能根据用户意图自己选择"的能力,不要做成常驻 toggle。它们应该是"按意图触发才出现"。
- 具体到当前实现:
+菜单 (添加文件上下文 / 插入命令 / 调用 CLI 工具) 全部收进一个入口 - 用户输
/才出 slash 命令选择器 - 用户输
@才出 @ 文件 / mention 选择器 - Agent 想用 MCP 工具时是它自己决定调用,不是用户每次发送前点一下"启用 MCP"
这条原则的反面是 Agent / MCP / CLI 各做一个 toggle 按钮放在工具栏 —— 早期版本就这样过,结果用户根本分不清"我要不要打开 MCP"。
Run 状态面板的职责是让用户在发送前理解 "this run":哪个引擎、哪个模型、什么权限、上下文用了多少。
它不是:
- 一个能在里面修改设置的弹层(修改请去对应 Settings 页)
- 一个 dashboard(分析 / 历史 / cost 都不属于这里)
- 一个 health 页(详细的健康检查请去
/settings#health)
每一行右边的 → 设置 链接是降权的(默认 40% opacity),鼓励"理解" 大过"操作"。如果未来发现某个修改动作高频被用户从面板里去找,那是一个独立设计任务(要不要把它升级成主操作),而不是"在面板里加更多按钮"。
用户加入 Chat 的每一份显式上下文(文件 / 目录 / @mention / 历史选区等)都必须满足三件事:
- 可见:以 chip 形式出现在输入框上方。看不到 = 用户不信任 Agent 在用什么
- 可移除:每个 chip 有移除按钮(带
aria-label="移除 X")。加错了能立刻撤 - 可估算成本:chip 上显示
~XKtoken 估算。Run 面板的上下文行汇总成(+总和 待加)。让用户在发送前知道"我这次会消耗多少"
这三件事任何一件做不到,chip 系统就退化成"装饰"。未来扩展 chip 类型(如 git diff、选区、记忆片段)必须先过这三道门:能不能可见?能不能移除?能不能估算?
1 是总纲(只关心输入框),2 + 3 是分类(什么常驻 / 什么意图触发),4 是 Run 面板的边界,5 是 Context chips 的边界。
每次设计新 Chat 页元素时按这个顺序检验:
- 它属于"非 AI 的确定性控制"吗?→ 按 2 处理(清楚但克制)
- 它属于"AI 能调度的能力"吗?→ 按 3 处理(意图触发,不常驻)
- 它属于"运行状态解释"吗?→ 按 4 处理(进 Run 面板,不进操作流)
- 它属于"用户给 Agent 的上下文"吗?→ 按 5 处理(chip 三要素)
- 都不是?→ 按 1 处理(不要加)
- 路由级 spinner 仍存在:刷新进
/chat/[id]大约 1s 路由级 spinner,然后才进入 composer 加载态。我们做的"composer 级加载文案"只覆盖 composer 已挂载但子组件数据未到的窗口 - Run 面板链接的"次级"度可能太弱:在浅色背景上 40% opacity 接近 disabled 观感
--user-bubbleCSS 变量是死代码:用户气泡不再引用,留作未来灵活性
现在 Chat 页解释了"用什么 Runtime / Model / 权限",但还没解释"Agent 会看什么"。第 9 条留作下个 phase:
- 文件树点 "Add to chat" → composer 上方出现 chip(文件名 / 类型 / 估算 tokens)
- 发送消息时把 chip 作为显式上下文传入(不再只是 UI 状态)
- Run 面板的"上下文"统计要把 chips 算进去
这一步做完,Chat 页才真正从"能发消息"变成"用户知道 Agent 带着什么去执行任务"。
- Run 面板从"配置快照"升级到"运行实况":本次用了哪些上下文、调了哪些工具、哪一步失败、为什么需要确认
- 多 Agent / @agent:必须等单 Agent 上下文+运行解释稳定后再做,否则会变成"多 Agent 同时跑但用户更不懂发生了什么"
feedback_composer_invisible_until_hover.md— 三层视觉规则feedback_describe_in_user_terms.md— 沟通要用界面语言feedback_pinned_default_hard_promise.md— Pinned + invalid 必须阻断feedback_propose_dont_ask.md— 同期反馈:不要 A/B/C 让用户选,给推荐 + 备选