diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts index baf860d8..947b147a 100644 --- a/apps/desktop/src/main.ts +++ b/apps/desktop/src/main.ts @@ -56,6 +56,7 @@ const PICK_FOLDER_CHANNEL = "desktop:pick-folder"; const CONFIRM_CHANNEL = "desktop:confirm"; const SET_THEME_CHANNEL = "desktop:set-theme"; const SET_SIDEBAR_OPACITY_CHANNEL = "desktop:set-sidebar-opacity"; +const SET_WINDOW_BUTTON_VISIBILITY_CHANNEL = "desktop:set-window-button-visibility"; const CONTEXT_MENU_CHANNEL = "desktop:context-menu"; const OPEN_EXTERNAL_CHANNEL = "desktop:open-external"; const MENU_ACTION_CHANNEL = "desktop:menu-action"; @@ -1173,6 +1174,21 @@ function registerIpcHandlers(): void { // applies the value through a CSS custom-property. }); + ipcMain.removeHandler(SET_WINDOW_BUTTON_VISIBILITY_CHANNEL); + ipcMain.handle(SET_WINDOW_BUTTON_VISIBILITY_CHANNEL, async (event, rawVisible: unknown) => { + if (process.platform !== "darwin" || typeof rawVisible !== "boolean") { + return; + } + + const owner = + BrowserWindow.fromWebContents(event.sender) ?? BrowserWindow.getFocusedWindow() ?? mainWindow; + if (!owner || owner.isDestroyed()) { + return; + } + + owner.setWindowButtonVisibility(rawVisible); + }); + ipcMain.removeHandler(CONTEXT_MENU_CHANNEL); ipcMain.handle( CONTEXT_MENU_CHANNEL, diff --git a/apps/desktop/src/preload.ts b/apps/desktop/src/preload.ts index a373d9b0..3350fa19 100644 --- a/apps/desktop/src/preload.ts +++ b/apps/desktop/src/preload.ts @@ -5,6 +5,7 @@ const PICK_FOLDER_CHANNEL = "desktop:pick-folder"; const CONFIRM_CHANNEL = "desktop:confirm"; const SET_THEME_CHANNEL = "desktop:set-theme"; const SET_SIDEBAR_OPACITY_CHANNEL = "desktop:set-sidebar-opacity"; +const SET_WINDOW_BUTTON_VISIBILITY_CHANNEL = "desktop:set-window-button-visibility"; const CONTEXT_MENU_CHANNEL = "desktop:context-menu"; const OPEN_EXTERNAL_CHANNEL = "desktop:open-external"; const MENU_ACTION_CHANNEL = "desktop:menu-action"; @@ -37,6 +38,8 @@ contextBridge.exposeInMainWorld("desktopBridge", { confirm: (message) => ipcRenderer.invoke(CONFIRM_CHANNEL, message), setTheme: (theme) => ipcRenderer.invoke(SET_THEME_CHANNEL, theme), setSidebarOpacity: (opacity) => ipcRenderer.invoke(SET_SIDEBAR_OPACITY_CHANNEL, opacity), + setWindowButtonVisibility: (visible) => + ipcRenderer.invoke(SET_WINDOW_BUTTON_VISIBILITY_CHANNEL, visible), showContextMenu: (items, position) => ipcRenderer.invoke(CONTEXT_MENU_CHANNEL, items, position), openExternal: (url: string) => ipcRenderer.invoke(OPEN_EXTERNAL_CHANNEL, url), onMenuAction: (listener) => { diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index 91658350..9c763386 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -4700,9 +4700,7 @@ export default function ChatView({ threadId, onMinimize }: ChatViewProps) {
+
diff --git a/apps/web/src/routes/_chat.tsx b/apps/web/src/routes/_chat.tsx index bbfa1c04..d15b0c22 100644 --- a/apps/web/src/routes/_chat.tsx +++ b/apps/web/src/routes/_chat.tsx @@ -8,6 +8,7 @@ import { CommandPalette } from "../components/CommandPalette"; import { ScreenshotTool, ScreenshotButton } from "../components/ScreenshotTool"; import { WorktreeCleanupDialog } from "../components/WorktreeCleanupDialog"; import { useHandleNewThread } from "../hooks/useHandleNewThread"; +import { readDesktopBridge } from "../lib/runtimeBridge"; import { isTerminalFocused } from "../lib/terminalFocus"; import { isMacPlatform } from "../lib/utils"; import { serverConfigQueryOptions } from "../lib/serverReactQuery"; @@ -19,7 +20,7 @@ import { useScreenshotStore } from "../screenshotStore"; import { useStore } from "../store"; import { resolveSidebarNewThreadEnvMode } from "~/components/Sidebar.logic"; import { useAppSettings } from "~/appSettings"; -import { Sidebar, SidebarProvider, SidebarRail } from "~/components/ui/sidebar"; +import { Sidebar, SidebarProvider, SidebarRail, useSidebar } from "~/components/ui/sidebar"; import { useAutoDeleteMergedThreads } from "~/hooks/useAutoDeleteMergedThreads"; import { useClientMode } from "~/hooks/useClientMode"; import { isMobileShell } from "../env"; @@ -215,6 +216,40 @@ function ChatRouteGlobalShortcuts() { return null; } +function ChatDesktopWindowButtonsSync() { + const { open } = useSidebar(); + + useEffect(() => { + if (typeof navigator === "undefined" || !isMacPlatform(navigator.platform)) { + return; + } + + const bridge = readDesktopBridge(); + if (!bridge || typeof bridge.setWindowButtonVisibility !== "function") { + return; + } + + void bridge.setWindowButtonVisibility(open); + }, [open]); + + useEffect(() => { + if (typeof navigator === "undefined" || !isMacPlatform(navigator.platform)) { + return; + } + + const bridge = readDesktopBridge(); + if (!bridge || typeof bridge.setWindowButtonVisibility !== "function") { + return; + } + + return () => { + void bridge.setWindowButtonVisibility(true); + }; + }, []); + + return null; +} + function ChatRouteLayout() { const navigate = useNavigate(); const { settings } = useAppSettings(); @@ -266,6 +301,7 @@ function ChatRouteLayout() { />
+ diff --git a/packages/contracts/src/ipc.ts b/packages/contracts/src/ipc.ts index 193d741b..88f62fd2 100644 --- a/packages/contracts/src/ipc.ts +++ b/packages/contracts/src/ipc.ts @@ -238,6 +238,7 @@ export interface DesktopBridge { confirm: (message: string) => Promise; setTheme: (theme: DesktopTheme) => Promise; setSidebarOpacity: (opacity: number) => Promise; + setWindowButtonVisibility: (visible: boolean) => Promise; showContextMenu: ( items: readonly ContextMenuItem[], position?: { x: number; y: number },