fix(usage): order quota windows chronologically on every provider card (#11241)

Validated on the combined 12-PR batch board: repro-7764 suite 17/17 on this tree (11/17 fail on a clean tip without the fix — proper red-to-green), typecheck:core clean, gates within baseline. Quota windows now order chronologically from the data shape instead of a provider whitelist. Thank you @pacocartones!
This commit is contained in:
Paco Cartones
2026-08-23 19:24:05 +02:00
committed by GitHub
parent 158c6ec233
commit 162ef913da
5 changed files with 215 additions and 3 deletions

View File

@@ -0,0 +1 @@
- **fix(usage):** keep session/weekly/monthly quota windows in chronological order on every provider card. The order is now derived from the quota keys themselves instead of a provider whitelist, so Claude, MiniMax, Z.ai and Command Code stop rendering the two bars in opposite positions across sibling accounts ([#7764](https://github.com/diegosouzapw/OmniRoute/issues/7764))

View File

@@ -19,7 +19,7 @@ import {
} from "../utils";
import QuotaMiniBar from "../QuotaMiniBar";
import { translateUsageOrFallback, type UsageTranslationValues } from "../i18nFallback";
import { hasFixedQuotaOrder } from "../quotaParsing";
import { hasFixedQuotaOrder, hasCanonicalWindowOrder, sortQuotasByWindow } from "../quotaParsing";
const CURRENCY_SYMBOLS: Record<string, string> = {
USD: "$",
@@ -92,9 +92,17 @@ export function sortQuotasByRemaining(quotas: any[]): any[] {
* parseQuotaData() already established. Every other provider still gets the
* remaining-percentage sort. Fixes #6687 (bars re-sorted by % undid the fixed
* session/weekly order).
*
* #7764 residual: providers outside that whitelist which nonetheless report
* rolling time windows (claude, minimax, zai, command-code, ...) are ordered
* chronologically via `hasCanonicalWindowOrder`/`sortQuotasByWindow`, so the
* expanded card agrees with the collapsed card (`topQuotas`) and with sibling
* accounts of the same provider.
*/
export function resolveQuotaDisplayOrder(providerId: string | undefined, quotas: any[]): any[] {
return hasFixedQuotaOrder(providerId) ? [...quotas] : sortQuotasByRemaining(quotas);
if (hasFixedQuotaOrder(providerId)) return [...quotas];
if (hasCanonicalWindowOrder(quotas)) return sortQuotasByWindow(quotas);
return sortQuotasByRemaining(quotas);
}
/** Pure helper — slices the sorted quotas down to the visible window. */

View File

@@ -22,6 +22,74 @@ export function hasFixedQuotaOrder(providerId: string | undefined): boolean {
return id === "codex" || GLM_FAMILY_PROVIDERS.includes(id) || KIMI_CODING_PROVIDERS.includes(id);
}
/**
* Canonical chronological rank of a rolling usage window, derived from the
* quota key itself rather than from a provider list.
*
* Providers name the same two windows in mutually incompatible ways —
* `"session (5h)"` (claude, minimax, kimi), `"5 Hours Quota"` (GLM/zai),
* `"five_hour"` (command-code, qwen-token-plan), `"code_5h"` (kimi-coding),
* plain `"session"` (codex) — so matching on the shape of the key is the only
* thing that generalizes. Returns `null` for anything that is not a recognizable
* time window (per-model buckets, credit balances, token counters), which is
* what keeps this from claiming quotas it has no opinion about.
*/
export function quotaWindowRank(name: unknown): number | null {
const key = String(name ?? "")
.trim()
.toLowerCase();
if (!key) return null;
// Order matters: "mcp_monthly" must not be caught by the weekly probe, and
// "5 Hours Quota" must not be caught by anything before the session probe.
if (/month/.test(key)) return 2;
if (/week|7\s*d\b|_7d\b|seven[_\s-]?day/.test(key)) return 1;
if (/session|hour|\b5\s*h\b|_5h\b/.test(key)) return 0;
return null;
}
/**
* #7764: whether a quota list is a set of rolling time windows whose relative
* order is inherent (session before weekly before monthly) and must therefore
* survive rendering.
*
* This is the structural counterpart to the provider whitelist above. The
* whitelist exists because a few providers need an order the window rank cannot
* express (Codex interleaves GPT-5.3-Codex-Spark windows and a banked-credit
* row between the canonical ones), but it went stale the moment any other
* provider started reporting session+weekly — claude, minimax, zai and
* command-code all do. Deriving the answer from the data means the next such
* provider is covered on arrival.
*
* Requires at least two DISTINCT ranks: with a single window there is no pair
* to keep stable, so the pre-existing worst-status-first sort is left alone.
*/
export function hasCanonicalWindowOrder(quotas: unknown): boolean {
if (!Array.isArray(quotas)) return false;
const ranks = new Set<number>();
for (const quota of quotas) {
if (!quota || (quota as any).isCredits) continue;
const rank = quotaWindowRank((quota as any).name);
if (rank !== null) ranks.add(rank);
}
return ranks.size >= 2;
}
/**
* Stable sort of a quota list into canonical window order. Unrecognized entries
* (credits, token counters, per-model buckets) sink below the windows while
* keeping their relative order, so nothing is lost or shuffled.
*/
export function sortQuotasByWindow<T>(quotas: T[]): T[] {
return [...quotas]
.map((quota, index) => ({ quota, index }))
.sort((a, b) => {
const ra = quotaWindowRank((a.quota as any)?.name) ?? 99;
const rb = quotaWindowRank((b.quota as any)?.name) ?? 99;
return ra - rb || a.index - b.index;
})
.map((entry) => entry.quota);
}
function quotaEntries(data: any): Array<[string, any]> {
return data?.quotas && typeof data.quotas === "object" ? Object.entries(data.quotas) : [];
}

View File

@@ -1,5 +1,5 @@
export { parseQuotaData } from "./quotaParsing";
import { hasFixedQuotaOrder } from "./quotaParsing";
import { hasFixedQuotaOrder, hasCanonicalWindowOrder, sortQuotasByWindow } from "./quotaParsing";
const PROVIDER_PLAN_FALLBACKS = new Set([
"claude code",
@@ -400,6 +400,15 @@ export function topQuotas(quotas: any[], n = 3, providerId?: string): any[] {
return filtered.slice(0, n);
}
// #7764 residual: any OTHER provider reporting rolling time windows (claude,
// minimax, zai, command-code, ...) has an equally inherent session→weekly→
// monthly order. Re-sorting those by remaining % makes two accounts of the
// same provider render the bars in opposite positions. Detected from the
// quota keys, so a new provider needs no list update.
if (hasCanonicalWindowOrder(filtered)) {
return sortQuotasByWindow(filtered).slice(0, n);
}
return [...filtered]
.sort((a, b) => {
const sa = STATUS_ORDER[quotaStatus(a)];

View File

@@ -5,6 +5,7 @@ import {
parseQuotaData,
hasFixedQuotaOrder,
} from "@/app/(dashboard)/dashboard/usage/components/ProviderLimits/quotaParsing";
import { resolveQuotaDisplayOrder } from "@/app/(dashboard)/dashboard/usage/components/ProviderLimits/parts/QuotaCardExpanded";
const quotaName = (quota: { name: string }) => quota.name;
@@ -62,3 +63,128 @@ test("#7764: providers WITHOUT a fixed order still sort worst-status-first (no r
const rendered = topQuotas(quotas, 3, "some-other-provider").map(quotaName);
assert.deepEqual(rendered, ["beta", "gamma", "alpha"]);
});
// ---------------------------------------------------------------------------
// #7764 residual: the original fix only whitelisted codex / GLM family / Kimi
// Coding in `hasFixedQuotaOrder`. Every OTHER provider that reports the same
// session + weekly rolling windows still gets re-sorted by remaining %, so two
// accounts of the SAME provider render the two bars in opposite positions
// depending on which window happens to be more depleted — the exact symptom in
// the report ("the indicators are not located in the same position per card").
//
// Quota names below are the real upstream keys, not simplified ones:
// claude → open-sse/services/usage/claude.ts:107,112 "session (5h)" / "weekly (7d)"
// minimax → open-sse/services/usage/minimax.ts:312,325 "session (5h)" / "weekly (7d)"
// zai → routed to getGlmUsage (open-sse/services/usage.ts:191-194)
// so it emits "5 Hours Quota" / "Weekly Quota" (glm.ts:33-34)
// command-code → open-sse/services/usage/command-code.ts:193,196 "five_hour" / "weekly"
// ---------------------------------------------------------------------------
/** Two refreshes of the same account family: in A the weekly window is the
* depleted one, in B it is the session window. A remaining-% sort flips the
* row order between the two; a canonical window order does not. */
function windowPair(sessionKey: string, weeklyKey: string) {
return {
depletedWeekly: {
quotas: {
[sessionKey]: { used: 9, total: 100, remainingPercentage: 91, resetAt: null },
[weeklyKey]: { used: 97, total: 100, remainingPercentage: 3, resetAt: null },
},
},
depletedSession: {
quotas: {
[sessionKey]: { used: 99, total: 100, remainingPercentage: 1, resetAt: null },
[weeklyKey]: { used: 43, total: 100, remainingPercentage: 57, resetAt: null },
},
},
};
}
const WINDOW_PROVIDERS: Array<{ provider: string; session: string; weekly: string }> = [
{ provider: "claude", session: "session (5h)", weekly: "weekly (7d)" },
{ provider: "minimax", session: "session (5h)", weekly: "weekly (7d)" },
{ provider: "minimax-cn", session: "session (5h)", weekly: "weekly (7d)" },
{ provider: "zai", session: "5 Hours Quota", weekly: "Weekly Quota" },
{ provider: "command-code", session: "five_hour", weekly: "weekly" },
];
for (const { provider, session, weekly } of WINDOW_PROVIDERS) {
test(`#7764 residual: ${provider} keeps session before weekly in the collapsed card across refreshes`, () => {
const { depletedWeekly, depletedSession } = windowPair(session, weekly);
const parsedA = parseQuotaData(provider, depletedWeekly);
const parsedB = parseQuotaData(provider, depletedSession);
// parseQuotaData already yields the canonical upstream order for both.
assert.deepEqual(parsedA.map(quotaName), [session, weekly]);
assert.deepEqual(parsedB.map(quotaName), [session, weekly]);
assert.deepEqual(
topQuotas(parsedA, 3, provider).map(quotaName),
[session, weekly],
`${provider}: collapsed card must not reorder rolling windows by remaining %`
);
assert.deepEqual(
topQuotas(parsedB, 3, provider).map(quotaName),
[session, weekly],
`${provider}: window order must be identical on the sibling account`
);
});
test(`#7764 residual: ${provider} expanded card window order matches the collapsed card`, () => {
const { depletedWeekly, depletedSession } = windowPair(session, weekly);
const parsedA = parseQuotaData(provider, depletedWeekly);
const parsedB = parseQuotaData(provider, depletedSession);
assert.deepEqual(resolveQuotaDisplayOrder(provider, parsedA).map(quotaName), [session, weekly]);
assert.deepEqual(resolveQuotaDisplayOrder(provider, parsedB).map(quotaName), [session, weekly]);
});
}
test("#7764 residual: a card whose quotas are NOT rolling windows still sorts worst-first", () => {
// Antigravity-style per-model buckets: no canonical chronological order
// exists, so the worst-status-first sort remains the useful one.
const parsed = parseQuotaData("antigravity", {
quotas: {
"gemini-3-pro": { used: 10, total: 100, remainingPercentage: 90 },
"gemini-3-flash": { used: 95, total: 100, remainingPercentage: 5 },
},
});
assert.deepEqual(topQuotas(parsed, 3, "antigravity").map(quotaName), [
"gemini-3-flash",
"gemini-3-pro",
]);
});
test("#7764 residual: a single rolling window plus credits is left to the remaining-% sort", () => {
// Only ONE window → no two windows to keep in a stable relative order, so
// nothing is claimed and the pre-existing behaviour is preserved.
const quotas = [
{ name: "credits", used: 0, total: 0, remainingPercentage: 90, isCredits: true },
{ name: "session (5h)", used: 95, total: 100, remainingPercentage: 5 },
];
assert.deepEqual(topQuotas(quotas, 3, "some-credit-provider").map(quotaName), [
"session (5h)",
"credits",
]);
});
test("#7764 residual: Claude per-model weekly windows keep upstream order and credits sink last", () => {
// Anthropic reports extra `weekly <model> (7d)` buckets plus an extra_usage
// credits row. The window sort must be STABLE: same-rank siblings keep the
// order parseQuotaData produced, and the credits row is not promoted.
const parsed = parseQuotaData("claude", {
quotas: {
"session (5h)": { used: 9, total: 100, remainingPercentage: 91 },
"weekly (7d)": { used: 97, total: 100, remainingPercentage: 3 },
"weekly designer (7d)": { used: 50, total: 100, remainingPercentage: 50 },
},
extraUsage: { is_enabled: true, monthly_limit: 100, used_credits: 10, utilization: 10 },
});
assert.deepEqual(topQuotas(parsed, 4, "claude").map(quotaName), [
"session (5h)",
"weekly (7d)",
"weekly designer (7d)",
"extra_usage",
]);
});