Skip to content

feat: macOS Electron native titlebar, dock icon, and drag regions#190

Open
davidliuk wants to merge 3 commits intomainfrom
feat/electron-ui-ux
Open

feat: macOS Electron native titlebar, dock icon, and drag regions#190
davidliuk wants to merge 3 commits intomainfrom
feat/electron-ui-ux

Conversation

@davidliuk
Copy link
Copy Markdown
Collaborator

@davidliuk davidliuk commented Apr 16, 2026

Summary

Bring the macOS Electron desktop client in line with native app conventions — custom titlebar, proper dock icon, and window drag regions.

1. Custom titlebar with drag regions (7c0aa85)

  • Switch titleBarStyle to hiddenInset on macOS so web content fills the full window and the traffic-light buttons overlay the top-left corner
  • Hide the sidebar logo/title on all Electron platforms (dock/taskbar already shows app identity; web version keeps the logo)
  • Add 68 px left padding on macOS to clear the traffic-light buttons; 32 px top padding in the collapsed sidebar strip
  • Mark sidebar header and main-content header as -webkit-app-region: drag; 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) — window.isElectron via preload can miss if the module evaluates before contextBridge is wired

2. macOS dock icon with native squircle shape (a7c3461)

  • Generate icon-dock.png with rounded corners (~22.37 % radius per Apple HIG) via sharp dest-in compositing
  • Size artwork to 82 % of canvas with transparent padding, matching the visual weight of Finder, Chrome, etc.
  • Fix .iconset sizes to the exact set iconutil expects (16/32/128/256/512 + @2x retina)
  • Add iconutil --convert icns step for proper .icns generation
  • Only set dock icon in dev mode; packaged builds rely on the bundled .icns

3. Dev dock label patch (d62ff19)

  • Patch CFBundleName / CFBundleDisplayName in Electron.app/Contents/Info.plist via PlistBuddy before launching, so the macOS dock reads "Dr. Claw" instead of "Electron" during development

Files changed

File What changed
electron/main.mjs hiddenInset titlebar on macOS; dev-only dock icon from icon-dock.png
electron/cli.mjs PlistBuddy patch for dev dock label
scripts/build-electron-icons.mjs Squircle mask, proper sizing, iconutil step, correct .iconset sizes
SidebarHeader.tsx Logo hidden on Electron; 68 px traffic-light padding on macOS; electron-drag/no-drag
SidebarCollapsed.tsx 32 px top padding on macOS; electron-drag/no-drag on buttons
MainContentHeader.tsx electron-drag on header area
src/index.css .electron-drag / .electron-no-drag rules outside @layer

Test plan

  • macOS Electron: traffic lights visible in top-left, sidebar logo hidden, window draggable from sidebar header and main content header
  • macOS Electron: dock icon is squircle-shaped with proper padding, matches size of native icons
  • macOS Electron: buttons in sidebar remain clickable (not swallowed by drag region)
  • Windows/Linux Electron: native title bar preserved, no extra padding, sidebar logo hidden
  • Web browser: sidebar logo and title shown, no drag-region side effects

@Zhang-Henry
Copy link
Copy Markdown
Collaborator

Review notes

CI is green and the drag-region approach looks right. Two issues I verified against the branch source:

1. Windows/Linux Electron will get bogus padding

electron/main.mjs only switches titleBarStyle to hiddenInset on macOS — Windows/Linux keep the native title bar:

titleBarStyle: isMac ? 'hiddenInset' : 'default',

But the React side detects Electron platform-agnostically:

// SidebarHeader.tsx
const isElectronApp = typeof navigator !== 'undefined' && /Electron/.test(navigator.userAgent);
// ...
<div style={{ paddingLeft: isElectronApp ? '68px' : '0px' }}>

and similarly in SidebarCollapsed.tsx:

style={{ paddingTop: /Electron/.test(navigator.userAgent) ? '32px' : '12px' }}

So on Windows/Linux Electron, where there is still a native title bar and no traffic lights in the sidebar, users get:

  • 68px of empty space to the left of the sidebar refresh/new/collapse buttons
  • 32px extra top padding in the collapsed sidebar strip
  • The !isElectronApp branch hides the sidebar logo entirely

Suggested fix: gate the safe-zone on macOS specifically, e.g. via navigator.platform / navigator.userAgentData.platform, or better, expose process.platform through the preload / contextBridge so the renderer has a reliable isMac flag.

2. Dead CSS rule

The file adds:

html[data-electron][data-platform="darwin"] .electron-mac-sidebar-header {
  padding-top: 38px;
}

but grepping the branch shows electron-mac-sidebar-header only appears in comments — it’s never applied to any element. data-electron / data-platform are also never set on <html> anywhere in the codebase. The inline paddingLeft: 68px / paddingTop: 32px styles are doing the actual work, so this CSS rule is dead. Either wire it up (set attributes in main.mjs preload and apply the class where needed) or remove it to avoid future confusion.

The electron-drag / electron-no-drag CSS placed outside @layer is correct and needed — Tailwind cascade layers would otherwise win. That part is fine.

@davidliuk
Copy link
Copy Markdown
Collaborator Author

