Skip to content

feat(usage-budget): tier-aware Usage Budget card for Enterprise + Pro [GET-20]#54

Merged
DevanshuNEU merged 4 commits into
OpenCodeIntel:mainfrom
DevanshuNEU:feat/lco-20-tier-aware-usage
Apr 26, 2026
Merged

feat(usage-budget): tier-aware Usage Budget card for Enterprise + Pro [GET-20]#54
DevanshuNEU merged 4 commits into
OpenCodeIntel:mainfrom
DevanshuNEU:feat/lco-20-tier-aware-usage

Conversation

@DevanshuNEU

Copy link
Copy Markdown
Contributor

Summary

Anthropic's /api/organizations/{orgId}/usage endpoint returns a different shape per account tier. The Usage Budget card silently rendered the "Open claude.ai" placeholder on Enterprise accounts because the response had five_hour: null / seven_day: null and the real data sat under extra_usage. This PR adds tier dispatch end-to-end so Enterprise users see their monthly spend and Pro/Personal users keep the existing layout with no regression.

Type of Change

  • feat — New feature

What Was Changed

Foundation (tier types + parser):

  • lib/message-types.tsUsageLimitsData, UsageBudgetResult, and StoreUsageLimitsMessage are now discriminated unions on kind: session (Pro/Personal/Max/Team), credit (Enterprise), unsupported (recognized 200 with neither shape).
  • lib/usage-limits-parser.ts (new) — single classifier from raw endpoint JSON to typed UsageLimitsData. Returns null only when the body is not even an inspectable object (caller treats as transient failure and keeps the previous render).
  • lib/usage-budget.tscomputeUsageBudget branches on kind. Credit path renders "$304.91 of $500.00 spent" via Intl.NumberFormat (currency formatter cached by code), "Resets May 1" via Intl.DateTimeFormat with Dec→Jan rollover, and zone classification on utilizationPct alone. New getTrackedUtilization(budget) returns the tier-appropriate value for delta tracking.
  • lib/conversation-store.tsgetUsageLimits re-tags pre-GET-20 untagged records as session on read so the upgrade is forward-compatible without a write migration.

Wiring:

  • entrypoints/claude-ai.content.tsfetchAndStoreUsageLimits uses parseUsageResponse; delta tracking uses getTrackedUtilization; both call sites gate on kind !== 'unsupported' before applying budget to overlay state.
  • entrypoints/background.tsSTORE_USAGE_LIMITS rebuilds the matching UsageLimitsData variant from the discriminated message.
  • lib/overlay-state.tsapplyUsageBudget narrowed to RenderableBudget (session | credit); the type system now rejects unsupported budgets at the boundary.

Render:

  • entrypoints/sidepanel/components/UsageBudgetCard.tsx — branches on budget.kind. Session keeps the existing two-bar layout; credit renders a single Monthly bar with the $X of $Y spent status, Resets {date}, and an Enterprise pill; unsupported (and on-tab !budget) shows "Saar can't read usage on this account type yet". New isClaudeTab prop disambiguates the off-tab "Open claude.ai" case.
  • entrypoints/sidepanel/App.tsx — passes isClaudeTab.
  • ui/overlay.ts — weekly bar gated to kind === 'session' (Enterprise has no weekly window). The "this reply" delta label switches to "% of monthly" on credit budgets so the wording matches the unit.

Tests (1,548 passing, 1,492 → 1,548 net +56):

  • tests/unit/usage-limits-parser.test.ts (new) — all four parser outcomes + dispatch priority + malformed input edges, anchored on the verbatim Enterprise fixture from 2026-04-23.
  • tests/unit/usage-budget-card.test.tsx (new) — render tests for AC#1/fix(background): cast storage reads to typed interfaces to resolve TS2322 #2/fix(inject): eliminate race condition in SSE token counting #3 (Enterprise spend bar, Pro/Personal session+weekly layout, Teams unsupported empty state, off-tab prompt).
  • tests/unit/overlay-delta-label.test.ts (new) — locks in "% of session" vs "% of monthly" based on budget kind.
  • tests/unit/usage-budget.test.ts — credit variant zone boundaries, currency formatting, Dec→Jan rollover, unknown-currency fallback, and getTrackedUtilization.
  • Existing fixtures retagged with kind: 'session' across audit, integration, and unit suites.

