Repository navigation
feat: menu bar app + pause subcommands - #2
Merged
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Ogmac.app, a SwiftUI menu bar utility that surfaces sync status, settings, and run history for the existing Python daemon. Thin observer + commander — readsstate.db, tailssync.log, editsconfig.yaml, and shells out to theogmacCLI for actions.ogmac pause/ogmac unpausesubcommands with apausedflag instate.dbthat survives reboots.sync.interval_secondsconfig field (the launchd plist has always been the only schedule source).Highlights
DispatchSourcefile-system watcher onstate.dbrefreshes the panel within ~200 ms of every daemon write — including manualSync nowclicks of any duration.?immutable=1SQLite open — the daemon's WAL-mode file can't be opened cleanly via GRDB without-shmwrite access; rawsqlite3with the immutable URI flag is the only mode that worked.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/:45ticks.appfamily (hollow rounded rectangle) to match the bundle icon. State is distinguished by tint + badge, not by glyph.~/Library/Logs/ogmac/menubar.logfor every refresh, watcher event, and reader error.Spec & plan
docs/superpowers/specs/2026-05-08-menu-bar-app-design.mddocs/superpowers/plans/2026-05-08-menu-bar-app-team-breakdown.mdWhat changed
Ogmac/, 17 test files + fixtures underOgmacTests/Contents.jsonstate.py,cli.py,config.py, +1 integration testproject.yml,Ogmac.xcodeproj/,Package.resolved,build_app.sh,Casks/ogmac.rb,exportOptions.plist,com.ogmac.sync.plist(entry point + schedule fixed)README.md,docs/operations.md,docs/troubleshooting.md,CHANGELOG.md.gitignore.DS_Store,xcuserdata/,*.local.mdTotal: +3952 / −9 across 75 files.
Test plan
xcodebuild -project Ogmac.xcodeproj -scheme Ogmac -configuration Debug test→ 62/62 Swift tests passpytest tests/→ 172 passed, 2 skipped (was 156 before)bash packaging/build_app.sh→ produces a runnabledist/Ogmac.app(universal, unsigned)open dist/Ogmac.app→ menu bar icon appears with healthy state; panel populates with real data on first clickogmac pause && ogmac sync && ogmac status→ no-op (skipped);ogmac unpause && ogmac sync→ real syncLSUIElement=trueactivation)Known issue
The launchd plist has a
NetworkChangeLaunchEventwithThrottleInterval=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