From d812e2f98f6ae2e49c6192d60e8471524c2936d1 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Sun, 6 Sep 2026 11:31:35 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=8F=20style:=20Center=20Every=20Row=20?= =?UTF-8?q?Glyph=20on=20the=20Avatar's=20Axis=20(#15643)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 📏 style: Center Every Row Glyph on the Avatar's Axis Two rails were in use under a message header. `AgentHandoff` and `AgentUpdate` gave their glyph a 24px slot — the width of the header avatar — so the glyph centered on the avatar's axis and the text started where the name does. Every other row used a 16px (thinking: 18px) slot: tool cards through `ProgressText`, `ToolCallGroup` headers, the phase card's check, and the streaming cursor a folded card renders beneath itself. Measured in Chromium against the avatar's left edge: avatar center 12, those glyphs 8–9, and their text at 24–26 against the name's 32. The cursor was also 5px low, because `.result-thinking` hangs its dot 11px above a text line's box, which is right for the streaming markdown cursor and wrong for a row. `ROW_GLYPH_SLOT` in `rows.ts` is the one definition: a 24px, centered slot (`min-w` rather than `w`, so a stacked icon strip can run wider). It is taken by `ProgressText`'s icon, both `ToolCallGroup` glyph branches, the phase check, the thinking button, and the cursor; `AgentHandoff`'s gap drops from 2.5 to 2 so its text meets the name too. The cursor stops borrowing `EmptyText` and draws the dot through `.result-thinking-inline`, a variant that puts the same pseudo-element in flow so the slot can center it. After: every glyph at 12, every text edge at 32, the dot centered in its row. * 📏 fix: Keep the Glyph Slot Inside the Row and the Dot Override Local Review follow-ups on the row rail. - The slot was 24px tall inside a 20px row. `ProgressText` positions its content absolutely at the row's top, so a taller slot could not be centered by the row and carried every call card's glyph and label 2px low. The slot keeps its 24px width and takes the row's 20px height. - The pending skill row rendered its icon straight into the gap; the real `SkillCall` that replaces it in place used the slot, so the swap moved the glyph 4px and the text 8px. Both rows share the slot now. - The cursor's in-flow dot no longer needs a global selector: `after:!static` on the element puts the one pseudo-element back in flow, and the `!` is what outranks the dot rule's three-class selector. The thinking button takes the shared slot instead of its own 24px square. - A `span` wrapped `StackedToolIcons`, which renders a `div` for a strip; the wrapper is a `div` now. * 📏 fix: Give ThinkingLabel the Shared Glyph Slot `ThinkingLabel` exists to keep the reasoning-unavailable row identical to the `ThinkingButton` row it stands in for, and the previous commit moved only the button onto the shared slot — a detached subagent projection would have kept its glyph at 9px and its text at 24px next to rows at 12px and 32px. * 📏 fix: Put the Image-Gen and Web-Search Glyphs in the Shared Slot `Part.tsx` routes image generation and web search to renderers that draw their own glyph instead of going through `ProgressText`, so the slot the previous commits gave every other row skipped them: their 16px icons and the favicon stack still sat on the old axis, and a labeled phase containing either call would have shifted sideways as it folded into the updated header. Both rows take `ROW_GLYPH_SLOT`; the web-search streaming row's gap drops from 2.5 to 2 so its text meets the name's edge like the rest. --- .../Messages/Content/ActivityPhaseGroup.tsx | 31 ++++++++++--------- .../Chat/Messages/Content/AgentHandoff.tsx | 2 +- .../Parts/OpenAIImageGen/OpenAIImageGen.tsx | 5 ++- .../Content/Parts/PendingSkillCall.tsx | 13 +++++--- .../Chat/Messages/Content/Parts/Thinking.tsx | 5 +-- .../Chat/Messages/Content/ProgressText.tsx | 7 ++++- .../Chat/Messages/Content/ToolCallGroup.tsx | 18 ++++++----- .../Chat/Messages/Content/WebSearch.tsx | 21 ++++++++----- .../__tests__/ActivityPhaseGroup.test.tsx | 19 +++++++++--- .../ContentParts.integration.test.tsx | 10 ++++-- .../components/Chat/Messages/Content/rows.ts | 12 +++++++ 11 files changed, 96 insertions(+), 47 deletions(-) diff --git a/client/src/components/Chat/Messages/Content/ActivityPhaseGroup.tsx b/client/src/components/Chat/Messages/Content/ActivityPhaseGroup.tsx index fcbac246c8..f31f39b245 100644 --- a/client/src/components/Chat/Messages/Content/ActivityPhaseGroup.tsx +++ b/client/src/components/Chat/Messages/Content/ActivityPhaseGroup.tsx @@ -12,8 +12,8 @@ import { } from '~/hooks'; import useSmoothStreaming from '~/hooks/Messages/useSmoothStreaming'; import { getActivityLabelText } from '~/utils/activityLabels'; -import { AttachmentGroup, EmptyText } from './Parts'; -import { TOOL_ROW_CLASSES } from './rows'; +import { ROW_GLYPH_SLOT, TOOL_ROW_CLASSES } from './rows'; +import { AttachmentGroup } from './Parts'; import { cn } from '~/utils'; /** Matches `EXPAND_TRANSITION` so the panel and the label ticker resolve on @@ -57,18 +57,15 @@ function schedulePostPaint(callback: () => void): () => void { } /** The header's icon slot. Its only job is geometric: every other row in the - * transcript opens with a 16px glyph and an 8px gap, so a summary rendered - * without one sits 24px to the left of the rows it replaces — the fold then - * moves its own text sideways at the moment the reader is trying to follow - * it. */ + * transcript opens with the same glyph slot and an 8px gap, so a summary + * rendered without one sits to the left of the rows it replaces — the fold + * then moves its own text sideways at the moment the reader is trying to + * follow it. */ function PhaseGlyph({ failed }: { failed: boolean }) { const Icon = failed ? TriangleAlert : Check; return (