fix(ui): stop Settings, Agent Detail and the Dashboard header widening the page (#2197) - #2782
Conversation
Review —
|
| width | header height | stats | controls | past row edge | overlap |
|---|---|---|---|---|---|
| 1600–900 | 30px | 813 → 113 | 692 | 0 | 0 |
| 768–640 | 91px | full width | full width | 0 | 0 |
So "unchanged above ~900px" is measured, not asserted, and the wrap point is where the ladder actually runs out.
[I1] The min-w-[4.5rem] floor is a magic number one level away from the thing it mirrors (confidence 7/10)
The floor is "the ladder's own agents-only width (~71px)", but the ladder lives in @container statsbar rules in Dashboard.vue's <style> (330px / 200px thresholds) and the floor is a Tailwind arbitrary value in the class string. Nothing fails if one moves. It is the right value; consider a comment on the @container block pointing back at the floor, so the next person editing either sees the pair.
[I2] flex-wrap on justify-between changes alignment when it wraps (confidence 8/10, cosmetic)
Once AgentHeader row 1 wraps, the actions cluster is alone on its line, so justify-between degenerates to flex-start and the buttons left-align where they used to sit right. Visible in the 375px screenshots and arguably fine on a phone — but it is a real visual change beyond "nothing shifts", and justify-end on the wrapped arm (or ml-auto on the cluster) would keep the previous alignment if that is preferred.
Checked and clean
- The spec measures the contract's actual statement — document scroll width — and names the widest unclipped element on failure. Given the issue's own mis-attribution, an element-shaped assertion would have been the wrong gate.
- The
readypredicates are load-bearing and proven so: the Settings arm passed with its own fix reverted on a fixed settle, because the admin-only<select>renders after/api/users/me. Catching that by running the negative control rather than re-reading the test is the right instinct, and the write-up says so. - Negative control reproduces the exact offenders (+157px naming the
<select>, +496px naming the action cluster) — so the gate bites. dashboard-stats-overflow.spec.js(bug: Dashboard stats bar — right controls overlap host telemetry below ~1170px effective width #1830) goes from red ondevto green here, which is the issue's stated regression gate.- Scope: 4 files, no unrelated churn.
Honest residual worth stating in the PR body
The sweep covers five pages. Agent Detail is fixed by fixing AgentHeader, but other agent-scoped tabs (Files, Schedules, Canvas) are not measured, so "the page body never scrolls" is proven for what is swept and assumed elsewhere.
336fb2f to
f94b4c0
Compare
4e6fff4 to
4458af1
Compare
f94b4c0 to
14fc78f
Compare
/review — post-rebase (stacked on
|
14fc78f to
459fcad
Compare
1b080af to
a2a60c0
Compare
459fcad to
dc0dee1
Compare
dc0dee1 to
69c677e
Compare
bc287f2 to
1c10dd1
Compare
…g the page (#2197) Three rigid flex rows scrolled the page body below 1024px, against the design-system rule that wide content scrolls inside its own container. The report blamed the tab strips, and that is not what was wrong. `OverflowTabs` was collapsing correctly the whole time, at 375px included: the hidden mirror row it measures against lives in a 0x0 `overflow: hidden` box and is invisible to the user and to `scrollWidth` alike, but a probe that does not skip clipped subtrees reports it as the widest thing on the page — which is how a measurement sweep came to name `NAV.-mb-px.flex` and the "More" button. Measured with clipped subtrees excluded, the real offenders are: * **Agent Detail** — three `AgentHeader` rows, each a non-wrapping flex row of two rigid clusters: identity + Workspace/Talk/Running, toggles + Tags + the live CPU/MEM/uptime readout, and the lifetime-cost line. Together ~790px of min-content, so the page widened at every width below ~1024px (+496px at 375). * **Settings** — the Default Model `<select>`, `flex-1` with the default `min-width: auto`. A select's min-content is its widest OPTION ("Claude Opus 5 — Most capable Opus (latest) (recommended)", ~417px), so one control scrolled the page by 157px at 375. `min-w-0` is the whole fix. The Dashboard overlap is a different failure with the same root shape. #1830 made the stats cluster elastic so it degrades instead of being clipped — but elastic with no floor means it collapses to ZERO, so the pressure never reached the controls and the row never wrapped. Measured at 640px: stats 0px wide (its clipped children still sitting under the controls' first button — the 8px the spec reports), controls pinned at their 692px max-content and running ~70px past the row's right edge. The row now wraps, the stats cluster carries the ladder's own agents-only floor (~71px) so the overflow becomes a wrap, and the controls may shrink and wrap their own buttons rather than being pinned wider than the row. Rendering above ~900px is unchanged — measured single 30px row at 1600/1440/1280/ 1024/900, wrapping to 91px at 768 and below, zero overlap at every width. `space-x-*` becomes `gap-*` on each row that can now wrap: a wrapped `space-x` row mis-indents every line after the first. Verified against a local dev instance in BOTH themes at 375 and 640 (light and dark, `dark` class asserted): `scrollWidth <= clientWidth` on Settings, Agent Detail, Dashboard, Operations and Library at 375/640/768/1024. `e2e/dashboard-stats-overflow.spec.js` (#1830), red on `dev`, is green. The new `e2e/body-horizontal-overflow.spec.js` measures the one thing the contract states — the document scroll width — and names the widest UNCLIPPED element on failure, because an element-shaped assertion is exactly what produced a report naming the wrong element. Proven to bite: with these three fixes reverted it fails Settings at +157px naming the `<select>`, and Agent Detail at +496px naming the action cluster. Its `ready` predicates are load-bearing, not politeness. The widest markup on both pages is gated behind a later render — Settings' admin-only `<select>` behind `/api/users/me`, Agent Detail's live stats behind the stats poll — and on a fixed settle the Settings arm PASSED with the fix reverted. That vacuous green was caught by running the negative control, not by reading the test. Fixes #2197 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q19uRCksdn4DiRAJ55rfpZ
…ith the code (#2197) — mechanical, per the merge-train note on the PR Dashboard.vue said the controls cluster is flex-shrink-0 (this PR removed it); AgentHeader.vue said min-w-0 truncates the name (the h1 carries no truncate). Prose only, no class changed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
merge-train: rebased onto |
69c677e to
c35446d
Compare
vybe
left a comment
There was a problem hiding this comment.
merge-train: validated (lane B), rebased onto dev, all checks green on the retargeted run.
Stacked on #2781 (
refactor/1925-overflowtabs-adoption) → #2780 → #2778. Review the last commit, or rebase ontodevonce the stack lands.Fixes #2197
The report named the wrong element
The issue blames the tab strips, and they are not what was wrong.
OverflowTabswas collapsing correctly the whole time, 375px included — its "More" trigger fits its container, asserted directly.What the original sweep found instead was the primitive's hidden mirror row: a
width: max-contentmeasuring row inside a0×0; overflow: hiddenbox. It is invisible to the user and toscrollWidthalike, but a probe that does not skip clipped subtrees reports it as by far the widest element on the page — which is howNAV.-mb-px.flexandBUTTON.ml-auto :: "More"ended up named as the worst offenders. My own first probe reproduced the same artifact before I filtered for clipping.What actually widened the page
AgentHeaderrows, each a non-wrapping flex row of two rigid clusters — identity + Workspace/Talk/Running, toggles + Tags + live CPU/MEM/uptime, lifetime cost. ~790px of min-content combined.<select>:flex-1with the defaultmin-width: auto. A select's min-content is its widest option (~417px), so one control scrolled the page.min-w-0is the entire fix.Both now wrap (
flex-wrap+gap-y-*), withmin-w-0where a long name should truncate rather than push.space-x-*becomesgap-*on every row that can now wrap — a wrappedspace-xrow mis-indents every line after the first.The Dashboard overlap is the same shape, one level up
#1830 made the stats cluster elastic so it degrades instead of being clipped. But elastic with no floor means it collapses to zero, so the pressure never reaches the controls and the row never wraps. Measured at 640px before the fix:
So the stats cluster's clipped, invisible children still sat under the controls' first button — the 8px the spec reports — while the controls ran ~70px past the row's right edge. Three changes: the row wraps, the stats cluster carries the ladder's own agents-only floor (
min-w-[4.5rem]≈ 71px) so the overflow becomes a wrap, and the controls may shrink and wrap their own buttons instead of being pinned wider than the row.Measured ladder after the fix — single row above ~900px, unchanged:
At 640 the stats cluster now gets the full width, so it shows more than before, not less.
Verification
e2e/dashboard-stats-overflow.spec.js(#1830) — red ondev(8px overlap, all three modes), green here.New
e2e/body-horizontal-overflow.spec.jsmeasures the one thing the contract actually states — the document scroll width — across/settings,/,/operations,/libraryand a live agent's detail page at 375/640/768/1024, and on failure names the widest unclipped element. That is deliberate: an element-shaped assertion is exactly what produced a report naming the wrong element.Proven to bite. With the three fixes reverted it fails with:
And a vacuous arm was caught doing it. The widest markup on both pages is gated behind a later render — Settings' admin-only
<select>behind/api/users/me, Agent Detail's live stats behind the stats poll — so on a fixed settle the Settings arm passed with its own fix reverted. Thereadypredicates are load-bearing, not politeness, and the negative control is what surfaced that, not reading the test.Full sweep in both themes (
darkclass asserted) at 375 and 640:scrollWidth <= clientWidthon every page. Frontend unit suite 132 files / 2916 tests green.agent-detail-tabs-overflow.spec.js(#1114) and the dashboard grid/list/mode-switcher/type-filter/hotkey specs all pass.One pre-existing failure, not from this stack
smoke.spec.js › @smoke api keys page loadsfails on plaindevtoo (verified by buildingorigin/devand running it against the same backend) —/api-keysno longer renders the expected copy. Out of scope here; worth its own issue.Note for the reviewer
The "collapsed tab strip fits its container at 375px" arm reads
[data-measure-tab]on Settings, which only renders throughOverflowTabsas of #2781. It is green in this stack and would time out if this PR were rebased ahead of that one.🤖 Generated with Claude Code
https://claude.ai/code/session_01Q19uRCksdn4DiRAJ55rfpZ