Thanks for the thorough review — both issues fixed in fe9f707.

1. Windows/Linux bogus padding → fixed

The preload already exposes process.platform via window.electronPlatform (and stamps data-platform on <html>), and useDesktop.ts already exports a synchronous isMacElectron constant — the sidebar components just weren't using it. Both SidebarHeader.tsx and SidebarCollapsed.tsx now import isMacElectron instead of sniffing navigator.userAgent:

  • paddingLeft: 68px → only on macOS Electron (traffic-light clearance)
  • paddingTop: 32px → only on macOS Electron (collapsed sidebar)
  • Logo/brand block → now visible on Windows/Linux Electron (no traffic lights to hide behind)

2. Dead CSS rule → removed

Deleted the html[data-electron][data-platform="darwin"] .electron-mac-sidebar-header rule. The class was never applied to any element (only mentioned in comments), and the inline styles handle the actual padding. The electron-drag / electron-no-drag rules outside @layer are preserved as-is.

@davidliuk
Copy link
Copy Markdown
Collaborator Author

Follow-up fix: macOS dock icon now uses squircle shape (011acb9)

The dock icon was showing as a raw square because app.dock.setIcon() bypasses macOS's native icon masking. Two changes:

  1. Icon build script (scripts/build-electron-icons.mjs):

    • Now generates icon-dock.png with rounded corners (~22.37% radius matching Apple HIG) via sharp dest-in compositing with an SVG mask
    • Fixed .iconset sizes to the exact set iconutil expects (16/32/128/256/512 + @2x retina) — removed invalid 64×64 and standalone 1024×1024 entries
    • Added iconutil --convert icns step for proper .icns generation on macOS
  2. electron/main.mjs:

    • app.dock.setIcon() now only runs in dev mode (!app.isPackaged) using the pre-masked icon-dock.png
    • Packaged builds skip the runtime override entirely and rely on the bundled .icns, which gets native squircle masking from macOS

Before → After (dock icon):

Before After
Raw square PNG, no corner rounding Squircle-masked, matches native macOS app icons

@davidliuk
Copy link
Copy Markdown
Collaborator Author

Follow-up: dock icon sizing + app name (8fe5a4b)

Two more fixes for the macOS dock appearance:

  1. Icon too large — the squircle was filling the entire dock tile edge-to-edge while native icons (Chrome, Finder, etc.) have ~18% padding. Now the artwork is sized to 82% of the canvas and centered on a transparent background before the squircle mask is applied, matching the visual weight of system icons.

  2. Dock label said "Electron" — in dev mode, the dock label comes from the Electron.app bundle's Info.plist, not app.setName(). Added a PlistBuddy patch step in cli.mjs that sets CFBundleName / CFBundleDisplayName to "Dr. Claw" before launching the Electron binary. Production builds are unaffected (electron-builder sets these in the .app bundle).

@davidliuk
Copy link
Copy Markdown
Collaborator Author

Hide sidebar logo on all Electron platforms (bb5cf68)

The sidebar logo/title was reappearing on macOS Electron because the previous fix gated visibility on isMacElectron — if the platform detection had any timing edge case, the logo would show.

Simplified: now uses isElectron (platform-agnostic) to hide the logo on all desktop clients. Rationale: the dock/taskbar already shows app identity, so the sidebar logo is redundant and competes for space with the macOS traffic lights. Web version keeps the logo and title as before.

  • macOS Electron: 68px left padding (traffic-light clearance) + no logo
  • Windows/Linux Electron: no extra padding + no logo
  • Web: logo + title shown as before

…fe-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
Overhaul the icon build pipeline so the macOS dock icon matches native
app icons (Chrome, Finder, etc.) in shape and visual weight.

Icon build script (scripts/build-electron-icons.mjs):
- Generate icon-dock.png with squircle-rounded corners (~22.37% radius
  per Apple HIG) via sharp dest-in compositing with an SVG mask
- Size artwork to 82% of canvas with transparent padding, matching the
  visual weight of system icons
- Fix .iconset sizes to the exact set iconutil expects (16/32/128/256/512
  + @2x retina), removing invalid 64x64 and standalone 1024x1024 entries
- Add iconutil --convert icns step for .icns generation on macOS

Electron main process (electron/main.mjs):
- Only set dock icon in dev mode (no .app bundle); packaged builds rely
  on the bundled .icns which gets native squircle masking from macOS
- Use the pre-masked icon-dock.png instead of the raw square icon.png

Made-with: Cursor
In dev mode the macOS dock shows "Electron" because the label comes from
the Electron.app bundle's Info.plist, not app.setName(). Patch
CFBundleName and CFBundleDisplayName via PlistBuddy before launching the
Electron binary so the dock reads "Dr. Claw" during development.
Production builds are unaffected (electron-builder sets these).

Made-with: Cursor
@davidliuk davidliuk force-pushed the feat/electron-ui-ux branch from bb5cf68 to d62ff19 Compare April 16, 2026 22:40
@davidliuk davidliuk changed the title feat: macOS Electron custom titlebar with traffic-light safe zone feat: macOS Electron native titlebar, dock icon, and drag regions Apr 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants