From 7c15cd46d234c8e08845e951a1c979aab3473efc Mon Sep 17 00:00:00 2001 From: iris Date: Tue, 18 Aug 2026 22:24:53 +0200 Subject: [PATCH 1/3] swarm-ui: don't break at a narrow viewport MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #3447. - Shell.css: .shell-header and .shell-nav wrap onto a second line below a breakpoint instead of forcing horizontal overflow — no hamburger/collapse menu, per mara's own 'don't break, don't over-invest' scoping. - LinksMenu.css: bumped .links-menu-button and .links-menu-item to the same 2.75em (~44px, WCAG 2.5.5) touch-target floor the shared ui/ kit's form controls and buttons already carry — this trigger is bespoke, not built from that kit, but the floor applies regardless. - Table.tsx/.css: wrapped the table in a .ui-table-scroll container (overflow-x: auto) so a wide table scrolls within its own box on a narrow viewport instead of forcing the whole page to overflow horizontally — not named in the issue's two concrete spots, but the same 'should not break' failure mode, and fixing it at the shared Table primitive covers every future caller. .create-agent-input's fixed width -> max-width was already fixed as part of #3448's form kit. Verified: tsc clean, build succeeds, screenshotted at 320px (hives nav wraps, table scrolls internally, page itself doesn't overflow) and 480px (create-agent form fields fit). --- .../packages/swarm-ui/src/shell/LinksMenu.css | 11 ++++-- .../packages/swarm-ui/src/shell/Shell.css | 10 ++++- .../packages/swarm-ui/src/ui/table/Table.css | 5 +++ .../packages/swarm-ui/src/ui/table/Table.tsx | 38 +++++++++++-------- 4 files changed, 45 insertions(+), 19 deletions(-) diff --git a/frontend/packages/swarm-ui/src/shell/LinksMenu.css b/frontend/packages/swarm-ui/src/shell/LinksMenu.css index 68dfc5fd..ddb2fd89 100644 --- a/frontend/packages/swarm-ui/src/shell/LinksMenu.css +++ b/frontend/packages/swarm-ui/src/shell/LinksMenu.css @@ -1,6 +1,10 @@ /* — a single header icon-button + popover. Kept visually quiet (no border/fill until interacted with) so it reads as chrome, - not another nav item. */ + not another nav item. Button is sized to the same touch-target floor + as the shared `ui/` kit (2.75em ≈ 44px, WCAG 2.5.5) even though it + isn't built from that kit — a bespoke icon trigger, not a form + control, but the floor applies regardless of which component drew + it. */ .links-menu { position: relative; margin-left: auto; @@ -9,8 +13,8 @@ display: flex; align-items: center; justify-content: center; - width: 2em; - height: 2em; + width: 2.75em; + height: 2.75em; padding: 0; border: 1px solid transparent; border-radius: 0.4em; @@ -43,6 +47,7 @@ display: flex; align-items: center; gap: 0.5em; + min-height: 2.75em; padding: 0.4em 0.6em; border-radius: 0.35em; color: var(--fg); diff --git a/frontend/packages/swarm-ui/src/shell/Shell.css b/frontend/packages/swarm-ui/src/shell/Shell.css index 4a6de52c..9b8833c1 100644 --- a/frontend/packages/swarm-ui/src/shell/Shell.css +++ b/frontend/packages/swarm-ui/src/shell/Shell.css @@ -2,10 +2,17 @@ renders into. Colour vars come from ../theme.css (same base16 slots the per-hive dashboard uses, so this reads as recognizably hyperhive) — the layout itself is this package's own, not - @hive/shared's chrome.css. */ + @hive/shared's chrome.css. + + `flex-wrap` on the header and nav rather than a hamburger/collapse + menu: a narrow viewport (phone or a tiled desktop window) wraps onto + a second line instead of overflowing — "don't break, don't + over-invest" is the explicit scope here, not full mobile navigation + redesign. */ .shell-header { display: flex; align-items: center; + flex-wrap: wrap; gap: 1.5em; padding: 0.75em 1.25em; border-bottom: 1px solid var(--border); @@ -18,6 +25,7 @@ } .shell-nav { display: flex; + flex-wrap: wrap; gap: 1em; } .shell-nav-link { diff --git a/frontend/packages/swarm-ui/src/ui/table/Table.css b/frontend/packages/swarm-ui/src/ui/table/Table.css index f990dae6..68407656 100644 --- a/frontend/packages/swarm-ui/src/ui/table/Table.css +++ b/frontend/packages/swarm-ui/src/ui/table/Table.css @@ -1,3 +1,8 @@ +/* Scrolls horizontally within its own box on a narrow viewport instead + of forcing the whole page to overflow — see Table.tsx's comment. */ +.ui-table-scroll { + overflow-x: auto; +} .ui-table { width: 100%; border-collapse: collapse; diff --git a/frontend/packages/swarm-ui/src/ui/table/Table.tsx b/frontend/packages/swarm-ui/src/ui/table/Table.tsx index 3e852063..e8e82c44 100644 --- a/frontend/packages/swarm-ui/src/ui/table/Table.tsx +++ b/frontend/packages/swarm-ui/src/ui/table/Table.tsx @@ -3,6 +3,12 @@ // their own cell rendering rather than this component knowing about // any particular row shape, so it stays reusable for the swarm-wide // agent roster later without a rewrite. +// +// Wrapped in a scrollable container rather than letting a wide table +// force the whole page to overflow horizontally on a narrow viewport — +// a `` doesn't shrink below its content's natural width on its +// own, so without this the page itself would break, not just look +// cramped. import type { ComponentChildren } from 'preact'; import './Table.css'; @@ -22,23 +28,25 @@ export function Table({ rowKey: (row: T) => string; }) { return ( -
- - - {columns.map((c) => ( - - ))} - - - - {rows.map((row) => ( - +
+
{c.header}
+ + {columns.map((c) => ( - + ))} - ))} - -
{c.render(row)}{c.header}
+ + + {rows.map((row) => ( + + {columns.map((c) => ( + {c.render(row)} + ))} + + ))} + + + ); } From ea7cc03fd7fd92e47ea3e13dbedf565d435c791c Mon Sep 17 00:00:00 2001 From: iris Date: Tue, 18 Aug 2026 22:29:30 +0200 Subject: [PATCH 2/3] swarm-ui: fix .shell-nav-link touch target argus caught MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The proposed scope on this issue named three things to give the 2.75em touch-target floor: nav links, buttons, and the links-menu trigger. Only the links-menu trigger actually got it in the first pass — the primary nav (hives/new agent/jobs/components), the highest-traffic touch target in the whole shell, was still sitting at padding: 0.25em 0 (~27px tall). display: flex + min-height rather than more padding: padding alone would have pushed the active-state border-bottom away from the text to reach 2.75em; flex centres the text within the full-height box so the indicator still sits directly under it. Verified: build succeeds, screenshotted at desktop width and 320px — active underline still sits correctly under the text at the taller height, wrap behavior from the earlier commit unaffected. --- frontend/packages/swarm-ui/src/shell/Shell.css | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/frontend/packages/swarm-ui/src/shell/Shell.css b/frontend/packages/swarm-ui/src/shell/Shell.css index 9b8833c1..48d0a31f 100644 --- a/frontend/packages/swarm-ui/src/shell/Shell.css +++ b/frontend/packages/swarm-ui/src/shell/Shell.css @@ -28,10 +28,20 @@ flex-wrap: wrap; gap: 1em; } +/* `display: flex` + `min-height` rather than more vertical padding: a + link's own height is text-line-height, and padding alone would have + pushed the border-bottom indicator away from the text to hit 2.75em + — flex centres the text within the full-height box instead, so the + indicator still sits directly under it. Same 2.75em (~44px, WCAG + 2.5.5) floor as the rest of the shared kit — this is the primary + nav, the single most-clicked/tapped element in the whole shell, so + it gets the floor same as everything else, not a smaller one. */ .shell-nav-link { + display: flex; + align-items: center; + min-height: 2.75em; color: var(--muted); text-decoration: none; - padding: 0.25em 0; border-bottom: 2px solid transparent; } .shell-nav-link:hover { From 328ba1959542ed8c0c86b42f4c301147c4ab5f52 Mon Sep 17 00:00:00 2001 From: iris Date: Tue, 18 Aug 2026 22:35:58 +0200 Subject: [PATCH 3/3] swarm-ui: fix nav underline gap argus caught in round 2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous fix put both the touch-target min-height and the active-state border-bottom on the same : centering the text within a 2.75em box pushed the border ~0.7em away from it, a real visible regression argus caught by actually rendering the CSS rather than reasoning about the box model abstractly. Split the two concerns onto two elements: the (.shell-nav-link) owns the full-height tappable box, an inner (.shell-nav-link-text) wrapping just the label owns the underline, so the indicator stays directly under the text regardless of the tappable box's height. Verified: tsc clean, build succeeds, screenshotted close-up (600x100) and at 320px — underline sits flush under the text in both, no gap. --- .../packages/swarm-ui/src/shell/Shell.css | 25 ++++++++++++------- .../packages/swarm-ui/src/shell/Shell.tsx | 8 ++++-- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/frontend/packages/swarm-ui/src/shell/Shell.css b/frontend/packages/swarm-ui/src/shell/Shell.css index 48d0a31f..b3bfffc4 100644 --- a/frontend/packages/swarm-ui/src/shell/Shell.css +++ b/frontend/packages/swarm-ui/src/shell/Shell.css @@ -28,25 +28,32 @@ flex-wrap: wrap; gap: 1em; } -/* `display: flex` + `min-height` rather than more vertical padding: a - link's own height is text-line-height, and padding alone would have - pushed the border-bottom indicator away from the text to hit 2.75em - — flex centres the text within the full-height box instead, so the - indicator still sits directly under it. Same 2.75em (~44px, WCAG - 2.5.5) floor as the rest of the shared kit — this is the primary - nav, the single most-clicked/tapped element in the whole shell, so - it gets the floor same as everything else, not a smaller one. */ +/* Touch target and active-state underline are deliberately on two + different elements. An earlier version put both `min-height: 2.75em` + and `border-bottom` on the same box: centering the text within a + 2.75em box pushes a bottom-edge border ~0.7em away from the text, + which argus caught by actually rendering it — a real visual + regression, not just a touch-target win. So: `.shell-nav-link` (the + ``) owns the full-height tappable box (same 2.75em / ~44px WCAG + 2.5.5 floor as the rest of the shared kit — this is the primary nav, + the single most-clicked/tapped element in the whole shell), and + `.shell-nav-link-text` (an inner span hugging just the text) owns + the underline, so the indicator stays directly under the label + regardless of how tall the tappable box around it is. */ .shell-nav-link { display: flex; align-items: center; min-height: 2.75em; color: var(--muted); text-decoration: none; - border-bottom: 2px solid transparent; } .shell-nav-link:hover { color: var(--fg); } +.shell-nav-link-text { + padding-bottom: 0.25em; + border-bottom: 2px solid transparent; +} .shell-nav-link-active { color: var(--fg); border-bottom-color: var(--purple); diff --git a/frontend/packages/swarm-ui/src/shell/Shell.tsx b/frontend/packages/swarm-ui/src/shell/Shell.tsx index fe5fc69d..70538e0b 100644 --- a/frontend/packages/swarm-ui/src/shell/Shell.tsx +++ b/frontend/packages/swarm-ui/src/shell/Shell.tsx @@ -34,8 +34,12 @@ const DEFAULT_BRAND = 'hyperhive swarm'; function NavLink({ href, label }: { href: string; label: string }) { const [active] = useRoute(href); return ( - - {label} + + {/* Touch target (min-height) lives on the so the whole row is + tappable; the active-state underline lives on this inner span + so it hugs the text instead of sitting at the bottom of the + full-height box, ~0.7em away from it. */} + {label} ); }