Skip to content

feat(usage): show remaining quota instead of used - #9889

Open
chrisdeeming wants to merge 19 commits into
pingdotgg:mainfrom
chrisdeeming:t3code/usage-limits-remaining
Open

feat(usage): show remaining quota instead of used#9889
chrisdeeming wants to merge 19 commits into
pingdotgg:mainfrom
chrisdeeming:t3code/usage-limits-remaining

Conversation

@chrisdeeming

@chrisdeeming chrisdeeming commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Stacks on #9875 and includes its commits. Merge that first; this diff is the top commit only.

What Changed

  • Limits bars and labels show what is left in each window instead of what has been used, on Usage → Limits and the /usage-limits panel, on web and mobile. Labels read "60% left".
  • The even-spending hairline moves from time elapsed to time left, so a fill sitting on the line still means on pace and a fill short of it means ahead. The web tooltip reads in the same terms.
  • Mobile's warning colours trigger on the remainder: amber at 30% left, red at 10% left.
  • One shared helper does the inversion and rounding, with a test. Pace thresholds are unchanged.

Why

Codex reports remaining quota, and T3 Chat settled on the same framing. Showing the same number in the same direction everywhere removes a mental flip between products.

UI Changes

Web, before and after:

web before
web after

iOS, before and after:

ios before
ios after

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes (no motion changes)

Built with Claude Fable 5.1 in Claude Code via T3 Code.

Note

Add /usage-limits composer command and show remaining quota

  • Adds a shared USAGE_LIMITS_COMMAND descriptor and isUsageLimitsCommand predicate so typing /usage-limits opens a local usage report instead of starting an agent turn.
  • The server injects the command into provider catalogs only for opted-in clients and republishes it on usage-limit source changes in ws.ts.
  • New collectProviderUsageLimits builds a timestamped report with native and pooled accounts; the web and mobile composers render it as a dismissible banner/card.
  • Switches WindowBar and WindowRow from used percentage to remainingPercent (inverted, clamped, rounded) and moves the timeline marker to time-left.
  • Behavioral Change: loadServerConfig now takes an opt-in argument; subscribeServerConfig filters source events by command coverage and may publish provider-status updates without a provider-registry event. LimitWindows now shows a reset countdown on every row.

Macroscope summarized 56127ea.


Note

Medium Risk
Touches WebSocket subscribeServerConfig merging/deduping and send interception in composers; behavior is gated by client opt-in, but mis-sync could affect slash-command catalogs or when limits panels dismiss.

Overview
Usage limits now show quota remaining (e.g. “60% left”) instead of percent used on web and mobile Usage → Limits and in composer panels. Bars and tooltips use a shared remainingPercent helper; the pace hairline tracks time left in the window, and mobile warning colors key off low remainder.

Adds a client-handled /usage-limits slash command for providers that appear under Usage → Limits. Web and mobile thread composers build a live UsageLimitsReport from server config snapshots and show it above the composer (dismiss or clear on send / turn or pending-approval changes). Typing or picking the command does not start an agent turn when there are no attachments; New task blocks it with guidance to use a thread or Settings.

Clients opt in with usageLimitsCommand: true on subscribeServerConfig; the server injects the command into provider slash catalogs via withUsageLimitsCommands and republishes when limit-source coverage changes (zipped with provider status). Older clients that do not opt in keep unchanged catalogs.

Shared package adds UsageLimitsReport, command detection, report collection, and tests; user docs describe the composer flow.

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

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Sep 5, 2026
Comment thread apps/server/src/ws.ts
Comment thread apps/mobile/src/features/threads/ThreadDetailScreen.tsx
@chrisdeeming
chrisdeeming force-pushed the t3code/usage-limits-remaining branch 3 times, most recently from aadfc96 to 47835ab Compare September 5, 2026 04:07
@chrisdeeming
chrisdeeming marked this pull request as ready for review September 5, 2026 04:08
Comment thread apps/mobile/src/features/threads/ComposerUsageLimits.tsx
@chrisdeeming
chrisdeeming force-pushed the t3code/usage-limits-remaining branch from 47835ab to dbdf483 Compare September 5, 2026 04:12
Comment thread apps/mobile/src/features/threads/NewTaskDraftScreen.tsx Outdated
Comment thread docs/user/usage.md Outdated
Comment thread packages/shared/src/usageLimits.ts
@chrisdeeming
chrisdeeming force-pushed the t3code/usage-limits-remaining branch 2 times, most recently from e9c81ae to 69fb3dc Compare September 5, 2026 04:18

