# Rate-limit Freshness Design

Audience: AI coding agents first.

## Goal

Keep every provider account snapshot current. Render only upstream-reported quota windows in tray menu and provider tabs. Reuse native GTK quota bars already implemented by `ui/account_card.py`.

## Contracts

- `AccountHealthClient.fetch(codex_home: Path, timeout_secs: float = 10.0) -> AccountSnapshot`
  - Read `windowDurationMins` from each Codex rate-limit window.
  - Normalize 300-minute windows into semantic 5h slot.
  - Normalize 10,080-minute windows into semantic 7d slot.
  - Preserve positional fallback only when older payload omits duration metadata.
  - Never label explicit unknown-duration windows as 5h or 7d.
- `Indicator._merge_snapshot(key: str, snapshot: AccountSnapshot) -> AccountSnapshot | None`
  - Treat successful probe as authoritative: absent quota windows clear cached values and reset times.
  - Preserve cached quota data during `UNKNOWN` or `BROKEN` probe results.
- `Indicator._open_popup() -> None`
  - Force one asynchronous all-account probe on every closed-to-open icon transition.
  - Keep cached dashboard immediately visible; update account cards and tray rows as each result reaches GTK main loop.
- Tray menu `show` signal MUST force one all-account probe. Do not register a second `popped-up` refresh handler.
- Provider-tab quota presentation remains `AccountCard` responsibility. Missing `QuotaBarVM` rows remain hidden. Present windows use native `Gtk.ProgressBar`, percentage-left text, and reset countdown.
- `AccountCard` MUST temporarily disable row `no-show-all` suppression before mapping quota-row children, then restore suppression so absent rows stay hidden during parent `show_all()` calls.
- Reset countdown MUST be omitted when upstream reset time is missing or expired.

## Data Flow

`icon click / GLib timer / Reload All` → `RefreshScheduler` → provider health adapter → authoritative successful snapshot → cache + tray item + `Dashboard.update()`.

## Failure Handling

- Spawn, timeout, transport, or authentication failure MUST NOT erase last known quota data.
- Failed probes MUST preserve last successful quota timestamp; attempt time only updates scheduler throttling state.
- Successful response with missing window MUST erase that window from memory, cache, tray, and dashboard.
- Refresh completion MUST remain per-account and lazy; one failing account MUST NOT block other account updates.

## Testing

- Parser regression: current Codex payload with one 10,080-minute `primary` window becomes 7d only.
- Merge regression: successful partial snapshot clears absent cached window; unknown snapshot preserves cache.
- Interaction regression: tray menu and popup open each probe all accounts even when cache age is below periodic interval.
- Reset regression: expired reset metadata renders no countdown in dashboard or tray menu.
- Existing view-model and account-card tests prove absent rows hide and present rows render native progress bars/reset text.
- Strict GTK visibility regression proves present rows map children before restoring `no-show-all` suppression.

## Architecture Decisions

- Keep existing two semantic quota slots. Deletion test: removing normalization forces provider schema assumptions into every tray/UI consumer.
- Keep existing `AccountCard`; new quota component fails deletion and single-adapter tests.
- Do not add generic arbitrary-window UI: current upstream contract exposes main 5h/7d semantics plus separate named buckets; request targets main account limits.
