Files
simpletopbar/AGENTS.md
T

75 lines
3.6 KiB
Markdown

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