# How UsageStore Manages State and Refresh Cycles in CodexBar

> Discover how UsageStore in CodexBar manages state and refresh cycles. Explore its adaptive timers and coalesced network requests for efficient data aggregation.

- Repository: [Peter Steinberger/CodexBar](https://github.com/steipete/CodexBar)
- Tags: internals
- Published: 2026-07-08

---

**The `UsageStore` class serves as the central `@Observable` hub that aggregates usage data, token costs, and provider status while orchestrating multi-stage refresh cycles through adaptive timers and coalesced network requests.**

CodexBar's menu-bar UI, CLI commands, and background workers rely on a single source of truth for all provider data. The `UsageStore`, implemented in [`Sources/CodexBar/UsageStore.swift`](https://github.com/steipete/CodexBar/blob/main/Sources/CodexBar/UsageStore.swift), leverages Swift's Observation framework to manage state while coordinating complex background refresh cycles. Understanding how this store handles state aggregation, adapts to system conditions, and prevents overlapping requests reveals the architecture behind the app's real-time usage tracking.

## Core State Architecture with @Observable

The `UsageStore` class is marked with `@Observable` to enable SwiftUI reactivity, storing plain values for UI-relevant properties while isolating heavy caches with `@ObservationIgnored`. At initialization, the store creates dictionaries to hold snapshots for every supported provider.

According to the source in [[`UsageStore.swift`](https://github.com/steipete/CodexBar/blob/main/UsageStore.swift)](https://github.com/steipete/CodexBar/blob/main/Sources/CodexBar/UsageStore.swift#L42-L78), the core state includes:

```swift
@Observable
final class UsageStore {
    var snapshots: [UsageProvider: UsageSnapshot] = [:]          // Latest usage bar data
    var errors:    [UsageProvider: String] = [:]                // Fetch errors
    var knownLimitsAvailabilityByProvider: [UsageProvider: UsageLimitsAvailability] = [:]
    var lastSourceLabels: [UsageProvider: String] = [:]
    var lastFetchAttempts: [UsageProvider: [ProviderFetchAttempt]] = [:]
    var tokenSnapshots: [UsageProvider: CostUsageTokenSnapshot] = [:]
    var tokenErrors:    [UsageProvider: String] = [:]
    var credits: CreditsSnapshot?                                 // Codex credits
    var statuses: [UsageProvider: ProviderStatus] = [:]          // Provider status pages
    var isRefreshing = false
    var refreshingProviders: Set<UsageProvider> = []
    // Additional derived caches marked with @ObservationIgnored
}

```

Properties that should not trigger UI updates—such as background tasks and in-memory caches—are wrapped with `@ObservationIgnored` (lines 80-120 in the same file). This design ensures that large dataset updates do not cause unnecessary SwiftUI view recomputations.

## Observing Settings Changes

The store automatically binds to `SettingsStore` changes during initialization. The `bindSettings()` method (lines 115-124) invokes `observeSettingsChanges()`, which uses `withObservationTracking` to detect any modifications to user preferences.

When settings change, the store:

- Rebinds the observation listener recursively.
- Invalidates the provider-availability cache via `invalidateProviderAvailabilityCache()`.
- Clears probe logs.
- Restarts background timers if `automaticallyStartsBackgroundWork` is true.
- Triggers post-startup refreshes including `refreshHistoricalDatasetIfNeeded()` and `refresh()`.

This reactive binding ensures that modifying the refresh frequency or enabling new providers immediately updates the store's behavior without requiring manual restarts.

## Timer Creation: Fixed vs Adaptive Refresh

The `startTimer(preservingResetBoundaryRefresh:)` method creates a background `Task` that sleeps and calls `refresh()` on a schedule. As implemented in [[`UsageStore.swift`](https://github.com/steipete/CodexBar/blob/main/UsageStore.swift)](https://github.com/steipete/CodexBar/blob/main/Sources/CodexBar/UsageStore.swift#L63-L95), the store supports both fixed intervals and an adaptive mode:

```swift
private func startTimer(preservingResetBoundaryRefresh: Bool = false) {
    self.timerTask?.cancel()
    self.adaptiveRefreshScheduledAt = nil
    
    let frequency = self.settings.refreshFrequency
    guard frequency != .manual else { return }
    
    if frequency == .adaptive {
        // Adaptive loop computes fresh delay each iteration
        self.timerTask = Task.detached(priority: .utility) { [weak self] in
            while !Task.isCancelled {
                guard let sleepDuration = await Self.nextAdaptiveTimerSleepDuration(for: self) else { return }
                try? await Task.sleep(for: sleepDuration)
                await self?.refresh()
            }
        }
        return
    }
    
    // Fixed-interval path
    guard let wait = frequency.seconds else { return }
    self.timerTask = Task.detached(priority: .utility) { [weak self] in
        while !Task.isCancelled {
            let sleepDuration = await self?.effectiveTimerSleepDuration(.seconds(wait)) ?? .seconds(wait)
            try? await Task.sleep(for: sleepDuration)
            await self?.refresh()
        }
    }
}

```

### Adaptive Refresh Policy Logic

When the user selects **adaptive** refresh frequency, the store consults `AdaptiveRefreshPolicy` to determine the next sleep interval based on power state, thermal state, and recent menu activity.

The decision logic resides in [`UsageStore+AdaptiveRefresh.swift`](https://github.com/steipete/CodexBar/blob/main/Sources/CodexBar/UsageStore+AdaptiveRefresh.swift#L15-L30):

```swift
static func adaptiveRefreshDecision(
    now: Date,
    lastMenuOpenAt: Date?,
    lowPowerModeEnabled: Bool,
    thermalState: ProcessInfo.ThermalState,
    policy: AdaptiveRefreshPolicy = AdaptiveRefreshPolicy()
) -> AdaptiveRefreshPolicy.Decision {
    policy.nextDelay(for: AdaptiveRefreshPolicy.Input(
        now: now,
        lastMenuOpenAt: lastMenuOpenAt,
        lowPowerModeEnabled: lowPowerModeEnabled,
        thermalState: thermalState))
}

```

The computed delay is logged via `logAdaptiveRefreshDecision` and can be overridden in tests via `refreshTimerSleepOverrideForTesting` (lines 44-58).

## The Multi-Stage Refresh Cycle

The public entry point `refresh(forceTokenUsage:)` forwards to `runRefresh`, which orchestrates several sub-steps. As defined in [[`UsageStore.swift`](https://github.com/steipete/CodexBar/blob/main/UsageStore.swift)](https://github.com/steipete/CodexBar/blob/main/Sources/CodexBar/UsageStore.swift#L88-L105), the method guards against concurrent refreshes using `isRefreshing`:

```swift
func refresh(forceTokenUsage: Bool = false) async {
    await self.runRefresh(forceTokenUsage: forceTokenUsage, startupConnectivityRetryAttempt: nil)
}

```

The `runRefresh` method (lines 115-178) performs the following sequence:

1. **Prepare refresh state** by persisting codex source corrections via `prepareRefreshState()`.
2. **Determine refresh phases** distinguishing startup vs. regular refreshes.
3. **Collect provider sets** using `enabledProvidersForBackgroundWork()` and `enabledProviders`.
4. **Schedule ancillary work** including storage-footprint checks, token refreshes, and OpenAI web scraping.

### Provider-Level Refresh Coordination

Each provider refresh executes inside a `TaskGroup` to enable parallel fetching. According to [[`UsageStore.swift`](https://github.com/steipete/CodexBar/blob/main/UsageStore.swift)](https://github.com/steipete/CodexBar/blob/main/Sources/CodexBar/UsageStore.swift#L180-L210):

```swift
await withTaskGroup(of: Void.self) { group in
    for provider in refreshProviders {
        group.addTask { await self.refreshProvider(provider, ...) }
        if availableRefreshProviders.contains(provider) {
            group.addTask { await self.refreshProviderStatus(provider) }
        }
    }
}

```

The `refreshProvider(_:coalesceIfRefreshing:)` method in [`UsageStore+Refresh.swift`](https://github.com/steipete/CodexBar/blob/main/Sources/CodexBar/UsageStore+Refresh.swift#L59-L104) utilizes `ProviderRefreshCoordinator` to **coalesce** overlapping requests, ensuring that if a background timer and manual refresh trigger simultaneously, only one network request executes per provider.

### Applying Refresh Outcomes

After fetching, `applyProviderRefreshOutcome` (lines 242-306) updates state dictionaries and handles edge cases:

```swift
switch outcome.result {
case .success(let result):
    // Store new UsageSnapshot with reset-backfill and quota handling
    // Update token-cost snapshot if applicable
    // Record source label and clear errors
    // Notify provider runtime of success
case .failure(let error):
    // Decide whether to surface error or preserve old snapshot
    // Record error string and notify runtime of failure
}

```

This method also performs **Claude-specific credential housekeeping**, invalidating caches and resetting derived state after credential changes to prevent stale data.

## Specialized Refresh Timers

Beyond the main refresh cycle, `UsageStore` maintains separate timers for token-cost data and reset-boundary detection.

### Token-Cost Refresh Cycle

Token-cost data refreshes on an independent schedule (defaulting to 1 hour) via `startTokenTimer()` in [[`UsageStore.swift`](https://github.com/steipete/CodexBar/blob/main/UsageStore.swift)](https://github.com/steipete/CodexBar/blob/main/Sources/CodexBar/UsageStore.swift#L200-L213):

```swift
private func startTokenTimer() {
    self.tokenTimerTask?.cancel()
    let wait = self.tokenFetchTTL   // 1 hour (configurable)
    self.tokenTimerTask = Task.detached(priority: .utility) { [weak self] in
        while !Task.isCancelled {
            try? await Task.sleep(for: .seconds(wait))
            await self?.scheduleTokenRefresh(force: false)
        }
    }
}

```

The `scheduleTokenRefresh` method creates a sequenced task that walks through `enabledProvidersForBackgroundWork()`, invoking `refreshTokenUsage(provider:force:)` for each provider in order (lines 215-260).

### Reset-Boundary Refresh

To capture usage resets that occur at minute boundaries, the store implements a **reset-boundary refresh** that triggers a short-lived extra refresh shortly after the regular cycle. The constants are defined at the top of [`UsageStore.swift`](https://github.com/steipete/CodexBar/blob/main/UsageStore.swift) (lines 13-15):

```swift
static let resetBoundaryRefreshGraceSeconds: TimeInterval = 30
static let resetBoundaryRefreshMinimumDelaySeconds: TimeInterval = 5

```

The `scheduleResetBoundaryRefreshIfNeeded` method schedules a task that sleeps for 30 seconds (or a minimum of 5 seconds) before triggering a lightweight `refresh(forceTokenUsage: false)` limited to providers likely to have crossed a quota reset. This logic resides in `UsageStore+ResetBoundaryRefresh.swift`.

## Concurrency Safety and Error Handling

All public entry points to `UsageStore` are marked `@MainActor`, ensuring thread-safe state updates. Background work runs off-main using `Task.detached(priority: .utility)` and the `ProviderRefreshCoordinator` prevents overlapping provider refreshes.

The store distinguishes between transient network failures, credential-change events, and permanent quota unavailability. When a fetch fails, `applyProviderRefreshOutcome` intelligently decides whether to preserve the prior snapshot or surface the error to the UI, maintaining stability during intermittent connectivity issues.

## Integrating UsageStore in SwiftUI

To use the store in a SwiftUI view, initialize it with dependencies and trigger refreshes manually when needed:

```swift
import SwiftUI
import CodexBar

struct ContentView: View {
    @StateObject private var usageStore = UsageStore(
        fetcher: UsageFetcher(),
        browserDetection: BrowserDetection(),
        settings: SettingsStore.shared
    )

    var body: some View {
        VStack {
            if let snap = usageStore.preferredSnapshot {
                Text("Used: \(snap.primary?.usedPercent ?? 0)%")
            } else {
                Text("Loading…")
            }
        }
        .onAppear {
            Task { await usageStore.refresh() }
        }
    }
}

```

To manually trigger an adaptive refresh or notify the store of user activity:

```swift
Task {
    // Force full refresh including token-cost tables
    await usageStore.refresh(forceTokenUsage: true)
}

// Notify store of menu interaction to adjust adaptive timing
usageStore.noteMenuOpened()

```

## Summary

- **Single observable source**: `UsageStore` aggregates all usage data, token costs, and provider status in one `@Observable` class, exposing only UI-relevant properties while caching heavy data with `@ObservationIgnored`.
- **Settings-driven reactivity**: Automatic observation of `SettingsStore` changes invalidates caches and restarts timers without manual intervention.
- **Adaptive timing**: The store adjusts refresh intervals based on power state, thermal conditions, and user interaction patterns via `AdaptiveRefreshPolicy`.
- **Coalesced refresh pipeline**: `ProviderRefreshCoordinator` prevents overlapping network requests while `TaskGroup` parallelism maximizes efficiency.
- **Robust error handling**: The store preserves snapshots during transient failures and performs provider-specific cleanup (such as Claude credential invalidation) when authentication changes.

## Frequently Asked Questions

### How does UsageStore prevent multiple simultaneous refresh requests?

The store uses a boolean `isRefreshing` flag checked at the start of `runRefresh()` to prevent concurrent full cycles. Additionally, the `ProviderRefreshCoordinator` coalesces overlapping requests for individual providers, ensuring that if a background timer and manual refresh trigger simultaneously, only one network request executes per provider.

### What triggers an adaptive refresh cycle adjustment?

The `AdaptiveRefreshPolicy` recalculates the next sleep duration based on four signals: the current time, the last menu open timestamp (`lastMenuOpenAt`), Low Power Mode status, and the current thermal state. Calling `noteMenuOpened()` records user interaction and may shorten the next refresh interval to ensure fresh data is available when the user is actively checking the menu.

### How does the store handle token-cost data differently from usage snapshots?

Token-cost data refreshes on a separate timer (`tokenTimerTask`) with a default 1-hour TTL, managed by `startTokenTimer()`. Unlike regular usage snapshots that refresh frequently (every few minutes), token costs are fetched sequentially via `scheduleTokenRefresh()` to avoid overwhelming API endpoints, while usage snapshots utilize parallel `TaskGroup` processing.

### Where does UsageStore handle errors without breaking the UI?

Error handling occurs primarily in `applyProviderRefreshOutcome()` within `UsageStore+Refresh.swift`. The method distinguishes between transient failures (preserving the previous snapshot) and permanent errors (surfacing messages to the UI). It also handles provider-specific logic such as clearing Claude OAuth caches when credential changes are detected, ensuring the UI never displays stale authentication errors.