v0.4.0 — /goal monitoring
Highlights
Claude Code CLI's /goal <condition> installs a Stop hook that makes Claude keep taking turns until the condition is satisfied. Previously there was no signal outside the terminal that a session was running under this mode. v0.4.0 surfaces /goal state directly in the menu bar so you can see at a glance which sessions are grinding on a verifiable end state.
Added
Menu-bar session count tint
The active-session count next to the status dot shifts to the system accent color whenever at least one active session has an in-progress goal. A separate glyph was rejected because a third 8pt mark beside the icon and status dot was too easy to misread as a second status indicator — folding the signal into the count keeps the menu bar uncluttered. Intentionally separate from the status dot's priority stack (error/warning/active) so goal state reads as a parallel signal, not a replacement for session health.
Per-session Goal banner
Inside each session row, between the context gauge and the expanded detail block, a compact banner shows the goal condition and elapsed time since installation.
- Collapsed: condition truncated to 2 lines.
- Expanded (click to toggle): scrollable region (up to 180pt tall) so multi-paragraph acceptance specs stay fully readable.
- The expand chevron and clickable surface hide for short single-line conditions so the collapsed row stays uncluttered and there is no dead tap target.
- Chevron direction (right/down) matches
SessionRow,AgentRow, andSessionDetailViewso every expandable surface in the app reads identically. - Active goals use an accent tint. Achieved goals step down to a neutral secondary tint with a "· Done" suffix — intentionally NOT green, so the status dot's green-means-active semantic stays unambiguous.
Changed
JSONLParser tracks goal_status in a single pass
scanTokensAndThinking now parses type:"attachment" rows with attachment.type:"goal_status" alongside tokens/thinking/skills. A cheap "goal_status" substring prefilter keeps JSON decoding off the hot path for unrelated attachment rows.
Rules:
met:false, sentinel:trueis the start marker — capturesconditionand the install timestamp.- A new start always resets the tracker so only the most recent goal is surfaced (stale achievements for prior goals are dropped).
met:trueafter a start marker is the achievement marker — freezeselapsedat that moment so the figure does not creep upward forever.- Orphan
met:truewith no prior start is ignored.
SessionExpandedData + ClaudeDataStore
SessionExpandedData gains activeGoal: GoalStatus?. The mtime cache path in ClaudeDataStore.loadSessionDetail includes it so goal state refreshes on the same cadence as other quick stats, with no extra parse passes. New hasActiveGoal computed property drives the menu-bar count tint.
Design decisions worth calling out
- Turn count removed from the banner. An earlier draft showed "N turns" next to the elapsed time, but the underlying figure (assistant-message count since the goal started) conflates tool_use roundtrips with user-perceived turns — a single user prompt that triggers several tool calls still produces several assistant messages, so "100 turns" was more noise than signal. The field is kept in
GoalStatusfor future reuse under a clearer label, but not surfaced in v0.4.0. - Sub-minute elapsed renders as "just now", not "1m". The shared
RelativeTimeFormatterrounds sub-minute intervals up to "1m", which misreads a freshly-installed goal as if a minute had already passed. A goal-localformatElapsedhelper handles this bucket (<60s→ "just now",<60m→ "Nm",≥1h→ "Hh Mm").
Tests
GoalStatusParsingTests covers no-goal / active goal elapsed tracking / achieved-goal freeze / most-recent-goal override / multiline Korean condition preservation / truncated file fallback, plus an opt-in fixture check against a real JSONL containing a goal event.
Version
Info.plist: 0.3.2 → 0.4.0, build 5 → 6.
Full Changelog: v0.3.2...v0.4.0