Magic Apps

Documentation

macmagic.app

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.

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):

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.