Files
simpletopbar/AGENTS.md
T

3.6 KiB

AGENTS.md

SimpleTopBar is a native macOS menu-bar app written in Swift/SwiftUI. Single executable SwiftPM package, no external dependencies (all Apple frameworks).

Architecture

  • Sources/SimpleTopBar/App.swift — @main entry. MenuBarExtra with .menuBarExtraStyle(.window) hosting TopBarView. AppDelegate handles the --self-test flag and the single-instance guard. LoginItem wraps SMAppService.
  • Sources/SimpleTopBar/AppListModel.swift — @MainActor ObservableObject singleton. refresh() reads NSWorkspace.shared.runningApplications and splits them by activationPolicy into regularApps and backgroundApps (both sorted by name). Subscribes to the NSWorkspace launch/terminate/ activate/hide/unhide notifications to refresh automatically.
  • Sources/SimpleTopBar/TopBarView.swift — the panel. section(...) renders the regular apps; backgroundSection is the collapsible dropdown for background apps. AppRow shows icon + name + active dot with a right-click Activate/Hide/Quit menu.
  • Sources/SimpleTopBar/SelfTest.swift — runSelfTest() powers --self-test.

The background dropdown

This is the core feature. Requirements:

  • The background-app section is a dropdown, not a flat list. Collapsed, it is one header row showing the count.
  • Expanded, it shows at most AppListModel.backgroundPageSize (5) apps. Any overflow is hidden behind a "Show N more" row, so the panel never grows unbounded. Collapsing resets the page.
  • AppListModel drives all of this: backgroundExpanded, backgroundVisibleCount, visibleBackgroundApps, hiddenBackgroundCount, canShowMoreBackgroundApps, toggleBackgroundExpanded(), showMoreBackgroundApps().
  • The pure helpers clampedVisibleCount and nextVisibleCount are the overflow math and are covered by --self-test. Keep them pure and nonisolated (no AppKit, no actor state).

Commands

swift build                          # debug build
swift run SimpleTopBar               # dev run (menu bar icon appears)
swift run SimpleTopBar --self-test   # headless overflow + classification check, exit 0/1
./build-app.sh 0.1.0                 # release build + .app bundle + icon + codesign
./release.sh 0.1.0                   # build-app.sh + dist/SimpleTopBar.zip + sha256
scripts/make-icon.sh                 # regenerate SimpleTopBar.app/Contents/Resources/Icon.icns

There is no test target and no linter/formatter configured. Verification = build

  • --self-test.

Bundle build & signing

build-app.sh does: release build → handwritten Info.plist (NOT generated from Package.swift) → make-icon.sh → codesign. It resolves a Developer ID Application identity from the login keychain and falls back to ad-hoc signing (-) if none is present. LSUIElement is true → no Dock icon. Bump CFBundleShortVersionString/CFBundleVersion via the build-app.sh version argument and pass the same version to release.sh.

Gotchas

  • AppListModel is @MainActor; the NSWorkspace observers hop back with Task { @MainActor in ... } because the notification closure is nonisolated.
  • The app excludes itself from the list via Bundle.main.bundleIdentifier, and the single-instance guard terminates stale copies with the same bundle id.
  • Reading activationPolicy is reliable but must happen on the main thread; keep all NSWorkspace access inside the model.
  • Don't add Accessibility/Screen Recording permission code: this app only reads the running-app list, which needs no TCC grants.
  • .gitignore covers .build/, SimpleTopBar.app/, dist/ — build artifacts should never be committed.