fix(web): render and filter usage as environments respond#9860

merged3
main
t3code/stream-usage-environment-data
openedSep 5, 2026byJJulius Marminge
View on GitHubTwo-way sync enabledPR last imported 5d ago

Usage waits for the slowest environment before showing any results. Render each response as it arrives and keep scan status in a permanent breadcrumb dropdown, so completing a scan does not move the page. The same dropdown filters costs, tokens, and limits; all environments start selected.

Validation: focused usage state/page tests cover progressive responses, selected totals, deduplication, cached refreshes, and empty/failed selections. Web typecheck and scoped lint pass. The web UI is shared with desktop; server contracts and native mobile UI are unchanged.

Both captures use the actual base/head at 1440×1000 with the same synthetic data and one of two environments still pending.

Progressive-loading recording

Implemented with GPT-6 in Codex.


Note

Medium Risk Changes how usage totals are computed and displayed (merge scope, deduplication, loading semantics); behavior is covered by new state tests but wrong edge cases could misreport costs.

Overview Usage no longer blocks the whole page until every environment finishes scanning. useUsage now accepts an optional environment selection, merges only selected answered summaries, and treats isPending / isPartial relative to that selection so totals can appear incrementally while others are still scanning.

A permanent environment dropdown in the Usage header replaces the old device strip and inline coverage banner. It filters cost, tokens, and limits (including UsageLimitsSection), shows per-environment scan/status (including incompatible server versions via exported isCompatibleUsageContractVersion), and embeds coverage/dedup notices so layout does not jump when scans complete. Refresh and limits refresh respect the current filter.

User docs note the new filter. New and updated tests cover progressive responses, filtered totals, dedup within a selection, cached refresh, and empty/failed selections.

Reviewed by Cursor Bugbot for commit ed79de694090076e4b038081658b0b5d9e709411. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Filter UsageLimitsSection by selected environment and update empty-state

Adds a selected-environment ID prop to UsageLimitsSection in UsageLimits.tsx. The component now filters environment presentations before collecting limit groups and sources, so provider limits render only for the chosen environments. The empty-state message now refers to the selected environment when no limits are reported.

Macroscope summarized ed79de6.

CcursorBotreviewed · 1 inline threadSep 5, 2026
Synced from GitHub

Stale Bugbot comment from a previous run.

apps/web/src/components/usage/UsageLimits.tsxline 440Outdated · c65b770Open thread
……@@ -426,10 +426,18 @@ function SourceLimits({ source, now }: { readonly source: LimitsSource; readonly
437+ ? presentations
438+ : new Map([...presentations].filter(([id]) => selectedEnvironmentIds.has(id)));
439+ const groups = collectLimitsGroups(selected);
440+ const sources = collectLimitSources(selected);
CcursorBoton line 440Sep 5, 2026

Limits empty copy ignores filter

Low Severity

The new environment filter can hide every limits-reporting environment, but the empty state still says no connected environment reports limits. On the Limits tab, a missing or cleared selection also says to connect or select an environment to see usage.

Additional Locations (1)

Reviewed by Cursor Bugbot for commit c65b7709e1b305db60fc551d8635eef42319c094. Configure here.

Fixed in 3c48e353a. The limits empty state now refers to selected environments, and clearing the filter on the Limits tab says to select an environment to see limits. Scoped lint and the usage/page tests pass.

Synced from GitHub

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR introduces a new environment-filtering experience and changes production usage aggregation and loading behavior across web and desktop, including costs, tokens, limits, refreshes, and partial-result display. The state tests provide coverage, but the breadth of the user-facing behavior warrants human review.

You can add or adjust custom eligibility rules. Learn more.

Synced from GitHub

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.5 KiB13.7 KiB+226 B (+1.6%)15.1 KiB✅
CodexThread snapshot wire7.0 KiB7.0 KiB+12 B (+0.2%)7.3 KiB✅
CodexLive turn WebSocket wire6.5 KiB6.7 KiB+214 B (+3.2%)7.8 KiB✅
CodexLive turn WebSocket decoded57.0 KiB58.5 KiB+1.5 KiB (+2.6%)66.4 KiB✅
CodexLive turn messages810+2 (+25.0%)21✅
ClaudeTotal thread wire13.6 KiB13.6 KiB−24 B (−0.2%)15.1 KiB✅
ClaudeThread snapshot wire7.0 KiB7.0 KiB+1 B (+0.0%)7.3 KiB✅
ClaudeLive turn WebSocket wire6.6 KiB6.5 KiB−25 B (−0.4%)7.8 KiB✅
ClaudeLive turn WebSocket decoded57.8 KiB57.8 KiB0 B (0.0%)66.4 KiB✅
ClaudeLive turn messages990 (0.0%)21✅

Baseline: d7fe47f · PR result: ed79de6 · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 113.8 KiB
  • Claude decoded thread snapshot: 114.5 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

Added 2 commits

  • fix(web): render usage as environments respondc061d55
  • fix(web): describe the selected limits empty statec5365fd
CcursorBotreviewed · 1 inline threadSep 5, 2026
Synced from GitHub

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is ON, but a cloud agent failed to start.

Reviewed by Cursor Bugbot for commit c5365fd1c732bbaacdf2f878d0d4532fc95cc4d6. Configure here.

apps/web/src/components/usage/UsagePage.tsxline 683Outdated · c5365fdOpen thread
……@@ -592,66 +601,145 @@ function UsageCoverageNotice({
680+ environment.error !== null
681+ ? "Unavailable"
682+ : staleEnvironments.includes(environment.environmentId)
683+ ? "Update required"
CcursorBoton line 683Sep 5, 2026

Stale status ignores unselected environments

Low Severity

The dropdown lists every environment but reads Update required from merged.staleEnvironments, which only includes the current selection. An unselected incompatible environment shows as Ready, and deselecting a stale one flips its status the same way.

Additional Locations (1)

Reviewed by Cursor Bugbot for commit c5365fd1c732bbaacdf2f878d0d4532fc95cc4d6. Configure here.

Fixed in ed79de694. Each menu row now checks its own summary against the same compatibility range used by the merger, independently of selection. Verified in the browser that deselecting an incompatible environment keeps Update required. Existing version-range tests, Usage state/page tests, web typecheck, and scoped lint pass.

Added 1 commit

  • fix(web): retain usage status for unselected environmentsed79de6