fix(ui): wrap message footer on narrow screens instead of overflowing (#4724-followup)

The assistant message footer (model · duration · tokens · cost · cache% · time · actions)
could overflow its row on mobile/narrow panes, pushing the per-message action buttons off
the right edge where they were unreachable. #4724 proposed ellipsing every stat, which
shrank them into unreadable stubs (D…, 98…). This instead lets .msg-foot flex-wrap to a
second line: every stat stays full-size + readable, the actions/time are pinned flex:0 0
auto so they never shrink and stay on-screen, and on wider screens where it already fits
wrap never triggers. Pure CSS. + source-structure regression test.

Co-authored-by: starship-s <starship-s@users.noreply.github.com>
This commit is contained in:
nesquena-hermes
2026-06-23 03:54:20 +00:00
parent 6a0a215b55
commit 064118d329
3 changed files with 82 additions and 2 deletions
+6
View File
@@ -3,6 +3,12 @@
## [Unreleased]
## [v0.51.597] — 2026-06-23 — Release VD (message footer wraps instead of overflowing on narrow screens)
### Fixed
- **On a phone or a narrow pane, a message's action buttons (edit / copy / retry) no longer get pushed off the right edge.** When an assistant message footer carried a lot of metadata — a long model name plus duration, token counts, cost, and cache-hit percent — the row could exceed the available width and shove the per-message action buttons off-screen, where they were unreachable on mobile. The footer now wraps to a second line when it doesn't fit, so every stat stays fully readable and the action buttons stay on-screen. On wider screens where it already fits, nothing changes. Thanks @starship-s for surfacing the overflow. (#4724-followup)
## [v0.51.596] — 2026-06-23 — Release VC (throttle reasoning SSE to stop tab freeze)
### Fixed
+10 -2
View File
@@ -5119,13 +5119,21 @@ main.main > #mainPlugin{display:none;}
display: flex;
align-items: center;
justify-content: flex-end;
/* #4724-followup: wrap to a second line on narrow screens (mobile / split panes)
instead of overflowing the row. A long model name + duration + token/cost stats
can exceed the width; without wrap the per-message action buttons get pushed off
the right edge and become unreachable on mobile. Wrapping keeps every stat fully
readable (no ellipsis) and the buttons on-screen; on desktop where it already fits,
wrap never triggers so nothing changes. */
flex-wrap: wrap;
row-gap: 2px;
gap: 6px;
margin-top: 4px;
font-size: 11px;
color: var(--muted);
}
.msg-foot .msg-actions { opacity: 1; margin-left: 0; }
.msg-foot .msg-time { font-size: 10.5px; opacity: .75; }
.msg-foot .msg-actions { opacity: 1; margin-left: 0; flex: 0 0 auto; }
.msg-foot .msg-time { font-size: 10.5px; opacity: .75; flex: 0 0 auto; }
.session-jump-btn--inline {
position: static;
right: auto;
+66
View File
@@ -0,0 +1,66 @@
"""Regression test for the #4724-followup message-footer wrap fix.
The bug: on a narrow screen (mobile / split pane) the assistant message footer
(model name · duration · token/cost stats · timestamp · action buttons) overflowed
its row, pushing the per-message action buttons (edit/copy/retry) off the right edge
where they were unreachable.
The rejected approach (#4724) made every usage span `flex: 0 1 auto` + ellipsis, which
shrank the stats into unreadable stubs (`D`, `98`). This fix instead lets `.msg-foot`
WRAP to a second line every stat stays fully readable and the buttons stay on-screen.
These are source-structure assertions on static/style.css (CSS isn't executed in the
suite), pinning the wrap behavior so it can't silently regress.
"""
import pathlib
REPO = pathlib.Path(__file__).parent.parent
CSS = (REPO / "static" / "style.css").read_text(encoding="utf-8")
def _rule_body(selector_literal: str) -> str:
"""Return the declaration block for the first rule whose head exactly matches."""
idx = CSS.find(selector_literal + " {")
if idx == -1:
idx = CSS.find(selector_literal + "{")
assert idx != -1, f"rule {selector_literal!r} not found in style.css"
brace = CSS.index("{", idx)
end = CSS.index("}", brace)
return CSS[brace + 1:end]
def test_msg_foot_wraps_not_overflows():
body = _rule_body(".msg-foot")
assert "flex-wrap: wrap" in body, (
".msg-foot must flex-wrap so a long footer wraps to a second line on narrow "
"screens instead of overflowing and pushing the action buttons off-screen"
)
# must still be a flex row
assert "display: flex" in body
def test_msg_foot_controls_pinned_not_shrunk():
# The actions + timestamp must NOT shrink/ellipsis — they stay full size and wrap
# as whole units, so the buttons are always intact and reachable.
actions = _rule_body(".msg-foot .msg-actions")
time = _rule_body(".msg-foot .msg-time")
assert "flex: 0 0 auto" in actions, ".msg-actions must be flex:0 0 auto (never shrink)"
assert "flex: 0 0 auto" in time, ".msg-time must be flex:0 0 auto (never shrink)"
def test_usage_stats_not_ellipsis_shrunk():
# Guard against regressing to the rejected ellipsis-everything approach: the usage
# spans must NOT be flex:0 1 auto with text-overflow:ellipsis (that produced the
# unreadable `D…` / `98…` stubs). They keep flex:0 0 auto and wrap as whole units.
# The usage spans share a grouped rule (.msg-usage-inline, .msg-duration-inline, …),
# so extract that group's block by its leading selector then the next `{`.
head = CSS.index(".msg-usage-inline,")
brace = CSS.index("{", head)
body = CSS[brace + 1: CSS.index("}", brace)]
assert "flex: 0 0 auto" in body, (
"usage spans must stay flex:0 0 auto (full size, wrap as a unit) — not "
"flex:0 1 auto+ellipsis, which shrinks them into unreadable stubs"
)
assert "text-overflow: ellipsis" not in body, (
"usage spans must not ellipsis-truncate; the footer wraps instead"
)