// Regression for issue #1125: on the macOS desktop app the native traffic-light// window controls are drawn over the frameless web content at// trafficLightPosition {x:16,y:17} (apps/desktop/src/main/index.ts) — three// 12px buttons with 20px center spacing, occupying x ∈ [16, 68].//// The original bug: the window minWidth was 720 but the web layout switches to// the mobile header at the 768px breakpoint, so narrowing the window into the// 720–767 band rendered the mobile hamburger (and, with the menu open, the// "executor Beta" brand) at the far left, directly under the lights.//// The fix has two parts, both asserted here:// 1. minWidth is now 768, so the desktop never drops into the mobile layout —// the lights only ever sit over the desktop sidebar header. This proves// the mobile hamburger bar is NOT present at the minimum width.// 2. The sidebar header is offset 88px to clear the lights, and the macOS// sidebar is widened (.desktop-macos-sidebar) so the brand wordmark and// the server-connection menu both clear the lights without colliding.//// Launches the REAL Electron app via Playwright against a throwaway HOME (same// harness as local-auth-mcp.test.ts). Captures via CDP page.screenshot:// - <state>-webview.png : the real, unmodified rendered content.// - <state>-overlap.png : the same view with the native macOS traffic lights// drawn at their CONFIGURED position so the cleared// gap is visible. (We draw them rather than// screen-capture them because the e2e host is a// GPU-less virtual framebuffer: macOS does not// composite the window's native chrome onto the// captured surface, so `screencapture` returns only// wallpaper. CDP renderer screenshots are unaffected.)import { mkdtempSync, rmSync } from "node:fs";import { createRequire } from "node:module";import { tmpdir } from "node:os";import { join } from "node:path";import { fileURLToPath } from "node:url"; import { expect } from "@effect/vitest";import { Effect } from "effect";import { _electron, type ElectronApplication, type Locator, type Page } from "playwright"; import { scenario } from "../src/scenario";import { RunDir } from "../src/services"; const appDir = fileURLToPath(new URL("../../apps/desktop/", import.meta.url));const electronBinary = createRequire(join(appDir, "package.json"))("electron") as string; // Rightmost edge of the traffic-light cluster (left edges 16/36/56, +12px) plus// a small margin. Header content must start at or beyond this to clear them.const TRAFFIC_LIGHTS_RIGHT = 72;// The window minWidth: the narrowest the desktop window can get, and the worst// case for fitting the sidebar header alongside the lights.const MIN_WIDTH = 768; const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); const launchDesktop = async (home: string): Promise<ElectronApplication> => _electron.launch({ executablePath: electronBinary, args: [appDir], cwd: appDir, env: { ...process.env, HOME: home }, timeout: 120_000, }); // Draw the native macOS traffic lights at the app's configured// trafficLightPosition. Faithful geometry: 12px buttons, 20px center spacing,// group origin (16,17). Removed before each "real" capture so it stays// unmodified, re-added for the "overlap" capture.const TRAFFIC_LIGHTS_JS = `(() => { const host = document.createElement('div'); host.id = '__repro_1125_trafficlights'; host.style.cssText = 'position:fixed;top:0;left:0;z-index:2147483647;pointer-events:none'; const dot = (left, color) => { const d = document.createElement('div'); d.style.cssText = 'position:absolute;width:12px;height:12px;border-radius:50%;top:17px;left:' + left + 'px;background:' + color + ';box-shadow:inset 0 0 0 0.5px rgba(0,0,0,.25)'; return d; }; host.appendChild(dot(16, '#FF5F57')); host.appendChild(dot(36, '#FEBC2E')); host.appendChild(dot(56, '#28C840')); document.body.appendChild(host);})()`; const REMOVE_LIGHTS_JS = `document.getElementById('__repro_1125_trafficlights')?.remove()`; const captureBoth = async (page: Page, runDir: string, state: string) => { await page.evaluate(REMOVE_LIGHTS_JS); await page.screenshot({ path: join(runDir, `${state}-webview.png`) }); await page.evaluate(TRAFFIC_LIGHTS_JS); await page.screenshot({ path: join(runDir, `${state}-overlap.png`) }); await page.evaluate(REMOVE_LIGHTS_JS);}; const box = async (locator: Locator, label: string) => { const b = await locator.boundingBox(); expect(b, `${label}: has a bounding box`).not.toBeNull(); return b!;}; scenario( "Desktop · #1125 the desktop stays out of the mobile layout and the sidebar header clears the macOS traffic lights", { timeout: 300_000 }, Effect.gen(function* () { const runDir = yield* RunDir; yield* Effect.promise(() => run(runDir)); }),); const run = async (runDir: string) => { const home = mkdtempSync(join(tmpdir(), "executor-repro-1125-")); const app = await launchDesktop(home); try { const page = await app.firstWindow({ timeout: 120_000 }); await page.waitForLoadState("domcontentloaded"); // The offset is gated on this class; without it the assertions below would // be meaningless, so prove it is active before trusting them. const isMacDesktop = await page.evaluate(() => document.documentElement.classList.contains("executor-desktop-macos"), ); expect(isMacDesktop, "renderer applied the macOS desktop class").toBe(true); // Try to shrink below the mobile breakpoint. The minWidth fix must clamp the // window at 768, so the desktop can never drop into the mobile layout. We ask // for 700 (the old reachable band) and assert it clamps up. const clampedWidth = await app.evaluate(async ({ BrowserWindow }) => { const win = BrowserWindow.getAllWindows()[0]!; win.setPosition(60, 40); win.setSize(700, 800); win.show(); win.focus(); return win.getSize()[0]; }); expect( clampedWidth, `window cannot shrink below the mobile breakpoint (clamped to ${clampedWidth}px)`, ).toBeGreaterThanOrEqual(MIN_WIDTH); const sidebar = page.locator("aside.desktop-macos-sidebar"); await sidebar.waitFor({ timeout: 60_000 }); await sleep(800); // Part 1: at the minimum width the mobile phone bar must NOT be shown, so // its hamburger can never land under the lights. const hamburgerVisible = await page .getByRole("button", { name: "Open navigation" }) .isVisible(); expect(hamburgerVisible, "mobile hamburger bar is hidden on the desktop at minimum width").toBe( false, ); const sidebarVisible = await sidebar.isVisible(); expect(sidebarVisible, "desktop sidebar is visible at minimum width").toBe(true); await captureBoth(page, runDir, "01-sidebar-header"); // Part 2a: the brand wordmark clears the traffic-light cluster. const header = sidebar.locator(".desktop-macos-titlebar"); const brand = await box(header.locator("a").first(), "sidebar brand"); expect( Math.round(brand.x), `sidebar brand left edge (${Math.round(brand.x)}px) clears the lights (>= ${TRAFFIC_LIGHTS_RIGHT}px)`, ).toBeGreaterThanOrEqual(TRAFFIC_LIGHTS_RIGHT); // Part 2b: the server-connection menu sits clear of the brand (no collision // with the "Beta" badge, the bug in the screenshot) and stays inside the // sidebar. const menu = await box( header.locator('button[aria-label^="Select Executor server"]'), "server-connection menu", ); const asideBox = await box(sidebar, "sidebar"); const gap = Math.round(menu.x - (brand.x + brand.width)); expect(gap, `server menu does not overlap the brand (gap ${gap}px)`).toBeGreaterThanOrEqual(8); expect( Math.round(menu.x + menu.width), "server menu stays within the sidebar", ).toBeLessThanOrEqual(Math.round(asideBox.x + asideBox.width)); } finally { await app.close().catch(() => {}); rmSync(home, { recursive: true, force: true }); }};
// Regression for issue #1125: on the macOS desktop app the native traffic-light
// window controls are drawn over the frameless web content at
// trafficLightPosition {x:16,y:17} (apps/desktop/src/main/index.ts) — three
// 12px buttons with 20px center spacing, occupying x ∈ [16, 68].
//
// The original bug: the window minWidth was 720 but the web layout switches to
// the mobile header at the 768px breakpoint, so narrowing the window into the
// 720–767 band rendered the mobile hamburger (and, with the menu open, the
// "executor Beta" brand) at the far left, directly under the lights.
//
// The fix has two parts, both asserted here:
// 1. minWidth is now 768, so the desktop never drops into the mobile layout —
// the lights only ever sit over the desktop sidebar header. This proves
// the mobile hamburger bar is NOT present at the minimum width.
// 2. The sidebar header is offset 88px to clear the lights, and the macOS
// sidebar is widened (.desktop-macos-sidebar) so the brand wordmark and
// the server-connection menu both clear the lights without colliding.
//
// Launches the REAL Electron app via Playwright against a throwaway HOME (same
// harness as local-auth-mcp.test.ts). Captures via CDP page.screenshot:
// - <state>-webview.png : the real, unmodified rendered content.
// - <state>-overlap.png : the same view with the native macOS traffic lights
// drawn at their CONFIGURED position so the cleared
// gap is visible. (We draw them rather than
// screen-capture them because the e2e host is a
// GPU-less virtual framebuffer: macOS does not
// composite the window's native chrome onto the
// captured surface, so `screencapture` returns only
// wallpaper. CDP renderer screenshots are unaffected.)
import { mkdtempSync, rmSync } from "node:fs";
import { createRequire } from "node:module";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
import { expect } from "@effect/vitest";
import { Effect } from "effect";
import { _electron, type ElectronApplication, type Locator, type Page } from "playwright";
import { scenario } from "../src/scenario";
import { RunDir } from "../src/services";
const appDir = fileURLToPath(new URL("../../apps/desktop/", import.meta.url));
const electronBinary = createRequire(join(appDir, "package.json"))("electron") as string;
// Rightmost edge of the traffic-light cluster (left edges 16/36/56, +12px) plus
// a small margin. Header content must start at or beyond this to clear them.
const TRAFFIC_LIGHTS_RIGHT = 72;
// The window minWidth: the narrowest the desktop window can get, and the worst
// case for fitting the sidebar header alongside the lights.
const MIN_WIDTH = 768;
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
const launchDesktop = async (home: string): Promise<ElectronApplication> =>
_electron.launch({
executablePath: electronBinary,
args: [appDir],
cwd: appDir,
env: { ...process.env, HOME: home },
timeout: 120_000,
});
// Draw the native macOS traffic lights at the app's configured
// trafficLightPosition. Faithful geometry: 12px buttons, 20px center spacing,
// group origin (16,17). Removed before each "real" capture so it stays
// unmodified, re-added for the "overlap" capture.
const TRAFFIC_LIGHTS_JS = `(() => {
const host = document.createElement('div');
host.id = '__repro_1125_trafficlights';
host.style.cssText =
'position:fixed;top:0;left:0;z-index:2147483647;pointer-events:none';
const dot = (left, color) => {
const d = document.createElement('div');
d.style.cssText =
'position:absolute;width:12px;height:12px;border-radius:50%;top:17px;left:' +
left + 'px;background:' + color + ';box-shadow:inset 0 0 0 0.5px rgba(0,0,0,.25)';
return d;
};
host.appendChild(dot(16, '#FF5F57'));
host.appendChild(dot(36, '#FEBC2E'));
host.appendChild(dot(56, '#28C840'));
document.body.appendChild(host);
})()`;
const REMOVE_LIGHTS_JS = `document.getElementById('__repro_1125_trafficlights')?.remove()`;
const captureBoth = async (page: Page, runDir: string, state: string) => {
await page.evaluate(REMOVE_LIGHTS_JS);
await page.screenshot({ path: join(runDir, `${state}-webview.png`) });
await page.evaluate(TRAFFIC_LIGHTS_JS);
await page.screenshot({ path: join(runDir, `${state}-overlap.png`) });
await page.evaluate(REMOVE_LIGHTS_JS);
};
const box = async (locator: Locator, label: string) => {
const b = await locator.boundingBox();
expect(b, `${label}: has a bounding box`).not.toBeNull();
return b!;
};
scenario(
"Desktop · #1125 the desktop stays out of the mobile layout and the sidebar header clears the macOS traffic lights",
{ timeout: 300_000 },
Effect.gen(function* () {
const runDir = yield* RunDir;
yield* Effect.promise(() => run(runDir));
}),
);
const run = async (runDir: string) => {
const home = mkdtempSync(join(tmpdir(), "executor-repro-1125-"));
const app = await launchDesktop(home);
try {
const page = await app.firstWindow({ timeout: 120_000 });
await page.waitForLoadState("domcontentloaded");
// The offset is gated on this class; without it the assertions below would
// be meaningless, so prove it is active before trusting them.
const isMacDesktop = await page.evaluate(() =>
document.documentElement.classList.contains("executor-desktop-macos"),
);
expect(isMacDesktop, "renderer applied the macOS desktop class").toBe(true);
// Try to shrink below the mobile breakpoint. The minWidth fix must clamp the
// window at 768, so the desktop can never drop into the mobile layout. We ask
// for 700 (the old reachable band) and assert it clamps up.
const clampedWidth = await app.evaluate(async ({ BrowserWindow }) => {
const win = BrowserWindow.getAllWindows()[0]!;
win.setPosition(60, 40);
win.setSize(700, 800);
win.show();
win.focus();
return win.getSize()[0];
});
expect(
clampedWidth,
`window cannot shrink below the mobile breakpoint (clamped to ${clampedWidth}px)`,
).toBeGreaterThanOrEqual(MIN_WIDTH);
const sidebar = page.locator("aside.desktop-macos-sidebar");
await sidebar.waitFor({ timeout: 60_000 });
await sleep(800);
// Part 1: at the minimum width the mobile phone bar must NOT be shown, so
// its hamburger can never land under the lights.
const hamburgerVisible = await page
.getByRole("button", { name: "Open navigation" })
.isVisible();
expect(hamburgerVisible, "mobile hamburger bar is hidden on the desktop at minimum width").toBe(
false,
);
const sidebarVisible = await sidebar.isVisible();
expect(sidebarVisible, "desktop sidebar is visible at minimum width").toBe(true);
await captureBoth(page, runDir, "01-sidebar-header");
// Part 2a: the brand wordmark clears the traffic-light cluster.
const header = sidebar.locator(".desktop-macos-titlebar");
const brand = await box(header.locator("a").first(), "sidebar brand");
expect(
Math.round(brand.x),
`sidebar brand left edge (${Math.round(brand.x)}px) clears the lights (>= ${TRAFFIC_LIGHTS_RIGHT}px)`,
).toBeGreaterThanOrEqual(TRAFFIC_LIGHTS_RIGHT);
// Part 2b: the server-connection menu sits clear of the brand (no collision
// with the "Beta" badge, the bug in the screenshot) and stays inside the
// sidebar.
const menu = await box(
header.locator('button[aria-label^="Select Executor server"]'),
"server-connection menu",
);
const asideBox = await box(sidebar, "sidebar");
const gap = Math.round(menu.x - (brand.x + brand.width));
expect(gap, `server menu does not overlap the brand (gap ${gap}px)`).toBeGreaterThanOrEqual(8);
expect(
Math.round(menu.x + menu.width),
"server menu stays within the sidebar",
).toBeLessThanOrEqual(Math.round(asideBox.x + asideBox.width));
} finally {
await app.close().catch(() => {});
rmSync(home, { recursive: true, force: true });
}
};