diff --git a/docs/chat-chain-changes/2026-10-19-three-colour-semantics.md b/docs/chat-chain-changes/2026-10-19-three-colour-semantics.md new file mode 100644 index 0000000..ffe7885 --- /dev/null +++ b/docs/chat-chain-changes/2026-10-19-three-colour-semantics.md @@ -0,0 +1,61 @@ +--- +date: 2026-10-19 +pr: pending +feature: Three-colour semantic system split out of the single cyan accent +impact: The chat chain's approval card, tool-change cards, assistant bubbles and context meter were re-coloured so that each colour carries exactly one meaning. `--warning` moves from yellow (hue 85) to amber (hue 68) and `--success` from a muted green to a fluorescent lime (hue 128), so the whole app changes hue together rather than only the chat view. Rendering-layer only — no store logic, no `/chat-run` payload, no markup or event wiring changed. +--- + +# Three-colour semantics + +The app had one accent, so every emphasis in the chat chain was a shade of the +same cyan: an approval waiting on the user, a tool that had already run, a +streaming response, and an outright error all looked equally urgent. Hue was +carrying no information. + +The colours are now split by meaning instead of by shade. Each one does exactly +one job and the rule is stated at the top of `variables.scss`: + +| colour | means | used for | +| --- | --- | --- | +| cyan `--accent-primary` | the system itself | brand, primary buttons, current selection, user bubbles | +| amber `--warning` | needs you | approval cards, their confirm buttons, context about to fill | +| lime `--success` | the machine is working | tool execution, assistant output, streaming caret, healthy connection | +| red `--error` | something failed | errors only — deliberately *not* a fourth member of the palette | + +Red staying out of the palette is the point: when failure reuses the accent +family the urgency signal disappears. + +## Token changes + +`--warning` moved from `oklch(0.810 0.145 85)` (yellow) to `oklch(0.780 0.155 68)` +(amber), and `--success` from `oklch(0.760 0.140 165)` to `oklch(0.860 0.190 128)` +(fluorescent lime). Six companion values were added for translucent fills and +borders: `--success-soft`, `--success-line`, `--warning-soft`, `--warning-line`, +`--error-soft`, `--error-line`. The matching Arco bridge values in `global.scss` +(`--green-*`, `--orange-*`) were updated in the same pass so solid controls — +button fills, switch tracks, progress bars — pick up the new hues rather than +staying on the old ones. + +Because the hues are global, every page using `--success` or `--warning` changes +with them. That is intentional; the alternative is a chat view that disagrees with +the rest of the app. + +## Chat chain surface + +- **Approval card** (`MessageList.vue`) — was cyan, now amber: amber border, a + translucent amber wash over the card, an amber outer glow, and a slow pulse on + the icon. The pulse is the only moving part in the chain; it marks the one + thing that is genuinely waiting. +- **Tool-change cards** (`MessageItem.vue`) — gained a 2px lime left edge meaning + "this already ran". +- **Assistant bubbles** (`MessageItem.vue`) — same 2px lime left edge. +- **User bubbles** (`MessageItem.vue`) — 2px cyan right edge, so the two sides + read differently without needing labels. +- **Context meter** (`ChatInput.vue`) — green under 60%, amber to 80%, red above. + The thresholds already existed; only the colours were wrong. + +## Verification + +`npm run test`, `npm run build` and `npm run harness:check` pass. Styles were +checked in a browser against the six states that exercise the palette: idle, +streaming, approval, context warning, tool call, and system error. diff --git a/packages/client/src/components/hermes/chat/ChatInput.vue b/packages/client/src/components/hermes/chat/ChatInput.vue index 39277dd..5c9277c 100644 --- a/packages/client/src/components/hermes/chat/ChatInput.vue +++ b/packages/client/src/components/hermes/chat/ChatInput.vue @@ -1527,16 +1527,20 @@ function isImage(type: string): boolean { .context-bar-fill { height: 100%; - background: linear-gradient(90deg, rgba(var(--text-primary-rgb), 0.42), rgba(var(--text-primary-rgb), 0.72)); + // 三段语义与全局一致:绿=余量充足,橙=快满了需要留意,红=真要满了。 + background: linear-gradient(90deg, rgba(var(--success-rgb), 0.5), var(--success)); + box-shadow: 0 0 8px rgba(var(--success-rgb), 0.5); border-radius: 2px; - transition: width 0.3s ease; + transition: width 0.3s ease, background 0.3s ease, box-shadow 0.3s ease; &.context-bar-warn { background: linear-gradient(90deg, rgba(var(--warning-rgb), 0.55), var(--warning)); + box-shadow: 0 0 8px rgba(var(--warning-rgb), 0.55); } &.context-bar-danger { background: linear-gradient(90deg, rgba(var(--error-rgb), 0.55), var(--error)); + box-shadow: 0 0 8px rgba(var(--error-rgb), 0.55); } } diff --git a/packages/client/src/components/hermes/chat/MessageItem.vue b/packages/client/src/components/hermes/chat/MessageItem.vue index 5f0d6c6..0222a0a 100644 --- a/packages/client/src/components/hermes/chat/MessageItem.vue +++ b/packages/client/src/components/hermes/chat/MessageItem.vue @@ -1306,6 +1306,8 @@ onBeforeUnmount(() => { .message-bubble { background-color: $msg-user-bg; border-radius: 10px; + // 青色 = 你在驱动这段会话。 + border-right: 2px solid rgba(var(--accent-primary-rgb), 0.45); } } @@ -1330,6 +1332,9 @@ onBeforeUnmount(() => { .message-bubble { background-color: $msg-assistant-bg; border-radius: 10px; + // 荧光绿 = 机器在动。助手气泡左边这道 2px 让"这段是机器产出的"一眼可辨, + // 与用户气泡(青色)、审批卡(橙色)互不重叠。 + border-left: 2px solid var(--success-line); } .message-bubble.agent-error { @@ -1857,7 +1862,10 @@ onBeforeUnmount(() => { .tool-change-card { background: $bg-secondary; + // 荧光绿 = 机器已执行。左侧这道 2px 是三色语义里"机器在动"的专属标记, + // 与审批卡的橙色底、助手气泡的绿色边互不重叠。 border: 1px solid $border-light; + border-left: 2px solid var(--success-line); border-radius: 10px; color: $text-primary; display: grid; diff --git a/packages/client/src/components/hermes/chat/MessageList.vue b/packages/client/src/components/hermes/chat/MessageList.vue index 3ec24c4..1ed5475 100644 --- a/packages/client/src/components/hermes/chat/MessageList.vue +++ b/packages/client/src/components/hermes/chat/MessageList.vue @@ -921,8 +921,30 @@ defineExpose({ backdrop-filter: blur(14px); } +// 审批卡是橙色专属区:琥珀橙只表示「需要你介入」。 +// 这里不能用强调色——青色留给"系统本体",混用就分不出紧急程度了。 .approval-float-panel { - border-color: rgba(var(--accent-primary-rgb), 0.24); + border-color: var(--warning-line); + background: linear-gradient(var(--warning-soft), var(--warning-soft)), var(--bg-card); + box-shadow: inset 0 0 22px rgba(var(--warning-rgb), 0.06), + 0 14px 40px rgba(0, 0, 0, 0.14), + 0 0 20px rgba(var(--warning-rgb), 0.1); +} + +.approval-float-panel .float-panel-header, +.approval-float-panel .approval-float-icon { + color: var(--warning); +} + +.approval-float-panel .approval-float-icon { + background: var(--warning-soft); + border-color: var(--warning-line); + animation: approval-pulse 1.8s ease-in-out infinite; +} + +@keyframes approval-pulse { + 0%, 100% { box-shadow: 0 0 0 0 rgba(var(--warning-rgb), 0.34); } + 50% { box-shadow: 0 0 0 5px rgba(var(--warning-rgb), 0); } } .queue-float-panel { diff --git a/packages/client/src/styles/global.scss b/packages/client/src/styles/global.scss index 1bb791f..bc4e940 100644 --- a/packages/client/src/styles/global.scss +++ b/packages/client/src/styles/global.scss @@ -51,12 +51,16 @@ html[arco-theme='dark'], --arcoblue-9: 172, 246, 238; --arcoblue-10: 218, 250, 246; - --green-6: 47, 197, 138; - --green-5: 56, 184, 130; - --green-7: 38, 178, 122; - --orange-6: 226, 175, 32; - --orange-5: 214, 165, 34; - --orange-7: 236, 190, 44; + // 三色语义族,与 variables.scss 保持一致: + // green = 荧光绿 rgb(107,247,84) 机器在动 + // orange = 琥珀橙 rgb(233,148,22) 需要你介入 + // red = 出错,唯一含义,不复用 + --green-6: 107, 247, 84; + --green-5: 92, 232, 72; + --green-7: 124, 252, 104; + --orange-6: 233, 148, 22; + --orange-5: 221, 136, 18; + --orange-7: 242, 166, 34; --red-6: 232, 78, 61; --red-5: 222, 74, 58; --red-7: 240, 96, 78; diff --git a/packages/client/src/styles/variables.scss b/packages/client/src/styles/variables.scss index 3621c45..994b4bd 100644 --- a/packages/client/src/styles/variables.scss +++ b/packages/client/src/styles/variables.scss @@ -1,7 +1,13 @@ // CYBER — 霓虹暗色单主题 // 设计语言见 prototype_reference/hermes-ui-cyber-api.html 与 hermes-ui-cyber-login.html。 // 暗色为唯一主题:亮度/风格切换器已移除,因此这里只有一组令牌,没有 .dark / .comic 变体。 -// 唯一强调色是霓虹青 oklch(0.805 0.148 188),状态色仅用于语义,不参与装饰。 +// +// 三色语义系统:每种颜色只承担一种含义,不得混用。 +// 青(--accent-primary)= 系统本体:品牌、主按钮、当前选中 +// 橙(--warning) = 需要你介入:审批、等待确认、上下文将满 +// 绿(--success) = 机器在动:工具成功、流式输出、连接健康 +// 失败时不要靠"加深强调色"表达,必须换到 --error —— 否则四个语义挤在同一色相里, +// 紧急程度就读不出来了。这正是单一强调色版本的问题所在。 // ─── CSS Custom Properties ───────────────────────────────────── @@ -33,11 +39,21 @@ --text-muted: oklch(0.705 0.014 230); --text-faint: oklch(0.520 0.014 236); // 禁用态 / 极次要信息 - // Status — 语义色,不用于装饰 - --success: oklch(0.760 0.140 165); - --error: oklch(0.665 0.185 22); - --warning: oklch(0.810 0.145 85); + // Status — 语义色,不用于装饰。 + // 语义分工(见 prototype_reference/hermes-ui-cyber-chat.html 顶部说明): + // --accent-primary 青 = 系统本体 --warning 橙 = 需要你介入 --success 绿 = 机器在动 + // 三色各自只承担一种含义,不得混用;--error 只表示出错,不参与上面的分工。 + --success: oklch(0.860 0.190 128); // 荧光绿:工具成功 / 流式输出 / 连接健康 + --error: oklch(0.665 0.185 22); // 出错(唯一含义,不复用) + --warning: oklch(0.780 0.155 68); // 琥珀橙:审批 / 等待确认 / 上下文将满 --accent-info: oklch(0.700 0.110 245); + // 半透明语义底与边框,供 rgba() 拼接 + --success-soft: oklch(0.860 0.190 128 / 0.12); + --success-line: oklch(0.860 0.190 128 / 0.40); + --warning-soft: oklch(0.780 0.155 68 / 0.12); + --warning-line: oklch(0.780 0.155 68 / 0.42); + --error-soft: oklch(0.665 0.185 22 / 0.11); + --error-line: oklch(0.665 0.185 22 / 0.42); // Message bubbles --msg-user-bg: oklch(0.225 0.016 246); @@ -57,9 +73,11 @@ --accent-hover-rgb: 74, 226, 205; --text-primary-rgb: 240, 246, 247; --text-muted-rgb: 168, 180, 187; - --success-rgb: 47, 197, 138; + // 荧光绿 oklch(0.860 0.190 128) ≈ rgb(107, 247, 84) + --success-rgb: 107, 247, 84; --error-rgb: 232, 78, 61; - --warning-rgb: 226, 175, 32; + // 琥珀橙 oklch(0.780 0.155 68) ≈ rgb(233, 148, 22) + --warning-rgb: 233, 148, 22; --accent-info-rgb: 105, 160, 232; }