3.6 KiB
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—@mainentry.MenuBarExtrawith.menuBarExtraStyle(.window)hostingTopBarView.AppDelegatehandles the--self-testflag and the single-instance guard.LoginItemwrapsSMAppService.Sources/SimpleTopBar/AppListModel.swift—@MainActorObservableObjectsingleton.refresh()readsNSWorkspace.shared.runningApplicationsand splits them byactivationPolicyintoregularAppsandbackgroundApps(both sorted by name). Subscribes to theNSWorkspacelaunch/terminate/ activate/hide/unhide notifications to refresh automatically.Sources/SimpleTopBar/TopBarView.swift— the panel.section(...)renders the regular apps;backgroundSectionis the collapsible dropdown for background apps.AppRowshows 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. AppListModeldrives all of this:backgroundExpanded,backgroundVisibleCount,visibleBackgroundApps,hiddenBackgroundCount,canShowMoreBackgroundApps,toggleBackgroundExpanded(),showMoreBackgroundApps().- The pure helpers
clampedVisibleCountandnextVisibleCountare the overflow math and are covered by--self-test. Keep them pure andnonisolated(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
AppListModelis@MainActor; theNSWorkspaceobservers hop back withTask { @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
activationPolicyis reliable but must happen on the main thread; keep allNSWorkspaceaccess inside the model. - Don't add Accessibility/Screen Recording permission code: this app only reads the running-app list, which needs no TCC grants.
.gitignorecovers.build/,SimpleTopBar.app/,dist/— build artifacts should never be committed.