From 525e51dcbb2f2974092890ea621bc50265d9df1e Mon Sep 17 00:00:00 2001 From: LukeParkerDev <10430890+Hona@users.noreply.github.com> Date: Sat, 25 Jul 2026 12:52:08 +1000 Subject: [PATCH 1/3] fix(desktop): remove titlebar inset in fullscreen The macOS traffic-light padding was keyed only by platform, so it remained after Electron hid the controls in fullscreen. --- .../app/src/components/titlebar-padding.test.ts | 13 +++++++++++++ packages/app/src/components/titlebar-padding.ts | 6 ++++++ packages/app/src/components/titlebar.tsx | 5 +++-- packages/app/src/context/platform.tsx | 3 +++ packages/desktop/src/main/ipc.ts | 5 +++++ packages/desktop/src/main/windows.ts | 6 ++++++ packages/desktop/src/preload/index.ts | 6 ++++++ packages/desktop/src/preload/types.ts | 2 ++ packages/desktop/src/renderer/index.tsx | 6 ++++++ 9 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 packages/app/src/components/titlebar-padding.test.ts create mode 100644 packages/app/src/components/titlebar-padding.ts diff --git a/packages/app/src/components/titlebar-padding.test.ts b/packages/app/src/components/titlebar-padding.test.ts new file mode 100644 index 000000000000..1c0993cd0f65 --- /dev/null +++ b/packages/app/src/components/titlebar-padding.test.ts @@ -0,0 +1,13 @@ +import { describe, expect, test } from "bun:test" +import { macTitlebarLeftPadding } from "./titlebar-padding" + +describe("macOS titlebar padding", () => { + test("reserves traffic light space while windowed", () => { + expect(macTitlebarLeftPadding(1, false)).toBe("84px") + expect(macTitlebarLeftPadding(2, false)).toBe("42px") + }) + + test("does not reserve traffic light space in fullscreen", () => { + expect(macTitlebarLeftPadding(1, true)).toBe("0px") + }) +}) diff --git a/packages/app/src/components/titlebar-padding.ts b/packages/app/src/components/titlebar-padding.ts new file mode 100644 index 000000000000..733420105578 --- /dev/null +++ b/packages/app/src/components/titlebar-padding.ts @@ -0,0 +1,6 @@ +const macTrafficLightsWidth = 84 + +export function macTitlebarLeftPadding(zoom: number, fullscreen: boolean) { + if (fullscreen) return "0px" + return `${macTrafficLightsWidth / zoom}px` +} diff --git a/packages/app/src/components/titlebar.tsx b/packages/app/src/components/titlebar.tsx index aa2f220e4949..9cb286c6cb55 100644 --- a/packages/app/src/components/titlebar.tsx +++ b/packages/app/src/components/titlebar.tsx @@ -29,6 +29,7 @@ import type { PromptSession } from "@/context/prompt" import "./titlebar.css" import { newTabTooltipKeybind } from "./command-tooltip-keybind" import { normalizeSessionInfo } from "@/utils/session" +import { macTitlebarLeftPadding } from "./titlebar-padding" type TauriDesktopWindow = { startDragging?: () => Promise @@ -237,8 +238,8 @@ export function Titlebar(props: { update?: TitlebarUpdate; debugTools?: { visibl }} style={{ "min-height": minHeight(), - // Keep native macOS traffic lights clear even when the desktop window is narrow. - "padding-left": mac() ? `${84 / zoom()}px` : 0, + // Keep visible native macOS traffic lights clear even when the desktop window is narrow. + "padding-left": mac() ? macTitlebarLeftPadding(zoom(), platform.windowFullscreen?.() ?? false) : 0, width: electronWindows() ? `env(titlebar-area-width, calc(100vw - ${windowsControlsWidth()}))` : undefined, "max-width": electronWindows() ? `env(titlebar-area-width, calc(100vw - ${windowsControlsWidth()}))` diff --git a/packages/app/src/context/platform.tsx b/packages/app/src/context/platform.tsx index cf343bfa7e91..4c374961a3f6 100644 --- a/packages/app/src/context/platform.tsx +++ b/packages/app/src/context/platform.tsx @@ -97,6 +97,9 @@ type PlatformBase = { /** Webview zoom level (desktop only) */ webviewZoom?: Accessor + /** Whether the native desktop window is fullscreen */ + windowFullscreen?: Accessor + /** Get whether native pinch/Ctrl-scroll zoom gestures are enabled (desktop only) */ getPinchZoomEnabled?(): Promise | boolean diff --git a/packages/desktop/src/main/ipc.ts b/packages/desktop/src/main/ipc.ts index 4563b688acea..5ecd4cb8c45a 100644 --- a/packages/desktop/src/main/ipc.ts +++ b/packages/desktop/src/main/ipc.ts @@ -227,6 +227,11 @@ export function registerIpcHandlers(deps: Deps) { return win?.isFocused() ?? false }) + ipcMain.handle("get-window-fullscreen", (event: IpcMainInvokeEvent) => { + const win = BrowserWindow.fromWebContents(event.sender) + return win?.isFullScreen() ?? false + }) + ipcMain.handle("set-window-focus", (event: IpcMainInvokeEvent) => { const win = BrowserWindow.fromWebContents(event.sender) win?.focus() diff --git a/packages/desktop/src/main/windows.ts b/packages/desktop/src/main/windows.ts index 7a594b5302e9..516d05e634ad 100644 --- a/packages/desktop/src/main/windows.ts +++ b/packages/desktop/src/main/windows.ts @@ -219,6 +219,7 @@ export function createMainWindow(id: string = randomUUID()) { state.manage(win) registerWindow(win, id) + wireFullscreen(win) loadWindow(win, "index.html") wireZoom(win) @@ -472,6 +473,11 @@ function wireZoom(win: BrowserWindow) { }) } +function wireFullscreen(win: BrowserWindow) { + win.on("enter-full-screen", () => win.webContents.send("window-fullscreen-changed", true)) + win.on("leave-full-screen", () => win.webContents.send("window-fullscreen-changed", false)) +} + function clampZoom(value: number) { return Math.min(Math.max(value, minZoomLevel), maxZoomLevel) } diff --git a/packages/desktop/src/preload/index.ts b/packages/desktop/src/preload/index.ts index 87eac1fa805e..1a0056dfe711 100644 --- a/packages/desktop/src/preload/index.ts +++ b/packages/desktop/src/preload/index.ts @@ -100,6 +100,12 @@ const api: ElectronAPI = { readClipboardImage: () => ipcRenderer.invoke("read-clipboard-image"), showNotification: (title, body) => ipcRenderer.send("show-notification", title, body), getWindowFocused: () => ipcRenderer.invoke("get-window-focused"), + getWindowFullscreen: () => ipcRenderer.invoke("get-window-fullscreen"), + onWindowFullscreenChanged: (cb) => { + const handler = (_: unknown, fullscreen: boolean) => cb(fullscreen) + ipcRenderer.on("window-fullscreen-changed", handler) + return () => ipcRenderer.removeListener("window-fullscreen-changed", handler) + }, setWindowFocus: () => ipcRenderer.invoke("set-window-focus"), showWindow: () => ipcRenderer.invoke("show-window"), relaunch: () => ipcRenderer.send("relaunch"), diff --git a/packages/desktop/src/preload/types.ts b/packages/desktop/src/preload/types.ts index 5fbace72c7ca..ea201af63edc 100644 --- a/packages/desktop/src/preload/types.ts +++ b/packages/desktop/src/preload/types.ts @@ -91,6 +91,8 @@ export type ElectronAPI = { readClipboardImage: () => Promise<{ buffer: ArrayBuffer; width: number; height: number } | null> showNotification: (title: string, body?: string) => void getWindowFocused: () => Promise + getWindowFullscreen: () => Promise + onWindowFullscreenChanged: (cb: (fullscreen: boolean) => void) => () => void setWindowFocus: () => Promise showWindow: () => Promise relaunch: () => void diff --git a/packages/desktop/src/renderer/index.tsx b/packages/desktop/src/renderer/index.tsx index b7d2d9a74dad..fa4c7f325e39 100644 --- a/packages/desktop/src/renderer/index.tsx +++ b/packages/desktop/src/renderer/index.tsx @@ -30,6 +30,10 @@ import "./styles.css" import { Splash } from "@opencode-ai/ui/logo" import { useTheme } from "@opencode-ai/ui/theme/context" +const [windowFullscreen, setWindowFullscreen] = createSignal(false) +window.api.onWindowFullscreenChanged(setWindowFullscreen) +void window.api.getWindowFullscreen().then(setWindowFullscreen) + const root = document.getElementById("root") if (import.meta.env.DEV && !(root instanceof HTMLElement)) { throw new Error(t("error.dev.rootNotFound")) @@ -294,6 +298,8 @@ const createPlatform = (windowState: DesktopWindowState): Platform => { webviewZoom, + windowFullscreen, + getPinchZoomEnabled: () => window.api.getPinchZoomEnabled(), setPinchZoomEnabled, From 6822ac80de55f3e2fad5374d573defe22e4f9238 Mon Sep 17 00:00:00 2001 From: LukeParkerDev <10430890+Hona@users.noreply.github.com> Date: Sat, 25 Jul 2026 13:12:27 +1000 Subject: [PATCH 2/3] refactor(app): drop dead tauri titlebar paths and animate macos inset The __TAURI__ window bridge is always undefined since the Tauri shell was removed; drag, maximize, and setTheme were no-ops (CSS app-region and native OS handling do the real work). The inset change now follows the macOS fullscreen transition and respects reduced motion. --- packages/app/src/components/titlebar.tsx | 95 ++---------------------- 1 file changed, 7 insertions(+), 88 deletions(-) diff --git a/packages/app/src/components/titlebar.tsx b/packages/app/src/components/titlebar.tsx index 9cb286c6cb55..c47eea3d96c8 100644 --- a/packages/app/src/components/titlebar.tsx +++ b/packages/app/src/components/titlebar.tsx @@ -5,7 +5,6 @@ import { IconButton } from "@opencode-ai/ui/icon-button" import { Icon } from "@opencode-ai/ui/icon" import { Button } from "@opencode-ai/ui/button" import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip" -import { useTheme } from "@opencode-ai/ui/theme/context" import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2" import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon" import { KeybindV2 } from "@opencode-ai/ui/v2/keybind-v2" @@ -31,27 +30,6 @@ import { newTabTooltipKeybind } from "./command-tooltip-keybind" import { normalizeSessionInfo } from "@/utils/session" import { macTitlebarLeftPadding } from "./titlebar-padding" -type TauriDesktopWindow = { - startDragging?: () => Promise - toggleMaximize?: () => Promise -} - -type TauriThemeWindow = { - setTheme?: (theme?: "light" | "dark" | null) => Promise -} - -type TauriApi = { - window?: { - getCurrentWindow?: () => TauriDesktopWindow - } - webviewWindow?: { - getCurrentWebviewWindow?: () => TauriThemeWindow - } -} - -const tauriApi = () => (window as unknown as { __TAURI__?: TauriApi }).__TAURI__ -const currentDesktopWindow = () => tauriApi()?.window?.getCurrentWindow?.() -const currentThemeWindow = () => tauriApi()?.webviewWindow?.getCurrentWebviewWindow?.() const legacyTitlebarHeight = 40 const v2TitlebarHeight = 36 const minTitlebarZoom = 0.25 @@ -75,7 +53,6 @@ export function Titlebar(props: { update?: TitlebarUpdate; debugTools?: { visibl const command = useCommand() const language = useLanguage() const settings = useSettings() - const theme = useTheme() const server = useServer() const navigate = useNavigate() const location = useLocation() @@ -86,7 +63,6 @@ export function Titlebar(props: { update?: TitlebarUpdate; debugTools?: { visibl const mac = createMemo(() => platform.platform === "desktop" && platform.os === "macos") const windows = createMemo(() => platform.platform === "desktop" && platform.os === "windows") - const electronWindows = createMemo(() => windows() && !tauriApi()) const linux = createMemo(() => platform.platform === "desktop" && platform.os === "linux") const web = createMemo(() => platform.platform === "web") const zoom = () => platform.webviewZoom?.() ?? 1 @@ -177,56 +153,6 @@ export function Titlebar(props: { update?: TitlebarUpdate; debugTools?: { visibl }, ]) - const getWin = () => { - if (platform.platform !== "desktop") return - return currentDesktopWindow() - } - - createEffect(() => { - if (platform.platform !== "desktop") return - - const scheme = theme.colorScheme() - const value = scheme === "system" ? null : scheme - - const win = currentThemeWindow() - if (!win?.setTheme) return - - void win.setTheme(value).catch(() => undefined) - }) - - const interactive = (target: EventTarget | null) => { - if (!(target instanceof Element)) return false - - const selector = - "button, a, input, textarea, select, option, [role='button'], [role='menuitem'], [contenteditable='true'], [contenteditable='']" - - return !!target.closest(selector) - } - - const drag = (e: MouseEvent) => { - if (platform.platform !== "desktop") return - if (e.buttons !== 1) return - if (interactive(e.target)) return - - const win = getWin() - if (!win?.startDragging) return - - e.preventDefault() - void win.startDragging().catch(() => undefined) - } - - const maximize = (e: MouseEvent) => { - if (platform.platform !== "desktop") return - if (interactive(e.target)) return - if (e.target instanceof Element && e.target.closest("[data-tauri-decorum-tb]")) return - - const win = getWin() - if (!win?.toggleMaximize) return - - e.preventDefault() - void win.toggleMaximize().catch(() => undefined) - } - return (
@@ -529,9 +454,6 @@ export function Titlebar(props: { update?: TitlebarUpdate; debugTools?: { visibl
- -
-
) }} @@ -551,7 +473,6 @@ export function Titlebar(props: { update?: TitlebarUpdate; debugTools?: { visibl
- {/*
*/}
- {!tauriApi() &&
} -
+
From 04d287b487ac08bf1a8c2838abbecb3043082b47 Mon Sep 17 00:00:00 2001 From: LukeParkerDev <10430890+Hona@users.noreply.github.com> Date: Sat, 25 Jul 2026 13:20:08 +1000 Subject: [PATCH 3/3] fix(app): avoid late fullscreen inset animation Electron emits macOS fullscreen events after the native transition completes, so animating the inset from those events delayed its alignment with the traffic lights. --- packages/app/src/components/titlebar.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/app/src/components/titlebar.tsx b/packages/app/src/components/titlebar.tsx index c47eea3d96c8..9cb2d6a6ce98 100644 --- a/packages/app/src/components/titlebar.tsx +++ b/packages/app/src/components/titlebar.tsx @@ -161,9 +161,6 @@ export function Titlebar(props: { update?: TitlebarUpdate; debugTools?: { visibl "h-9 bg-v2-background-bg-deep overflow-visible": useV2Titlebar(), "h-10 bg-background-base overflow-hidden": !useV2Titlebar(), "order-last": bottom(), - // Track the macOS fullscreen transition while the traffic lights animate away. - "transition-[padding-left] duration-[240ms] ease-[cubic-bezier(0.22,1,0.36,1)] motion-reduce:transition-none": - mac(), }} style={{ "min-height": minHeight(),