From 7c0aa85c3b933d20cfc85862c914ccc6c5854923 Mon Sep 17 00:00:00 2001 From: David Liu Date: Thu, 16 Apr 2026 15:39:59 -0700 Subject: [PATCH 1/3] feat: macOS Electron custom titlebar with drag regions and sidebar safe-zone Switch titleBarStyle to hiddenInset on macOS so web content fills the full window and the traffic-light buttons (close/minimize/maximize) overlay the top-left corner. Sidebar adjustments: - Hide the sidebar logo/title on all Electron platforms (desktop clients show identity in the dock/taskbar; web version keeps the logo) - Add 68px left padding on macOS to clear the traffic-light buttons - Add 32px top padding in the collapsed sidebar strip on macOS - Mark sidebar header and main content header as -webkit-app-region:drag so the window is draggable; interactive children use no-drag - Place drag-region CSS outside @layer to avoid Tailwind cascade issues Detection uses navigator.userAgent (Chromium injects "Electron/x.y.z" unconditionally) rather than preload-dependent window.isElectron, which can miss if the module evaluates before contextBridge is wired. Made-with: Cursor --- electron/main.mjs | 2 +- .../view/subcomponents/MainContentHeader.tsx | 13 ++++-- .../view/subcomponents/SidebarCollapsed.tsx | 12 ++++-- .../view/subcomponents/SidebarHeader.tsx | 42 ++++++++++++------- src/index.css | 18 +++++++- 5 files changed, 64 insertions(+), 23 deletions(-) diff --git a/electron/main.mjs b/electron/main.mjs index aff4e274..42bc4119 100644 --- a/electron/main.mjs +++ b/electron/main.mjs @@ -738,7 +738,7 @@ function createWindow(baseUrl) { backgroundColor: '#0b1220', autoHideMenuBar: !isMac, icon: iconPath, - titleBarStyle: 'default', + titleBarStyle: isMac ? 'hiddenInset' : 'default', webPreferences: { contextIsolation: true, sandbox: true, diff --git a/src/components/main-content/view/subcomponents/MainContentHeader.tsx b/src/components/main-content/view/subcomponents/MainContentHeader.tsx index c0ec3490..8088bb68 100644 --- a/src/components/main-content/view/subcomponents/MainContentHeader.tsx +++ b/src/components/main-content/view/subcomponents/MainContentHeader.tsx @@ -13,9 +13,15 @@ export default function MainContentHeader({ onMenuClick, }: MainContentHeaderProps) { return ( -
+ /* + * electron-drag: makes the header bar a window drag handle on macOS. + * Interactive children (title area with potential buttons, tab switcher) + * use electron-no-drag to restore normal pointer events inside the drag + * region. The trailing empty flex-1 div stays draggable as dead space. + */ +
-
+
{isMobile && }
-
+
{selectedProject && activeTab !== 'dashboard' && activeTab !== 'trash' && ( + {/* Empty right spacer — stays as drag region */}
diff --git a/src/components/sidebar/view/subcomponents/SidebarCollapsed.tsx b/src/components/sidebar/view/subcomponents/SidebarCollapsed.tsx index 1f191f69..58c46d48 100644 --- a/src/components/sidebar/view/subcomponents/SidebarCollapsed.tsx +++ b/src/components/sidebar/view/subcomponents/SidebarCollapsed.tsx @@ -18,14 +18,18 @@ export default function SidebarCollapsed({ onShowVersionModal, t, }: SidebarCollapsedProps) { + const isMacDesktop = typeof navigator !== 'undefined' + && /Electron/.test(navigator.userAgent) && /Macintosh/.test(navigator.userAgent); + return (
{/* Expand button with brand logo */}
); + // UA detection — Chromium always injects "Electron/x.y.z" into the user + // agent regardless of preload/contextBridge, so this is unconditionally + // reliable. Module-level detection via window.isElectron can miss if the + // bundle evaluates before the preload wires contextBridge values. + const isDesktopApp = typeof navigator !== 'undefined' && /Electron/.test(navigator.userAgent); + const isMacDesktop = isDesktopApp && /Macintosh/.test(navigator.userAgent); + return (
{/* Desktop header */} -
-
- {IS_PLATFORM ? ( - - - - ) : ( - +
+
+ {!isDesktopApp && ( + IS_PLATFORM ? ( + + + + ) : ( +
+ +
+ ) )} -
+