feat: menu-bar item as live activity monitor - #13
Open
deehw wants to merge 2 commits into
Open
Conversation
The aggregate menu-bar item was a lone dominant-status dot + count — low-contrast, no identity, and collapsing every session to one dot. Rework it into a live activity monitor that surfaces the single most important thing across all sessions, mirroring the dashboard's priority: needs-you → working → idle → empty. - AggregateActivity: pure, Equatable view-model (make(from:now:)) that composes the one line to show — approval target when waiting, failed tool on error, the freshest working session's live tool + preview + minute-elapsed, or a calm idle count. All formatting is here, unit tested with a pinned now. - AggregateMenuBarLabel: thin renderer — icon + count badge + truncated activity text, urgency tint/pill for needs-you, and a legible empty glyph (the old circle.dotted was nearly invisible over a wallpaper). Width-capped so a long preview can't push the clock off-screen. - Fix empty-until-opened bug: boot() ran only from the dropdown content's .task, so the provider never started (and the menu bar showed empty) until the user first opened the popover. Boot from the always-present label task instead; boot() is idempotent. Fields the label reads (activeTools, recentTools, status, aiTitle) are already in EnrichedSession.coreEqual, so the store republishes on tool changes and the line stays current without a timer. No animations (StaticStatusIcon) — the menu bar stays calm. Verified: 229 tests green, perf gate OK, headless renders of every state in light + dark under /tmp/menubar-snapshots/agg-all-*.png. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Fleet count: surface the total live-session count as a badge (when > 1) in idle/working/needs-you-single, and always in the accessibility label, so background sessions aren't hidden when one session is the headline. Neutral color when calm, tinted only when urgent, so a working "5" doesn't misread as "5 busy". - Lead selection: pick the freshest working session by its most-recent active-tool startedAt (a coreEqual field) instead of updatedAt, which SessionStore.uiEqual deliberately ignores — keying on it could show a stale lead. This is the exact coreEqual/uiEqual invariant CLAUDE.md warns about. - Idle icon: use the dominant status glyph, not a hardcoded green dot, so an alive paused/stopped/unknown session keeps its own icon instead of masquerading as healthy-idle (preserves the .unknown forward-compat signal). - Accessibility: announce the session count in every non-empty state. - Width cap: apply the 220pt cap to the growable text only, not the whole label, so empty/idle can't reserve a wide item. - Remove the redundant boot() call from DashboardView's .task (the always-present label task already boots). - Share the minute-suffix format via ElapsedFormatter.minuteSuffix so the menu-bar line and per-session row can't drift. Elapsed advancement (review finding): a TimelineView in the MenuBarExtra *label* hangs the app at launch (caught by the test host: "test runner hung before establishing connection"), and the codebase intentionally keeps timers off the menu bar. So the elapsed stays minute-granularity, advancing on store republish (frequent during active work); documented the trade-off inline. 232 tests green, perf gate OK, headless renders refreshed. Co-Authored-By: Claude Opus 4.8 (1M context) <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.
Reworks the macOS menu-bar item from a lone, low-contrast dominant-status dot into a live activity monitor that mirrors the dashboard — it surfaces the single most important thing across all live sessions.
Behavior (priority: needs-you → working → idle → empty)
circle.dottedwas nearly invisible over a wallpaper)● 5— dot + fleet count● Bash · xcodebuild…● 5 Agent · audit retry paths— fleet count + freshest session's live tool🔔 approve Bash · rm -rf build— orange pill🔔 3 approve Bash · …— fleet count stays visible◉ 2 need you— red pill◉ Bash failed— red pillWhat's here
AggregateActivity— pure,Equatable, unit-tested view-model (make(from:now:)) that composes the line. 18 tests.AggregateMenuBarLabel— thin renderer: icon + fleet-count badge + truncated activity text + urgency pill. No animations (menu bar stays calm).boot()only ran from the dropdown's.task, so the provider never started (menu bar showed empty) until the popover was first opened. Now boots from the always-present label task.Review-driven fixes (high-effort code review)
startedAt(acoreEqualfield), notupdatedAt(whichuiEqualignores) — the invariant CLAUDE.md warns about.boot()removed; minute-suffix format shared viaElapsedFormatter.minuteSuffix.TimelineViewin aMenuBarExtralabel hangs the app at launch (caught by the test host — "test runner hung before establishing connection"), and the codebase intentionally keeps timers off the menu bar. So elapsed stays minute-granularity, advancing on store republish (frequent during active work); trade-off documented inline.Verification
scripts/verify.shgreen: build + full suite (232 tests, 0 failures) + perf gate (0.24s < 0.45s) + snapshot render. Every state eyeballed in light + dark via headless renders (synthetic data — no personal info).🤖 Generated with Claude Code