diff --git a/packages/app/e2e/regression/tabs.spec.ts b/packages/app/e2e/regression/tabs.spec.ts index 17908249d01..46d6ab7332a 100644 --- a/packages/app/e2e/regression/tabs.spec.ts +++ b/packages/app/e2e/regression/tabs.spec.ts @@ -463,6 +463,45 @@ for (const [window, fullscreen, start] of [ }) } +// macOS 26 traffic lights sit 14px down and are 14px tall. With vertical tabs the channel icon goes below them, over +// Home's icon. A fullscreen window has no traffic lights, so the icon stays in the header beside the sidebar toggle. +for (const [window, fullscreen] of [ + ["windowed", false], + ["fullscreen", true], +] as const) { + test(`sidebar channel icon sits below the macOS traffic lights (${window})`, async ({ page }) => { + await mockWorkspace(page, { + name: "Tabs", + sessions: [a], + seed: { settings: { appearance: { tabLayout: "vertical" } } }, + }) + await page.goto( + `/e2e/utils/macos-window.html?${new URLSearchParams({ server: SERVER, route: "/", fullscreen: String(fullscreen) })}`, + ) + const sidebar = page.locator('[data-slot="vertical-tabs-sidebar"]') + const indicator = sidebar.locator('[data-slot="channel-indicator"]') + await expect(indicator).toBeVisible() + + const icon = await indicator.boundingBox() + const toggle = await sidebar.getByRole("button", { name: "Toggle sidebar", exact: true }).boundingBox() + const home = await sidebar.getByRole("button", { name: "Home", exact: true }).boundingBox() + const homeIcon = await sidebar.getByRole("button", { name: "Home", exact: true }).locator("svg").boundingBox() + + if (!icon || !toggle || !home || !homeIcon) throw new Error("sidebar header has no bounding boxes") + + if (fullscreen) { + expect(icon.y + icon.height / 2).toBe(toggle.y + toggle.height / 2) + return + } + + expect(icon.y).toBeGreaterThan(14 + 14) + expect(icon.x + icon.width / 2).toBe(homeIcon.x + homeIcon.width / 2) + expect(icon.y + icon.height).toBeLessThan(home.y) + // The toggle keeps the session header's center line beside the icon. + expect(toggle.y + toggle.height / 2).toBe(32) + }) +} + test("vertical tab gaps hover and select the nearest row", async ({ page }) => { await mockWorkspace(page, { name: "Tabs", diff --git a/packages/app/src/shell/titlebar/titlebar.tsx b/packages/app/src/shell/titlebar/titlebar.tsx index c34d89014c8..4dcafa804a3 100644 --- a/packages/app/src/shell/titlebar/titlebar.tsx +++ b/packages/app/src/shell/titlebar/titlebar.tsx @@ -114,6 +114,7 @@ export function Titlebar(props: { verticalTabs?: { mount?: HTMLElement } }) { const titlebarItems = useTitlebarItems() const hideVerticalTitlebar = createMemo(() => !!props.verticalTabs && !windows()) const homeInSidebarHeader = () => !windows() && !macTrafficLights() && !channelIndicatorVisible + const channelIndicatorBelowTrafficLights = () => macTrafficLights() && channelIndicatorVisible const back = () => { const next = backPath(history) @@ -782,12 +783,15 @@ export function Titlebar(props: { verticalTabs?: { mount?: HTMLElement } }) { > {/* Shares the session header's 48px center line. Without traffic lights or a channel - icon, Home fills the start so the row doesn't leave an empty band. */} + icon, Home fills the start so the row doesn't leave an empty band. With both, the + channel icon goes under the traffic lights and the row grows only by what it needs. */}
{/* The sidebar's 10px start padding already covers part of the traffic lights. */} - + + +
} >
{homeButton(true)}
- +
+ +
{homeButton(true)}