From 76ca49077691d2b9fb0dfe19d223a769963d6f28 Mon Sep 17 00:00:00 2001 From: Dinesh Pandiyan Date: Thu, 22 Oct 2026 09:52:22 +1100 Subject: [PATCH] feat: size activity rail to header height and icons --- DESIGN.md | 12 ++++++++---- apps/web/src/style.css | 12 ++++++++---- apps/web/src/tokens.css | 5 +++-- apps/web/test/browser/app.spec.ts | 24 +++++++++++++++++++++++- 4 files changed, 42 insertions(+), 11 deletions(-) diff --git a/DESIGN.md b/DESIGN.md index 6d570a34..f9c57181 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -236,15 +236,19 @@ such detail into a token. New layout spacing must use the scale. - Header lead: brand and divider identify the app. Desktop navigation controls live in the activity rail. Below 768px, Repos and file-sidebar controls remain in the header because the rail is hidden. -- Activity bar: a fixed `--activity-width` column (48px; 52px for coarse - pointers), spanning the workbench below the header to the shell's bottom inset. +- Activity bar: a fixed `--activity-width` column equal to `--topbar-height` + (36px; 44px for coarse pointers), spanning the workbench below the header to + the shell's bottom inset. The rail mirrors the header rotated: items match + header icon buttons (32px desktop, 44px touch) with the header's inset (2px + desktop, flush touch); coarse pointers add a 4px block inset so flush items + clear the island corners. Repos (repository icon) followed by Diff (file-diff icon) select one shared panel. Repos is the initial view. Clicking the active view toggles its panel; clicking another view opens it. Keep the rail and selected panel in one rounded island, separated by a 1px `--border-muted` edge. Views use `--activity-item` square ghost buttons, - 20px icons and `--radius-md`; selection uses accent text on `--accent-bg` - with `aria-current="page"`, while `aria-expanded` describes panel visibility. + 16px icons, 1.7px strokes and `--radius-control`, like header actions; + selection uses accent text on `--accent-bg` with `aria-current="page"`, while `aria-expanded` describes panel visibility. Account actions sit at the bottom. Below 768px account actions move into the header before the rightmost theme control. Below 1012px navigation panels become modal drawers. - Repos panel: shared navigation width on `--panel`, sharing the file tree diff --git a/apps/web/src/style.css b/apps/web/src/style.css index 4399c8e0..25c27bae 100644 --- a/apps/web/src/style.css +++ b/apps/web/src/style.css @@ -268,7 +268,8 @@ kbd { flex-direction: column; justify-content: space-between; width: var(--activity-width); - padding: var(--space-1) 0; + /* Match the header's inset around its buttons on every edge. */ + padding: calc((var(--activity-width) - var(--activity-item)) / 2); background: transparent; } .activity-views, @@ -284,11 +285,10 @@ kbd { .activity-bar button { width: var(--activity-item); height: var(--activity-item); - border-radius: var(--radius-md); } .activity-bar button svg { - width: var(--icon-lg); - height: var(--icon-lg); + width: var(--icon-base); + height: var(--icon-base); stroke-width: 1.7; } .activity-bar button:hover, @@ -1604,4 +1604,8 @@ h2 { .project-picker-option { min-height: var(--touch-target); } + /* Flush touch targets stay inside the island's rounded corners. */ + .activity-bar { + padding-block: var(--space-1); + } } diff --git a/apps/web/src/tokens.css b/apps/web/src/tokens.css index 666d4acb..cd924d07 100644 --- a/apps/web/src/tokens.css +++ b/apps/web/src/tokens.css @@ -73,8 +73,9 @@ --diff-gutter: var(--space-3); --shell-gutter: var(--space-1-5); --sidebar-width: 330px; - --activity-item: var(--control-large); - --activity-width: calc(var(--activity-item) + 2 * var(--space-1)); + /* Rail mirrors the header: its width is the header height, items are header buttons. */ + --activity-item: var(--control-height); + --activity-width: var(--topbar-height); --reading-width: 42.5rem; --layer-sidebar: 10; --layer-backdrop: 9; diff --git a/apps/web/test/browser/app.spec.ts b/apps/web/test/browser/app.spec.ts index 47a675e3..d538af3c 100644 --- a/apps/web/test/browser/app.spec.ts +++ b/apps/web/test/browser/app.spec.ts @@ -1485,6 +1485,16 @@ test("header icons and sidebar tab indicator use design-system sizes", async ({ if (!topbar || !divider) throw new Error("Missing header divider"); const topbarBox = topbar.getBoundingClientRect(); const dividerBox = divider.getBoundingClientRect(); + const find = (selector: string) => { + const element = document.querySelector(selector); + if (!element) throw new Error(`Missing ${selector}`); + return element; + }; + const railViewButton = find(".activity-bar [aria-current='page']"); + const themeElement = find("#theme"); + const rail = find(".activity-bar").getBoundingClientRect(); + const railView = railViewButton.getBoundingClientRect(); + const themeButton = themeElement.getBoundingClientRect(); const indicator = getComputedStyle(tab, "::after"); const colorProbe = document.createElement("span"); colorProbe.style.color = "var(--accent)"; @@ -1513,6 +1523,13 @@ test("header icons and sidebar tab indicator use design-system sizes", async ({ ), dividerHeight: dividerBox.height, headerHeight: topbarBox.height, + railWidth: rail.width, + railViewSize: [railView.width, railView.height], + railViewInset: railView.left - rail.left, + headerButtonSize: [themeButton.width, themeButton.height], + headerButtonInset: themeButton.top - topbarBox.top, + railViewRadius: getComputedStyle(railViewButton).borderRadius, + headerButtonRadius: getComputedStyle(themeElement).borderRadius, accent, }; }); @@ -1520,13 +1537,18 @@ test("header icons and sidebar tab indicator use design-system sizes", async ({ expect(styles).toMatchObject({ refresh: "16px", theme: "16px", - view: "20px", + view: "16px", tabBorderWidth: "1px", indicatorOpacity: "1", dividerHeight: 16, headerHeight: 36, }); expect(styles.identity).toBe(styles.viewOptions); + // The rail mirrors the header: width equals header height, items match header buttons. + expect(styles.railWidth).toBe(styles.headerHeight); + expect(styles.railViewSize).toEqual(styles.headerButtonSize); + expect(styles.railViewInset).toBe(styles.headerButtonInset); + expect(styles.railViewRadius).toBe(styles.headerButtonRadius); expect(styles.chromeStrokes.every((stroke) => stroke === "1.7px")).toBe(true); expect(styles.dividerCenterOffset).toBeLessThanOrEqual(0.5); expect(styles.indicatorColor).toBe(styles.accent);