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>
67 lines
3.0 KiB
Python
67 lines
3.0 KiB
Python
"""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"
|
|
)
|