function accountLabel(account: UsageLimitsReport["accounts"][number]): string {
return account.instanceId
? providerLimitsLabel(account, (driver) => getDriverOption(driver)?.label)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium chat/ComposerUsageLimits.tsx:12

Native accounts with the same driver render the same providerLimitsLabel (and often plan), so users cannot identify which account owns a quota or reset-credit action. Because the instanceId branch ignores account.label, the instance identifier collected by collectProviderUsageLimits is lost; include that label in the native-account heading.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/chat/ComposerUsageLimits.tsx around line 12:

Native accounts with the same driver render the same `providerLimitsLabel` (and often plan), so users cannot identify which account owns a quota or reset-credit action. Because the `instanceId` branch ignores `account.label`, the instance identifier collected by `collectProviderUsageLimits` is lost; include that label in the native-account heading.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed on the base branch (#9875): the panel heading now shows the instance id whenever a native account has no display name and its id differs from the driver, so siblings of the same driver and plan read apart.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I'm unable to act on this request because you do not have permissions within this repository.

Comment thread apps/mobile/src/features/threads/ThreadDetailScreen.tsx Outdated
@chrisdeeming
chrisdeeming force-pushed the t3code/usage-limits-remaining branch from 69fb3dc to c4eb7bb Compare September 5, 2026 04:21
Comment thread apps/mobile/src/features/threads/ComposerUsageLimits.tsx

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread apps/server/src/ws.ts Outdated
@chrisdeeming
chrisdeeming force-pushed the t3code/usage-limits-remaining branch 3 times, most recently from ddeabea to 193fff2 Compare September 5, 2026 04:30

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread apps/mobile/src/features/threads/ThreadDetailScreen.tsx Outdated
Comment thread apps/mobile/src/features/threads/ComposerUsageLimits.tsx Outdated
@macroscopeapp

macroscopeapp Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR adds a new /usage-limits workflow across web, mobile, shared contracts, and websocket streaming while changing default client capability negotiation and exposing quota/reset-credit behavior in the composer. Its cross-cutting runtime impact and unresolved medium-severity findings require human review.

Not approved because:

  • 4 blocking correctness issues found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

@chrisdeeming
chrisdeeming force-pushed the t3code/usage-limits-remaining branch from 193fff2 to 1d1c782 Compare September 5, 2026 04:36
Comment thread apps/mobile/src/features/threads/ThreadDetailScreen.tsx Outdated
@chrisdeeming
chrisdeeming force-pushed the t3code/usage-limits-remaining branch 5 times, most recently from 8d052b6 to df8e6cf Compare September 5, 2026 05:02
Comment thread apps/web/src/components/ChatView.tsx Outdated
}
// The message is leaving, so that thread's limits snapshot is now stale. The
// uploads above may have outlasted a navigation, so only that thread's clears.
clearUsageLimitsFor(routeThreadKey);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium components/ChatView.tsx:6678

clearUsageLimitsFor removes the open limits panel even when persistThreadSettingsForNextTurn or startThreadTurn returns Failure, so a message that never starts and spends quota still closes the panel. The same ordering occurs in onSubmitPlanFollowUp; defer both clears until after a successful startThreadTurn.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/ChatView.tsx around line 6678:

`clearUsageLimitsFor` removes the open limits panel even when `persistThreadSettingsForNextTurn` or `startThreadTurn` returns `Failure`, so a message that never starts and spends quota still closes the panel. The same ordering occurs in `onSubmitPlanFollowUp`; defer both clears until after a successful `startThreadTurn`.

@chrisdeeming
chrisdeeming force-pushed the t3code/usage-limits-remaining branch 3 times, most recently from d80b244 to 1ca3548 Compare September 5, 2026 05:34

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 1ca3548. Configure here.

Comment thread apps/web/src/components/usage/UsageLimits.tsx
@chrisdeeming
chrisdeeming force-pushed the t3code/usage-limits-remaining branch 6 times, most recently from 22ac779 to 509fd55 Compare September 5, 2026 06:30
Sending /usage-limits in a thread opens the current model's limits above
the composer, built from the same snapshot as Usage > Limits. The command
resolves in the client without starting a turn, so nothing is written to
the thread; the panel closes on dismiss or the next send.

Web renders it as a composer notice so it stacks under warnings and uses
the standard dismiss. Mobile docks an opaque card in the approval slot.
The server only advertises the command for providers present in Limits.

Model: Claude Fable 5.1. Harness: Claude Code in T3 Code.
Review follow-ups for /usage-limits: drop the snapshot when the thread or
model changes, keep it hidden rather than dropped while the provider list
is unavailable, clear it only when a message actually leaves, clear it
when a later request has nothing to show, let submissions carrying
attachments or contexts send as prompts, and report a usage-limit source
that failed to read even though it has no accounts left to match.

Model: Claude Fable 5.1. Harness: Claude Code in T3 Code.
Seed the provider status stream with the current providers so a usage
limit source refresh reaches clients before any provider change, with a
test. Tie the /usage-limits snapshot to the turn and clear it once an
approval or question is answered, since the agent then spends quota.

Model: Claude Fable 5.1. Harness: Claude Code in T3 Code.
The /usage-limits panel now reads provider data at render, so a redeemed
reset credit or refreshed probe shows through instead of a frozen
snapshot. Mobile New Task no longer offers the command and refuses to
send it, since only the thread composer can answer it.

Model: Claude Fable 5.1. Harness: Claude Code in T3 Code.
Choosing /usage-limits from the composer menu opens the panel at once
instead of leaving the command in the draft to be sent. Typing it out and
sending still works as a fallback.

Model: Claude Fable 5.1. Harness: Claude Code in T3 Code.
Compare provider status pairings against the snapshot the client holds
instead of dropping the first one, so a refresh between snapshot and
subscription still goes out. A usage-limit source that failed to read
now counts for every driver, so its error reaches the panel. Only a
successful approval or answer clears the panel, and New Task lets a
prompt with attachments through.

Model: Claude Fable 5.1. Harness: Claude Code in T3 Code.
Native accounts in the panel show their instance id when they share a
driver and have no display name. A response that resolves after
navigating away clears only the originating thread's panel. The catalog
republish comparator now lives in shared, counts a failed source as
covering every driver, and is tested.

Model: Claude Fable 5.1. Harness: Claude Code in T3 Code.
A report with no accounts, such as a failed hub and no native windows,
now renders its own heading row with the close control.

Model: Claude Fable 5.1. Harness: Claude Code in T3 Code.
A send can outlast a navigation while attachments upload, so it clears
only the originating thread's limits panel instead of whatever is open.

Model: Claude Fable 5.1. Harness: Claude Code in T3 Code.
Only a delivered approval or answer resumes the agent, so a failed reply
leaves the snapshot in place.

Model: Claude Fable 5.1. Harness: Claude Code in T3 Code.
Source-backed rows showed only the hub kind, so two accounts from one
hub were indistinguishable. They now carry the hub and account id.

Model: Claude Fable 5.1. Harness: Claude Code in T3 Code.
Ownership was a prefix match on a composite key; thread ids may contain
the delimiter, so the panel now stores its thread key and compares it.

Model: Claude Fable 5.1. Harness: Claude Code in T3 Code.
Model: Claude Fable 5.1. Harness: Claude Code in T3 Code.
A send that fails before the turn starts spends no quota, so it leaves
the panel in place. Both the composer and plan follow-up paths clear
after a successful start.

Model: Claude Fable 5.1. Harness: Claude Code in T3 Code.
The command name is only T3's where Limits has data for the selected
provider; elsewhere a provider's own command of that name is sent
through untouched, on the menu, on send, and in New Task. The panel key
now includes any pending approval or question, so an answer from any
client closes it, which also retires the local response wrappers. The
web key stays hidden rather than dropped while a server thread is not
yet loaded.

Model: Claude Fable 5.1. Harness: Claude Code in T3 Code.
Clients that handle the command locally say so on the config
subscription; the server injects it into provider catalogs only for
them. An older client, which would send the injected command to the
provider as a prompt, never sees it. The one-shot config fetch stays
without it.

Model: Claude Fable 5.1. Harness: Claude Code in T3 Code.
The menu handler and the send intercept now require an available panel
key, so a reconnect or loading thread leaves the text in the composer.

Model: Claude Fable 5.1. Harness: Claude Code in T3 Code.
Picking /usage-limits with attachments or contexts aboard inserts the
command text instead, so it sends as a prompt, matching the typed path.

Model: Claude Fable 5.1. Harness: Claude Code in T3 Code.
Limits bars and labels now show what is left in each window, matching
Codex and T3 Chat. The even-spending mark moves to the time left so the
fill sits on the line when spending is on pace, and mobile warns as the
remainder drops below 30% and 10%. Applies to Usage > Limits and the
/usage-limits panel on web and mobile.

Model: Claude Fable 5.1. Harness: Claude Code in T3 Code.
@chrisdeeming
chrisdeeming force-pushed the t3code/usage-limits-remaining branch from 509fd55 to 56127ea Compare September 5, 2026 06:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL 500-999 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant