Skip to content

feat: menu bar app + pause subcommands - #2

Merged
razbahri merged 3 commits into
mainfrom
feature/menu-bar-app
May 9, 2026
Merged

razbahri merged 3 commits into
mainfrom
feature/menu-bar-app

Conversation

@razbahri

@razbahri razbahri commented May 9, 2026

Copy link
Copy Markdown
Owner

Summary

  • Adds Ogmac.app, a SwiftUI menu bar utility that surfaces sync status, settings, and run history for the existing Python daemon. Thin observer + commander — reads state.db, tails sync.log, edits config.yaml, and shells out to the ogmac CLI for actions.
  • Adds ogmac pause / ogmac unpause subcommands with a paused flag in state.db that survives reboots.
  • Drops the dead sync.interval_seconds config field (the launchd plist has always been the only schedule source).

Highlights

  • No polling. A DispatchSource file-system watcher on state.db refreshes the panel within ~200 ms of every daemon write — including manual Sync now clicks of any duration.
  • ?immutable=1 SQLite open — the daemon's WAL-mode file can't be opened cleanly via GRDB without -shm write access; raw sqlite3 with the immutable URI flag is the only mode that worked.
  • Last change row that shows the most recent run with non-zero CRUD activity (or "Up to date · checked X min ago" when only no-op syncs exist).
  • History filter that hides the every-2-min NetworkChange-triggered no-op syncs (see issue Daemon syncs every ~2 min due to NetworkChange LaunchEvent #1) and keeps only meaningful runs and scheduled :00/:15/:30/:45 ticks.
  • Custom app icon — dark squircle with a white rounded-rectangle outline, programmatically rendered in 7 sizes (16/32/64/128/256/512/1024 px).
  • Menu bar SF Symbols use the app family (hollow rounded rectangle) to match the bundle icon. State is distinguished by tint + badge, not by glyph.
  • Diagnostic log at ~/Library/Logs/ogmac/menubar.log for every refresh, watcher event, and reader error.

Spec & plan

  • Design: docs/superpowers/specs/2026-05-08-menu-bar-app-design.md
  • Agent breakdown: docs/superpowers/plans/2026-05-08-menu-bar-app-team-breakdown.md

What changed

Area Files
Swift app 27 source files under Ogmac/, 17 test files + fixtures under OgmacTests/
App icon 7 PNGs + Contents.json
Python daemon state.py, cli.py, config.py, +1 integration test
Packaging project.yml, Ogmac.xcodeproj/, Package.resolved, build_app.sh, Casks/ogmac.rb, exportOptions.plist, com.ogmac.sync.plist (entry point + schedule fixed)
Docs README.md, docs/operations.md, docs/troubleshooting.md, CHANGELOG.md
.gitignore .DS_Store, xcuserdata/, *.local.md

Total: +3952 / −9 across 75 files.

Test plan

  • xcodebuild -project Ogmac.xcodeproj -scheme Ogmac -configuration Debug test → 62/62 Swift tests pass
  • pytest tests/ → 172 passed, 2 skipped (was 156 before)
  • bash packaging/build_app.sh → produces a runnable dist/Ogmac.app (universal, unsigned)
  • open dist/Ogmac.app → menu bar icon appears with healthy state; panel populates with real data on first click
  • ogmac pause && ogmac sync && ogmac status → no-op (skipped); ogmac unpause && ogmac sync → real sync
  • Verify Settings window opens to front when "Settings…" is clicked (LSUIElement=true activation)
  • Verify History sheet shows only meaningful runs (no every-2-min noise)

Known issue

The launchd plist has a NetworkChange LaunchEvent with ThrottleInterval=120, causing the daemon to fire every ~2 min on Wi-Fi roams / VPN reconnects / DHCP renewals. Filed as #1 with three options. The menu bar app's History filter works around it client-side; the underlying daemon behavior is unchanged in this PR.

🤖 Generated with Claude Code

razbahri and others added 3 commits May 8, 2026 22:46
Thin observer + commander design — Swift+SwiftUI menu bar app that reads
state.db / sync.log / config.yaml and shells out to the existing CLI.
Adds a small Python diff (pause flag, pause/unpause subcommands) and
drops the dead sync.interval_seconds field as side work.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
7-agent decomposition with 4 execution waves. F+P parallel in Wave 0,
R+C parallel in Wave 1, V+G parallel in Wave 2, I terminal in Wave 3.
File ownership map is collision-free except for one sanctioned handoff
(OgmacApp.swift, F→I).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds Ogmac.app, a SwiftUI menu bar utility that surfaces sync status,
settings, and run history for the existing Python daemon. The daemon's
launchd schedule and CLI surface are unchanged; the app is a thin
observer + commander that reads state.db, tails sync.log, edits
config.yaml, and shells out to the ogmac CLI for actions.

## Menu bar app

- Status icon — uses SF Symbol "app" family (hollow rounded rectangle,
  matches the bundle icon motif). State distinguished by tint + badge:
  healthy=app.fill/accent, warning=app.badge/yellow,
  error=app.badge.fill/red, paused=app/secondary,
  needsLogin=app.dashed, syncing=arrow.triangle.2.circlepath.
- Live updates via DispatchSource file-system watcher on state.db —
  no periodic polling. Refresh fires within ~200ms of each daemon
  write, regardless of how long the sync took.
- Panel: next-sync countdown, "Last change" row that shows the most
  recent run with non-zero CRUD activity (or "Up to date · checked X
  min ago" when only no-op syncs exist), Sync now, Settings, History,
  Pause/Resume, Quit.
- Settings scene with three tabs (Connection / Sync / Privacy);
  atomic temp-file + rename writes to ~/.config/ogmac/config.yaml.
  Launch-at-login via SMAppService.
- History view with meaningful-runs filter — hides the every-2-min
  network-change-triggered no-op syncs, keeps real changes, failures,
  and scheduled :00/:15/:30/:45 ticks. Inline "← Back" navigation
  inside the dropdown (avoids MenuBarExtra sheet collapse bug).
- First-launch screen when ogmac CLI is missing or config can't load.
- Diagnostic log at ~/Library/Logs/ogmac/menubar.log captures every
  refresh, watcher event, and reader error.
- Reads state.db with raw SQLite3 + URI ?immutable=1 (the daemon's
  WAL-mode file can't be opened cleanly via GRDB DatabasePool/Queue
  without -shm coordination access).

## App bundle icon

Custom AppIcon.appiconset rendered programmatically in 7 sizes
(16/32/64/128/256/512/1024 px). Dark squircle background with a white
rounded-rectangle outline forming the ╭─╮╰─╯ shape — visible in
Finder, Spotlight, and notifications.

## Python daemon

- ogmac pause / ogmac unpause subcommands. paused flag persists in
  state.db (run_state row, mirroring the existing disabled pattern).
  _run_sync early-returns when paused.
- sync.interval_seconds dropped from SyncConfig — was never read;
  StartCalendarInterval in the launchd plist is the only schedule
  source. Legacy configs still containing the field are silently
  accepted with a DeprecationWarning.

## Packaging

- Xcode project generated by XcodeGen (project.yml). macOS 14
  minimum (panel uses @Environment(\.openSettings)). LSUIElement=true,
  no Dock icon. Unsigned build via packaging/build_app.sh produces
  dist/Ogmac.app. Homebrew cask spec at packaging/Casks/ogmac.rb
  with xattr post-install for the unsigned build.
- SPM lockfile (Package.resolved) pins GRDB.swift 6.29.3 and Yams
  5.4.0 for reproducible builds.

## Tests

62 Swift tests cover IconState resolution, PanelSummary formatting,
StateReader/LogReader/ConfigStore round-trips with fixtures,
OgmacRunner shim-based command tests, SettingsViewModel save flow,
and AppLaunch smoke tests. Python suite expanded to 172 (was 156)
covering the pause flag, the deprecated-field warning, and the
end-to-end pause flow.

## Docs

- README: Menu bar app section with install steps and behavior.
- docs/operations.md: Pause syncing, How the panel stays fresh,
  History view filter, Diagnostic log, Launch at login.
- docs/troubleshooting.md: CLI not found, icon doesn't update,
  History empty, Settings window doesn't appear.
- docs/superpowers/specs/2026-05-08-menu-bar-app-design.md
- docs/superpowers/plans/2026-05-08-menu-bar-app-team-breakdown.md
- CHANGELOG: Unreleased entries for everything above.

## Known issue

The launchd plist has a NetworkChange LaunchEvent with
ThrottleInterval=120 that causes the daemon to fire every ~2 min on
network activity. Filed as #1; the menu bar app's
History filter works around it client-side.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@razbahri
razbahri merged commit 0e5e0b3 into main May 9, 2026
3 checks passed
@razbahri
razbahri deleted the feature/menu-bar-app branch May 9, 2026 09:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant