From 00c9d2332b117527ecaa24a332f6a747c69a5a5d Mon Sep 17 00:00:00 2001 From: SunSinD Date: Tue, 27 Oct 2026 22:21:26 -0400 Subject: [PATCH 1/4] fix(usage): keep account rows compact and clear --- .../src/features/usage/UsageLimitsPooled.tsx | 15 +++- .../usage/UsageLimitsPooled.browser.test.tsx | 77 +++++++++++++++++++ .../components/usage/UsageLimitsPooled.tsx | 26 +++++-- docs/user/usage.md | 10 ++- packages/shared/src/usageLimits.test.ts | 27 ++++++- packages/shared/src/usageLimits.ts | 5 +- 6 files changed, 140 insertions(+), 20 deletions(-) create mode 100644 apps/web/src/components/usage/UsageLimitsPooled.browser.test.tsx diff --git a/apps/mobile/src/features/usage/UsageLimitsPooled.tsx b/apps/mobile/src/features/usage/UsageLimitsPooled.tsx index 19e7e7e03c6e3..7c1cbf718f367 100644 --- a/apps/mobile/src/features/usage/UsageLimitsPooled.tsx +++ b/apps/mobile/src/features/usage/UsageLimitsPooled.tsx @@ -204,8 +204,15 @@ function PoolWindowCard({ {pool.remainingPercent}% - left + + {pool.members.length > 1 ? "average left" : "left"} + + {pool.members.length > 1 ? ( + + Across {pool.members.length} accounts + + ) : null} {pool.pace ? ( {PACE_LABEL[pool.pace]} @@ -214,7 +221,7 @@ function PoolWindowCard({ {description ? {description} : null} {nextRefill ? ( - ↻ +{nextRefill.restoresPercent}%{" "} + Next account reset{" "} {formatResetsIn(nextRefill.member.window, now) ?.replace("resets in ", "in ") .replace("resets now", "now")} @@ -604,7 +611,9 @@ export function UsageLimitAccountScreen({ route }: AccountScreenProps) { ) : null} {reset && reset.restoresPercent > 0 ? ( - Restores {reset.restoresPercent}% of the pool + After reset:{" "} + {Math.round(Math.min(100, remainingPercent(window) + reset.member.refillPercent))} + % left on this account ) : null} diff --git a/apps/web/src/components/usage/UsageLimitsPooled.browser.test.tsx b/apps/web/src/components/usage/UsageLimitsPooled.browser.test.tsx new file mode 100644 index 0000000000000..8a02f5ef03bac --- /dev/null +++ b/apps/web/src/components/usage/UsageLimitsPooled.browser.test.tsx @@ -0,0 +1,77 @@ +import { EnvironmentId, ProviderDriverKind, UsageLimitSourceId } from "@t3tools/contracts"; +import type { LimitPresentations } from "@t3tools/shared/usageLimits"; +import { expect, it } from "vite-plus/test"; + +import { render } from "../../../test/render"; +import { AppAtomRegistryProvider } from "../../rpc/atomRegistry"; +import { UsageLimitsPooled } from "./UsageLimitsPooled"; +import "../../index.css"; + +it("keeps the account label and reset summary on a one-account row of a multi-account provider", async () => { + const now = Date.parse("2026-10-28T01:00:00.000Z"); + const checkedAt = new Date(now).toISOString(); + const driver = ProviderDriverKind.make("codex"); + const presentations: LimitPresentations = new Map([ + [ + EnvironmentId.make("demo"), + { + entry: { target: { label: "Demo" } }, + serverConfig: { + providers: [], + usageLimitSources: [ + { + id: UsageLimitSourceId.make("demo"), + kind: "cliproxy", + label: "Demo", + checkedAt, + accounts: [ + { + id: "session-only", + driver, + usageLimits: { + checkedAt, + windows: [ + { + id: "primary", + kind: "session", + label: "Session", + usedPercent: 29, + resetsAt: "2026-10-28T03:55:00.000Z", + }, + ], + }, + }, + { + id: "monthly-only", + driver, + usageLimits: { + checkedAt, + windows: [ + { + id: "primary", + kind: "monthly", + label: "Monthly", + usedPercent: 2, + resetsAt: "2026-11-26T18:00:00.000Z", + }, + ], + }, + }, + ], + }, + ], + }, + }, + ], + ]); + const container = document.createElement("div"); + container.style.width = "1000px"; + await using screen = await render( + + + , + { container }, + ); + await expect.element(screen.getByText("monthly-only", { exact: true }).first()).toBeVisible(); + await expect.element(screen.getByText("Next account reset in 29d 17h")).toBeVisible(); +}); diff --git a/apps/web/src/components/usage/UsageLimitsPooled.tsx b/apps/web/src/components/usage/UsageLimitsPooled.tsx index 7157607dabafc..05df9c586a519 100644 --- a/apps/web/src/components/usage/UsageLimitsPooled.tsx +++ b/apps/web/src/components/usage/UsageLimitsPooled.tsx @@ -192,7 +192,10 @@ function SegmentPopover({ ) : null} {timeLeft !== null ? {timeLeft}% left by now : null} {reset && reset.restoresPercent > 0 ? ( - +{reset.restoresPercent}% of pool + + {Math.round(Math.min(100, remaining + reset.member.refillPercent))}% left on this + account + ) : null} {credits && redeem ? ( @@ -495,10 +498,12 @@ function PoolBar({ pool, color, now, + providerAccountCount, }: { readonly pool: LimitPoolWindow; readonly color: string; readonly now: number; + readonly providerAccountCount: number; }) { const restores = new Map(pool.resets.map((reset) => [reset.member.account.key, reset])); return ( @@ -518,7 +523,7 @@ function PoolBar({ color={color} now={now} index={position + 1} - showAccountName={pool.columns.length > 1} + showAccountName={providerAccountCount > 1} /> ) : null, )} @@ -531,12 +536,14 @@ function PoolWindowCard({ pool, color, now, + providerAccountCount, label, description, }: { readonly pool: LimitPoolWindow; readonly color: string; readonly now: number; + readonly providerAccountCount: number; readonly label?: string | undefined; readonly description?: string | undefined; }) { @@ -550,17 +557,23 @@ function PoolWindowCard({ {pool.remainingPercent}% - left + {pool.members.length > 1 ? "average left" : "left"} {pool.pace ? : null} - {nextRefill && pool.columns.length > 1 ? ( + {pool.members.length > 1 ? ( + + Across {pool.members.length} accounts + + ) : null} + {nextRefill && providerAccountCount > 1 ? ( - ↻ +{nextRefill.restoresPercent}% + Next account reset{" "} + {formatResetsIn(nextRefill.member.window, now)?.replace("resets ", "")} ) : null} - + {description ? (

{description}

) : null} @@ -590,6 +603,7 @@ function PoolSection({ pool, now }: { readonly pool: LimitPool; readonly now: nu { }); }); - it("preserves gaps without counting missing windows toward pooled quota", () => { + it("fills each row with only accounts reporting that window", () => { const [pool] = collectLimitPools( [ account("a", [window]), @@ -859,14 +859,35 @@ describe("pooled account columns", () => { now, ); expect(keys(pool!)).toEqual([ - ["a", "b", null], - [null, "b", "c"], + ["a", "b"], + ["b", "c"], ]); expect(pool!.windows[1]!.members.map((member) => member.account.key)).toEqual(["b", "c"]); expect(pool!.windows[1]!.remainingPercent).toBe(40); expect(pool!.windows[1]!.resets.map((reset) => reset.restoresPercent)).toEqual([40, 20]); }); + it("gives two Codex session accounts equal slots and a monthly-only account its own full row", () => { + const codex = (key: string, windows: LimitAccount["limits"]["windows"]) => ({ + ...account(key, windows), + driver: ProviderDriverKind.make("codex"), + }); + const [pool] = collectLimitPools( + [ + codex("a", [window, { ...weekly, usedPercent: 90 }]), + codex("b", [window, { ...weekly, usedPercent: 100 }]), + codex("monthly", [{ ...window, kind: "monthly", usedPercent: 2 }]), + ], + now, + ); + expect(keys(pool!)).toEqual([["a", "b"], ["a", "b"], ["monthly"]]); + expect(pool!.windows[1]!.remainingPercent).toBe(5); + expect(pool!.windows[1]!.members.map((member) => 100 - member.window.usedPercent)).toEqual([ + 10, 0, + ]); + expect(pool!.windows[2]!.remainingPercent).toBe(98); + }); + it("falls back to weekly resets when no account reports a session", () => { const [pool] = collectLimitPools( [ diff --git a/packages/shared/src/usageLimits.ts b/packages/shared/src/usageLimits.ts index 01e2ad59a332d..e0a97ff3384c8 100644 --- a/packages/shared/src/usageLimits.ts +++ b/packages/shared/src/usageLimits.ts @@ -467,7 +467,6 @@ function poolWindows(accounts: readonly LimitAccount[], now: number): readonly L } } const pools = [...byKey].map(([key, members]): LimitPoolWindow => { - const memberByAccount = new Map(members.map((member) => [member.account.key, member])); const first = members[0]!.window; const id = members[0]!.account.driver === "codex" ? key : first.id; const usedPercent = members.reduce((sum, m) => sum + m.window.usedPercent, 0) / members.length; @@ -498,9 +497,7 @@ function poolWindows(accounts: readonly LimitAccount[], now: number): readonly L kind: first.kind, label: first.label, members, - columns: accounts.map( - (account) => memberByAccount.get(account.key) ?? { account, window: null }, - ), + columns: members, usedPercent: Math.round(usedPercent), remainingPercent: Math.round(100 - usedPercent), pace: meanElapsed === null ? null : paceOfShares(timedUsed, meanElapsed), From 6f384544da7b3fb3997893d7573aa8856983e63f Mon Sep 17 00:00:00 2001 From: SunSinD Date: Tue, 27 Oct 2026 22:39:22 -0400 Subject: [PATCH 2/4] fix(usage): show the earliest account reset --- .../src/features/usage/UsageLimitsPooled.tsx | 6 +++--- .../usage/UsageLimitsPooled.browser.test.tsx | 19 ++++++++++++++++++- .../components/usage/UsageLimitsPooled.tsx | 6 +++--- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/apps/mobile/src/features/usage/UsageLimitsPooled.tsx b/apps/mobile/src/features/usage/UsageLimitsPooled.tsx index 7c1cbf718f367..34f1100a511da 100644 --- a/apps/mobile/src/features/usage/UsageLimitsPooled.tsx +++ b/apps/mobile/src/features/usage/UsageLimitsPooled.tsx @@ -186,7 +186,7 @@ function PoolWindowCard({ const inlineLabels = wide && segmentWidth >= 96; const inlineReset = wide && segmentWidth >= 150; const inlineName = wide && segmentWidth >= 220; - const nextRefill = pool.resets.find((reset) => reset.restoresPercent > 0); + const nextReset = pool.resets[0]; const openAccount = (account: LimitAccount) => navigation.navigate("SettingsUsageAccount", { accountKey: account.key, @@ -219,10 +219,10 @@ function PoolWindowCard({ ) : null} {description ? {description} : null} - {nextRefill ? ( + {nextReset ? ( Next account reset{" "} - {formatResetsIn(nextRefill.member.window, now) + {formatResetsIn(nextReset.member.window, now) ?.replace("resets in ", "in ") .replace("resets now", "now")} diff --git a/apps/web/src/components/usage/UsageLimitsPooled.browser.test.tsx b/apps/web/src/components/usage/UsageLimitsPooled.browser.test.tsx index 8a02f5ef03bac..f8ec549eed3ae 100644 --- a/apps/web/src/components/usage/UsageLimitsPooled.browser.test.tsx +++ b/apps/web/src/components/usage/UsageLimitsPooled.browser.test.tsx @@ -35,12 +35,28 @@ it("keeps the account label and reset summary on a one-account row of a multi-ac id: "primary", kind: "session", label: "Session", - usedPercent: 29, + usedPercent: 0, resetsAt: "2026-10-28T03:55:00.000Z", }, ], }, }, + { + id: "later-session", + driver, + usageLimits: { + checkedAt, + windows: [ + { + id: "primary", + kind: "session", + label: "Session", + usedPercent: 29, + resetsAt: "2026-10-28T05:00:00.000Z", + }, + ], + }, + }, { id: "monthly-only", driver, @@ -74,4 +90,5 @@ it("keeps the account label and reset summary on a one-account row of a multi-ac ); await expect.element(screen.getByText("monthly-only", { exact: true }).first()).toBeVisible(); await expect.element(screen.getByText("Next account reset in 29d 17h")).toBeVisible(); + await expect.element(screen.getByText("Next account reset in 2h 55m")).toBeVisible(); }); diff --git a/apps/web/src/components/usage/UsageLimitsPooled.tsx b/apps/web/src/components/usage/UsageLimitsPooled.tsx index 05df9c586a519..59bb0bdc4dc06 100644 --- a/apps/web/src/components/usage/UsageLimitsPooled.tsx +++ b/apps/web/src/components/usage/UsageLimitsPooled.tsx @@ -547,7 +547,7 @@ function PoolWindowCard({ readonly label?: string | undefined; readonly description?: string | undefined; }) { - const nextRefill = pool.resets.find((reset) => reset.restoresPercent > 0); + const nextReset = pool.resets[0]; return (
@@ -566,10 +566,10 @@ function PoolWindowCard({ Across {pool.members.length} accounts ) : null} - {nextRefill && providerAccountCount > 1 ? ( + {nextReset && providerAccountCount > 1 ? ( Next account reset{" "} - {formatResetsIn(nextRefill.member.window, now)?.replace("resets ", "")} + {formatResetsIn(nextReset.member.window, now)?.replace("resets ", "")} ) : null}
From a29ec1a94df2be2add875f1057df60a5960ccbd8 Mon Sep 17 00:00:00 2001 From: maria-rcks Date: Fri, 30 Oct 2026 14:14:17 +0000 Subject: [PATCH 3/4] fix(usage): keep small account reset projections visible --- apps/mobile/src/features/usage/UsageLimitsPooled.tsx | 2 +- .../src/components/usage/UsageLimitsPooled.browser.test.tsx | 4 +++- apps/web/src/components/usage/UsageLimitsPooled.tsx | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/mobile/src/features/usage/UsageLimitsPooled.tsx b/apps/mobile/src/features/usage/UsageLimitsPooled.tsx index 34f1100a511da..dd663e5fb7f23 100644 --- a/apps/mobile/src/features/usage/UsageLimitsPooled.tsx +++ b/apps/mobile/src/features/usage/UsageLimitsPooled.tsx @@ -609,7 +609,7 @@ export function UsageLimitAccountScreen({ route }: AccountScreenProps) { Even pace: {evenPace}% left by now ) : null} - {reset && reset.restoresPercent > 0 ? ( + {reset && reset.member.refillPercent > 0 ? ( After reset:{" "} {Math.round(Math.min(100, remainingPercent(window) + reset.member.refillPercent))} diff --git a/apps/web/src/components/usage/UsageLimitsPooled.browser.test.tsx b/apps/web/src/components/usage/UsageLimitsPooled.browser.test.tsx index f8ec549eed3ae..28626eb32da58 100644 --- a/apps/web/src/components/usage/UsageLimitsPooled.browser.test.tsx +++ b/apps/web/src/components/usage/UsageLimitsPooled.browser.test.tsx @@ -51,7 +51,7 @@ it("keeps the account label and reset summary on a one-account row of a multi-ac id: "primary", kind: "session", label: "Session", - usedPercent: 29, + usedPercent: 0.6, resetsAt: "2026-10-28T05:00:00.000Z", }, ], @@ -91,4 +91,6 @@ it("keeps the account label and reset summary on a one-account row of a multi-ac await expect.element(screen.getByText("monthly-only", { exact: true }).first()).toBeVisible(); await expect.element(screen.getByText("Next account reset in 29d 17h")).toBeVisible(); await expect.element(screen.getByText("Next account reset in 2h 55m")).toBeVisible(); + await screen.getByRole("button", { name: /^later-session: 99% left/ }).click(); + await expect.element(screen.getByText("100% left on this account")).toBeVisible(); }); diff --git a/apps/web/src/components/usage/UsageLimitsPooled.tsx b/apps/web/src/components/usage/UsageLimitsPooled.tsx index 59bb0bdc4dc06..b97e67ab37acc 100644 --- a/apps/web/src/components/usage/UsageLimitsPooled.tsx +++ b/apps/web/src/components/usage/UsageLimitsPooled.tsx @@ -191,7 +191,7 @@ function SegmentPopover({ ) : null} {timeLeft !== null ? {timeLeft}% left by now : null} - {reset && reset.restoresPercent > 0 ? ( + {reset && reset.member.refillPercent > 0 ? ( {Math.round(Math.min(100, remaining + reset.member.refillPercent))}% left on this account From 2b36ab37f0518d36fea7016b560b054be5c06cfc Mon Sep 17 00:00:00 2001 From: SunSinD Date: Fri, 30 Oct 2026 20:01:52 -0400 Subject: [PATCH 4/4] test(usage): check pooled account labels --- .../web/src/components/usage/UsageLimitsPooled.browser.test.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/web/src/components/usage/UsageLimitsPooled.browser.test.tsx b/apps/web/src/components/usage/UsageLimitsPooled.browser.test.tsx index e30080249edab..a60f0a85025d8 100644 --- a/apps/web/src/components/usage/UsageLimitsPooled.browser.test.tsx +++ b/apps/web/src/components/usage/UsageLimitsPooled.browser.test.tsx @@ -151,6 +151,8 @@ it("keeps the account label and reset summary on a one-account row of a multi-ac await expect.element(screen.getByText("monthly-only", { exact: true }).first()).toBeVisible(); await expect.element(screen.getByText("Next account reset in 29d 17h")).toBeVisible(); await expect.element(screen.getByText("Next account reset in 2h 55m")).toBeVisible(); + await expect.element(screen.getByText("average left", { exact: true })).toBeVisible(); + await expect.element(screen.getByText("Across 2 accounts", { exact: true })).toBeVisible(); await screen.getByRole("button", { name: /^later-session: 99% left/ }).click(); await expect.element(screen.getByText("100% left on this account")).toBeVisible(); });