From 82ea09d9cafc38e1bc69ca94c6e6e47e3176b03f Mon Sep 17 00:00:00 2001 From: Terry Tan Date: Tue, 25 Aug 2026 13:14:23 -0700 Subject: [PATCH 1/2] fix(codex): show five-hour quota on account cards --- gui/src/codex-quota-utils.ts | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/gui/src/codex-quota-utils.ts b/gui/src/codex-quota-utils.ts index 2c8b323ca21..6bf60518c93 100644 --- a/gui/src/codex-quota-utils.ts +++ b/gui/src/codex-quota-utils.ts @@ -1,9 +1,12 @@ export interface AccountQuota { weeklyPercent?: number; fiveHourPercent?: number; + /** Codex account API aliases for the same five-hour window. */ + shortPercent?: number; monthlyPercent?: number; weeklyResetAt?: number; fiveHourResetAt?: number; + shortResetAt?: number; monthlyResetAt?: number; customWindows?: { label: string; percent: number; resetAt?: number }[]; resetCredits?: number; @@ -16,11 +19,19 @@ export function isThirtyDayOnlyPlan(plan: string | null | undefined): boolean { } export function normalizeQuotaForPlan(quota: AccountQuota | null, plan: string | null | undefined): AccountQuota | null { - if (!quota || !isThirtyDayOnlyPlan(plan)) return quota; + if (!quota) return null; + const normalized = quota.shortPercent === undefined && quota.shortResetAt === undefined + ? quota + : { + ...quota, + fiveHourPercent: quota.fiveHourPercent ?? quota.shortPercent, + fiveHourResetAt: quota.fiveHourResetAt ?? quota.shortResetAt, + }; + if (!isThirtyDayOnlyPlan(plan)) return normalized; return { - ...(quota.monthlyPercent !== undefined ? { monthlyPercent: quota.monthlyPercent } : {}), - ...(quota.monthlyResetAt !== undefined ? { monthlyResetAt: quota.monthlyResetAt } : {}), - ...(quota.resetCredits !== undefined ? { resetCredits: quota.resetCredits } : {}), - updatedAt: quota.updatedAt, + ...(normalized.monthlyPercent !== undefined ? { monthlyPercent: normalized.monthlyPercent } : {}), + ...(normalized.monthlyResetAt !== undefined ? { monthlyResetAt: normalized.monthlyResetAt } : {}), + ...(normalized.resetCredits !== undefined ? { resetCredits: normalized.resetCredits } : {}), + updatedAt: normalized.updatedAt, }; } From 8b41b0e331f382a37c19d2f988eddc57c73e966a Mon Sep 17 00:00:00 2001 From: bitkyc08-arch Date: Wed, 26 Aug 2026 05:37:53 +0900 Subject: [PATCH 2/2] test(gui): pin the short-window alias for the five-hour quota row (#2616) The fix ships without a regression, and this is a rendering change: the review guidance asks for one near the subsystem. Pins all four cases - the alias renders, the canonical value wins when both are present (an alias must not overwrite a probe reading), a quota with neither alias is still returned by identity, and a 30-day plan still strips non-monthly windows. Falsified: collapsing the normalization to a passthrough reddens exactly this test and nothing else. --- tests/rate-limit-reset-credits.test.ts | 37 ++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/rate-limit-reset-credits.test.ts b/tests/rate-limit-reset-credits.test.ts index 122d78c01a7..b775700c8db 100644 --- a/tests/rate-limit-reset-credits.test.ts +++ b/tests/rate-limit-reset-credits.test.ts @@ -239,6 +239,43 @@ describe("rate-limit reset credits", () => { expect(normalizeQuotaForPlan(quota, "pro")).toBe(quota); }); + /** + * The five-hour window reaches the GUI under TWO names (#2616). + * + * `QuotaBars` reads `fiveHour*`, which is what the per-account provider probe reports. The + * Codex pool declares the same window as `short*` (auth-api.ts, and `account-api.ts` says + * outright that "the two surfaces name the same idea differently and both reach this DTO"). + * A Codex account card therefore had a five-hour quota upstream and rendered no five-hour + * row at all. + * + * The canonical name wins when both are present: `short*` is an alias to fall back to, not + * an override, or a pool snapshot could quietly replace a probe reading. + */ + it("renders the Codex pool's short window as the five-hour window (#2616)", async () => { + const { normalizeQuotaForPlan } = await import("../gui/src/codex-quota-utils"); + + expect(normalizeQuotaForPlan({ shortPercent: 71, shortResetAt: 555, updatedAt: 1 }, "pro")) + .toMatchObject({ fiveHourPercent: 71, fiveHourResetAt: 555 }); + + // Canonical values win; the alias does not overwrite a probe reading. + expect(normalizeQuotaForPlan( + { fiveHourPercent: 10, fiveHourResetAt: 111, shortPercent: 71, shortResetAt: 555, updatedAt: 1 }, + "pro", + )).toMatchObject({ fiveHourPercent: 10, fiveHourResetAt: 111 }); + + // A quota carrying neither alias is still returned by identity, so the common path adds + // no allocation and no behavior change. + const untouched = { weeklyPercent: 5, updatedAt: 2 }; + expect(normalizeQuotaForPlan(untouched, "pro")).toBe(untouched); + + // A 30-day plan still strips non-monthly windows, alias or not — #1791's burst-window + // carve-out lives on the server DTO, not in this GUI normalizer. + expect(normalizeQuotaForPlan( + { shortPercent: 71, shortResetAt: 555, monthlyPercent: 12, updatedAt: 3 }, + "go", + )).toEqual({ monthlyPercent: 12, updatedAt: 3 }); + }); + it("does not exclude team or workspace plans from ticket badges", async () => { const [pool, helpers] = await Promise.all([ Bun.file("gui/src/components/CodexAccountPool.tsx").text(),