fix(dashboard): make quota card ordering deterministic (#9329)

Validated in local merge-train (devbox-vm-06-dev002) @ combined-tip (FAST gates — only pre-existing audit.test.ts flake).
This commit is contained in:
Xiangzhe
2026-08-06 09:41:49 +08:00
committed by GitHub
parent 062555ce98
commit 88a2fd26c7
4 changed files with 319 additions and 47 deletions

View File

@@ -2,6 +2,9 @@
import type { ReactNode } from "react";
import QuotaCard from "./QuotaCard";
import { PROVIDER_ORDER } from "./constants";
import { compareProviderGroups } from "./utils";
import { compareTr } from "@/shared/utils/turkishText";
interface Props {
connections: any[];
@@ -47,50 +50,63 @@ export default function QuotaCardGrid({
}: Props) {
if (connections.length === 0) return null;
// Group connections by provider, preserving the order from sortedConnections.
// Group connections by provider (preserving in-group order), then order the
// groups deterministically: PROVIDER_ORDER rank → label (locale-aware) →
// key. Without this the group order followed first-appearance in the
// status/reset-sorted list, so groups shuffled whenever quota refreshed.
const groups = new Map<string, typeof connections>();
for (const conn of connections) {
const list = groups.get(conn.provider) ?? [];
list.push(conn);
groups.set(conn.provider, list);
}
const orderedProviders = [...groups.keys()].sort((a, b) =>
compareProviderGroups(a, b, {
providerOrder: PROVIDER_ORDER,
providerLabels,
compare: compareTr,
})
);
return (
<div className="columns-1 2xl:columns-2 gap-6 [column-fill:_balance]">
{[...groups.entries()].map(([provider, conns]) => (
<div key={provider} className="flex flex-col gap-3 break-inside-avoid mb-6">
<h3 className="text-sm font-semibold text-text-main flex items-center gap-2">
{providerLabels[provider] || provider}
<span className="text-xs font-normal text-text-muted">
({conns.length} account{conns.length !== 1 ? "s" : ""})
</span>
</h3>
<div className="grid grid-cols-[repeat(auto-fit,minmax(min(100%,280px),1fr))] gap-3">
{conns.map((conn) => (
<QuotaCard
key={conn.id}
connection={conn}
quota={quotaData[conn.id]}
loading={!!loading[conn.id]}
error={errors[conn.id] || null}
refreshedAt={lastRefreshedAt[conn.id]}
emailsVisible={emailsVisible}
providerLabel={providerLabels[conn.provider] || conn.provider}
onRefresh={() => onRefresh(conn.id, conn.provider)}
onOpenCutoff={() => onOpenCutoff(conn)}
onOpenResetCredits={() => onOpenResetCredits?.(conn.id, conn.provider)}
onToggleActive={(nextActive) => onToggleActive(conn.id, nextActive)}
togglingActive={togglingActiveId === conn.id}
redeemingResetCredit={redeemingResetCreditId === conn.id}
loadingResetCredits={loadingResetCreditsId === conn.id}
quotaVisibility={quotaVisibility}
onHideQuota={onHideQuota ? (q) => onHideQuota(conn.provider, q) : undefined}
onShowQuota={onShowQuota ? (q) => onShowQuota(conn.provider, q) : undefined}
/>
))}
{orderedProviders.map((provider) => {
const conns = groups.get(provider)!;
return (
<div key={provider} className="flex flex-col gap-3 break-inside-avoid mb-6">
<h3 className="text-sm font-semibold text-text-main flex items-center gap-2">
{providerLabels[provider] || provider}
<span className="text-xs font-normal text-text-muted">
({conns.length} account{conns.length !== 1 ? "s" : ""})
</span>
</h3>
<div className="grid grid-cols-[repeat(auto-fit,minmax(min(100%,280px),1fr))] gap-3">
{conns.map((conn) => (
<QuotaCard
key={conn.id}
connection={conn}
quota={quotaData[conn.id]}
loading={!!loading[conn.id]}
error={errors[conn.id] || null}
refreshedAt={lastRefreshedAt[conn.id]}
emailsVisible={emailsVisible}
providerLabel={providerLabels[conn.provider] || conn.provider}
onRefresh={() => onRefresh(conn.id, conn.provider)}
onOpenCutoff={() => onOpenCutoff(conn)}
onOpenResetCredits={() => onOpenResetCredits?.(conn.id, conn.provider)}
onToggleActive={(nextActive) => onToggleActive(conn.id, nextActive)}
togglingActive={togglingActiveId === conn.id}
redeemingResetCredit={redeemingResetCreditId === conn.id}
loadingResetCredits={loadingResetCreditsId === conn.id}
quotaVisibility={quotaVisibility}
onHideQuota={onHideQuota ? (q) => onHideQuota(conn.provider, q) : undefined}
onShowQuota={onShowQuota ? (q) => onShowQuota(conn.provider, q) : undefined}
/>
))}
</div>
</div>
</div>
))}
);
})}
</div>
);
}

View File

@@ -12,6 +12,7 @@ import {
calculatePercentage,
matchesProviderFilter,
buildProviderOptions,
compareQuotaConnections,
} from "./utils";
import Card from "@/shared/components/Card";
import { CardSkeleton } from "@/shared/components/Loading";
@@ -529,8 +530,12 @@ export default function ProviderLimits({
);
const sortedConnections = useMemo(() => {
return [...filteredConnections].sort(
(a, b) => (PROVIDER_ORDER[a.provider] || 99) - (PROVIDER_ORDER[b.provider] || 99)
return [...filteredConnections].sort((a, b) =>
compareQuotaConnections(a, b, {
providerOrder: PROVIDER_ORDER,
providerLabels: PROVIDER_LABEL,
compare: compareTr,
})
);
}, [filteredConnections]);
const visibleQuotaData = useVisibleQuotaData(sortedConnections, quotaData);
@@ -650,9 +655,10 @@ export default function ProviderLimits({
return true;
});
// Inside each group we still want "critical first, then alert, then ok,
// then empty; tiebreak by soonest reset". Provider order between groups
// is enforced separately via PROVIDER_ORDER.
// Provider rank stays the outer sort key so each group keeps its fixed
// slot (mirrors dashboard/providers determinism); "critical first, then
// alert, then ok, then empty; tiebreak by soonest reset" only orders
// accounts inside their own provider group.
const statusRank: Record<StatusKey, number> = {
critical: 0,
alert: 1,
@@ -660,14 +666,21 @@ export default function ProviderLimits({
empty: 3,
all: 4,
};
return [...filtered].sort((a, b) => {
const sa = statusRank[statusByConnection[a.id] || "empty"];
const sb = statusRank[statusByConnection[b.id] || "empty"];
if (sa !== sb) return sa - sb;
const ra = getSoonestResetMs(visibleQuotaData[a.id]?.quotas);
const rb = getSoonestResetMs(visibleQuotaData[b.id]?.quotas);
return ra - rb;
});
return [...filtered].sort((a, b) =>
compareQuotaConnections(a, b, {
providerOrder: PROVIDER_ORDER,
providerLabels: PROVIDER_LABEL,
compare: compareTr,
accountCompare: (x, y) => {
const sx = statusRank[statusByConnection[x.id] || "empty"];
const sy = statusRank[statusByConnection[y.id] || "empty"];
if (sx !== sy) return sx - sy;
const rx = getSoonestResetMs(visibleQuotaData[x.id]?.quotas);
const ry = getSoonestResetMs(visibleQuotaData[y.id]?.quotas);
return rx - ry;
},
})
);
}, [
sortedConnections,
tierByConnection,

View File

@@ -620,3 +620,110 @@ export function buildProviderOptions(
}
return Array.from(seen).sort(compare);
}
// --- Deterministic quota-card ordering -------------------------------------
// Mirrors the dashboard/providers rule (`providerPageUtils.ts::
// sortProviderEntriesByName`): every level of ordering must end in a stable,
// data-independent tiebreak so cards never re-flow between refreshes.
//
// Before this, `visibleConnections` globally sorted ALL connections by
// status then soonest reset, and QuotaCardGrid grouped by first-appearance —
// so each provider group's position was decided by whichever of its accounts
// happened to sort first (status/reset change every refresh → groups
// shuffled). Provider rank is now a sort key again, so a group's position is
// fixed by PROVIDER_ORDER and account status/reset only orders accounts
// inside their own group.
export interface QuotaOrderConnection {
id?: unknown;
provider?: unknown;
name?: unknown;
email?: unknown;
displayName?: unknown;
}
/** Label/name key: providers-page `getProviderSortLabel` — case-insensitive display name. */
function quotaConnLabel(conn: QuotaOrderConnection): string {
const name = typeof conn.name === "string" ? conn.name : "";
const provider = typeof conn.provider === "string" ? conn.provider : "";
return (name || provider).toLowerCase();
}
/** Technical tiebreak key: providers-page `providerId.localeCompare(...)` — ASCII on purpose. */
function quotaConnTiebreak(conn: QuotaOrderConnection): string {
const email = typeof conn.email === "string" ? conn.email : "";
const id = typeof conn.id === "string" ? conn.id : String(conn.id ?? "");
return email || id;
}
function providerRank(provider: unknown, providerOrder: Record<string, number>): number {
const key = typeof provider === "string" ? provider : "";
return providerOrder[key] ?? 99;
}
/**
* Order connections for the quota card grid. Levels (first non-zero wins):
* 1. `PROVIDER_ORDER` rank — keeps each provider group glued to its fixed slot.
* 2. Provider label (locale-aware, case-insensitive) — orders unranked providers.
* 3. Provider key ASCII — deterministic tiebreak between aliased/equal labels.
* 4. `accountCompare` (optional) — in-group intent (critical-first, soonest reset).
* 5. Account label, then email/id ASCII — so equal-status accounts never shuffle.
*/
export function compareQuotaConnections<T extends QuotaOrderConnection>(
a: T,
b: T,
opts: {
providerOrder: Record<string, number>;
providerLabels?: Record<string, string>;
accountCompare?: (a: T, b: T) => number;
compare?: (a: string, b: string) => number;
}
): number {
const cmp = opts.compare ?? ((x: string, y: string) => x.localeCompare(y));
const labels = opts.providerLabels ?? {};
const ra = providerRank(a.provider, opts.providerOrder);
const rb = providerRank(b.provider, opts.providerOrder);
if (ra !== rb) return ra - rb;
const pa = typeof a.provider === "string" ? a.provider : "";
const pb = typeof b.provider === "string" ? b.provider : "";
const providerLabelCmp = cmp(labels[pa] ?? pa, labels[pb] ?? pb);
if (providerLabelCmp !== 0) return providerLabelCmp;
if (pa !== pb) return pa < pb ? -1 : 1;
if (opts.accountCompare) {
const acc = opts.accountCompare(a, b);
if (acc !== 0) return acc;
}
const accountLabelCmp = cmp(quotaConnLabel(a), quotaConnLabel(b));
if (accountLabelCmp !== 0) return accountLabelCmp;
const ta = quotaConnTiebreak(a);
const tb = quotaConnTiebreak(b);
return ta < tb ? -1 : ta > tb ? 1 : 0;
}
/**
* Order provider group keys for rendering. Same provider-level rule as
* `compareQuotaConnections` (rank → label → key), used by QuotaCardGrid to
* place group headers deterministically.
*/
export function compareProviderGroups(
a: string,
b: string,
opts: {
providerOrder: Record<string, number>;
providerLabels?: Record<string, string>;
compare?: (a: string, b: string) => number;
}
): number {
const cmp = opts.compare ?? ((x: string, y: string) => x.localeCompare(y));
const labels = opts.providerLabels ?? {};
const ra = providerRank(a, opts.providerOrder);
const rb = providerRank(b, opts.providerOrder);
if (ra !== rb) return ra - rb;
const labelCmp = cmp(labels[a] ?? a, labels[b] ?? b);
if (labelCmp !== 0) return labelCmp;
return a < b ? -1 : a > b ? 1 : 0;
}

View File

@@ -0,0 +1,136 @@
import test from "node:test";
import assert from "node:assert/strict";
// Guards the deterministic quota-card ordering fix. Before this, the quota
// dashboard globally sorted ALL connections by status then soonest reset and
// grouped by first-appearance — so each provider group's position was decided
// by whichever account happened to sort first, and groups shuffled on every
// refresh. These tests pin the providers-page-style rule: provider rank →
// provider label → provider key fixes each group's slot, and status/reset only
// orders accounts inside their own group with a deterministic name/email/id
// tiebreak.
const utils =
await import("../../src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.tsx");
const constants =
await import("../../src/app/(dashboard)/dashboard/usage/components/ProviderLimits/constants.ts");
const { compareQuotaConnections, compareProviderGroups } = utils;
const { PROVIDER_ORDER, PROVIDER_LABEL } = constants;
const OPTS = { providerOrder: PROVIDER_ORDER, providerLabels: PROVIDER_LABEL };
function ids(conns) {
return conns.map((c) => c.id);
}
test("quota-order: provider rank dominates account status (group slot is fixed)", () => {
// antigravity (rank 1) ok account vs codex (rank 4) critical account.
// Pre-fix, the critical codex account floated above antigravity and dragged
// the codex group to the top. Now antigravity's rank wins.
const okAntigravity = { id: "a", provider: "antigravity", name: "Ag" };
const criticalCodex = { id: "c", provider: "codex", name: "Cx" };
// accountCompare that would put codex first if provider rank were ignored:
const criticalFirst = (x, y) =>
(x.provider === "codex" ? 0 : 1) - (y.provider === "codex" ? 0 : 1);
const sorted = [criticalCodex, okAntigravity].sort((a, b) =>
compareQuotaConnections(a, b, { ...OPTS, accountCompare: criticalFirst })
);
assert.deepEqual(ids(sorted), ["a", "c"], "provider rank outranks account status");
});
test("quota-order: accountCompare orders accounts within the same provider group", () => {
const a = { id: "a", provider: "codex", name: "Acct A" };
const b = { id: "b", provider: "codex", name: "Acct B" };
// b is "critical" (rank 0), a is "ok" (rank 2) via the injected comparator.
const statusOf = { a: 2, b: 0 };
const criticalFirst = (x, y) => statusOf[x.id] - statusOf[y.id];
const sorted = [a, b].sort((x, y) =>
compareQuotaConnections(x, y, { ...OPTS, accountCompare: criticalFirst })
);
assert.deepEqual(ids(sorted), ["b", "a"], "critical account first inside its group");
});
test("quota-order: deterministic name tiebreak makes equal-status accounts stable", () => {
const make = () => [
{ id: "1", provider: "codex", name: "Zeta" },
{ id: "2", provider: "codex", name: "Alpha" },
{ id: "3", provider: "codex", name: "Mid" },
];
const noStatus = () => 0;
const first = [...make()].sort((x, y) =>
compareQuotaConnections(x, y, { ...OPTS, accountCompare: noStatus })
);
const second = [...make()]
.reverse()
.sort((x, y) => compareQuotaConnections(x, y, { ...OPTS, accountCompare: noStatus }));
assert.deepEqual(ids(first), ["2", "3", "1"], "name alphabetical");
assert.deepEqual(ids(first), ids(second), "input order does not change output");
});
test("quota-order: unranked providers fall back to label alphabetical, after ranked", () => {
// github is ranked 3; the two fake providers are unranked (→ 99). Two
// unranked providers order among themselves by label (here label falls back
// to the provider key since neither is in PROVIDER_LABEL).
const conns = [
{ id: "1", provider: "zzz-unranked", name: "X" },
{ id: "2", provider: "aaa-unranked", name: "Y" },
{ id: "3", provider: "github", name: "Z" },
];
const sorted = [...conns].sort((a, b) => compareQuotaConnections(a, b, OPTS));
assert.deepEqual(
ids(sorted),
["3", "2", "1"],
"ranked first; unranked by label (labels fall back to provider key here)"
);
});
test("quota-order: group keys order by rank then label then key", () => {
const keys = ["codex", "antigravity", "some-new-provider", "github"];
const sorted = [...keys].sort((a, b) => compareProviderGroups(a, b, OPTS));
assert.deepEqual(
sorted,
["antigravity", "github", "codex", "some-new-provider"],
"PROVIDER_ORDER rank 1,3,4 then unranked provider last"
);
});
test("quota-order: aliased providers with equal rank break tie by provider key", () => {
// xai-oauth and xao are both rank 16 in PROVIDER_ORDER. Deterministic key
// tiebreak (ASCII) must decide, not insertion order.
const a = ["xai-oauth", "xao"].sort((x, y) => compareProviderGroups(x, y, OPTS));
const b = ["xao", "xai-oauth"].sort((x, y) => compareProviderGroups(x, y, OPTS));
assert.deepEqual(a, b, "stable regardless of input order");
assert.deepEqual(a[0], "xai-oauth", "ASCII: 'xai-oauth' < 'xao'");
});
test("quota-order: full grid end-to-end keeps group slots fixed while ordering accounts inside", () => {
// Simulate the real pipeline: flat list across providers, mixed statuses.
const conns = [
{ id: "ag1", provider: "antigravity", name: "Ag One" },
{ id: "cx-ok", provider: "codex", name: "Codex Ok" },
{ id: "gh1", provider: "github", name: "Gh One" },
{ id: "cx-crit", provider: "codex", name: "Codex Crit" },
{ id: "ag2", provider: "antigravity", name: "Ag Two" },
];
// codex-crit is critical, everything else ok.
const statusOf = { "cx-crit": 0 };
const criticalFirst = (x, y) => (statusOf[x.id] ?? 2) - (statusOf[y.id] ?? 2);
const sorted = [...conns].sort((a, b) =>
compareQuotaConnections(a, b, { ...OPTS, accountCompare: criticalFirst })
);
// Provider group slots follow rank: antigravity(1) → github(3) → codex(4).
// The critical codex account does NOT pull the codex group to the front.
assert.deepEqual(
sorted.map((c) => c.provider),
["antigravity", "antigravity", "github", "codex", "codex"]
);
// Inside codex, critical first.
assert.deepEqual(ids(sorted).slice(3), ["cx-crit", "cx-ok"]);
// Inside antigravity, name alphabetical.
assert.deepEqual(ids(sorted).slice(0, 2), ["ag1", "ag2"]);
});