diff --git a/src/renderer/src/app-shell/TitlebarLeftControls.tsx b/src/renderer/src/app-shell/TitlebarLeftControls.tsx index 352e3d121fba..1ef2affdaae8 100644 --- a/src/renderer/src/app-shell/TitlebarLeftControls.tsx +++ b/src/renderer/src/app-shell/TitlebarLeftControls.tsx @@ -15,6 +15,7 @@ import { } from '@/store/slices/worktree-nav-history' import { useShortcutLabel } from '../hooks/useShortcutLabel' import { useAppStore } from '../store' +import { macTrafficLightChromeForWindow } from '@/lib/desktop-window-chrome' import { hasCustomTitleBar, isMac } from './app-window-chrome' import type { AppChromeLayout } from './use-app-chrome-layout' @@ -42,7 +43,10 @@ export function TitlebarLeftControls({ layout }: { layout: AppChromeLayout }): R }`} >
- {isMac && !layout.isFullScreen ? ( + {macTrafficLightChromeForWindow({ + isMac, + isFullScreen: layout.isFullScreen + }).showPad ? (
) : hasCustomTitleBar ? ( /* Why: Windows/Linux remove the native title bar, so render the logo plus a ยทยทยท button that pops the application menu (as Alt does). */ diff --git a/src/renderer/src/app-shell/app-window-chrome.ts b/src/renderer/src/app-shell/app-window-chrome.ts index 8305650c4d03..23c4faa3e97c 100644 --- a/src/renderer/src/app-shell/app-window-chrome.ts +++ b/src/renderer/src/app-shell/app-window-chrome.ts @@ -1,5 +1,6 @@ import { isPairedWebClientWindow, + macTrafficLightChromeForWindow, shouldRenderDesktopWindowChrome } from '@/lib/desktop-window-chrome' @@ -19,4 +20,8 @@ export const WINDOW_CONTROLS_HEIGHT = hasCustomTitleBar ? '36px' : '0px' // Why: macOS paints traffic lights on the window's top-left edge. Windows and Linux paint their // controls on the right, so only macOS needs a surface to keep the left edge uncovered. -export const MAC_TRAFFIC_LIGHTS_WIDTH = isMac ? '80px' : '0px' +// A paired web client on a Mac has no traffic lights (#19299). +export const MAC_TRAFFIC_LIGHTS_WIDTH = macTrafficLightChromeForWindow({ + isMac, + isFullScreen: false +}).width diff --git a/src/renderer/src/lib/desktop-window-chrome.test.ts b/src/renderer/src/lib/desktop-window-chrome.test.ts index a4bde1495ddd..afe25ade04ab 100644 --- a/src/renderer/src/lib/desktop-window-chrome.test.ts +++ b/src/renderer/src/lib/desktop-window-chrome.test.ts @@ -1,5 +1,14 @@ -import { describe, expect, it } from 'vitest' -import { shouldRenderDesktopWindowChrome } from './desktop-window-chrome' +import { afterEach, describe, expect, it, vi } from 'vitest' +import { + macTrafficLightChromeForWindow, + macTrafficLightsWidth, + shouldRenderDesktopWindowChrome, + shouldShowMacTrafficLightPad +} from './desktop-window-chrome' + +afterEach(() => { + vi.unstubAllGlobals() +}) describe('shouldRenderDesktopWindowChrome', () => { it('renders custom chrome for frameless desktop Linux and Windows windows', () => { @@ -16,3 +25,46 @@ describe('shouldRenderDesktopWindowChrome', () => { expect(shouldRenderDesktopWindowChrome({ platform: 'win32', isWebClient: true })).toBe(false) }) }) + +describe('mac traffic-light inset', () => { + it('reserves the inset on desktop macOS, including while fullscreen hides the pad', () => { + expect(macTrafficLightsWidth({ isMac: true, isWebClient: false })).toBe('80px') + expect( + shouldShowMacTrafficLightPad({ isMac: true, isWebClient: false, isFullScreen: false }) + ).toBe(true) + expect( + shouldShowMacTrafficLightPad({ isMac: true, isWebClient: false, isFullScreen: true }) + ).toBe(false) + }) + + it('does not reserve a traffic-light inset in the paired web client on a Mac', () => { + expect(macTrafficLightsWidth({ isMac: true, isWebClient: true })).toBe('0px') + expect( + shouldShowMacTrafficLightPad({ isMac: true, isWebClient: true, isFullScreen: false }) + ).toBe(false) + expect( + shouldShowMacTrafficLightPad({ isMac: true, isWebClient: true, isFullScreen: true }) + ).toBe(false) + }) + + it('reads the paired web flag for both the inset width and the pad', () => { + vi.stubGlobal('__ORCA_WEB_CLIENT__', true) + expect(macTrafficLightChromeForWindow({ isMac: true, isFullScreen: false })).toEqual({ + width: '0px', + showPad: false + }) + + vi.stubGlobal('__ORCA_WEB_CLIENT__', false) + expect(macTrafficLightChromeForWindow({ isMac: true, isFullScreen: false })).toEqual({ + width: '80px', + showPad: true + }) + }) + + it('does not reserve a traffic-light inset on Windows or Linux', () => { + expect(macTrafficLightsWidth({ isMac: false, isWebClient: false })).toBe('0px') + expect( + shouldShowMacTrafficLightPad({ isMac: false, isWebClient: false, isFullScreen: false }) + ).toBe(false) + }) +}) diff --git a/src/renderer/src/lib/desktop-window-chrome.ts b/src/renderer/src/lib/desktop-window-chrome.ts index 59683f155185..affa660c1378 100644 --- a/src/renderer/src/lib/desktop-window-chrome.ts +++ b/src/renderer/src/lib/desktop-window-chrome.ts @@ -21,3 +21,39 @@ export function shouldRenderDesktopWindowChrome({ }: DesktopWindowChromeInput): boolean { return !isWebClient && (platform === 'win32' || platform === 'linux') } + +/** Desktop macOS keeps the left inset for native traffic lights. A paired browser has none. */ +export function macTrafficLightsWidth(input: { + isMac: boolean + isWebClient: boolean +}): '80px' | '0px' { + return input.isMac && !input.isWebClient ? '80px' : '0px' +} + +/** The pad element is hidden in fullscreen; the width token above stays, matching desktop today. */ +export function shouldShowMacTrafficLightPad(input: { + isMac: boolean + isWebClient: boolean + isFullScreen: boolean +}): boolean { + return input.isMac && !input.isWebClient && !input.isFullScreen +} + +/** + * One read of the paired-web flag feeds both the width token and the pad. + * Call sites that pass `isWebClient` themselves can drift apart. + */ +export function macTrafficLightChromeForWindow(input: { isMac: boolean; isFullScreen: boolean }): { + width: '80px' | '0px' + showPad: boolean +} { + const isWebClient = isPairedWebClientWindow() + return { + width: macTrafficLightsWidth({ isMac: input.isMac, isWebClient }), + showPad: shouldShowMacTrafficLightPad({ + isMac: input.isMac, + isWebClient, + isFullScreen: input.isFullScreen + }) + } +}