From c9f43bab85c62eff583aea9ca53410392e82b6d9 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Date: Fri, 10 Jul 2026 19:38:35 -0300 Subject: [PATCH] fix(providers): stop quota card re-sorting Codex/GLM bars by remaining % (#6687) (#6722) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(providers): stop quota card re-sorting Codex/GLM bars by remaining % (#6687) QuotaCardExpanded.tsx unconditionally re-sorted quotas by remaining percentage via sortQuotasByRemaining(), discarding the deterministic CODEX_QUOTA_ORDER/GLM_QUOTA_ORDER window order quotaParsing.ts's sortCodexOrder()/sortGlmOrder() had already established. A new hasFixedQuotaOrder() + resolveQuotaDisplayOrder() skip the re-sort for providers with a fixed window order (codex, glm family), threading providerId from QuotaCard.tsx through to the display layer. Regression guard: tests/unit/quota-card-expanded-fixed-order-6687.test.ts * chore(6722): re-sync onto release tip; CHANGELOG entry → changelog.d fragment (fragments-first) --- .../fixes/6722-quota-card-fixed-order.md | 1 + .../components/ProviderLimits/QuotaCard.tsx | 1 + .../parts/QuotaCardExpanded.tsx | 20 ++++- .../components/ProviderLimits/quotaParsing.ts | 13 +++- ...ota-card-expanded-fixed-order-6687.test.ts | 74 +++++++++++++++++++ 5 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 changelog.d/fixes/6722-quota-card-fixed-order.md create mode 100644 tests/unit/quota-card-expanded-fixed-order-6687.test.ts diff --git a/changelog.d/fixes/6722-quota-card-fixed-order.md b/changelog.d/fixes/6722-quota-card-fixed-order.md new file mode 100644 index 0000000000..ea2d2f7bbd --- /dev/null +++ b/changelog.d/fixes/6722-quota-card-fixed-order.md @@ -0,0 +1 @@ +- **fix(providers):** the provider quota card's weekly/session bars re-sorted by remaining percentage instead of staying in a fixed, deterministic order ([#6687](https://github.com/diegosouzapw/OmniRoute/issues/6687)) — `QuotaCardExpanded.tsx`'s `sortQuotasByRemaining()` (added in #5977) was applied unconditionally via `useMemo(() => sortQuotasByRemaining(quotas), [quotas])`, undoing the deterministic `CODEX_QUOTA_ORDER`/`GLM_QUOTA_ORDER` window order `quotaParsing.ts`'s `sortCodexOrder()`/`sortGlmOrder()` (added in #6336) already established for Codex and the GLM family — since #6336 never touched `QuotaCardExpanded.tsx`, the two orderings never composed, so e.g. a Codex `session` window with less headroom than `weekly` rendered after it instead of staying first. A new `hasFixedQuotaOrder()` (`quotaParsing.ts`) and `resolveQuotaDisplayOrder()` (`QuotaCardExpanded.tsx`) now skip the remaining-% re-sort for providers with a fixed window order, threading `providerId` from `QuotaCard.tsx` through to the display layer; every other provider still gets the remaining-% sort. Regression guard: `tests/unit/quota-card-expanded-fixed-order-6687.test.ts`. diff --git a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/QuotaCard.tsx b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/QuotaCard.tsx index 96a88afd05..7c7833b7f9 100644 --- a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/QuotaCard.tsx +++ b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/QuotaCard.tsx @@ -110,6 +110,7 @@ export default function QuotaCard({ /> = { USD: "$", @@ -31,6 +32,18 @@ export function sortQuotasByRemaining(quotas: any[]): any[] { ); } +/** + * Pure helper — resolves the display order for a provider's quotas. + * Providers with a deterministic fixed-window order (codex, glm family — see + * quotaParsing.ts's sortCodexOrder()/sortGlmOrder()) keep the order + * parseQuotaData() already established. Every other provider still gets the + * remaining-percentage sort. Fixes #6687 (bars re-sorted by % undid the fixed + * session/weekly order). + */ +export function resolveQuotaDisplayOrder(providerId: string | undefined, quotas: any[]): any[] { + return hasFixedQuotaOrder(providerId) ? [...quotas] : sortQuotasByRemaining(quotas); +} + /** Pure helper — slices the sorted quotas down to the visible window. */ export function getVisibleQuotas(sortedQuotas: any[], expanded: boolean): any[] { return expanded ? sortedQuotas : sortedQuotas.slice(0, DEFAULT_VISIBLE_ROWS); @@ -38,6 +51,7 @@ export function getVisibleQuotas(sortedQuotas: any[], expanded: boolean): any[] interface Props { quotas: any[]; + providerId?: string; loading: boolean; error: string | null; message?: string | null; @@ -155,6 +169,7 @@ function QuotaDetailRow({ q }: { q: any }) { export default function QuotaCardExpanded({ quotas, + providerId, loading, error, message, @@ -174,7 +189,10 @@ export default function QuotaCardExpanded({ translateUsageOrFallback(t, key, fallback, values); const [expanded, setExpanded] = useState(false); - const sortedQuotas = useMemo(() => sortQuotasByRemaining(quotas), [quotas]); + const sortedQuotas = useMemo( + () => resolveQuotaDisplayOrder(providerId, quotas), + [quotas, providerId] + ); const visibleQuotas = useMemo( () => getVisibleQuotas(sortedQuotas, expanded), [sortedQuotas, expanded] diff --git a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/quotaParsing.ts b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/quotaParsing.ts index aa210469a1..d1d29eeb1a 100644 --- a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/quotaParsing.ts +++ b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/quotaParsing.ts @@ -9,6 +9,17 @@ const CODEX_QUOTA_ORDER: Record = { gpt_5_3_codex_spark_weekly: 3, banked_reset_credits: 4, }; +const GLM_FAMILY_PROVIDERS = ["glm", "glm-cn", "glmt", "opencode-go"]; + +/** + * Providers whose quotas already get a deterministic fixed-window order from + * sortGlmOrder()/sortCodexOrder() below. Display layers (e.g. QuotaCardExpanded) + * must not re-sort these by remaining percentage, or they undo this order (#6687). + */ +export function hasFixedQuotaOrder(providerId: string | undefined): boolean { + const id = String(providerId || "").toLowerCase(); + return id === "codex" || GLM_FAMILY_PROVIDERS.includes(id); +} function quotaEntries(data: any): Array<[string, any]> { return data?.quotas && typeof data.quotas === "object" ? Object.entries(data.quotas) : []; @@ -190,7 +201,7 @@ function sortProviderModelOrder(provider: string, quotas: any[]) { } function sortGlmOrder(providerId: string, quotas: any[]) { - if (!["glm", "glm-cn", "glmt", "opencode-go"].includes(providerId)) return; + if (!GLM_FAMILY_PROVIDERS.includes(providerId)) return; quotas.sort((a, b) => (GLM_QUOTA_ORDER[a.name] ?? 99) - (GLM_QUOTA_ORDER[b.name] ?? 99)); } diff --git a/tests/unit/quota-card-expanded-fixed-order-6687.test.ts b/tests/unit/quota-card-expanded-fixed-order-6687.test.ts new file mode 100644 index 0000000000..6d7f7aec98 --- /dev/null +++ b/tests/unit/quota-card-expanded-fixed-order-6687.test.ts @@ -0,0 +1,74 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { parseQuotaData } from "@/app/(dashboard)/dashboard/usage/components/ProviderLimits/quotaParsing"; +import { resolveQuotaDisplayOrder } from "@/app/(dashboard)/dashboard/usage/components/ProviderLimits/parts/QuotaCardExpanded"; + +interface TestQuota { + name: string; + [key: string]: unknown; +} + +// Regression for #6687: quotaParsing.ts applies a deterministic CODEX_QUOTA_ORDER +// (session:0, weekly:1, ...) via sortCodexOrder(). QuotaCardExpanded.tsx used to +// unconditionally re-sort the already-ordered array by remaining percentage via +// sortQuotasByRemaining(), discarding the fixed window order. resolveQuotaDisplayOrder() +// is the fix: it keeps parseQuotaData()'s order for providers with a fixed window +// order (codex, glm family) and only applies the remaining-% sort elsewhere. + +test("#6687 codex: display order stays session-then-weekly regardless of remaining %", () => { + const rawCodexData = { + quotas: { + session: { used: 90, total: 100, remainingPercentage: 10, resetAt: null }, + weekly: { used: 10, total: 100, remainingPercentage: 90, resetAt: null }, + }, + }; + + const parsed = parseQuotaData("codex", rawCodexData); + // Sanity check: quotaParsing.ts's sortCodexOrder should order session before weekly. + assert.deepEqual( + parsed.map((q: TestQuota) => q.name), + ["session", "weekly"] + ); + + // The display layer must NOT undo the fixed order, even though weekly + // (90% remaining) would sort before session (10% remaining) by percentage. + const displayed = resolveQuotaDisplayOrder("codex", parsed); + assert.deepEqual( + displayed.map((q: TestQuota) => q.name), + ["session", "weekly"] + ); +}); + +test("#6687 glm family: display order stays session-then-weekly regardless of remaining %", () => { + const rawGlmData = { + quotas: { + session: { used: 95, total: 100, remainingPercentage: 5, resetAt: null }, + weekly: { used: 5, total: 100, remainingPercentage: 95, resetAt: null }, + }, + }; + + const parsed = parseQuotaData("glm", rawGlmData); + assert.deepEqual( + parsed.map((q: TestQuota) => q.name), + ["session", "weekly"] + ); + + const displayed = resolveQuotaDisplayOrder("glm", parsed); + assert.deepEqual( + displayed.map((q: TestQuota) => q.name), + ["session", "weekly"] + ); +}); + +test("#6687 non-fixed-order providers still sort by remaining percentage descending", () => { + const quotas = [ + { name: "low", remainingPercentage: 10 }, + { name: "high", remainingPercentage: 90 }, + ]; + + const displayed = resolveQuotaDisplayOrder("openai", quotas); + assert.deepEqual( + displayed.map((q: TestQuota) => q.name), + ["high", "low"] + ); +});