From 19518fb6931b5546736677ee262c81390d9b34e3 Mon Sep 17 00:00:00 2001 From: arnestrickmann <115920878+arnestrickmann@users.noreply.github.com> Date: Wed, 4 Nov 2026 15:52:25 -0800 Subject: [PATCH 1/2] feat: hide the macOS desktop title bar --- packages/desktop/src/app-url.ts | 1 + packages/desktop/src/env.ts | 2 +- packages/desktop/src/preload.ts | 38 ++++++++++++++++++- packages/desktop/src/templates/splash.css | 10 +++++ packages/desktop/src/window.ts | 3 ++ packages/web/src/components/agent-shell.tsx | 2 +- packages/web/src/components/app-shell.tsx | 8 +++- packages/web/src/index.css | 42 +++++++++++++++++++++ packages/web/src/lib/desktop.test.ts | 32 ++++++++++++++++ packages/web/src/lib/desktop.ts | 7 ++++ packages/web/src/main.tsx | 5 +++ 11 files changed, 145 insertions(+), 5 deletions(-) create mode 100644 packages/desktop/src/app-url.ts create mode 100644 packages/web/src/lib/desktop.test.ts diff --git a/packages/desktop/src/app-url.ts b/packages/desktop/src/app-url.ts new file mode 100644 index 00000000..c03f12e9 --- /dev/null +++ b/packages/desktop/src/app-url.ts @@ -0,0 +1 @@ +export const PRODUCTION_APP_URL = 'https://app.personalagentproxy.com'; diff --git a/packages/desktop/src/env.ts b/packages/desktop/src/env.ts index f977c165..bf18ac0f 100644 --- a/packages/desktop/src/env.ts +++ b/packages/desktop/src/env.ts @@ -1,6 +1,6 @@ import {app} from 'electron'; -const PRODUCTION_APP_URL = 'https://app.personalagentproxy.com'; +import {PRODUCTION_APP_URL} from './app-url'; // A packaged app is hosted Personal Agent Proxy's. In development, DESKTOP_APP_URL, which dev.ts // sets from the root .env's APP_URL: the api refuses sign-ins from any other origin. diff --git a/packages/desktop/src/preload.ts b/packages/desktop/src/preload.ts index a955a6d0..fe139d32 100644 --- a/packages/desktop/src/preload.ts +++ b/packages/desktop/src/preload.ts @@ -1,12 +1,48 @@ -import {contextBridge, ipcRenderer, type IpcRendererEvent} from 'electron'; +import {contextBridge, ipcRenderer, type IpcRendererEvent, webFrame} from 'electron'; +import {PRODUCTION_APP_URL} from './app-url'; import {RESTART_CHANNEL, UPDATE_READY_CHANNEL} from './channels'; +const APP_ORIGIN = new URL( + process.env.NODE_ENV === 'production' + ? PRODUCTION_APP_URL + : (process.env.DESKTOP_APP_URL ?? 'http://localhost:5173'), +).origin; + +// A newly installed shell can briefly load an older hosted web build during a rollout or rollback. +// Keep that page draggable and below the traffic lights until a title-bar-aware build adds the +// `desktop-app` class and takes over with its column-specific layout. Keep its 2rem height in step +// with packages/desktop/src/templates/splash.css and packages/web/src/index.css. +if (process.platform === 'darwin' && window.location.origin === APP_ORIGIN) { + void webFrame.insertCSS(` + html:not(.desktop-app) body::before { + position: fixed; + z-index: 60; + top: 0; + right: 0; + left: 0; + height: 2rem; + content: ''; + -webkit-app-region: drag; + } + + html:not(.desktop-app) #root > :first-child { + box-sizing: border-box; + padding-top: 2rem; + } + + html:not(.desktop-app) [data-slot='sidebar-inner'] { + padding-top: 2rem; + } + `); +} + // What the page may ask of the app, as `window.personalAgentProxyDesktop`, typed on the web app's // side in packages/web/src/lib/desktop.ts: whether an update is downloaded, word when one is, and // the restart that installs it. The main process answers the app's own pages only (updates.ts). contextBridge.exposeInMainWorld('personalAgentProxyDesktop', { + titleBarHidden: process.platform === 'darwin', updateReady: (): Promise => ipcRenderer.invoke(UPDATE_READY_CHANNEL), onUpdateReady: (listener: (version: string) => void): (() => void) => { const handler = (_event: IpcRendererEvent, version: string) => listener(version); diff --git a/packages/desktop/src/templates/splash.css b/packages/desktop/src/templates/splash.css index 06be6cda..dc76a2fe 100644 --- a/packages/desktop/src/templates/splash.css +++ b/packages/desktop/src/templates/splash.css @@ -34,6 +34,16 @@ body { line-height: 1.5; -webkit-font-smoothing: antialiased; } +body::before { + position: fixed; + top: 0; + right: 0; + left: 0; + /* Keep in step with the fallback in preload.ts and the variable in packages/web/src/index.css. */ + height: 2rem; + content: ''; + -webkit-app-region: drag; +} main { display: grid; gap: 1.5rem; diff --git a/packages/desktop/src/window.ts b/packages/desktop/src/window.ts index 68eebbdb..fa673059 100644 --- a/packages/desktop/src/window.ts +++ b/packages/desktop/src/window.ts @@ -109,6 +109,9 @@ export function createMainWindow(): BrowserWindow { show: false, minWidth: 800, minHeight: 600, + // Let the page fill the native title bar on macOS while keeping its traffic lights in their + // standard position. The page provides the drag surface the hidden frame no longer supplies. + ...(process.platform === 'darwin' ? {titleBarStyle: 'hidden'} : {}), // The window's icon where the window carries one, Windows and Linux, in a development run; // a packaged app has its own, and no build/ beside it. ...(app.isPackaged ? {} : {icon: path.join(__dirname, '../build/icon.png')}), diff --git a/packages/web/src/components/agent-shell.tsx b/packages/web/src/components/agent-shell.tsx index 18197b2c..20549a3f 100644 --- a/packages/web/src/components/agent-shell.tsx +++ b/packages/web/src/components/agent-shell.tsx @@ -26,7 +26,7 @@ export function AgentShell({children, feedback = true}: {children: ReactNode; fe }).toString(); return ( -
+
{panel !== undefined && desktop && ( -