How to Test

  1. Pull the branch and run bun run compile && bun run test && bun run build — all three green.
  2. bun run dev and load the unpacked build from .output/chrome-mv3/.
  3. Pro/Personal account (regression check, AC#2): open claude.ai, send a message, verify the side panel Usage Budget card still shows Session and Weekly bars with correct percentages. The overlay weekly bar still renders. The "this reply" line says "X% of session".
  4. Enterprise account (AC#1, primary fix): open claude.ai on the Northeastern Enterprise account, send a message, verify the card shows a single Monthly bar with "$X.XX of $Y.YY spent", "Resets May 1", and an "Enterprise" pill in the header. The overlay weekly bar is hidden. The "this reply" line says "X% of monthly".
  5. Teams / unrecognized account (AC#3): on any account whose /usage response lacks both shapes, verify the card shows "Saar can't read usage on this account type yet".
  6. Off-tab empty state: switch to a non-Claude tab; verify the card shows "Open claude.ai to load usage data".
  7. Storage migration: with a pre-GET-20 record already in chrome.storage.local, verify the card still renders the session layout (the read shim re-tags untagged records).

Checklist

  • Tests pass locally (bun run test) — 55 files, 1,548 tests.
  • TypeScript compiles clean (bun run compile).
  • Extension builds without errors (bun run build).
  • No secrets, hardcoded URLs, or sensitive tokens exposed.
  • Comments are professional and clear — no emojis, no AI-generated filler.
  • Commit messages follow conventional commits (4 commits: 3× feat, 1× test).

Related Issues

Closes GET-20.

Notes for Reviewer

  • Option B label switch: the overlay's "this reply" line previously hard-coded "% of session". On Enterprise, lastDeltaUtilization is monthly utilization, so the wording was misleading. Approach picked in plan: switch the label by state.usageBudget.kind rather than dropping the % entirely or adding overlay-state plumbing for tier separately.
  • Read shim only, no write migration: legacy untagged storage records are re-tagged as session on read. Storage is left untouched; the next write overwrites with the fully-tagged shape. This matches the issue's out-of-scope clause and avoids a migration write that has to defend against partial failures.
  • Currency formatter cache: keyed by ISO 4217 code and bounded by the number of currencies Anthropic ever returns (one per account). Failed codes cache as null so we don't retry the constructor.
  • Rebased onto fresh main post-PR chore(gitignore): ignore .claude/ and graphify-out/ tooling output #53 (gitignore) — no merge commit.

…ants [GET-20]

The usage endpoint returns a different payload per account tier; rendering
half-populated bars on Enterprise accounts has been the visible bug since
April. UsageLimitsData and UsageBudgetResult are now discriminated unions on
kind, lib/usage-limits-parser.ts is the single classifier from raw JSON, and
the budget agent branches on kind to produce a tier-appropriate render
contract (session windows, monthly credit, or unsupported). Conversation
store re-tags pre-GET-20 records as session on read so the upgrade is
forward-compatible without a write migration.
Replace the inline session-shape validation in fetchAndStoreUsageLimits with
parseUsageResponse so all three tier variants flow through the same dispatch
path. STORE_USAGE_LIMITS becomes a discriminated message; the background
handler rebuilds the matching UsageLimitsData variant without re-parsing.
Delta tracking now uses getTrackedUtilization, which returns sessionPct on
session and utilizationPct on credit so the math is the same on both tiers
in tier-appropriate units. Overlay state narrows to renderable variants
only — the unsupported budget has no UI to draw and is gated out at the
call site.
… [GET-20]

UsageBudgetCard branches on budget.kind: session keeps the existing
session+weekly layout, credit renders a single monthly spend bar with the
agent's "$X of $Y spent" label and an Enterprise pill, and unsupported
shows "Saar can't read usage on this account type yet". A new isClaudeTab
prop disambiguates the empty state — off-tab users get a prompt to open
claude.ai instead.

The in-page overlay's weekly bar is now scoped to session budgets (credit
has no weekly window) and the "this reply" delta label switches to "% of
monthly" when the budget is credit-tier, since lastDeltaUtilization is
tracked in tier-appropriate units.
…abel [GET-20]

New coverage:
  - usage-limits-parser.test.ts: all four parser outcomes (session, credit,
    unsupported, null) plus dispatch priority and malformed-input edges,
    anchored on the verbatim Enterprise fixture from 2026-04-23.
  - usage-budget.test.ts: credit variant zone boundaries, currency-aware
    "$X of $Y spent" label, Dec→Jan reset rollover, unknown-currency
    fallback, and getTrackedUtilization.
  - usage-budget-card.test.tsx: render tests for the three GET-20 ACs —
    Enterprise spend bar, Pro/Personal session+weekly layout, Teams /
    unrecognized empty state, plus the off-tab "Open claude.ai" branch.
  - overlay-delta-label.test.ts: locks in Option B from the plan — "% of
    session" on session budgets, "% of monthly" on credit budgets.

Existing fixtures retagged with kind: 'session' across audit, integration,
and unit suites. 1,548 tests green.
@vercel

vercel Bot commented Apr 26, 2026

Copy link
Copy Markdown

@DevanshuNEU is attempting to deploy a commit to the Dev's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Apr 26, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@DevanshuNEU has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 16 minutes and 36 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 16 minutes and 36 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 11cd9907-7e86-4de5-82e5-6955e2809330

📥 Commits

Reviewing files that changed from the base of the PR and between 414a727 and 189a314.

📒 Files selected for processing (20)
  • entrypoints/background.ts
  • entrypoints/claude-ai.content.ts
  • entrypoints/sidepanel/App.tsx
  • entrypoints/sidepanel/components/UsageBudgetCard.tsx
  • lib/conversation-store.ts
  • lib/message-types.ts
  • lib/overlay-state.ts
  • lib/usage-budget.ts
  • lib/usage-limits-parser.ts
  • tests/audit/usage-budget-audit.test.ts
  • tests/integration/agent-pipeline.test.ts
  • tests/integration/contracts.test.ts
  • tests/unit/conversation-store.test.ts
  • tests/unit/overlay-delta-label.test.ts
  • tests/unit/overlay-state.test.ts
  • tests/unit/overlay-weekly-cap.test.ts
  • tests/unit/usage-budget-card.test.tsx
  • tests/unit/usage-budget.test.ts
  • tests/unit/usage-limits-parser.test.ts
  • ui/overlay.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@vercel

vercel Bot commented Apr 26, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
getsaar Ready Ready Preview, Comment Apr 26, 2026 1:00am

@DevanshuNEU DevanshuNEU merged commit 79e701f into OpenCodeIntel:main Apr 26, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant