Iterations 1–69 archived in DEVLOG-archive.md.
- Notification ordering refactor:
AgentNotifyNotificationServicenow postsUNNotificationRequestfirst, then plays custom sound. This reduces timing skew between Notification Center card creation and audible feedback. - Custom sound preflight: Added
NotifySoundManager.canPlay(for:service:)so notification content can choose between custom path (sound=nil) and system default (.default) before posting. - Playback failure hardening:
NotifySoundManager.play()/playTest()now verifyAVAudioPlayer.play()success instead of assuming playback started; failed starts no longer report success. - Fallback behavior: When custom sound is selected but fails to start after notification delivery, service now plays a fallback alert tone to avoid silent notifications.
- Tests added:
AgentNotifyNotificationServiceBehaviorTests.testPostPlaysCustomSoundAfterNotificationRequestAddedAgentNotifyNotificationServiceBehaviorTests.testPostTriggersFallbackSoundWhenCustomPlaybackFailsNotifySoundManagerTests.testCanPlayReturnsTrueWhenCategoryHasExistingFileNotifySoundManagerTests.testCanPlayReturnsFalseWhenCategoryFilesAreMissingNotifySoundManagerTests.testPlayReturnsFalseWhenAudioFileCannotBeDecoded
./scripts/test.sh통과
- DEVLOG split: Moved iterations 1–69 (plus superseded 70–76) to
DEVLOG-archive.md. DEVLOG.md reduced from 811 to ~190 lines, keeping only iterations 70–91 which reflect the current codebase state. - All 279 tests passing
- Cursor/Copilot (
cachedOrThrow): On API failure (network error, 401, etc.), returns last cached UsageMetric from UserDefaults if reset time hasn't passed. Previously, any API error immediately threw and ViewModel showed zero. - Gemini (
resolveMetric): When no log events found in current daily window, prefers cached non-zero value until daily reset passes. Same pattern as Codex (Iteration 89). - Test isolation: All three test suites now use per-test
UserDefaults(suiteName:)to prevent cross-test cache pollution. - All 279 tests passing
- Idle-session cache:
CodexUsageProvidernow caches last non-zero usage metrics in UserDefaults (codexUsageCache.fiveHour,codexUsageCache.weekly). When rate_limits window becomes stale (no active session), cached values are preserved until reset time passes — matching Claude provider's existing pattern (Iteration 35-36) - resolveMetric(): New method wraps window resolution with cache logic: save non-zero results, prefer cached over zero when cache reset time is still in the future
- Test isolation:
CodexUsageProviderTestsnow uses per-testUserDefaults(suiteName:)to prevent cross-test cache pollution - New tests:
testPrefersCachedUsageWhenWindowBecomesStale,testCacheExpiredWhenResetTimePasses - All 279 tests passing
- Single-instance guard:
AppDelegate.terminateIfAlreadyRunning()checksNSRunningApplicationfor other processes with the same bundle ID and callsNSApp.terminate(nil)if found - Test-safe: Skips the check when
XCTestConfigurationFilePathenvironment variable is present (test host shares bundle ID) - All 277 tests passing
- Two-step guide: Updated DMG background from single "Drag to Applications" to numbered steps: "1. Drag AgentBar to Applications" + "2. Open AgentBar to get started"
- Script refactored: Extracted
load_font()anddraw_centered_text()helpers; step 2 uses slightly smaller font and dimmer alpha for visual hierarchy - All 277 tests passing
scripts/generate-dmg-background.py: Python3+Pillow script generating 1200x800 Retina background with slate gradient, chevron arrow, and "Drag to Applications" hint textdocs/assets/dmg-background@2x.png: Pre-generated background image committed for reuse across releasesscripts/create-styled-dmg.sh:create-dmgwrapper with 600x400 window, app icon at (150,200), Applications drop link at (450,200), volume icon, and hidden.appextensionscripts/release.sh: Replaced barehdiutil createwithcreate-styled-dmg.shcall; addedcreate-dmgprerequisite check- All 277 tests passing
- UsageHistoryStore: snapshot + append-log(
usage-history.events.jsonl) 구조로 전환- 기록 시 전체 snapshot rewrite 대신 log append
- load 시 log replay
- 이벤트 수/파일 크기 임계치 기반 compact(정렬 + snapshot 저장 + log 제거)
- day/secondary upsert를 index map 기반으로 최적화
- UsageHistoryDayRecord:
secondarySampleCount필드 추가- secondary 평균 계산 분모를
sampleCount에서 분리해 희석 오류 방지 - 구버전 데이터 디코딩 하위호환 유지
- secondary 평균 계산 분모를
- UsageHistoryViewModel:
refreshGeneration+ 취소 가능한 단일refreshTask도입- 겹치는 refresh 요청에서 stale 결과 반영 차단
- secondary 히트맵의 sample count는
secondarySampleCount사용
- KeychainManager: load 결과를
LoadOutcome(value, shouldCache)로 분리- transient Keychain 오류(
errSecInteractionNotAllowed등)는 캐시하지 않음 - 안정 상태(
errSecItemNotFound등)만 캐시
- transient Keychain 오류(
- 테스트 추가
UsageHistoryStoreTests: secondary 평균 분모 분리 검증UsageHistoryViewModelTests: refresh overlap 시 최신 generation 우선 반영 검증UsageViewModelTests: Keychain in-process cache의 안정/일시 오류 캐시 정책 검증
./scripts/test.sh통과
- UsageViewModel.storedPlanName(for:): Read plan name from UserDefaults for Claude/Codex/Cursor when fetchUsage() fails, so the plan label still appears next to the service name
- All 273 tests passing
- ServiceType darkColor/lightColor: Codex changed from emerald-500/300 to gray-500/300 (
0.42, 0.45, 0.49/0.71, 0.73, 0.76) - Test updated:
testCodexDarkColorIsGray500 - All 273 tests passing
- ServiceType.hasFiveHourSevenDayStructure: Computed property checking
fiveHourLabel == "5h" && weeklyLabel == "7d"— only Claude and Codex qualify; Z.ai (MCP) is excluded because MCP monthly is not comparable to 7d cycles - UsageHistoryViewModel: Filter services by
hasFiveHourSevenDayStructurewhenselectedWindow == .secondary - Test updated:
testNon5h7dServiceIsExcludedFromSecondaryWindowverifies Z.ai panel is absent in secondary view - All 273 tests passing
- KeychainManager load cache: Added in-process
[String: CachedValue]cache toload(account:)— first call hits Security framework, all subsequent calls return cached result with zero SecItemCopyMatching calls. Invalidated bysave()/delete()only - KeychainManager dataProtection skip: When
errSecMissingEntitlementis detected (ad-hoc signing), subsequent calls skip the dataProtection store query entirely - All 273 tests passing
- Added per-service
Daily Usage Trendline chart to the right side of the heatmap using stored daily peak usage values - Extended day history persistence to keep peak/average
usedvalues and corresponding unit metadata - Added top guide text in History tab clarifying tile semantics:
- Daily Heatmap:
1 tile = 1 day(weekday ticks on the left) - 7d Cycle Consistency:
1 tile = 1 reset cycle
- Daily Heatmap:
- Updated cycle section title to explicitly include tile meaning
- Updated plan document to include guide text requirement
- Build and tests pass
- Moved
Historytab to the rightmost position in Settings (Usage->Notifications->History) - Reworked
UsageHistoryViewModelfrom single-service state to all-service panels- added
UsageHistoryServicePanel - computes panel data for every available service in one refresh
- sorts panels by usage frequency (active days) descending
- tie-breakers: average daily peak, then stable service order
- added
- Updated
UsageHistoryTabView- removed service dropdown
- renders all services in one screen (service sections stacked vertically)
- keeps global window/range controls
- keeps per-service daily heatmap summary and conditional 7d cycle consistency block
- Updated
UsageHistoryViewModelTeststo new multi-panel API and added frequency ordering test - Updated
docs/USAGE_HISTORY_IMPLEMENTATION_PLAN.mdto match UI behavior (all services + frequency order + rightmost History tab) - Build and tests pass
- AgentBar.xctestplan (Fast): Excludes 3 slow integration test classes (NotifySocketListenerLifecycleTests, HookScriptFallbackTests, AgentNotifyMonitorSocketReceiveTests). Parallel execution enabled. 249 tests, ~15s.
- AgentBarFull.xctestplan (Full): All 267 tests with parallel execution. ~22s. For pre-commit validation.
- Shared xcscheme: Created AgentBar.xcscheme linking Fast plan as default, Full plan as alternate.
- CLAUDE.md updated: Added fast/full/single-class test commands to Build & Run section.
- TEST_HOST kept: Removing TEST_HOST/BUNDLE_LOADER caused linker errors since tests use
@testable import AgentBar. Kept app-hosted testing; speedup comes from parallelism and slow test exclusion. - All 267 tests passing
- Rebuilt debug app with
xcodebuild build -project AgentBar.xcodeproj -scheme AgentBar -configuration Debug -derivedDataPath build -quiet - Attempted runtime handoff:
- terminated existing AgentBar process (
pkill -x AgentBar) - attempted to relaunch app bundle (
open build/Build/Products/Debug/AgentBar.app)
- terminated existing AgentBar process (
- In this execution environment,
openreturned LaunchServices error-600and direct binary launch exited immediately, so persistent UI runtime verification could not be completed from the agent side - Delivered build artifact path for local verification:
build/Build/Products/Debug/AgentBar.app
- Added
UsageHistoryStoreTests:- day record peak/average aggregation
- secondary 5-minute bucket upsert behavior
- retention pruning (day + sample windows)
- persistence round-trip
- corrupt store backup and reset
- Added
UsageHistoryViewModelTests:- heatmap cell count and level mapping
- daily summary calculation
- 7d cycle grouping and summary metrics
- non-7d cycle panel disable behavior
- Updated
UsageViewModelTests:- verifies history records only successful provider results
- verifies no history write when all providers fail
- Updated
SettingsViewBehaviorTestswithSettingsTab.historycoverage - Test suite passes via
./scripts/test.sh
- Added
UsageHistoryTabViewinAgentBar/Views/Settings/UsageHistoryTabView.swift - Added
Historytab inSettingsViewwith service/window/range controls - Implemented
Daily Heatmapcontribution-style grid with tooltip + legend + summary cards - Implemented conditional
7d Cycle Consistencysection with cycle strip and summary metrics - Added empty states for no history and insufficient 7d cycle data
- Build passes
- Added
UsageHistoryViewModelinAgentBar/ViewModels/UsageHistoryViewModel.swift - Implemented daily heatmap data generation (
7 x weeks) and daily summary metrics - Implemented secondary sample cycle grouping by
resetAtfor 7d consistency analysis - Added cycle metrics:
- completion rate
- days to 80% / 100%
- high-band hours (
>=80%, capped segment) - current completion streak
- Wired history refresh to
Notification.Name.usageHistoryChanged - Build passes
UsageViewModelnow acceptshistoryStore: UsageHistoryStoreProtocolfor dependency injectionfetchAllUsage()now tracks provider outcomes as success/failure separately- Only successful fetch results are recorded to history via
historyStore.record(samples:recordedAt:) - Failure fallback rows (
zeroUsageData) remain visible in UI but are excluded from history recording - Added
Notification.Name.usageHistoryChangedbroadcast after successful history writes - Regenerated Xcode project with
xcodegen generateto include newly added history source files in build - Build passes
- Added
UsageHistorymodels inAgentBar/Models/UsageHistory.swift:UsageHistoryDayRecordUsageHistorySecondarySampleUsageHistoryStoreFile(schema v2)UsageHistoryWindow
- Added
UsageHistoryStoreactor inAgentBar/Infrastructure/UsageHistoryStore.swiftwithUsageHistoryStoreProtocol - Implemented persisted history storage at
~/Library/Application Support/AgentBar/usage-history.json - Implemented day-level aggregation, secondary sample collection, retention pruning, and atomic JSON writes
- Added corrupt file recovery and legacy schema v1 migration path
- Build passes
- Root cause: When
fetchUsage()threw (e.g. OAuth token expired overnight),UsageViewModel.zeroUsageData()returnedweeklyUsage: nil, hiding the 7d row entirely even though cached 7d data was still valid (reset time not yet passed) - Cache fallback on API failure: Added
cachedOrThrow(_:)toClaudeUsageProvider— on any API error (401, network, etc.), checks UserDefaults cache before throwing. If at least one cached window (5h or 7d) has a valid reset time still in the future, returns cached values instead of throwing - Behavior: 5h resets after sleep → shows 0%. 7d still valid → shows cached %. Both windows expired + API failing → throws as before
- Tests added:
testFallsBackToCacheOnAPIFailureWhenSevenDayCacheValid(401 with valid 7d cache),testFallsBackToCacheOnMissingCredentials(nil token with valid 7d cache) - All 227 tests passing
- AppIcon asset catalog: Created
Assets.xcassets/AppIcon.appiconsetwith all macOS icon sizes (16–512@2x), added PBXResourcesBuildPhase to Xcode project so the icon is included in the app bundle - README.md: Simplified to match v0.4 feature set — cleaner service table, feature list, install/build sections
- All 225 tests passing