diff --git a/BENCH-NOTES.md b/BENCH-NOTES.md index 8fdffce27f5d..5231cfec64f8 100644 --- a/BENCH-NOTES.md +++ b/BENCH-NOTES.md @@ -223,3 +223,8 @@ 2026-08-21 22:46 · Optically aligned the active Plan label with its 2/3 counter and kept the canonical spinner running across the entire hover preview · ui/src/styles/chat/progress-card.css, ITERATION-NOTES.md · COMPONENT #124301 multiline | BENCH 2026-08-21 22:55 · Made Plan use the real spinner ring at rest and animate it only on hover, then replace it with a check, the truncated final step, and 3/3 when complete · ui/src/components/session-progress-card.ts, ui/src/styles/chat/progress-card.css, ITERATION-NOTES.md · COMPONENT #124301 multiline | BENCH 2026-08-21 22:56 · Kept the active Plan spinner rotating continuously instead of gating its motion on hover · ui/src/styles/chat/progress-card.css, ITERATION-NOTES.md · COMPONENT #124301 multiline | BENCH +2026-08-22 00:03 · Kept bottom error and status underlaps behind the composer so Interrupted no longer paints across the footer or send action · ui/src/styles/chat/layout.css, ITERATION-NOTES.md · COMPONENT #124301 multiline | BENCH +2026-08-22 07:33 · Normalized every in-scope Above composer neighbor to the bench width and flow instead of inheriting transcript-only offsets · ui/src/test-helpers/composer-bench.css · COMPONENT #124301 multiline | BENCH +2026-08-22 07:33 · Unified info, danger and offline underlap rows at a vertically centered height with quieter borders · ui/src/styles/chat/layout.css · COMPONENT #124301 multiline | BENCH +2026-08-22 07:33 · Projected the canonical reconnect badge onto every queued row while the composer is offline · ui/src/pages/chat/components/chat-composer-view.ts, ui/src/pages/chat/components/chat-composer-queue.ts · COMPONENT #124301 multiline | BENCH +2026-08-22 07:33 · Matched running tasks to composer and Plan chrome and aligned both on one shared status row when they coexist · ui/src/styles/chat/tool-cards.css, ui/src/test-helpers/composer-bench.ts, ui/src/test-helpers/composer-bench.css · COMPONENT #124301 multiline | BENCH diff --git a/ITERATION-NOTES.md b/ITERATION-NOTES.md index 585c8067803f..c3d106b60f5f 100644 --- a/ITERATION-NOTES.md +++ b/ITERATION-NOTES.md @@ -114,6 +114,11 @@ Local-only working ledger. Re-read after every implementation block; move an ite - [x] Align the Plan step label and 2/3 counter, and keep its canonical spinner active across the full hover preview. - [x] Keep the active Plan spinner visible but paused until hover; replace it with a check and the final step plus 3/3 when complete. - [x] Keep the active Plan spinner rotating continuously, independent of hover. +- [x] Keep bottom error and status underlaps behind the composer footer and send action. +- [x] Normalize all in-scope Above composer neighbors to the real composer width and flow. +- [x] Vertically center every lower composer status band and soften info, danger, and offline borders. +- [x] Show the canonical reconnect pill on every queued row while offline. +- [x] Match running tasks to Plan/composer chrome and share one horizontal status row when both appear. ## Observed queue contract diff --git a/ui/src/pages/chat/components/chat-composer-queue.ts b/ui/src/pages/chat/components/chat-composer-queue.ts index 014ec4b0ce03..0a0c84d6ebbf 100644 --- a/ui/src/pages/chat/components/chat-composer-queue.ts +++ b/ui/src/pages/chat/components/chat-composer-queue.ts @@ -13,6 +13,7 @@ import { renderChatAuthorAvatar } from "./chat-author-avatar.ts"; type ChatQueueProps = { queue: ChatQueueItem[]; + offline?: boolean; canAbort?: boolean; onQueueRetry?: (id: string) => void; onQueueSteer?: (id: string) => void; @@ -114,11 +115,13 @@ function renderChatQueueItem( ) { const authorAvatar = renderChatAuthorAvatar(item.sender); const hasAuthorAvatar = authorAvatar !== nothing; - const stateLabel = sendStateLabel(item); + const stateLabel = props.offline + ? t("chat.queue.states.waitingForReconnect") + : sendStateLabel(item); const failed = item.sendState === "failed" || item.sendState === "unconfirmed"; const isSteer = item.queueMode === "steer"; const steerMode = isSteer && !failed; - const reconnecting = item.sendState === "waiting-reconnect"; + const reconnecting = props.offline || item.sendState === "waiting-reconnect"; const busy = item.sendState === "executing-command"; const editing = props.editingId === item.id; const canSteer = diff --git a/ui/src/pages/chat/components/chat-composer-view.ts b/ui/src/pages/chat/components/chat-composer-view.ts index df50bafb146c..7c496a5ffe84 100644 --- a/ui/src/pages/chat/components/chat-composer-view.ts +++ b/ui/src/pages/chat/components/chat-composer-view.ts @@ -254,6 +254,7 @@ export function renderChatComposerView(context: ChatComposerViewContext) { const queue = renderChatQueue({ queue: props.queue, + offline: props.offline, canAbort: showAbortableUi, onQueueRetry: props.connected && canCompose ? props.onQueueRetry : undefined, onQueueSteer: props.connected && canCompose ? props.onQueueSteer : undefined, diff --git a/ui/src/styles/chat/layout.css b/ui/src/styles/chat/layout.css index 1685ab502473..e4f63c18313a 100644 --- a/ui/src/styles/chat/layout.css +++ b/ui/src/styles/chat/layout.css @@ -2971,6 +2971,10 @@ button.chat-pr__diff { --chat-composer-editor-line: calc(var(--chat-composer-editor-size) * 1.5); position: relative; + /* Queue, goal and status rows deliberately underlap the composer. Keep the + writing surface in the foreground so their backgrounds cannot paint over + the footer controls while their exposed content remains below it. */ + z-index: 1; container-name: chat-composer; container-type: inline-size; display: flex; @@ -3137,6 +3141,7 @@ button.chat-pr__diff { .agent-chat__composer-underlaps { --chat-composer-bottom-underlap: 18px; + --chat-composer-underlap-row-height: 44px; position: relative; z-index: 0; @@ -3147,7 +3152,7 @@ button.chat-pr__diff { ) 0 0; padding-top: calc(var(--chat-composer-shell-gap) + var(--chat-composer-bottom-underlap)); - border: 1px solid color-mix(in srgb, var(--border) 78%, transparent); + border: 1px solid color-mix(in srgb, var(--border) 16%, transparent); border-top: 0; border-radius: 0 0 calc(20px * var(--openclaw-corner-radius-scale)) calc(20px * var(--openclaw-corner-radius-scale)); @@ -3158,7 +3163,7 @@ button.chat-pr__diff { .agent-chat__composer-underlaps:has(.agent-chat__composer-error):not( :has(.agent-chat__offline-hint) ) { - border-color: color-mix(in srgb, var(--danger) 26%, transparent); + border-color: color-mix(in srgb, var(--danger) 16%, transparent); } .agent-chat__composer-underlaps:has(.agent-chat__composer-errors) { @@ -3168,13 +3173,14 @@ button.chat-pr__diff { .agent-chat__composer-underlaps:has(.agent-chat__composer-notice):not( :has(.agent-chat__composer-error) ) { + border-color: color-mix(in srgb, var(--info) 16%, transparent); background: color-mix(in srgb, var(--info) 9%, var(--chat-composer-surface)); } .agent-chat__composer-underlaps:has(.agent-chat__offline-hint):not( :has(.agent-chat__composer-errors) ) { - border-color: color-mix(in srgb, var(--warn) 26%, transparent); + border-color: color-mix(in srgb, var(--warn) 16%, transparent); background: color-mix(in srgb, var(--warn) 9%, var(--chat-composer-surface)); } @@ -3191,9 +3197,12 @@ button.chat-pr__diff { } .agent-chat__offline-hint { + display: flex; + min-height: var(--chat-composer-underlap-row-height); + align-items: center; box-sizing: border-box; width: 100%; - padding: 7px 14px; + padding: 8px 14px; background: color-mix(in srgb, var(--warn) 9%, var(--chat-composer-surface)); color: color-mix(in srgb, var(--warn) 72%, var(--text)); font-size: 11px; @@ -4138,7 +4147,7 @@ button.chat-pr__diff { align-items: center; gap: 8px; box-sizing: border-box; - min-height: 36px; + min-height: var(--chat-composer-underlap-row-height, 44px); padding: 8px 10px 8px 14px; font-size: var(--control-ui-text-sm); line-height: 1.35; @@ -4150,7 +4159,7 @@ button.chat-pr__diff { align-items: center; gap: 8px; box-sizing: border-box; - min-height: 36px; + min-height: var(--chat-composer-underlap-row-height, 44px); padding: 8px 10px 8px 14px; font-size: var(--control-ui-text-sm); line-height: 1.35; diff --git a/ui/src/styles/chat/tool-cards.css b/ui/src/styles/chat/tool-cards.css index a143a2138bc1..f5f6e69e3004 100644 --- a/ui/src/styles/chat/tool-cards.css +++ b/ui/src/styles/chat/tool-cards.css @@ -2048,8 +2048,9 @@ min-height: 34px; padding: 0 12px; color: var(--muted); - background: color-mix(in srgb, var(--bg-elevated) 76%, transparent); - border: 1px solid color-mix(in srgb, var(--text-strong) 11%, transparent); + background: var(--chat-composer-surface, var(--card)); + border: 1px solid + var(--chat-composer-hairline, color-mix(in srgb, var(--text-strong) 11%, transparent)); border-radius: var(--radius-full); font-size: var(--control-ui-text-xs); font-variant-numeric: tabular-nums; diff --git a/ui/src/test-helpers/composer-bench.css b/ui/src/test-helpers/composer-bench.css index c2085da6608e..fbd2f2c7480d 100644 --- a/ui/src/test-helpers/composer-bench.css +++ b/ui/src/test-helpers/composer-bench.css @@ -479,6 +479,32 @@ body { width: 100%; } +.composer-bench__composer-stack { + width: 100%; +} + +.composer-bench__composer-stack[data-shared-status-row] { + display: grid; +} + +.composer-bench__composer-stack[data-shared-status-row] + > :is(.composer-bench__tasks-status, .agent-chat__composer-shell) { + grid-area: 1 / 1; +} + +.composer-bench__composer-stack[data-shared-status-row] > .composer-bench__tasks-status { + z-index: 5; + align-self: start; + justify-self: start; + width: auto; + padding-top: 8px; + margin: 0; +} + +.composer-bench__composer-stack[data-shared-status-row] .chat-tasks-status { + margin: 0; +} + .composer-bench__tasks-status { box-sizing: border-box; width: 100%; @@ -492,13 +518,19 @@ body { } .composer-bench__neighbor { + position: relative; box-sizing: border-box; width: 100%; margin-bottom: 8px; } -.composer-bench__neighbor > :first-child { - margin-top: 0; +.composer-bench__neighbor > * { + position: relative; + inset: auto; + box-sizing: border-box; + width: 100%; + max-width: none; + margin: 0; } @media (prefers-reduced-motion: reduce) { diff --git a/ui/src/test-helpers/composer-bench.ts b/ui/src/test-helpers/composer-bench.ts index d4a8bf4321f8..80d92403e9b3 100644 --- a/ui/src/test-helpers/composer-bench.ts +++ b/ui/src/test-helpers/composer-bench.ts @@ -1421,7 +1421,13 @@ function renderChatSurface(sessionList: SessionsListResult) { onGatewayQuestionSkip: () => publishState({ inset: "none" }), onSlashIntent: () => {}, }); - return html`${renderBenchNeighbor()}${renderBenchTasksStatus()}${composer}`; + const shareStatusRow = state.tasks !== "none" && state.plan !== "none"; + return html`${renderBenchNeighbor()}