From 33e4de5374617cecfa497410697447c92ea9009c Mon Sep 17 00:00:00 2001 From: olddonkey Date: Sat, 22 Aug 2026 21:21:16 -0700 Subject: [PATCH 1/2] fix(gui): align the sidebar foot's four rows The foot stacks language, theme, proxy and GitHub two pixels apart, so a row that measures itself differently reads as a step in the stack. Three independent defects put all four out of line at once, measured in the running GUI at a 1280px viewport (sidebar content spans x 14 to 217): - the proxy label's text started at x=24 against x=49 for its three neighbours, because it is the only row with no icon and so never cleared the 16px icon + 9px gap gutter; - the GitHub orbs ended at x=217 against x=207 for the proxy orbs and the language chevron, because that row was the only one with no trailing inset; - the proxy row was 44px tall against 35.5px for the rest, because it padded 8px around 28px orbs the other rows do not carry. The GitHub row gains the same 10px trailing inset the rows above it already use. The proxy row hands its padding to the label, which then also clears the icon gutter: the row's height goes back to being set by its text, like its neighbours, instead of by the taller orbs beside it. After the change all four rows share one text column (49px), one trailing edge (207px) and one height (35.5px) -- verified in the running GUI at 1280px and in the 420px drawer, and for every dash.actions translation. Co-Authored-By: Claude Opus 5 --- gui/src/styles.css | 14 ++++++++++---- gui/tests/sidebar-rows.test.ts | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/gui/src/styles.css b/gui/src/styles.css index dadf818cad..e00c1acc2b 100644 --- a/gui/src/styles.css +++ b/gui/src/styles.css @@ -328,9 +328,11 @@ input[type="checkbox"], input[type="radio"] { accent-color: var(--accent); } /* GitHub row: the label keeps the full-width link affordance, the two circular satellites (star, update) sit at the trailing edge without shrinking the label's - hover target. The update orb only exists when an update is available, so the row + hover target. That edge is the sidebar's 10px content inset, not the rail wall, so + the orbs stack under the lang chevron and the proxy orbs instead of hanging 10px + further out than both. The update orb only exists when an update is available, so the row is one or two circles wide — never a placeholder. */ -.sidebar-github-row { display: flex; align-items: center; gap: 4px; min-width: 0; } +.sidebar-github-row { display: flex; align-items: center; gap: 4px; min-width: 0; padding-right: 10px; } .sidebar-github-link { flex: 1 1 auto; min-width: 0; } .sidebar-github-actions { display: flex; align-items: center; gap: 4px; flex: 0 0 auto; } .sidebar-orb { @@ -388,8 +390,12 @@ input[type="checkbox"], input[type="radio"] { accent-color: var(--accent); } row with a quiet label. The stop control used to be a full-width button; pairing it with restart as icons keeps the foot from growing a fifth stacked row and puts the destructive action next to the recovery action it is most often confused with. */ -.sidebar-action-row { display: flex; align-items: center; gap: 4px; min-width: 0; padding: 8px 10px; } -.sidebar-action-label { flex: 1 1 auto; min-width: 0; color: var(--muted); font-size: var(--text-control); } +.sidebar-action-row { display: flex; align-items: center; gap: 4px; min-width: 0; padding-right: 10px; } +/* Left padding matches .sidebar-link, plus the 16px icon + 9px gap gutter the + iconless label has to clear to sit in the same text column as its neighbours. + Owning the block padding here (rather than on the row) keeps the row the same + height as the other three, which the 28px orbs would otherwise inflate. */ +.sidebar-action-label { flex: 1 1 auto; min-width: 0; padding: 8px 10px 8px calc(10px + 16px + 9px); color: var(--muted); font-size: var(--text-control); } .sidebar-action-orbs { display: flex; align-items: center; gap: 4px; flex: 0 0 auto; } .sidebar-orb--danger { color: var(--red); } .sidebar-orb--danger:hover:not(:disabled) { background: var(--red-soft); color: var(--red); border-color: var(--red); } diff --git a/gui/tests/sidebar-rows.test.ts b/gui/tests/sidebar-rows.test.ts index b711c5421f..ad33140dee 100644 --- a/gui/tests/sidebar-rows.test.ts +++ b/gui/tests/sidebar-rows.test.ts @@ -50,6 +50,39 @@ test("the orphaned sidebar switch styles are gone", async () => { expect(css).not.toContain(".nav-entry-claude .switch"); }); +test("the foot's four rows share one text column and one trailing inset", async () => { + /* + * The foot stacks lang, theme, proxy and GitHub two pixels apart, so any row that + * measures itself differently is visible as a step in the stack. All four shipped + * out of line at once: the proxy label sat 25px left of its neighbours because it + * has no icon to clear, its row was 8.5px taller because it padded around 28px orbs + * the others do not have, and the GitHub orbs hung 10px further out because that row + * was the only one with no trailing inset. + */ + const css = await Bun.file(new URL("../src/styles.css", import.meta.url)).text(); + const rule = (selector: string) => { + const at = css.indexOf(`${selector} {`); + expect(at).toBeGreaterThan(-1); + return css.slice(at, css.indexOf("}", at)); + }; + + // The column every label sits in, owned by the rows that carry an icon. + for (const selector of [".lang-toggle", ".theme-toggle", ".sidebar-link"]) { + expect(rule(selector)).toContain("padding: 8px 10px"); + expect(rule(selector)).toContain("gap: 9px"); + } + + // The proxy label has no icon, so it clears that gutter itself. Holding the block + // padding on the label rather than the row is what keeps the row's height tied to + // its text, like its neighbours, instead of to the taller orbs beside it. + expect(rule(".sidebar-action-label")).toContain("padding: 8px 10px 8px calc(10px + 16px + 9px)"); + expect(rule(".sidebar-action-row")).not.toContain("padding: 8px 10px"); + + // Trailing controls stop on the same inset as the lang chevron above them. + expect(rule(".sidebar-action-row")).toContain("padding-right: 10px"); + expect(rule(".sidebar-github-row")).toContain("padding-right: 10px"); +}); + test("Claude Code is still reachable, just not as a duplicate row", async () => { // Removing the shortcut must not remove the destination. const routing = await Bun.file(new URL("../src/app-routing.ts", import.meta.url)).text(); From f4dee0fefc9223419173e85c1c8e9380ae241e9e Mon Sep 17 00:00:00 2001 From: olddonkey Date: Sat, 22 Aug 2026 22:42:02 -0700 Subject: [PATCH 2/2] test(gui): reject proxy-row block padding in every spelling CodeRabbit's review of the previous commit: the guard rejected only the exact shorthand the row shipped with, so `padding: 8px 0`, a lone `padding-top`, or `padding-block` would each restore the extra height around the 28px orbs and still pass. Reject the whole family instead. `padding-right` survives both patterns because "padding" is followed by "-", never by a colon. Verified by mutation: each of the three bypasses above turns the test red, and the unmutated file still passes. Co-Authored-By: Claude Opus 5 --- gui/tests/sidebar-rows.test.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/gui/tests/sidebar-rows.test.ts b/gui/tests/sidebar-rows.test.ts index ad33140dee..c53243c42c 100644 --- a/gui/tests/sidebar-rows.test.ts +++ b/gui/tests/sidebar-rows.test.ts @@ -76,10 +76,20 @@ test("the foot's four rows share one text column and one trailing inset", async // padding on the label rather than the row is what keeps the row's height tied to // its text, like its neighbours, instead of to the taller orbs beside it. expect(rule(".sidebar-action-label")).toContain("padding: 8px 10px 8px calc(10px + 16px + 9px)"); - expect(rule(".sidebar-action-row")).not.toContain("padding: 8px 10px"); + + /* + * Reject block padding on the row in every spelling, not just the shorthand it + * shipped with: `padding: 8px 0`, or a lone `padding-top`, would hand the 28px orbs + * back control of the row height and still slip past a check for the exact original + * string. `padding-right` survives both patterns — "padding" is followed by "-", + * never by a colon. + */ + const proxyRow = rule(".sidebar-action-row"); + expect(proxyRow).not.toMatch(/padding\s*:/); + expect(proxyRow).not.toMatch(/padding-(top|bottom|block)/); // Trailing controls stop on the same inset as the lang chevron above them. - expect(rule(".sidebar-action-row")).toContain("padding-right: 10px"); + expect(proxyRow).toContain("padding-right: 10px"); expect(rule(".sidebar-github-row")).toContain("padding-right: 10px"); });