Native UI outside the web views
Both apps render the Google Workspace page in Chromium and draw every other macOS surface with real
system controls. The ownership rule, the native toolbar and the helper-hosted settings window are
their subject. Each surface's own mechanics live elsewhere: the menu bar in
menu-bar.md, notifications in notifications.md, widgets in
widgets.md, and Focus filters in focus-filters.md.
Who draws what
Electron renders the Google page and nothing else. Everything outside those web contents is a real macOS control:
flowchart TD
subgraph NativeOS["Native macOS (AppKit & SwiftUI)"]
TB["Cocoa NSToolbar (C++ N-API addon)"]
Pop["Menu bar status item & popover (Swift helper)"]
Settings["Settings window (Swift helper)"]
Dock["NSDockTile dynamic date (Core Graphics / Core Text)"]
Widgets["Desktop & Lock Screen widgets (WidgetKit .appex)"]
Intents["Focus filters & App Intents (.appex)"]
end
subgraph WebViews["Chromium WebContentsViews"]
Gmail["Gmail (isolated persist: partition)"]
Calendar["Google Calendar (isolated persist: partition)"]
end
NativeOS --- WebViews
| Surface | Drawn by | Where |
|---|---|---|
| Toolbar | Native N-API addon, in-process | native/ |
| Dock tile | Native N-API addon, in-process | native/ |
| Settings window | Swift helper | swift/ |
| Menu bar item & popover | Swift helper | swift/ |
| Notifications | Swift helper | swift/ |
| Widgets | WidgetKit extension | .appex |
| Focus filters | App Intents extension | .appex |
The helper and the addon are separate processes, and the extensions are OS-hosted bundles nothing can
call into. process-model-and-ipc.md covers how each is reached. The
project draws with real system controls rather than hand-rolled lookalikes, because native controls
track whichever macOS release is running; the principle is in design.md.
Toolbar
Both apps host an in-process native AppKit NSToolbar (.unified style), built in the app's
native/ package and reached through the N-API addon. It draws over the window's titlebar rather
than inside a web view.
NSSegmentedControlis the multi-account switcher, set as anNSToolbarItem's view. It is a real AppKit control rather than a hosted SwiftUI view, so it renders with each OS's own chrome; a hosted SwiftUI view gets no chrome of its own and renders its segments as loose, unaligned labels in the titlebar.NSToolbarItemGroupgroups related buttons. It is built by assigning realNSToolbarItemsubitems rather than with theimages:convenience initialiser, whose segments do not reflect a subitem'sisEnabled, so the group could only grey out as a whole.controlRepresentation = .expandedkeeps every member visible and preserves the dividers.- SF Symbols load from the OS with
[NSImage imageWithSystemSymbolName:symbolName accessibilityDescription:nil].
Enabled state
The main process owns an item's enabled state and pushes it over toolbar_set_item_enabled. Items
override validate() and read from a shared store, because assigning isEnabled once does not hold:
AppKit revalidates on its own schedule, and its default validation enables any item whose target
responds to its action, undoing the assignment. The lookup descends into groups, because
toolbar.items holds a group rather than its members and validateVisibleItems() only validates the
items it lists.
Sheet anchoring
Electron hangs sheets from the top of the content view, which on a hiddenInset window spans the
titlebar, so a sheet covered the toolbar it belonged to. attachNativeToolbar offsets them with
BaseWindow.setSheetOffset(TOOLBAR_HEIGHT), the same constant the web view is inset by.
Click dispatch
AppKit fires the Objective-C action selector, which calls an N-API ThreadSafeFunction to send the
action string and parameters back into the Node event loop.
Shared building blocks
Both apps build on SuiteNative (packages/swift-native):
AccountSegmentedControl: a real AppKitNSSegmentedControl, so the segmented chrome is whatever the running OS draws.ToolbarManagerBase: manages the window lifecycle, re-attaches the toolbar when the window is re-created, and persists palette customisation.ItemValidation: a central validation registry that preserves enabled states through AppKit validation cycles.
Each app supplies its own item set: Magimail's is in
../magimail/overview.md's Native toolbar, Magical's in
../magical/overview.md's Native toolbar.
Settings window
The Swift helper renders Settings as SwiftUI views inside an NSTabViewController. The main process
does nothing but forward the request: there is no HTML settings window to fall back to, so when the
helper binary is missing there is no settings window at all.
The helper is restarted freely, so it holds no authority over what Settings shows. The main process pushes the settings file, the account list and the app version with the request, and the window draws from whatever that push last carried. Opening Settings from the helper's own menu therefore goes through the main process rather than instantiating the window locally, which would show the built-in defaults on a fresh helper and miss an account added since.
Dock tile
The Dock tile is drawn in-process rather than from static image files. Magical's date tile, its
midnight rollover and its badge are in ../magical/dock-icon.md, and the
drawing rule and the badge-triage rule are in brand-and-icons.md.