Skip to content

fix(billing): normalize TeamUsage API response to prevent crash on navigation#488

Merged
graycyrus merged 1 commit intotinyhumansai:mainfrom
graycyrus:fix/billing-page-crash-normalization
Apr 10, 2026
Merged

fix(billing): normalize TeamUsage API response to prevent crash on navigation#488
graycyrus merged 1 commit intotinyhumansai:mainfrom
graycyrus:fix/billing-page-crash-normalization

Conversation

@graycyrus
Copy link
Copy Markdown
Contributor

@graycyrus graycyrus commented Apr 10, 2026

Summary

  • Add normalizeTeamUsage() to creditsApi.ts to safely map backend JSON fields to the TeamUsage interface with fallback defaults
  • Add defensive ?? 0 guards on all .toFixed() call sites in InferenceBudget.tsx and Conversations.tsx
  • Switch BillingPanel.tsx from Promise.all to Promise.allSettled so a single failing API call doesn't blank the entire page
  • Add 6 test cases covering normalization (camelCase, snake_case, legacy fields, empty object, null input, integration)

Problem

  • creditsApi.getTeamUsage() returned raw backend JSON without normalization
  • If the backend returned null, undefined, or differently-named fields, calling .toFixed(2) on undefined threw a TypeError that crashed the React tree
  • BillingPanel used Promise.all, meaning if getTeamUsage() failed, getCurrentPlan() and getBalance() results were also discarded — the entire page went blank
  • TypeScript's generic cast on callCoreCommand<TeamUsage>() masked the mismatch at compile time, so yarn typecheck passed cleanly

Solution

  • Added normalizeTeamUsage() following the existing normalizeCreditBalance() pattern — maps camelCase, snake_case, and legacy field names to the interface with safe numeric defaults via normalizeUsd()
  • Wired normalization into getTeamUsage() so all consumers (BillingPanel, useUsageState, Conversations) get safe data automatically
  • Added ?? 0 defense-in-depth on .toFixed() call sites in case normalization is bypassed
  • Changed BillingPanel to Promise.allSettled with per-result logging for partial rendering on API failure

Submission Checklist

  • Unit tests — 6 new test cases in creditsApi.test.ts covering normalization scenarios
  • E2E / integration — Not added; billing page requires auth + active subscription. Covered by unit tests on the normalization layer.
  • Doc comments — Existing doc comments on TeamUsage fields are sufficient
  • Inline comments — No new comments needed; code follows the established normalization pattern

Impact

  • Desktop only (billing page is a settings sub-route)
  • No performance impact — normalization is a lightweight object mapping
  • No migration needed — normalization is additive and handles both old and new field names
  • Fixes crash on first navigation to /settings/billing when backend returns unexpected field shapes

Related

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Improved resilience of billing information loading—if one data source fails, others still load successfully.
    • Fixed null-safety handling in team budget display calculations to prevent rendering errors.
  • Tests

    • Enhanced test coverage for team usage data processing.

…vigation

getTeamUsage() returned raw backend JSON without normalization, so
undefined/null numeric fields caused .toFixed() TypeErrors that crashed
the billing page. Add normalizeTeamUsage() (matching the existing
normalizeCreditBalance pattern), defensive ?? 0 guards on .toFixed()
call sites, and switch BillingPanel to Promise.allSettled for partial
rendering on API failure.

Closes tinyhumansai#482
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 10, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 23733772-c4ea-4e15-9e8b-5829c1eb3d54

📥 Commits

Reviewing files that changed from the base of the PR and between ce5523c and 266a03b.

📒 Files selected for processing (6)
  • .claude/memory.md
  • app/src/components/settings/panels/BillingPanel.tsx
  • app/src/components/settings/panels/billing/InferenceBudget.tsx
  • app/src/pages/Conversations.tsx
  • app/src/services/api/__tests__/creditsApi.test.ts
  • app/src/services/api/creditsApi.ts

📝 Walkthrough

Walkthrough

This PR addresses Payment page crashes by implementing client-side normalization of TeamUsage data from the Rust backend, making API data fetching more resilient with Promise.allSettled, and adding null-safe field handling throughout billing-related UI components.

Changes

Cohort / File(s) Summary
Data Normalization & API
app/src/services/api/creditsApi.ts, app/src/services/api/__tests__/creditsApi.test.ts
Added new normalizeTeamUsage() function to reconcile backend JSON shape differences (snake_case to camelCase conversion, numeric coercion, field derivation). Updated getTeamUsage() to apply normalization to RPC responses. Comprehensive test suite validates normalization logic, including edge cases with null/undefined input and field mapping.
Error Handling & Fetch Strategy
app/src/components/settings/panels/BillingPanel.tsx
Replaced Promise.all().then().catch() with Promise.allSettled().then() to prevent single API failure from aborting entire batch fetch. Added per-request fulfilled/rejected handling with targeted debug logging for individual failures; retained .finally() cleanup logic.
Null-Safe UI Components
app/src/components/settings/panels/billing/InferenceBudget.tsx, app/src/pages/Conversations.tsx
Added nullish coalescing operators (?? 0) to guard numeric budget fields (remainingUsd, cycleBudgetUsd, cycleLimit5hr, fiveHourCapUsd) before calling .toFixed(2) in formatted currency displays, preventing crashes when normalization yields null values.
Documentation
.claude/memory.md
Updated memory notes documenting the new client-side normalization pattern for getTeamUsage() and clarifying two distinct TeamUsage types with different semantics across the codebase.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • PR #318: Extends TeamUsage shape and adds normalizers with null-safe handling for team usage fields; directly precedes this PR's normalization approach.
  • PR #438: Introduces bypassCycleLimit derivation in normalizeTeamUsage and UI gating logic using TeamUsage flags; overlaps with this PR's normalization and bypass flag handling.
  • PR #465: Updates TeamUsage field representation and consumers; shares the same namespace/schema concerns this PR addresses through normalization.

Suggested reviewers

  • senamakel

Poem

🐰 Through tangled types and null-y dread,
The Payment page once fell down dead.
But normalized shapes and coalesced care,
Now handle the data with defensive flair!
No more crashes on that billing flight,
The rabbit's refactoring sets things right.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@graycyrus graycyrus merged commit f32c0d5 into tinyhumansai:main Apr 10, 2026
8 of 9 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Apr 27, 2026
5 tasks
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.

[Bug] Payment page breaks on navigation

1 participant