# Session Limits Menu Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use /ship (recommended) or /executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Show each account's 5h and 7d session-limit usage in the tray click menu with progress bars and accurate remaining time when available.

**Architecture:** Extend existing `AccountSnapshot` with optional reset timestamps. Keep GTK menu as the display surface; render text progress rows from the existing indicator label formatter. Preserve routing behavior by keeping quota decisions percentage-only.

**Tech Stack:** Python, GTK 3 / AyatanaAppIndicator, pytest fake GTK test harness.

---

## Wave Plan

| Wave | Tasks | Files touched | Safe to parallelize? |
|------|-------|---------------|----------------------|
| 1 | Task 1 | `health_client.py`, `cdx.py`, cache tests | single task |
| 2 | Task 2 | `indicator.py`, `tests/test_indicator.py` | single task |
| 3 | Task 3 | targeted compatibility tests | single task |

Execution strategy: `sequential`.

## File Map

- `health_client.py` — owns `AccountSnapshot`, app-server rate-limit parsing, health-cache write shape.
- `cdx.py` — reads health cache for routing resolver; must tolerate extended cache shape.
- `indicator.py` — owns tray menu labels and account row updates.
- `tests/test_indicator.py` — fake GTK coverage for label/menu behavior.
- `tests/test_cld.py` / relevant cache tests — cover health cache read/write compatibility if already present; otherwise add focused tests near current health-cache tests.

## Task 1: Extend Health Snapshot Contract

**Wave:** 1  
**Blocks:** Task 2, Task 3  
**Blocked by:** —

**Files:**
- Modify: `health_client.py` — add optional reset timestamp fields and parser helpers.
- Modify: `cdx.py` — read extended cache shape without breaking old caches.
- Test: existing health/cache tests, or add focused tests if no direct coverage exists.

**Contract (pin EXACTLY):**
- Dataclass fields:
  - `primary_resets_at: float | None = None`
  - `secondary_resets_at: float | None = None`
- Helper shape: parse a rate-limit window object into `(used_pct: int | None, resets_at: float | None)`.
- Cache JSON keys:
  - `primary_resets_at`
  - `secondary_resets_at`

**Behavior:**
- Preserve constructor compatibility where current tests use `AccountSnapshot(HealthStatus.OK, pct, pct)`.
- Accept numeric epoch seconds as reset time.
- Accept ISO timestamp strings if present in Codex response fields.
- Unknown/missing/invalid reset metadata becomes `None`.
- Old cache files without reset keys remain valid.
- `RoutingResolver` continues to consume only `primary_used_pct` and `secondary_used_pct`.

**Acceptance:**
- Run: `pytest tests/test_cld.py tests/test_systray_codex_switcher.py -q`
- Expected: PASS with no warnings.

- [ ] Write tests covering cache round-trip and old-cache compatibility.
- [ ] Implement snapshot/cache/parser contract.
- [ ] Run acceptance check.
- [ ] Commit: `git add health_client.py cdx.py tests/test_cld.py tests/test_systray_codex_switcher.py && git commit -m "feat: track session limit reset times"`

## Task 2: Render Progress Rows In Tray Menu

**Wave:** 2  
**Blocks:** Task 3  
**Blocked by:** Task 1

**Files:**
- Modify: `indicator.py` — account label formatting and helper functions.
- Modify: `tests/test_indicator.py` — fake GTK assertions for multi-line labels.

**Contract (pin EXACTLY):**
- Formatter seam: `Indicator._account_label(account: Account, snapshot: AccountSnapshot | None = None, now: float | None = None) -> str`
- Progress row format:
  - `5h [############] 100% (0h 1m left)`
  - `7d [###---------] 25% (5d 6h left)`
- Bar width: 12 characters.

**Behavior:**
- Existing no-snapshot and broken-account labels remain unchanged.
- Healthy account with percent data renders account header plus one row per known window.
- Unknown percent omits that window row.
- Percent values are clamped to `0..100` for display and bar fill.
- Remaining-time suffix appears only when reset timestamp is in the future.
- 5h suffix uses hours/minutes; 7d suffix uses days/hours.
- No local estimate when reset timestamp absent.

**Acceptance:**
- Run: `pytest tests/test_indicator.py -q`
- Expected: PASS with no warnings.

- [ ] Write failing tests for both reset-time and percent-only labels.
- [ ] Implement formatter helpers and menu label update.
- [ ] Run acceptance check.
- [ ] Commit: `git add indicator.py tests/test_indicator.py && git commit -m "feat: show session limits in tray menu"`

## Task 3: Compatibility Verification

**Wave:** 3  
**Blocks:** —
**Blocked by:** Task 1, Task 2

**Files:**
- Modify only if required by failures: `tests/test_account_registry.py`, `tests/test_systray_codex_switcher.py`, `tests/test_indicator.py`.

**Contract (pin EXACTLY):**
- Full targeted test gate covers account registry, systray wiring, indicator UI, health/cache consumers.

**Behavior:**
- Do not rewrite unrelated dirty worktree changes.
- Do not change routing threshold semantics.
- Do not add a new popover/window.

**Acceptance:**
- Run: `pytest tests/test_account_registry.py tests/test_systray_codex_switcher.py tests/test_indicator.py tests/test_cld.py -q`
- Expected: PASS with no warnings.

- [ ] Run full targeted gate.
- [ ] Fix only failures caused by this feature.
- [ ] Commit any compatibility fixes with exact touched paths.
- [ ] If files changed, commit: `git add tests/test_account_registry.py tests/test_systray_codex_switcher.py tests/test_indicator.py tests/test_cld.py && git commit -m "test: cover tray session limit compatibility"`
