Skip to content

systems app shell

Nik edited this page May 30, 2026 · 1 revision

App shell and UI

Active contributors: Nik, Ran

Purpose

The app shell is the menu bar application that hosts everything else: it owns the process lifecycle, the status-bar item and menu, the Sparkle updater, server startup/shutdown ordering, notifications, auth-directory monitoring, and the SwiftUI settings window. DroidProxy is an LSUIElement app, so it has no dock icon and no main window — the menu bar item is the primary surface, and settings open in an on-demand window.

The shell spans src/Sources/main.swift, src/Sources/AppDelegate.swift, and src/Sources/SettingsView.swift, supported by src/Sources/IconCatalog.swift, src/Sources/LogoView.swift, and src/Sources/NotificationNames.swift.

Directory layout

src/Sources/
  main.swift              # NSApplicationMain entry point
  AppDelegate.swift       # menu bar, lifecycle, Sparkle, server ordering
  SettingsView.swift      # SwiftUI settings window
  IconCatalog.swift       # thread-safe NSImage cache
  LogoView.swift          # inline-SVG wordmark
  NotificationNames.swift # shared Notification.Name constants

Key abstractions

Type Role
main.swift Creates NSApplication.shared, assigns an AppDelegate, and calls NSApplicationMain.
AppDelegate NSApplicationDelegate + NSWindowDelegate + UNUserNotificationCenterDelegate. Owns statusItem, menu, serverManager, thinkingProxy, the Sparkle SPUStandardUpdaterController, the AuthDirectoryMonitor, and the settings window.
SettingsView SwiftUI Form with all settings sections. Holds AuthManager, OAuthUsageTracker, and @AppStorage-backed AppPreferences.
ServiceRow / AccountRowView Reusable provider row with expandable account list, disable/remove actions, and the round-robin note.
VisualEffectBlur NSViewRepresentable wrapping NSVisualEffectView for behind-window blur.
IconCatalog Singleton thread-safe NSImage cache keyed by name + size + template flag.
LogoView Renders the DroidProxy wordmark from an inline SVG as a template image.
NotificationNames serverStatusChanged and authDirectoryChanged; droidProxyThemeChanged is declared in AppDelegate.swift.

How it works

Launch and server ordering

main.swift boots NSApplication with an AppDelegate. applicationDidFinishLaunching forces the dark appearance, builds the main menu (so Cmd+C/V/X/A work), builds the menu bar, instantiates ServerManager and ThinkingProxy, warms commonly used icons, requests notification authorization, starts the servers, and registers observers for serverStatusChanged and authDirectoryChanged.

startServer() starts the ThinkingProxy first, then pollForProxyReadiness polls up to 60 times at 50 ms intervals for thinkingProxy.isRunning. Only once the proxy is ready does it call serverManager.start; if the backend fails, the proxy is stopped again to keep state consistent. stopServer() reverses the order — proxy first, then backend.

sequenceDiagram
    participant M as main.swift
    participant A as AppDelegate
    participant TP as ThinkingProxy :8317
    participant SM as ServerManager :8318

    M->>A: NSApplicationMain + delegate
    A->>A: applicationDidFinishLaunching
    A->>A: setupMainMenu / setupMenuBar
    A->>A: init ServerManager + ThinkingProxy
    A->>A: preloadIcons, configureNotifications
    A->>TP: startServer() -> thinkingProxy.start()
    loop poll up to 60x @ 50ms
        A->>TP: thinkingProxy.isRunning?
    end
    TP-->>A: ready
    A->>SM: serverManager.start { success }
    alt backend ok
        SM-->>A: success
        A->>A: updateMenuBarStatus + notify "Server Started"
    else backend failed
        A->>TP: thinkingProxy.stop()
        A->>A: notify "Server Failed"
    end
Loading

Menu bar

setupMenuBar builds a variable-length status item and a menu: a status line, Open Settings, Start/Stop Server (tag startStop), Copy Server URL (tag copyURL), Open Dashboard (tag dashboard), Check for Updates... (targeting the Sparkle SPUStandardUpdaterController), and Quit. updateMenuBarStatus rewrites the status line, the start/stop title, and the enabled state of the copy/dashboard items, then refreshes the icon. updateStatusBarIcon pulls icon-active.png / icon-inactive.png from IconCatalog and falls back to the network / network.slash SF Symbols if the bundled asset is missing. Copy Server URL copies http://<host>:8317, and Open Dashboard opens http://<host>:8318/management.html (host derived from AppPreferences.bindAddress, with 0.0.0.0 displayed as localhost/127.0.0.1).

