# Session Limits Menu Design

Audience: AI coding agents first.

## Goal

On tray icon click, show each account's session-limit usage in readable rows:

- `5h [------------] XX% (Xh Xm left)`
- `7d [------------] XX% (Xd Xh left)`

Preserve existing account switching, broken-account repair, routing fallback, and health cache behavior.

## Recommended Approach

### Approach 1: Extend Existing Tray Menu

Add richer labels to existing GTK account menu rows. Keep account selection radio items; append two compact limit rows per healthy account when data exists.

| Dimension | Assessment |
|-----------|------------|
| Robustness | Reuses existing `AccountSnapshot`, cache, refresh, and tests. Missing reset metadata degrades to percent-only. |
| Long-term | One health-data contract; no second tray surface to maintain. |
| Scalability | O(accounts) menu work; current app already lists all accounts on click. |
| Performance | No new background process beyond existing health refresh. |
| Reversibility | Two-way door: labels/data fields can be changed without storage migration. |

**Weakness:** GTK menu labels are visually limited; progress bars are text bars, not native widgets.

### Approach 2: Tray Popover Window

Open a small GTK window/popup with native progress bars and richer layout.

| Dimension | Assessment |
|-----------|------------|
| Robustness | More UI state, focus behavior, and platform edge cases. |
| Long-term | Better visual ceiling, but larger surface than needed. |
| Scalability | Fine for account count, but window lifecycle adds complexity. |
| Performance | Slightly more GTK widget work; still negligible. |
| Reversibility | Two-way door, but more code to delete if wrong. |

**Weakness:** Overbuilds requested click-menu behavior.

**Recommended: Approach 1** — user asked for click tray visibility, not a new window. Existing architecture already has account rows and health snapshots; extending that contract is the least risky path.

## Data Contract

Extend `health_client.AccountSnapshot` with optional per-window reset metadata:

```python
primary_resets_at: float | None
secondary_resets_at: float | None
```

Semantics:

- `primary_*` = 5h window.
- `secondary_*` = 7d window.
- values are Unix epoch seconds when server-provided reset/remaining metadata exists.
- absent reset metadata MUST render no time-left suffix, not an estimate.
- existing cache keys `primary_used_pct` and `secondary_used_pct` remain unchanged.
- cache may add `primary_resets_at` and `secondary_resets_at`; old caches without fields remain valid.

`AccountHealthClient.fetch()` extracts percentages exactly as today and additionally extracts reset time from the rate-limit window if Codex returns a numeric epoch or ISO timestamp. If response shape lacks reset metadata, leave reset fields `None`.

## UI Contract

`Indicator._account_label(account, snapshot, now=None) -> str`

Behavior:

- No snapshot: current label shape remains `Alias · plan`.
- `BROKEN`: current warning label remains.
- `OK` with percentages: append two text progress rows after account header.
- Use fixed 12-cell ASCII bars: filled cells = rounded `percent / 100 * 12`; empty cells = `-`.
- Clamp percent display and bar fill to `0..100`; preserve `None` as unknown and omit that window row.
- Time-left suffix appears only when `resets_at > now`.
- 5h format: `Xh Xm left`.
- 7d format: `Xd Xh left`.
- If reset time is expired or absent, omit parenthetical suffix.

Required display shape for healthy account with both windows:

```text
Rafa · plus
5h [######------] 50% (2h 30m left)
7d [###---------] 25% (5d 6h left)
```

## Data Flow

1. Tray refresh calls `AccountHealthClient.fetch(account.codex_home)`.
2. Health client returns `AccountSnapshot`.
3. Indicator stores snapshot in `self.snapshots`.
4. Indicator label formatter renders progress rows on menu build/update.
5. Health cache writes/reads the extended fields for `cdx` routing compatibility.

`routing_resolver` behavior MUST NOT change: quota exhaustion still uses percentage fields only.

## Error Handling

- Invalid or unknown rate-limit response fields produce `None`, not exceptions.
- Cache read ignores non-numeric reset fields.
- Health fetch timeout/broken behavior remains unchanged.
- UI never invents remaining time from local refresh time.

## Tests

Add/update tests for:

- `AccountSnapshot` cache round-trip with reset fields.
- old cache shape remains readable.
- progress bar label with both windows and reset times.
- percent-only label when reset times absent.
- broken/unknown accounts keep existing behavior.
- routing resolver still works with extended snapshot.

## Architecture Decisions

- Keep one `AccountSnapshot` contract; do not add a separate UI-only session-limit model.
- Keep text bars in menu; do not introduce a GTK popover until native widget layout is actually required.
- Treat missing reset metadata as unknown; never estimate server windows locally.
