# 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 ```bash 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.