Notifications, monitoring, and theme

Notifications are delivered through UNUserNotificationCenter (showNotification), with willPresent configured to show banners while the app is foreground. startMonitoringAuthDirectory installs an AuthDirectoryMonitor; on change it posts authDirectoryChanged, and handleAuthDirectoryChanged re-fronts the settings window so newly discovered accounts appear. A themeObserver listens for droidProxyThemeChanged and re-runs applyTheme, which toggles between an opaque black window (OLED / full opacity) and a clear non-opaque window (translucent Liquid Glass).

Settings window

createSettingsWindow builds a 1000×900 titled window with a transparent titlebar (titlebarAppearsTransparent, hidden title, movable by background) so the traffic-light buttons float over the content, then hosts SettingsView in an NSHostingView. The window is not released on close; windowDidClose removes the theme observer and clears the reference.

Termination

quit stops the servers, waits 0.3 s, then terminates. applicationWillTerminate removes observers, stops the auth monitor, and stops the servers; applicationShouldTerminate also stops the servers and returns .terminateNow.

SettingsView sections

SettingsView is a grouped Form with these sections:

  • Server status — capsule button that starts/stops ServerManager.
  • OAuth Quota UsageoauthUsageDashboard plus a refresh button driving OAuthUsageTracker; shown only when a Codex or Claude provider/account is present.
  • Launch at loginSMAppService.mainApp register/unregister (macOS 13+).
  • Auth files — "Open Folder" opens ~/.cli-proxy-api/.
  • Factory custom models — Apply / Re-apply writes DroidProxy model aliases into ~/.factory/settings.json (timestamped .bak first), reconciled against checkFactoryModelsInstalled.
  • Remote Management — collapsible allow-remote toggle, secret-key field, and a beta-gated bind-address field.
  • Logging — verbose toggle plus "Open Logs" for ~/.cli-proxy-api/logs/.
  • Services — one providerServiceRow per provider (claude, codex, antigravity, kimi, and beta-gated cursor) plus the collapsible Codex fast-mode subsection (GPT 5.3 Codex / 5.4 / 5.5 toggles).

ServiceRow and AccountRowView render the per-provider enable toggle, the expandable connected-account list, per-account disable/remove with confirmation, and the round-robin-with-auto-failover note when more than one account is enabled. The Liquid Glass helpers (droidGlassCard, droidGlassCapsule, droidGlassProminent, droidGlassPlain) gate on macOS 26 availability and fall back to flat fills/standard button styles. The header carries a Beta toggle, a background-opacity slider, and an OLED/Liquid-Glass theme button; the background composes VisualEffectBlur with radial gradients (or solid black under OLED).

Integration points

  • Drives ServerManager and ThinkingProxy: the shell owns startup ordering, the start/stop menu, and the server-status capsule.
  • Renders AuthManager accounts (via ServiceRow) and OAuthUsageTracker windows (via the usage dashboard).
  • Writes Factory settings through DroidProxyModelCatalog when Apply / Re-apply runs.
  • Reads/writes AppPreferences through @AppStorage (fast-mode toggles, remote access, bind address, theme, opacity, verbose logging, beta flag).
  • Notifications: posts and observes serverStatusChanged, authDirectoryChanged, and droidProxyThemeChanged.

Entry points for modification

  • Add a menu item: extend setupMenuBar and, if state-dependent, updateMenuBarStatus (assign a tag in MenuTag).
  • Change startup/shutdown ordering or readiness polling: edit startServer / pollForProxyReadiness / stopServer.
  • Add a settings section or provider row: edit SettingsView.body and providerServiceRow.
  • Change window chrome or theming: edit createSettingsWindow / applyTheme and the background block in SettingsView.
  • Add a cached icon: call IconCatalog.shared.image(named:resizedTo:template:) and optionally warm it in preloadIcons.

Key source files

File Role
src/Sources/main.swift NSApplicationMain entry point.
src/Sources/AppDelegate.swift Menu bar, lifecycle, Sparkle, server ordering, notifications, theme, settings window.
src/Sources/SettingsView.swift SwiftUI settings form and reusable rows.
src/Sources/IconCatalog.swift Thread-safe NSImage cache.
src/Sources/LogoView.swift Inline-SVG wordmark.
src/Sources/NotificationNames.swift Shared Notification.Name constants.

Related pages

Clone this wiki locally