diff --git a/apps/desktop/src/main/index.ts b/apps/desktop/src/main/index.ts index 3fae852f9a..09fa2c5fe3 100644 --- a/apps/desktop/src/main/index.ts +++ b/apps/desktop/src/main/index.ts @@ -113,6 +113,14 @@ if (!gotTheLock) { return shell.openExternal(url); }); + // IPC: toggle immersive mode — hides the macOS traffic lights so full-screen + // modals (create-workspace, onboarding) can place UI in the top-left corner + // without fighting the native window controls' hit-test. + ipcMain.handle("window:setImmersive", (_event, immersive: boolean) => { + if (process.platform !== "darwin") return; + mainWindow?.setWindowButtonVisibility(!immersive); + }); + createWindow(); setupAutoUpdater(() => mainWindow); diff --git a/apps/desktop/src/preload/index.d.ts b/apps/desktop/src/preload/index.d.ts index d15c31f37f..04b3e7878b 100644 --- a/apps/desktop/src/preload/index.d.ts +++ b/apps/desktop/src/preload/index.d.ts @@ -5,6 +5,8 @@ interface DesktopAPI { onAuthToken: (callback: (token: string) => void) => () => void; /** Open a URL in the default browser. */ openExternal: (url: string) => Promise; + /** Hide macOS traffic lights for full-screen modals; restore when false. */ + setImmersiveMode: (immersive: boolean) => Promise; } interface UpdaterAPI { diff --git a/apps/desktop/src/preload/index.ts b/apps/desktop/src/preload/index.ts index 470e4ac585..2bf34acced 100644 --- a/apps/desktop/src/preload/index.ts +++ b/apps/desktop/src/preload/index.ts @@ -13,6 +13,9 @@ const desktopAPI = { }, /** Open a URL in the default browser */ openExternal: (url: string) => ipcRenderer.invoke("shell:openExternal", url), + /** Toggle immersive mode — hide macOS traffic lights for full-screen modals */ + setImmersiveMode: (immersive: boolean) => + ipcRenderer.invoke("window:setImmersive", immersive), }; const updaterAPI = { diff --git a/packages/views/modals/create-workspace.tsx b/packages/views/modals/create-workspace.tsx index 4eb33a1cbd..860f5379e7 100644 --- a/packages/views/modals/create-workspace.tsx +++ b/packages/views/modals/create-workspace.tsx @@ -2,6 +2,7 @@ import { useRef, useState } from "react"; import { useNavigation } from "../navigation"; +import { useImmersiveMode } from "../platform"; import { toast } from "sonner"; import { ArrowLeft } from "lucide-react"; import { Input } from "@multica/ui/components/ui/input"; @@ -24,6 +25,11 @@ import { } from "../workspace/slug"; export function CreateWorkspaceModal({ onClose }: { onClose: () => void }) { + // This modal is full-screen, so it covers the app titlebar. On macOS desktop + // we hide the traffic lights for its lifetime so the Back button in the top- + // left corner isn't stolen by the native controls' hit-test. No-op elsewhere. + useImmersiveMode(); + const router = useNavigation(); const createWorkspace = useCreateWorkspace(); const [name, setName] = useState(""); @@ -87,10 +93,20 @@ export function CreateWorkspaceModal({ onClose }: { onClose: () => void }) { showCloseButton={false} className="inset-0 flex h-full w-full max-w-none sm:max-w-none translate-0 flex-col items-center justify-center rounded-none bg-background ring-0 shadow-none" > + {/* Top drag region — restores window-drag ability that the full-screen + modal would otherwise swallow. Transparent; web browsers ignore the + -webkit-app-region property, so this is safe cross-platform. */} +
+