From 348fd414d1ae13438fdc55f855d8514e263420c4 Mon Sep 17 00:00:00 2001 From: Pritom Mazumdar Date: Mon, 22 Jun 2026 13:14:01 +0530 Subject: [PATCH] test(e2e): guard sidebar brand vs macOS titlebar cluster (#366) (#374) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #366 (brand text overlapped by the fixed macOS TitlebarNav cluster on board routes) is already fixed on main: #263 made the shell render the topbar on every route, so the sidebar always hangs below the 56px titlebar band and the brand never lands in the cluster's lane. Add an e2e regression guard that locks the invariant in for the routes the issue named — the brand must not overlap the fixed cluster and the "Agent Orchestrator" wordmark must stay readable (not truncated) — on the home board route and the project board route, plus across a board→session transition (the persistent brand must not jump). Drives the live renderer under a forced macOS UA. If a topbar-less route is ever reintroduced, these fail. Verified: passes against current main with no source changes; typecheck clean. Co-authored-by: Claude Opus 4.8 --- frontend/e2e/titlebar-brand.spec.ts | 82 +++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 frontend/e2e/titlebar-brand.spec.ts diff --git a/frontend/e2e/titlebar-brand.spec.ts b/frontend/e2e/titlebar-brand.spec.ts new file mode 100644 index 000000000..efffbc7a1 --- /dev/null +++ b/frontend/e2e/titlebar-brand.spec.ts @@ -0,0 +1,82 @@ +import { expect, test, type Locator, type Page } from "@playwright/test"; + +// Regression guard for #366 (macOS): the sidebar's "Agent Orchestrator" brand +// must never sit under the fixed TitlebarNav cluster, and the wordmark must stay +// readable. The original bug was board routes (`/` and `/projects/:id`) having no +// topbar, so the sidebar stayed at top-0 and the brand landed in the cluster's +// 56px lane. It is now fixed structurally — the shell renders the topbar on every +// route, so the sidebar always hangs below the titlebar band — and these tests +// lock that invariant in: if a topbar-less route is ever reintroduced, they fail. +// +// macOS-only: TitlebarNav (and the bug) gate on navigator.userAgent looking like +// a Mac, read once at module load. Force a Mac UA so this is deterministic +// regardless of the host/CI OS. +test.use({ + userAgent: + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", +}); + +const brand = (page: Page) => page.getByText("Agent Orchestrator", { exact: true }); + +// Two boxes overlap iff they intersect on both axes. +function overlaps(a: { x: number; y: number; width: number; height: number }, b: typeof a) { + return a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y; +} + +// The brand has `truncate` (overflow:hidden), so it stays "visible" even +// when clipped to nothing. Compare scroll vs client width to prove the wordmark +// is actually fully rendered, not just present-but-clipped. +async function isTruncated(span: Locator) { + return span.evaluate((el) => el.scrollWidth > el.clientWidth + 1); +} + +async function expectBrandClearsCluster(page: Page) { + const cluster = page.locator(".titlebar-nav"); + await expect(cluster).toBeVisible(); + const span = brand(page); + await expect(span).toBeVisible(); + + const clusterBox = await cluster.boundingBox(); + const brandBox = await span.boundingBox(); + expect(clusterBox).not.toBeNull(); + expect(brandBox).not.toBeNull(); + + expect(overlaps(brandBox!, clusterBox!)).toBe(false); + expect(await isTruncated(span)).toBe(false); +} + +test("home board route: brand clears the macOS titlebar cluster and stays readable", async ({ page }) => { + await page.goto("/"); + await expect(page.getByText("Projects")).toBeVisible(); + await expectBrandClearsCluster(page); +}); + +test("project board route: brand clears the macOS titlebar cluster and stays readable", async ({ page }) => { + await page.goto("/"); + await expect(page.getByText("Projects")).toBeVisible(); + + // In-app nav to /projects/:id (a hard load boots the router at the board). + await page.getByRole("button", { name: "Open api-gateway dashboard" }).click(); + // The active project row marks itself aria-current=page once navigation lands. + await expect(page.locator('[aria-current="page"]')).toBeVisible(); + + await expectBrandClearsCluster(page); +}); + +test("brand stays put and readable when navigating board → session", async ({ page }) => { + await page.goto("/"); + await expect(page.getByText("Projects")).toBeVisible(); + + const boardBrandBox = await brand(page).boundingBox(); + expect(boardBrandBox).not.toBeNull(); + + await page.getByRole("button", { name: "Open Split terminal mux responsibilities" }).click(); + await expect(page.locator(".dashboard-app-header")).toBeVisible(); + + const sessionBrandBox = await brand(page).boundingBox(); + expect(sessionBrandBox).not.toBeNull(); + // Persistent shell element: no vertical/horizontal jump across the transition. + expect(Math.abs(sessionBrandBox!.x - boardBrandBox!.x)).toBeLessThanOrEqual(1); + expect(Math.abs(sessionBrandBox!.y - boardBrandBox!.y)).toBeLessThanOrEqual(1); + await expectBrandClearsCluster(page); +});