From 9f84adcb7fb6530ddc2059126f15aa8e8d9fd377 Mon Sep 17 00:00:00 2001 From: sameerr03 Date: Tue, 8 Sep 2026 03:16:11 +0530 Subject: [PATCH] fix(usage): say pace in words and drop the refill line for single accounts The pooled limits card showed pace as a hover-only glyph, and a "+63% in 4d 12h" line that, with one account, only restated the big number and the segment countdown beside it. Pace is now a plain sentence under the number, shared by web and mobile. The next-reset line appears only for pools with several accounts, and names who resets next and how much of the pool that restores. Co-Authored-By: Claude Fable 5.1 --- .../src/features/usage/UsageLimitsPooled.tsx | 39 ++++++++++--------- .../src/features/usage/UsageLimitsSection.tsx | 3 +- apps/web/src/components/usage/UsageLimits.tsx | 22 ++++++----- .../components/usage/UsageLimitsPooled.tsx | 35 ++++++++++------- docs/user/usage.md | 5 ++- packages/shared/src/usageLimits.ts | 7 ++++ 6 files changed, 65 insertions(+), 46 deletions(-) diff --git a/apps/mobile/src/features/usage/UsageLimitsPooled.tsx b/apps/mobile/src/features/usage/UsageLimitsPooled.tsx index dd62f4952d1f..9bd63ac00750 100644 --- a/apps/mobile/src/features/usage/UsageLimitsPooled.tsx +++ b/apps/mobile/src/features/usage/UsageLimitsPooled.tsx @@ -7,6 +7,7 @@ import { collectLimitPools, formatDuration, formatResetsIn, + PACE_LABEL, remainingPercent, type LimitAccount, type LimitPoolWindow, @@ -26,7 +27,6 @@ import { ResetCredits } from "./UsageLimitsSection"; import { useProviderColors } from "./usageProviders"; const DRIVER_LABEL: Partial> = { codex: "Codex", claudeAgent: "Claude" }; -const PACE_LABEL = { ahead: "Ahead of pace", on: "On pace", under: "Under pace" } as const; function accountName(account: LimitAccount) { if (account.displayName) return account.displayName; @@ -78,7 +78,9 @@ function PoolWindowCard({ readonly environmentIds: readonly string[] | null; }) { const navigation = useNavigation(); - const nextRefill = pool.resets.find((reset) => reset.restoresPercent > 0); + // Only worth a line with several accounts; with one it restates the number and the segment. + const nextRefill = + pool.members.length > 1 ? pool.resets.find((reset) => reset.restoresPercent > 0) : undefined; const openAccount = (account: LimitAccount) => navigation.navigate("SettingsSheet", { screen: "SettingsContent", @@ -95,26 +97,27 @@ function PoolWindowCard({ }); return ( - - - {pool.label} - - - {pool.remainingPercent}% - - left - + + {pool.label} + + + {pool.remainingPercent}% + + left {pool.pace ? ( - {PACE_LABEL[pool.pace]} + {PACE_LABEL[pool.pace]} + ) : null} + {nextRefill ? ( + + + {accountName(nextRefill.member.account)} + {" "} + resets {nextRefill.at <= now ? "now" : `in ${formatDuration(nextRefill.at - now)}`}, + restoring {nextRefill.restoresPercent}% + ) : null} - {nextRefill ? ( - - ↻ +{nextRefill.restoresPercent}%{" "} - {nextRefill.at <= now ? "now" : `in ${formatDuration(nextRefill.at - now)}`} - - ) : null} {pool.members.map(({ account, window }, index) => ( = { - ahead: { label: "Ahead of pace: spending faster than the window elapses", icon: TrendingUpIcon }, - on: { label: "On pace with the window", icon: GaugeIcon }, - under: { label: "Under pace: headroom left for the rest of the window", icon: TrendingDownIcon }, +const PACE_ICON: Record = { + ahead: TrendingUpIcon, + on: GaugeIcon, + under: TrendingDownIcon, }; /** The series colour the cost chart uses for this driver, so the two views read as one. */ @@ -51,23 +52,26 @@ export function barColor(driver: ServerProvider["driver"]): string { return kind ? PROVIDER_PRESENTATION[kind].color : "var(--foreground)"; } -/** Pace as a glyph with the words on hover. */ -export function PaceIcon({ pace }: { readonly pace: LimitPace }) { - const Icon = PACE[pace].icon; +/** + * Pace as a glyph with the words on hover, for rows whose bar already draws + * the even-spending mark the glyph summarises. + */ +function PaceIcon({ pace }: { readonly pace: LimitPace }) { + const Icon = PACE_ICON[pace]; return ( } > - {PACE[pace].label} + {PACE_LABEL[pace]} ); } diff --git a/apps/web/src/components/usage/UsageLimitsPooled.tsx b/apps/web/src/components/usage/UsageLimitsPooled.tsx index 0c17779daa3c..c0a74582c823 100644 --- a/apps/web/src/components/usage/UsageLimitsPooled.tsx +++ b/apps/web/src/components/usage/UsageLimitsPooled.tsx @@ -8,6 +8,7 @@ import { type LimitPool, type LimitPoolMember, type LimitPoolWindow, + PACE_LABEL, remainingPercent, } from "@t3tools/shared/usageLimits"; import { TicketIcon } from "lucide-react"; @@ -21,13 +22,7 @@ import { getDriverOption } from "../settings/providerDriverMeta"; import { RedactedSensitiveText } from "../settings/RedactedSensitiveText"; import { Button } from "../ui/button"; import { Popover, PopoverPopup, PopoverTrigger } from "../ui/popover"; -import { - PaceIcon, - ResetCreditDialog, - barColor, - resetCreditsSummary, - useResetCredit, -} from "./UsageLimits"; +import { ResetCreditDialog, barColor, resetCreditsSummary, useResetCredit } from "./UsageLimits"; /** `someone@example.com` → `SE`: enough to tell accounts apart, too little to identify one. */ function accountInitials(email: string): string { @@ -468,9 +463,10 @@ function PoolBar({ } /** - * Big pooled number and the segment bar. The bar is sorted by reset, so who - * refills next is its left edge; the exact time and share restored live in - * each segment's popover rather than a list restating the bar. + * Big pooled number, pace in words, and the segment bar. With several + * accounts the card also names who refills next and how much of the pool + * that hands back, since the big number alone cannot say. With one account + * that would only restate the number and the segment, so it is left out. */ function PoolWindowCard({ pool, @@ -482,7 +478,8 @@ function PoolWindowCard({ readonly now: number; }) { // The soonest reset that hands anything back; an untouched account resets to no effect. - const nextRefill = pool.resets.find((reset) => reset.restoresPercent > 0); + const nextRefill = + pool.members.length > 1 ? pool.resets.find((reset) => reset.restoresPercent > 0) : undefined; return (
@@ -492,12 +489,20 @@ function PoolWindowCard({ {pool.remainingPercent}% left - {pool.pace ? : null} + {pool.pace ? ( + {PACE_LABEL[pool.pace]} + ) : null} {nextRefill ? ( - - ↻ +{nextRefill.restoresPercent}%{" "} - {nextRefill.at <= now ? "now" : `in ${formatDuration(nextRefill.at - now)}`} + + + + resets {nextRefill.at <= now ? "now" : `in ${formatDuration(nextRefill.at - now)}`}, + restoring {nextRefill.restoresPercent}% + ) : null}
diff --git a/docs/user/usage.md b/docs/user/usage.md index 4be084ea299e..b4744d7ffe14 100644 --- a/docs/user/usage.md +++ b/docs/user/usage.md @@ -42,8 +42,9 @@ the dialog. **Usage → Limits** pools every subscription account it can see per provider, so with several Codex or Claude accounts across your environments and hubs you read one number per window rather than a list. Each window card shows how much of the pool is left and a bar with one segment per account, -ordered by which resets soonest; when the provider reports reset times, the card also says when -the next reset lands and how much it hands back. The hatched +ordered by which resets soonest. When the provider reports reset times, the card says whether you +are spending faster or slower than the window elapses, and with more than one account, which +account resets next and how much of the pool that hands back. The hatched part of a segment is what that reset restores. Tap a segment or account row for the account's plan, where it is signed in, and its reset time. On web, you can hover too. Codex accounts with banked reset credits show a ticket count and the **Use reset** action in the account details. On narrow screens, numbered rows below diff --git a/packages/shared/src/usageLimits.ts b/packages/shared/src/usageLimits.ts index 784b1ada3e31..fe7d13f978eb 100644 --- a/packages/shared/src/usageLimits.ts +++ b/packages/shared/src/usageLimits.ts @@ -507,6 +507,13 @@ export function elapsedShare(window: ServerProviderUsageWindow, now: number): nu export type LimitPace = "ahead" | "on" | "under"; +/** Pace in words, shared so every client describes the same state the same way. */ +export const PACE_LABEL: Record = { + ahead: "Spending faster than the clock", + on: "Spending in step with the clock", + under: "Spending slower than the clock", +}; + /** * Usage against the clock. Spending evenly leaves the same share of quota as * there is time left in the window; within five points of that counts as on