Releases: anhyobin/mac-app-for-claude-code
Release list
v0.6.1 — Accurate workflow phase counts + live progress
Fixed
- Workflow phase counts stuck at "1/3" or "0/2" even after the run finished. Phase completeness was derived from the agents' 60-second mtime heuristic (
isActive), so a just-finished workflow whose last phase still had freshly-touched agent files read as incomplete — and the mtime cache then froze that wrong snapshot permanently. Phase completeness now uses the authoritative per-agentstatefield ("done"/"error" = terminal) fromworkflows/{id}.json, falling back to the mtime heuristic only for older data that lacksstate. Verified against all 27 completed workflows on disk — every one now reports an accuraten/n. - Conditionally-skipped phases shown as forever-incomplete. A declared phase that dispatched zero agents (e.g. a "Fix" phase with nothing to fix) was counted as incomplete even on a finished workflow, producing counts like
2/3. An empty phase now mirrors the workflow's terminal status (completed → done, running → pending). - Mismatched token and agent totals. The loader summed tokens across every agent file on disk (including retries and nested sub-agents — e.g. 188 files) while
agentCountcame from the state JSON (e.g. 87), producing inconsistent figures. When the state JSON is present, its pre-computedtotalTokens/totalToolCalls/agentCount(the logical-agent totals) are now treated as authoritative.
Added
- Phase skeleton and agent-progress aggregate for running workflows. The agent-to-phase mapping is only written to disk at completion, so a running workflow now shows its phase skeleton (names and order, e.g.
Research → Build → Verify) parsed from the script'smeta.phases, plus an accurate "M/N agents done" aggregate computed fromjournal.jsonlstarted/result events. Per-phase agent attribution remains a completed-only view (mid-run attribution is not recoverable from disk).
Install: download ClaudeCodeMonitor.zip, unzip, move ClaudeCodeMonitor.app to /Applications, and open. (arm64, macOS 14+, ad-hoc signed — right-click → Open on first launch.)
v0.6.0 — Workflow visibility
Summary
Workflow visibility. When a dynamic workflow fans out into a fleet of subagents, the monitor no longer goes dark. A new "Workflows" section at the top of the session detail view surfaces each run as a per-phase agent tree, and the menu-bar count gently pulses purple while a workflow is running.
The problem
Dynamic workflows (e.g. ultracode orchestration) spawn agents under a directory the app never read. SubagentLoader scans the flat subagents/agent-*.jsonl, but workflow agents live one level deeper at subagents/workflows/{wf_id}/agent-*.jsonl. A workflow could spin up a dozen agents and burn hundreds of thousands of tokens with zero signal in the UI.
What you get
- Workflows section at the top of an expanded session — above the flat agent list, so the phase structure stays prominent.
- Phase tree per workflow: each phase shows its agents; a completed phase gets a check, an in-progress one a spinner. Running workflows are expanded by default; completed ones collapse to a one-line summary you can click to expand.
- Per-agent detail — click any workflow agent to expand its recent messages, modified files, and tool breakdown, exactly like flat subagents (the nested transcript path is handled transparently).
- Live progress — a progress bar (completed agents / total) and running token total, refreshed on the same 5s cadence as the rest of the app.
- Menu-bar signal — the active-session count tints purple and pulses while any workflow is running. A
/goalin progress still takes priority (blue), so an explicit goal is never masked.
How "running" is detected
The run-state file workflows/{wf_id}.json is written only at completion (startTime + durationMs == timestamp), so its status field can't tell you a workflow is currently running. Detection therefore reads two signals:
| Signal | Source | Meaning |
|---|---|---|
| Unfinished agent | journal.jsonl — a started event with no matching result |
Primary "still running" signal |
| Definitive done | workflows/{wf_id}.json status: "completed" |
Overrides everything — the file only exists at completion |
| Activity fallback | agent-dir mtime within 60s | Bridges brief gaps between journal writes |
This ordering closes a subtle bug where a just-finished workflow could otherwise stay pinned as a spinner forever (its directory mtime never changes again once complete).
Under the hood
- New:
WorkflowInfo/WorkflowPhase/WorkflowRunStatusmodels,WorkflowJournal(pure running-detection parser),WorkflowLoader(disk loader with per-wf_idmtime caching), and theWorkflowSectionview. SubagentScanwas extracted fromSubagentLoaderso the flat and workflow loaders share one identical JSONL scanner (DRY) — flat-subagent behavior is unchanged.SubagentInfo,AgentRow, andAgentDetailVieware reused for workflow-phase agents;AgentRow/loadAgentDetailgained an optionalworkflowIdto resolve the nested transcript path, with consistent cache keys across read, write, and the live-refresh loop.
Tests
WorkflowJournalTests (running-detection: complete / unfinished / malformed-line / empty / result-before-started / dedup) and WorkflowLoaderTests (workflow-name parsing, phase mapping) cover the pure logic.
Note: XCTest requires the full Xcode SDK; on Command Line Tools–only machines
swift buildandbash scripts/build-app.shsucceed, whileswift testruns on a machine with Xcode. The disk-loading path was additionally verified against real on-disk workflow transcripts.
Version
Info.plist: 0.5.0 → 0.6.0, build 8 → 9.
Full Changelog: v0.5.0...v0.6.0
v0.5.0 — Opus 4.8 support
Summary
Adds support for Opus 4.8 (claude-opus-4-8), the flagship model introduced in Claude Code 2.1.154. Sessions running 4.8 now show the correct "Opus 4.8" model badge and — crucially — a context gauge scaled to the 1M-token window rather than 200K.
Why this matters
Opus 4.8 ships with a 1M-token context window as the API default — no beta header, no [1m] settings suffix required. But the JSONL message.model field only ever carries the bare id claude-opus-4-8 (verified across 3,185 turns on real transcripts; the [1m] suffix never appears there).
If 4.8 fell through to the generic opus branch, ModelContextLimits would report 200K, and every 4.8 session would over-report context-gauge fullness by 5× — the exact failure mode that bit Opus 4.7 in CC 2.1.117.
What changed
ModelContextLimits—claude-opus-4-8joins the inherent-1M branch alongside 4.7, so the context gauge is always computed against 1M regardless ofsettings.json.ModelNameFormatter—opus-4-8→ "Opus 4.8", listed newest-first so it's never shadowed by 4.7/4.6. Provider-prefixed ids (us.anthropic.claude-opus-4-8) resolve too.- The model-family color (orange/Opus), badge, and
(1M)label all flow through automatically — the parser, settings reader, and badge views are model-agnostic and needed no change.
Tests
Regression guards added to ContextUsageRatioTests:
claude-opus-4-8(and the provider-prefixed form) →1_000_000without anysettings.json[1m]mapping — closes the 5×-over-report failure mode.- Display name resolves to "Opus 4.8" and is never shadowed by 4.7/4.6.
Note: this repo's test target uses XCTest, which requires the full Xcode SDK. On Command Line Tools–only machines
swift buildandbash scripts/build-app.shsucceed; runswift teston a machine with Xcode installed.
Version
Info.plist: 0.4.1 → 0.5.0, build 7 → 8.
Full Changelog: v0.4.1...v0.5.0
v0.4.1 — Fix space-bearing cwd JSONL lookup
Summary
A bug-fix release. Sessions whose cwd contained spaces (e.g. ~/Documents/Solutions Arhitect/Public Events/Claude Webinar) were showing only their task list — model badge, context gauge, thinking counter, skill counter, and /goal banner all came up empty.
Root cause
PathDecoder.encodedProjectPath only replaced / and . with -, leaving spaces intact. The Claude CLI, however, also collapses spaces to - when naming ~/.claude/projects/ subdirectories, so the encoder produced a path that never matched on disk:
| Input cwd | App-encoded (before) | Actual directory |
|---|---|---|
/Users/anhyobin/Documents/Solutions Arhitect/Public Events/Claude Webinar |
-Users-anhyobin-Documents-Solutions Arhitect-Public Events-Claude Webinar ❌ |
-Users-anhyobin-Documents-Solutions-Arhitect-Public-Events-Claude-Webinar |
Because the main session JSONL never loaded, every value derived from it (mainTokens, mainModel, mainLastTurnUsage, mainThinkingBlockCount, mainSkillCounts, activeGoal) was empty. Only TaskLoader survived because tasks live under ~/.claude/tasks/{sessionId}/ — keyed by sessionId, not by encoded cwd — so the symptom was "task list shows up, nothing else does."
Fix
One-line change in PathDecoder.swift to also collapse spaces to -. The encoder is shared by SubagentLoader and both JSONL load paths in ClaudeDataStore, so the fix flows through every call site automatically.
Tests
New PathDecoderTests covers:
- Space-bearing path (the regression that motivated the fix)
- No-space path (regression guard — encoder must remain a no-op for the previously-working case)
- Dot handling
Version
Info.plist: 0.4.0 → 0.4.1, build 6 → 7.
Full Changelog: v0.4.0...v0.4.1
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
v0.3.2 — Mid-session model swaps + 4.7 (1M) label
Bug Fixes
Mid-session /model swaps now reflected in the UI
Switching models mid-session with /model (e.g. Opus → Sonnet) left every UI surface stuck on the original model — session row badge, context gauge, warning threshold, and menu-bar dot all kept reporting the first model the session saw.
Root cause: Both JSONL parsers (scanSessionSummary, scanTokensAndThinking) captured the model field only from the first assistant message in a session, guarded by an if model == nil check. Subsequent assistant turns with a different model were ignored.
Fix: The guard is removed. Both parsers now overwrite model on every assistant turn, so mainModel reflects whichever model served the most recent turn — matching the existing last-turn rule used by mainLastTurnUsage. Context-window ratio and warning limit are now evaluated against the model that actually produced the last turn.
(1M) label rule extended to the 4.7 generation
ModelNameFormatter.displayName previously suppressed the (1M) suffix for 4.7 models, on the (incorrect) assumption that 1M was implicit for that generation.
Fix: The 4-7 special-case is removed. Opus/Sonnet/Haiku 4.7 now follow the same ~/.claude/settings.json rule as 4.6 — if an ANTHROPIC_*MODEL env var maps the model with a [1m] suffix, the badge shows "Opus 4.7 (1M)"; otherwise just "Opus 4.7". One rule, no generation-specific exceptions.
Additional
- Test coverage expanded to assert the
(1M)rule across all three 4.7 families (opus/sonnet/haiku), and the ordering-regression guard now tolerates the settings-dependent suffix.
Full Changelog: v0.3.1...v0.3.2
v0.3.1
Bug Fix
Context Gauge 1M Detection
Models configured as 1M-context variants (e.g., us.anthropic.claude-opus-4-6-v1[1m]) were incorrectly shown with a 200K context limit in the gauge.
Root cause: The JSONL API response only contains short model IDs (claude-opus-4-6) without the [1m] suffix, so the previous keyword-based detection could never match.
Fix: The app now reads ~/.claude/settings.json at launch to cross-reference ANTHROPIC_MODEL and related env vars. If the configured model contains both the JSONL model's family-version key and [1m], the context gauge correctly uses 1M as the denominator.
Additional
- Model badge now shows "(1M)" suffix for non-4.7 models running with 1M context (e.g., "Opus 4.6 (1M)")
- New
ClaudeSettingsReaderutility — reads settings once per app launch, cached for performance
Full Changelog: v0.3.0...v0.3.1
v0.3.0 — Skill Tool Usage Visualization
Added
-
Skill tool usage visualization — extracts per-skill invocation counts
from both the main session JSONL and every subagent JSONL, keyed by the
full skill name (plugin namespaces likeoh-my-claudecode:hudpreserved).
Surfaced in two places:- Session detail (expanded view): a one-line
🧩 Skills:summary
below the thinking-block row, sorted by count DESC / name ASC. Shows
top 4 entries joined by·; overflow is+N more. Bound to
totalSkillCounts(main + all subagents, active and completed) so
skill calls remain visible after subagents drop off the active list. - Agent detail (expanded agent row): a purple-tinted chip section
usingFlowLayout, placed between the existing Tools and Files
sections. Count shown only when > 1.
- Session detail (expanded view): a one-line
-
Skill counter badge (
🧩 N) on collapsed active-session and
recent-session rows, matching the existing thinking-counter (🧠 N)
pattern. Hidden when zero. Active rows use the session-wide total
(main + subagents); recent rows use main-session counts only (subagent
data is not loaded for recent sessions).
Fixed
- App footer version was hardcoded at "v0.2.0" — now reads from
Info.plist dynamically so it stays in sync on every version bump.
Full Changelog
v0.2.0: Opus 4.7 + Apple-native status dot
Highlights
- Opus 4.7 recognition with family accent colors (Opus=orange, Sonnet=blue, Haiku=green). Sonnet/Haiku 4.7 patterns are pre-registered for future releases.
- Model badge on active session rows (previously missing) and recent session rows
- Context-window gauge — 2pt hairline bar with orange at 80%, red at 95%. Expanded detail shows
312K / 1M (31%) - Extended-thinking counter (🧠 N) on every session row, with full "Thinking: N blocks" label in the expanded view
- Menu-bar status dot — 8pt overlay with a priority stack (error > warning > active > inactive), following Apple's convention of reserving pulse for in-transition states
Added
- Model badge on active and recent session rows with family accent color (Opus=orange, Sonnet=blue, Haiku=green). Active sessions previously showed no model at all.
- Opus 4.7 recognition in
ModelNameFormatter. Sonnet and Haiku 4.7 patterns are pre-registered for when those models ship; today only Opus 4.7 is released. The 4.7 generation is matched before 4.6 to avoid prefix shadowing. Also filled in the missinghaiku-4-6entry. - Extended-thinking block counter (🧠 N) on every session row, parsed from
content[].type == "thinking"blocks. Hidden when zero. Expanded detail shows the full label "Thinking: N blocks" under the Context line. - Context-window gauge — 2pt hairline bar showing
lastTurn usage / model max. Secondary below 80%, orange at 80–94%, red at 95%+. Expanded detail adds a tabular-num readout. Backed by a newModelContextLimitstable (1M for 4.7-generation models, 200K for earlier generations) and acontextUsageRatiothat uses only the last assistant turn's usage snapshot to avoid per-turn cache_read over-counting. - Menu-bar status dot — 8pt overlay driven by a priority stack: error (red) > warning (orange, context ≥ 95%) > processing (blue, pulsing — reserved for v0.3) > active (green) > inactive (gray) > hidden. Green is static; pulse is reserved for in-transition states only.
Changed
JSONLParser.scanTokensOnlyremoved; callers use the newscanTokensAndThinkingwhich returns tokens, thinking count, model, and the last-turn usage snapshot in a single pass.Info.plistbundle version bumped to0.2.0(build2).
Notes
- Opus 4.7 uses a new tokenizer — token counts may read 1.0~1.35× higher than Opus 4.6 for the same work. Mixed-model comparisons may look inflated for Opus 4.7 sessions.
Full Changelog: v0.1.1...v0.2.0
v0.1.1
Fixed
- Menu bar dropdown collapsed to ~70pt, hiding the session list and recent
sessions between the header and the footer.MenuBarExtra(style: .window)
sizes its window from the content's ideal size, and the innerScrollView
reports an ideal height of 0. Pinning a fixed height (.frame(height:))
instead of a maximum (.frame(maxHeight:)) now allows the scroll view to
fill the window. Observed on macOS 26.4; unclear which older versions are
affected.
Changed
- Extracted the menu bar dropdown width and height into a
private enum Layout
inMenuBarContentView.swiftso the values live in one place.
Full Changelog: v0.1.0...v0.1.1