fix(ui): give the mobile refresh controls labels and full width
At <=640px the RAG "Sync sources" pill and the History "Refresh" pill squeezed down to tiny icon-only buttons — hard to discover and tap on a phone. They are now full-width labeled pills: - the RAG page-head row wraps so the Sync pill drops below the "Knowledge base" title at full width; the History page-head already wrapped the pill below its title block - the Sync label's min(16rem, 40vw) cap lifts on mobile (min-width: 0 engages the ellipsis) so the live-file text truncates against the full width instead of the 40vw cap - the Refresh glyph joins its visible label (it stays hidden on desktop, where the label carries the pill) This matches the established mobile full-width pill language (New chat / Share / stale-ban Regenerate). The three unit tests that pinned the old icon-only CSS are updated to pin the new behavior.
This commit is contained in:
+16
-13
@@ -2259,8 +2259,8 @@ details.thinking .thinking-text ul { margin: 0 0 0.5rem; }
|
|||||||
.history-refresh:hover { background: #f55a72; color: var(--bg); }
|
.history-refresh:hover { background: #f55a72; color: var(--bg); }
|
||||||
.history-refresh:disabled { opacity: 0.6; cursor: wait; }
|
.history-refresh:disabled { opacity: 0.6; cursor: wait; }
|
||||||
/* The refresh glyph is hidden on desktop (the label carries the
|
/* The refresh glyph is hidden on desktop (the label carries the
|
||||||
pill); it is the whole control below 640px (the <=640 block
|
pill); below 640px it joins the visible label in the full-width
|
||||||
mirrors the phase-46 auth-link icon-only convention). */
|
pill (the <=640 block — the icon-only squeeze is gone). */
|
||||||
.history-refresh svg { width: 16px; height: 16px; display: none; }
|
.history-refresh svg { width: 16px; height: 16px; display: none; }
|
||||||
|
|
||||||
/* ---------- History page (phase 50) ----------
|
/* ---------- History page (phase 50) ----------
|
||||||
@@ -3663,17 +3663,20 @@ details.thinking .thinking-text ul { margin: 0 0 0.5rem; }
|
|||||||
.shared-note { font-size: 0.88rem; }
|
.shared-note { font-size: 0.88rem; }
|
||||||
.footer-inner { flex-direction: column; gap: 0.2rem; text-align: center; }
|
.footer-inner { flex-direction: column; gap: 0.2rem; text-align: center; }
|
||||||
main { padding-bottom: env(safe-area-inset-bottom, 0); }
|
main { padding-bottom: env(safe-area-inset-bottom, 0); }
|
||||||
/* Sync button goes icon-only on mobile; the label hides, aria-label
|
/* Sync + History refresh: full-width LABELED pills on mobile (the
|
||||||
keeps the accessible name. The spinning icon is the visible running
|
icon-only squeeze is gone — a symbol-only control is too small a
|
||||||
state on a touch screen. */
|
target to discover on a phone). The RAG page-head row wraps so
|
||||||
.sync-btn { padding: 0.4rem 0.3rem; }
|
the Sync pill drops below the "Knowledge base" title at full
|
||||||
.sync-label { display: none; }
|
width; the History page-head already wraps the pill below its
|
||||||
/* Phase 77 task 03: the History refresh pill goes icon-only like
|
title block. The Sync label's min(16rem, 40vw) cap lifts so the
|
||||||
the phase-46 auth pill (the aria-label keeps the accessible
|
live-file text ellipsizes against the FULL width (min-width: 0
|
||||||
name); the page-head row above wraps the pill below the title
|
lets the nowrap flex item shrink, which is what engages the
|
||||||
block when the width runs out. */
|
ellipsis); the refresh glyph joins its visible label. The
|
||||||
.history-refresh { padding: 0.4rem 0.3rem; }
|
aria-labels keep the accessible names. */
|
||||||
.history-refresh-label { display: none; }
|
.page-head-row { flex-wrap: wrap; }
|
||||||
|
.sync-btn { width: 100%; }
|
||||||
|
.sync-label { max-width: none; min-width: 0; }
|
||||||
|
.history-refresh { width: 100%; }
|
||||||
.history-refresh svg { display: block; }
|
.history-refresh svg { display: block; }
|
||||||
.sync-result {
|
.sync-result {
|
||||||
position: absolute !important;
|
position: absolute !important;
|
||||||
|
|||||||
+4
-3
@@ -691,9 +691,10 @@
|
|||||||
page-head — OUTSIDE the table wrap — so it stays
|
page-head — OUTSIDE the table wrap — so it stays
|
||||||
reachable while the empty state is showing. The house
|
reachable while the empty state is showing. The house
|
||||||
inline-SVG refresh glyph (aria-hidden) + the visible
|
inline-SVG refresh glyph (aria-hidden) + the visible
|
||||||
"Refresh" label (the phase-46 auth-link convention:
|
"Refresh" label (visible at every width — below 640px
|
||||||
label visible >=640px, icon-only below; the aria-label
|
the pill goes full width and the glyph joins the
|
||||||
keeps the accessible name in both). history.js binds it
|
label; the aria-label keeps the accessible name in
|
||||||
|
both). history.js binds it
|
||||||
in the admin branch (the view is admin-gated; anonymous
|
in the admin branch (the view is admin-gated; anonymous
|
||||||
sees the gate instead) and announces the outcome in
|
sees the gate instead) and announces the outcome in
|
||||||
#history-status. -->
|
#history-status. -->
|
||||||
|
|||||||
@@ -721,9 +721,9 @@ def test_history_refresh_button_css_reuses_the_new_chat_language() -> None:
|
|||||||
hidden on desktop (the label carries the pill) — and the global
|
hidden on desktop (the label carries the pill) — and the global
|
||||||
:focus-visible ring applies (no button-scoped focus override).
|
:focus-visible ring applies (no button-scoped focus override).
|
||||||
The page-head flex row is SCOPED to #view-history (the other four
|
The page-head flex row is SCOPED to #view-history (the other four
|
||||||
views' page-heads are untouched). Below 640px the pill goes
|
views' page-heads are untouched). Below 640px the pill goes full
|
||||||
icon-only (the phase-46 auth-link convention — the aria-label
|
width with the label visible (the icon-only squeeze is gone —
|
||||||
keeps the accessible name)."""
|
the glyph joins the label)."""
|
||||||
css = _asset("styles.css")
|
css = _asset("styles.css")
|
||||||
block = re.search(r"\.history-refresh \{([\s\S]*?)\n\}", css)
|
block = re.search(r"\.history-refresh \{([\s\S]*?)\n\}", css)
|
||||||
assert block, "styles.css must style .history-refresh"
|
assert block, "styles.css must style .history-refresh"
|
||||||
@@ -746,11 +746,11 @@ def test_history_refresh_button_css_reuses_the_new_chat_language() -> None:
|
|||||||
mobile = re.search(r"@media \(max-width: 640px\) \{([\s\S]*?)\n\}\n", css)
|
mobile = re.search(r"@media \(max-width: 640px\) \{([\s\S]*?)\n\}\n", css)
|
||||||
assert mobile, "the 640px media query must exist"
|
assert mobile, "the 640px media query must exist"
|
||||||
mbody = mobile.group(1)
|
mbody = mobile.group(1)
|
||||||
assert ".history-refresh-label { display: none; }" in mbody, (
|
assert ".history-refresh { width: 100%; }" in mbody, (
|
||||||
"icon-only below 640px (the phase-46 convention)"
|
"full width below 640px (the icon-only squeeze is gone)"
|
||||||
)
|
)
|
||||||
assert ".history-refresh svg { display: block; }" in mbody, (
|
assert ".history-refresh svg { display: block; }" in mbody, (
|
||||||
"the glyph is the whole control below 640px"
|
"the glyph joins the visible label below 640px"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -380,8 +380,10 @@ def test_sync_label_css_ellipsis_truncation() -> None:
|
|||||||
""".sync-label: the live-file label ellipsizes a long
|
""".sync-label: the live-file label ellipsizes a long
|
||||||
source/relative/path inside the pill (A4) — inline-block with the
|
source/relative/path inside the pill (A4) — inline-block with the
|
||||||
min(16rem, 40vw) cap, overflow hidden, text-overflow ellipsis, no
|
min(16rem, 40vw) cap, overflow hidden, text-overflow ellipsis, no
|
||||||
wrap, baseline-aligned; the mobile squeeze's display:none override
|
wrap, baseline-aligned; on mobile the pill goes full width with
|
||||||
(icon-only button) stays."""
|
the label visible (the cap lifts, and min-width: 0 engages the
|
||||||
|
ellipsis against the full width — the icon-only squeeze is
|
||||||
|
gone)."""
|
||||||
css = _css()
|
css = _css()
|
||||||
block = re.search(r"\.sync-label\s*\{([^}]*)\}", css)
|
block = re.search(r"\.sync-label\s*\{([^}]*)\}", css)
|
||||||
assert block, "styles.css must style .sync-label"
|
assert block, "styles.css must style .sync-label"
|
||||||
@@ -397,8 +399,12 @@ def test_sync_label_css_ellipsis_truncation() -> None:
|
|||||||
assert prop in body, f".sync-label must carry {prop!r}"
|
assert prop in body, f".sync-label must carry {prop!r}"
|
||||||
mobile = re.search(r"@media \(max-width: 640px\) \{([\s\S]*?)\n\}", css)
|
mobile = re.search(r"@media \(max-width: 640px\) \{([\s\S]*?)\n\}", css)
|
||||||
assert mobile, "the ≤640px media query must stay"
|
assert mobile, "the ≤640px media query must stay"
|
||||||
assert ".sync-label { display: none; }" in mobile.group(1), (
|
mbody = mobile.group(1)
|
||||||
"the mobile icon-only override must survive the ellipsis rule"
|
assert ".sync-btn { width: 100%; }" in mbody, (
|
||||||
|
"the mobile Sync pill is full width (the icon-only squeeze is gone)"
|
||||||
|
)
|
||||||
|
assert ".sync-label { max-width: none; min-width: 0; }" in mbody, (
|
||||||
|
"the label stays visible and ellipsizes against the full width"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -326,7 +326,6 @@ def test_mobile_menu_rows_are_comfortable_targets() -> None:
|
|||||||
for untouched in (
|
for untouched in (
|
||||||
".new-chat-label { display: none; }",
|
".new-chat-label { display: none; }",
|
||||||
".auth-label { display: none; }",
|
".auth-label { display: none; }",
|
||||||
".sync-label { display: none; }",
|
|
||||||
":root { --header-h: 58px; }",
|
":root { --header-h: 58px; }",
|
||||||
):
|
):
|
||||||
assert untouched in mobile, f"the ≤640px block lost {untouched!r}"
|
assert untouched in mobile, f"the ≤640px block lost {untouched!r}"
|
||||||
|
|||||||
Reference in New Issue
Block a user