From f14d608ce7a036b46095f6dfa0fcf4a296d98895 Mon Sep 17 00:00:00 2001 From: 4gray Date: Sun, 4 Oct 2026 09:08:30 +0200 Subject: [PATCH] fix(workspace): start the macOS rail below the traffic lights The macOS rail reserved 36px for the traffic lights, so the first link's hover surface touched the third light and sat above the content area. The lights live in the 56px header band; the rail now starts its first link at 56px, level with the content area and the dashboard hero. A macOS Electron E2E asserts the alignment and a gap below the native window buttons (getWindowButtonPosition); it fails with the old 36px. Co-Authored-By: Claude Opus 5.5 --- .changes/workspace-rail-traffic-light-gap.md | 6 +++ .../src/window-controls.e2e.ts | 39 +++++++++++++++++++ docs/architecture/workspace-shell.md | 6 ++- .../workspace-shell-rail.component.scss | 5 ++- 4 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 .changes/workspace-rail-traffic-light-gap.md diff --git a/.changes/workspace-rail-traffic-light-gap.md b/.changes/workspace-rail-traffic-light-gap.md new file mode 100644 index 000000000..dcb03ea74 --- /dev/null +++ b/.changes/workspace-rail-traffic-light-gap.md @@ -0,0 +1,6 @@ +--- +type: fix +area: workspace +--- + +On macOS the side rail now starts level with the page content and the dashboard banner, so its first button no longer crowds the window's close, minimize and zoom buttons. diff --git a/apps/electron-backend-e2e/src/window-controls.e2e.ts b/apps/electron-backend-e2e/src/window-controls.e2e.ts index d65b463bd..fba81345f 100644 --- a/apps/electron-backend-e2e/src/window-controls.e2e.ts +++ b/apps/electron-backend-e2e/src/window-controls.e2e.ts @@ -187,3 +187,42 @@ test.describe('Custom window controls', () => { } }); }); + +test.describe('macOS traffic lights', () => { + test.skip( + process.platform !== 'darwin', + 'Only macOS draws the native traffic lights over the rail' + ); + + test('@electron the first rail link starts with the content area, clear of the lights', async ({ + dataDir, + }) => { + const app = await launchElectronApp(dataDir); + + try { + const page = app.mainWindow; + await expect(page.locator('.app-rail')).toHaveClass(/is-macos/); + const firstLink = page.locator('.app-rail a').first(); + await expect(firstLink).toBeVisible(); + + const [linkBox, contentBox] = await Promise.all([ + firstLink.boundingBox(), + page.locator('.workspace-content').boundingBox(), + ]); + // Aligned with the content area, where the dashboard hero starts. + expect( + Math.abs((linkBox?.y ?? 0) - (contentBox?.y ?? -100)) + ).toBeLessThanOrEqual(1); + + const lights = await app.electronApp.evaluate(({ BrowserWindow }) => + BrowserWindow.getAllWindows()[0]?.getWindowButtonPosition() + ); + // The buttons are about 14pt tall; keep a visible gap below them. + expect(linkBox?.y ?? 0).toBeGreaterThanOrEqual( + (lights?.y ?? 0) + 14 + 16 + ); + } finally { + await closeElectronApp(app); + } + }); +}); diff --git a/docs/architecture/workspace-shell.md b/docs/architecture/workspace-shell.md index bf70c49cf..6eafed5a5 100644 --- a/docs/architecture/workspace-shell.md +++ b/docs/architecture/workspace-shell.md @@ -326,7 +326,11 @@ The Electron window hides the native title bar on all desktop platforms (`titleBarStyle: 'hidden'` in `apps/electron-backend/src/app/app.ts`): 1. macOS keeps the native traffic lights (`titleBarOverlay: true`, - `trafficLightPosition`); the renderer draws no window buttons. + `trafficLightPosition`); the renderer draws no window buttons. The lights + sit in the 56 px header band above the rail, so the macOS rail + (`.app-rail.is-macos`) starts its first link at 56 px: level with the + content area and the dashboard hero, with its hover surface clear of the + lights. `window-controls.e2e.ts` checks both on macOS. 2. Windows and Linux use renderer-drawn window controls (`app-window-controls`, `libs/ui/components/src/lib/window-controls/`). `frame` is intentionally left untouched so native resize borders and diff --git a/libs/workspace/shell/feature/src/lib/workspace-shell/components/workspace-shell-rail/workspace-shell-rail.component.scss b/libs/workspace/shell/feature/src/lib/workspace-shell/components/workspace-shell-rail/workspace-shell-rail.component.scss index d3e056d90..cad7c1cf6 100644 --- a/libs/workspace/shell/feature/src/lib/workspace-shell/components/workspace-shell-rail/workspace-shell-rail.component.scss +++ b/libs/workspace/shell/feature/src/lib/workspace-shell/components/workspace-shell-rail/workspace-shell-rail.component.scss @@ -29,8 +29,11 @@ --rail-link-active-border: var(--app-selection-border); --rail-link-active-glow: var(--app-selection-glow); + // The traffic lights sit in the header band (56px, see the shell grid). + // The first link starts below that band, where the content area and the + // dashboard hero start, so its hover surface never touches the lights. &.is-macos { - padding-top: 36px; + padding-top: 56px; } &::-webkit-scrollbar {