Add SimpleTopBar menu bar app with collapsible background app list
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user