Add rate-limit rings to the active Codex pet overlay

Resolved 💬 0 comments Opened May 4, 2026 by dinesh-git17 Closed May 4, 2026

Component

Codex app, pet overlay, account rate limits

Problem

The Codex app already exposes account rate-limit data, but that information requires opening app surfaces that pull attention away from the current work. The pet overlay is already a glanceable status surface. When the pet is active, it should also show remaining Codex capacity without adding text clutter.

Proposal

When the Codex pet overlay is active, render two ambient progress rings around the active pet:

  • Inner ring: 5-hour Codex remaining rate limit.
  • Outer ring: weekly Codex remaining rate limit.

The rings attach to the active pet, not a specific pet asset. They should work with built-in pets and custom pets without changing the custom pet manifest or spritesheet contract.

When the pet is tucked away or the overlay is closed, no ring UI or polling work should remain active.

Data Contract

Prefer the same in-memory account rate-limit state already used by the Codex app. If the overlay needs to read through App Server, use account/rateLimits/read for the initial value and account/rateLimits/updated for changes.

The selector should prefer rateLimitsByLimitId.codex when present, with rateLimits as the backward-compatible fallback when its limitId is codex.

The relevant App Server shape is:

{
  "rateLimitsByLimitId": {
    "codex": {
      "limitId": "codex",
      "primary": {
        "usedPercent": 25,
        "windowDurationMins": 300,
        "resetsAt": 1730947200
      },
      "secondary": {
        "usedPercent": 40,
        "windowDurationMins": 10080,
        "resetsAt": 1731548400
      }
    }
  }
}

Map windows by value, not object position:

  • windowDurationMins === 300 maps to the 5-hour ring.
  • windowDurationMins === 10080 maps to the weekly ring.
  • remainingPercent = clamp(100 - usedPercent, 0, 100).

Ignore events or payloads without Codex rate-limit data. If either required window is missing, render muted tracks and expose limits unavailable in the detail chips. Do not show stale or partial values as current.

Rate-limit data is per Codex account, not per pet. Rings should persist across pet swaps and rebind to the new active pet.

Visual Contract

Use a quiet signal-halo treatment:

  • Both rings are visible in the default pet overlay state.
  • The design has no endpoint dots in any state.
  • The active pet remains centred inside the ring stack.
  • The rings must not obscure the pet face, silhouette, or idle animation.
  • Track rings use rgba(255, 255, 255, 0.07) on the current dark overlay.

Default ring colours:

  • 5-hour ring: #78d0c1.
  • Weekly ring: #aeb6ff.

Threshold colours:

  • More than 15% remaining: use the default ring colour.
  • 15% or less remaining: use warning colour #e5b76f.
  • 5% or less remaining: use danger colour #d97775.

Apply thresholds independently per ring. Stroke colour should snap to the new threshold state when the value commits, independent of progress animation.

Geometry rules:

  • Keep at least 10px between the pet bounding box and the inner edge of the inner ring.
  • Keep at least 10px between the inner ring outer edge and the outer ring inner edge.
  • Centre the ring stack on the rendered pet bounding box within 2 CSS px.
  • Scale stroke width from the reference 12px, clamped to 8px through 18px.

Interaction And Accessibility

Default state shows only the active pet and the two rings.

Hover, keyboard focus, or touch detail reveals two compact chips:

  • 5 hr with the inner ring percentage.
  • week with the outer ring percentage.

Chip placement:

  • Anchor chips below the ring stack, centred to the ring centre.
  • Keep 8px to 12px of space from the outer ring.
  • Flip chips above the ring stack when the overlay would overflow the viewport.
  • Do not overlap the rendered pet.

Pointer and touch behaviour:

  • On mouse or trackpad pointer devices, hover reveals chips and click is a no-op for this feature.
  • On touch devices, use tap to toggle chip detail only if the pet has no existing tap handler.
  • If the pet already uses tap, use long press of at least 450ms.
  • Keyboard focus always exposes chip detail.

Accessibility:

  • The overlay container should be focusable.
  • The container should always expose an aria-label with both values.
  • Render a visually hidden aria-live="polite" region on mount.
  • Keep the live region mounted and update only its text.
  • Announce only when the displayed integer percentage or availability changes.

Example label:

Rate limits: 5-hour 79 percent remaining, weekly 81 percent remaining.

Motion

Value changes may animate progress over 180ms to 300ms with a nonlinear ease. Respect prefers-reduced-motion: reduce by disabling progress animation and chip reveal transitions.

Acceptance Criteria

  • When the pet overlay is active, the active pet renders with two rings.
  • When the pet is tucked away, no ring UI remains visible.
  • Inner ring progress uses the latest 5-hour remaining percentage.
  • Outer ring progress uses the latest weekly remaining percentage.
  • The implementation maps windows by windowDurationMins, not by primary or secondary object position.
  • No endpoint dots render in default, hover, focus, or touch detail states.
  • Hover, focus, touch tap, or touch long press reveals the two chips.
  • Mouse and trackpad click does not toggle chip state.
  • Missing data renders muted tracks and limits unavailable, not stale values.
  • Active-pet switching does not hardcode a specific pet.
  • The active pet remains readable and centred at the actual overlay size.
  • Keyboard focus exposes the same detail state as hover.
  • Accessible text always includes both windows and displayed values.
  • aria-live announces only displayed percentage or availability changes.
  • Overlay close releases subscriptions, timers, file handles, and watchers.

Non-Goals

  • Do not change the custom pet package format.
  • Do not require pet authors to bake rings into spritesheets.
  • Do not add endpoint dots, floating labels, badges, or extra status text to the default overlay state.
  • Do not depend on local JSONL session scraping inside the app if the app already has account rate-limit state.

Suggested Verification

  • Unit-test the rate-limit selector against rateLimitsByLimitId.codex.
  • Test fallback behaviour when only rateLimits is present.
  • Test missing-data, missing-window, and invalid-percentage cases.
  • Test ring percentages at 79, 50, 15, and 0.
  • Test threshold colours at 16, 15, 5, and 4.
  • Test no endpoint dots across default, hover, focus, and touch detail.
  • Test hover, focus, tap, and long-press detail behaviour.
  • Test reduced-motion behaviour.
  • Test active-pet switching with at least one built-in pet and one custom pet.
  • Test overlay teardown for subscriptions and timers.

View original on GitHub ↗