Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions gui/src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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); }
Expand Down
43 changes: 43 additions & 0 deletions gui/tests/sidebar-rows.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,49 @@ 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)");

/*
* 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(proxyRow).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();
Expand Down
Loading