From 8ce80febf3032df0bf4dec739a3dc1a61e7ca594 Mon Sep 17 00:00:00 2001 From: Bob Lee Date: Sat, 1 Aug 2026 03:14:25 -0700 Subject: [PATCH] fix(flow-chat): centre single-line user messages in their bubble The action cluster keeps its 28px layout box while idle at `opacity: 0`, so it outgrew the ~22px first-line box and became the tallest item in the flex row. With `align-items: flex-start` that pinned a single-line message to the top of an over-tall row, leaving 14.24px below the text against 8.36px above it, and the text read as sitting above the bubble's centre. - collapse the action cluster to the first-line box so the text drives the row height and the bubble's symmetric vertical padding is restored - centre the buttons on the first line (previously 2.94px low) - derive the line box from the existing typography tokens so the fix follows the user's font-size preference - fold the `--failed` variant's one-sided margin into the shared rule, which also removes its residual 2.33px offset Multi-line messages are unaffected: their text already exceeded the cluster height, so bubble heights there are unchanged. --- .../components/modern/UserMessageItem.scss | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/web-ui/src/flow_chat/components/modern/UserMessageItem.scss b/src/web-ui/src/flow_chat/components/modern/UserMessageItem.scss index cd92063b1b..a5ed5b0fe2 100644 --- a/src/web-ui/src/flow_chat/components/modern/UserMessageItem.scss +++ b/src/web-ui/src/flow_chat/components/modern/UserMessageItem.scss @@ -6,6 +6,15 @@ .user-message-item { box-sizing: border-box; + /* + * Height of the first text line. The action cluster collapses to this box so + * its taller hit targets never drive the row height — see + * `.user-message-item__actions`. Variants that restyle the text (e.g. + * `--failed`) re-point it to their own line box. + */ + --user-message-line-box: calc( + var(--flowchat-font-size-base) * var(--flowchat-text-line-height) + ); /* * Fills the reading column instead of hugging its text: the bubble shares the * exact leading and trailing edge as the tool-call bars and body text below @@ -110,6 +119,18 @@ align-items: center; gap: 0.125rem; flex-shrink: 0; + /* + * The 28px hit targets are taller than the first-line box, and they keep that + * layout box while idle at `opacity: 0`. Left alone the cluster becomes the + * tallest item in `.user-message-item__main`, so `align-items: flex-start` + * pins a single-line message to the top of an over-tall row and the text + * reads as sitting above the bubble's centre. Collapsing the cluster to the + * first-line box hands the row height back to the text — restoring the + * bubble's symmetric vertical padding — and centres the buttons on the first + * line for taller messages. + */ + margin-top: calc((var(--user-message-line-box) - 28px) / 2); + margin-bottom: calc((var(--user-message-line-box) - 28px) / 2); } .user-message-item__blocks { @@ -182,6 +203,8 @@ --user-message-failed-line-box: calc( var(--user-message-failed-font-size) * var(--user-message-failed-line-height) ); + /* Smaller text than the default bubble, so the shared line box follows it. */ + --user-message-line-box: var(--user-message-failed-line-box); background: transparent; border: none; @@ -248,8 +271,8 @@ .user-message-item__actions { flex: 0 0 auto; align-items: center; - /* Match `.user-message-item__copy-btn` height (28px) to first-line box. */ - margin-top: calc((var(--user-message-failed-line-box) - 28px) / 2); + /* Vertical centring on the first line comes from the base rule, which reads + the `--user-message-line-box` override above. */ } .user-message-item__failed-body .user-message-item__steering-tag {