fix(ui): surface Codex Spark windows in dashboard quota views

Mirror the server-side CodexQuotaWindow metadata extension on the UI
side: api-client.ts gains category/cadence/featureLabel; utils.ts
breakdown returns additionalWindows so Spark quota does not pollute
core 5h/weekly badges; quota-tooltip-content renders Spark inline with
prettified labels (GPT-5.3-Codex-Spark -> Codex Spark). Adds breakdown
unit tests and i18n strings for English/Chinese/Vietnamese/Japanese.
This commit is contained in:
yousiki
2026-04-27 15:50:31 +09:00
parent 942a8ce12d
commit 725d95b0d2
5 changed files with 475 additions and 28 deletions
@@ -259,11 +259,17 @@ export function QuotaTooltipContent({ quota, resetTime }: QuotaTooltipContentPro
// Codex provider tooltip
if (isCodexQuotaResult(quota)) {
const { fiveHourWindow, weeklyWindow, codeReviewWindows, unknownWindows } =
const { fiveHourWindow, weeklyWindow, codeReviewWindows, additionalWindows, unknownWindows } =
getCodexQuotaBreakdown(quota.windows);
const fiveHourResetAt = quota.coreUsage?.fiveHour?.resetAt ?? fiveHourWindow?.resetAt ?? null;
const weeklyResetAt = quota.coreUsage?.weekly?.resetAt ?? weeklyWindow?.resetAt ?? null;
const orderedWindows = [fiveHourWindow, weeklyWindow, ...codeReviewWindows, ...unknownWindows]
const orderedWindows = [
fiveHourWindow,
weeklyWindow,
...codeReviewWindows,
...additionalWindows,
...unknownWindows,
]
.filter((w): w is NonNullable<typeof w> => !!w)
.filter(
(w, index, arr) =>
+17 -1
View File
@@ -645,7 +645,11 @@ export interface QuotaResult {
/** Codex rate limit window */
export interface CodexQuotaWindow {
/** Window label: "Primary", "Secondary", "Code Review (Primary)", "Code Review (Secondary)" */
/**
* Window label, e.g.: "Primary", "Secondary",
* "Code Review (Primary)", "Code Review (Secondary)",
* or "GPT-5.3-Codex-Spark (Primary)" for additional rate limits.
*/
label: string;
/** Percentage used (0-100) */
usedPercent: number;
@@ -655,6 +659,18 @@ export interface CodexQuotaWindow {
resetAfterSeconds: number | null;
/** ISO timestamp when quota resets, null if unknown */
resetAt: string | null;
/**
* Window category indicating the bucket this window belongs to.
* Optional for back-compat with cached data emitted before this field existed.
* - 'usage' -> standard rate_limit usage windows
* - 'code-review' -> code_review_rate_limit windows
* - 'additional' -> additional_rate_limits[] windows (e.g. GPT-5.3 Codex Spark)
*/
category?: 'usage' | 'code-review' | 'additional';
/** Cadence of the window: '5h' = primary, 'weekly' = secondary. Optional for legacy data. */
cadence?: '5h' | 'weekly';
/** Raw upstream feature label (e.g. 'GPT-5.3-Codex-Spark', 'Code Review'); absent for plain usage windows. */
featureLabel?: string;
}
/** Core Codex usage window (5h/weekly) extracted from raw windows */
+12
View File
@@ -1626,6 +1626,9 @@ const resources = {
codeReview5h: 'Code review (5h)',
codeReviewWeekly: 'Code review (weekly)',
codeReview: 'Code review',
codexAdditional5h: '{{name}} (5h)',
codexAdditionalWeekly: '{{name}} (weekly)',
codexAdditional: '{{name}}',
},
sponsorButton: {
title: 'Sponsor this project on GitHub',
@@ -4098,6 +4101,9 @@ const resources = {
codeReview5h: '代码审查(5 小时)',
codeReviewWeekly: '代码审查(每周)',
codeReview: '代码审查',
codexAdditional5h: '{{name}}5 小时)',
codexAdditionalWeekly: '{{name}}(每周)',
codexAdditional: '{{name}}',
},
sponsorButton: {
title: '在 GitHub 上赞助此项目',
@@ -6630,6 +6636,9 @@ const resources = {
codeReview5h: 'Đánh giá mã (5h)',
codeReviewWeekly: 'Đánh giá mã (hàng tuần)',
codeReview: 'Đánh giá mã',
codexAdditional5h: '{{name}} (5h)',
codexAdditionalWeekly: '{{name}} (hàng tuần)',
codexAdditional: '{{name}}',
},
sponsorButton: {
title: 'Tài trợ dự án này trên GitHub',
@@ -9766,6 +9775,9 @@ const resources = {
codeReview5h: 'コードレビュー(5時間)',
codeReviewWeekly: 'コードレビュー(週次)',
codeReview: 'コードレビュー',
codexAdditional5h: '{{name}}5時間)',
codexAdditionalWeekly: '{{name}}(週次)',
codexAdditional: '{{name}}',
},
rawEditorSection: {
rawConfig: 'Raw 設定',
+122 -25
View File
@@ -332,12 +332,33 @@ export type CodexWindowKind =
| 'code-review-5h'
| 'code-review-weekly'
| 'code-review'
| 'additional-5h'
| 'additional-weekly'
| 'unknown';
/**
* Map raw Codex API window labels into semantic buckets.
* Map a Codex window into a semantic bucket. Prefers explicit category metadata
* (post-2026-04 windows) and falls back to label sniffing for legacy cached data.
*/
export function getCodexWindowKind(label: string): CodexWindowKind {
export function getCodexWindowKind(labelOrWindow: string | CodexQuotaWindow): CodexWindowKind {
if (typeof labelOrWindow === 'object' && labelOrWindow !== null) {
const w = labelOrWindow;
if (w.category === 'additional') {
return w.cadence === 'weekly' ? 'additional-weekly' : 'additional-5h';
}
if (w.category === 'code-review') {
return w.cadence === 'weekly' ? 'code-review-weekly' : 'code-review-5h';
}
if (w.category === 'usage') {
return w.cadence === 'weekly' ? 'usage-weekly' : 'usage-5h';
}
// No category metadata -> fall through to label sniffing.
return getCodexWindowKindFromLabel(w.label);
}
return getCodexWindowKindFromLabel(labelOrWindow);
}
function getCodexWindowKindFromLabel(label: string): CodexWindowKind {
const lower = (label || '').toLowerCase();
const isCodeReview = lower.includes('code review') || lower.includes('code_review');
const isPrimary = lower.includes('primary');
@@ -354,7 +375,29 @@ export function getCodexWindowKind(label: string): CodexWindowKind {
return 'unknown';
}
type CodexWindowSummary = Pick<CodexQuotaWindow, 'label' | 'resetAfterSeconds'>;
/**
* Strip a leading "GPT-X.Y-Codex-" prefix from a feature label and turn the
* remainder into a "Codex <Feature>" display name. Other labels pass through unchanged.
*
* Examples:
* "GPT-5.3-Codex-Spark" -> "Codex Spark"
* "OmniReview" -> "OmniReview"
* "" -> ""
*/
export function prettifyCodexFeatureLabel(featureLabel: string): string {
const trimmed = (featureLabel || '').trim();
if (!trimmed) return '';
const stripped = trimmed.replace(/^GPT-[\d.]+-Codex-/i, '');
if (stripped !== trimmed && stripped.length > 0) {
return `Codex ${stripped}`;
}
return trimmed;
}
type CodexWindowSummary = Pick<
CodexQuotaWindow,
'label' | 'resetAfterSeconds' | 'category' | 'cadence' | 'featureLabel'
>;
/**
* Infer code-review window cadence by comparing against usage windows.
@@ -364,7 +407,7 @@ function inferCodeReviewCadence(
window: CodexWindowSummary,
allWindows: CodexWindowSummary[]
): '5h' | 'weekly' | null {
const kind = getCodexWindowKind(window.label);
const kind = getCodexWindowKind(window);
if (kind === 'code-review-weekly') return 'weekly';
const reset = window.resetAfterSeconds;
@@ -372,14 +415,14 @@ function inferCodeReviewCadence(
const usage5h = allWindows.find(
(w) =>
getCodexWindowKind(w.label) === 'usage-5h' &&
getCodexWindowKind(w) === 'usage-5h' &&
typeof w.resetAfterSeconds === 'number' &&
isFinite(w.resetAfterSeconds) &&
w.resetAfterSeconds > 0
);
const usageWeekly = allWindows.find(
(w) =>
getCodexWindowKind(w.label) === 'usage-weekly' &&
getCodexWindowKind(w) === 'usage-weekly' &&
typeof w.resetAfterSeconds === 'number' &&
isFinite(w.resetAfterSeconds) &&
w.resetAfterSeconds > 0
@@ -400,10 +443,16 @@ export function getCodexWindowDisplayLabel(
const currentWindow: CodexWindowSummary =
typeof labelOrWindow === 'string'
? { label, resetAfterSeconds: null }
: { label, resetAfterSeconds: labelOrWindow.resetAfterSeconds };
: {
label,
resetAfterSeconds: labelOrWindow.resetAfterSeconds,
category: labelOrWindow.category,
cadence: labelOrWindow.cadence,
featureLabel: labelOrWindow.featureLabel,
};
const context = allWindows.length > 0 ? allWindows : [currentWindow];
switch (getCodexWindowKind(label)) {
switch (getCodexWindowKind(currentWindow)) {
case 'usage-5h':
return i18n.t('quotaTooltip.fiveHourLimit');
case 'usage-weekly':
@@ -416,6 +465,15 @@ export function getCodexWindowDisplayLabel(
if (inferred === 'weekly') return i18n.t('utils.codeReviewWeekly');
return i18n.t('utils.codeReview');
}
case 'additional-5h':
case 'additional-weekly': {
const pretty = prettifyCodexFeatureLabel(currentWindow.featureLabel ?? '');
const name = pretty || label;
const kind = getCodexWindowKind(currentWindow);
if (kind === 'additional-5h') return i18n.t('utils.codexAdditional5h', { name });
if (kind === 'additional-weekly') return i18n.t('utils.codexAdditionalWeekly', { name });
return i18n.t('utils.codexAdditional', { name });
}
case 'unknown':
return label;
}
@@ -425,11 +483,18 @@ export interface CodexQuotaBreakdown {
fiveHourWindow: CodexQuotaWindow | null;
weeklyWindow: CodexQuotaWindow | null;
codeReviewWindows: CodexQuotaWindow[];
/** Additional rate-limit windows (e.g. GPT-5.3 Codex Spark). Excluded from core 5h/weekly summary. */
additionalWindows: CodexQuotaWindow[];
unknownWindows: CodexQuotaWindow[];
}
/**
* Break down Codex windows into core usage windows (5h + weekly) and auxiliary windows.
* Break down Codex windows into core usage windows (5h + weekly), code review,
* additional (e.g. Spark), and unknown buckets.
*
* Prefers explicit category metadata when present so 'additional' windows
* (e.g. GPT-5.3 Codex Spark) do not displace core usage windows in the summary.
* Falls back to label sniffing for legacy cached data without metadata.
*/
export function getCodexQuotaBreakdown(windows: CodexQuotaWindow[]): CodexQuotaBreakdown {
if (!windows || windows.length === 0) {
@@ -437,6 +502,7 @@ export function getCodexQuotaBreakdown(windows: CodexQuotaWindow[]): CodexQuotaB
fiveHourWindow: null,
weeklyWindow: null,
codeReviewWindows: [],
additionalWindows: [],
unknownWindows: [],
};
}
@@ -444,34 +510,64 @@ export function getCodexQuotaBreakdown(windows: CodexQuotaWindow[]): CodexQuotaB
let fiveHourWindow: CodexQuotaWindow | null = null;
let weeklyWindow: CodexQuotaWindow | null = null;
const codeReviewWindows: CodexQuotaWindow[] = [];
const additionalWindows: CodexQuotaWindow[] = [];
const unknownWindows: CodexQuotaWindow[] = [];
// Eligible windows for the 5h/weekly fallback (must NOT include code-review or additional).
const nonCodeReviewWindows: CodexQuotaWindow[] = [];
for (const window of windows) {
const kind = getCodexWindowKind(window.label);
const hasCategoryMetadata = windows.some((w) => Boolean(w.category));
switch (kind) {
case 'usage-5h':
if (!fiveHourWindow) fiveHourWindow = window;
if (hasCategoryMetadata) {
for (const window of windows) {
if (window.category === 'usage') {
if (window.cadence === '5h' && !fiveHourWindow) fiveHourWindow = window;
else if (window.cadence === 'weekly' && !weeklyWindow) weeklyWindow = window;
nonCodeReviewWindows.push(window);
break;
case 'usage-weekly':
if (!weeklyWindow) weeklyWindow = window;
nonCodeReviewWindows.push(window);
break;
case 'code-review-5h':
case 'code-review-weekly':
case 'code-review':
} else if (window.category === 'code-review') {
codeReviewWindows.push(window);
break;
case 'unknown':
} else if (window.category === 'additional') {
additionalWindows.push(window);
} else {
// Window has no category but the batch carries metadata for others -> treat as unknown.
unknownWindows.push(window);
nonCodeReviewWindows.push(window);
break;
}
}
} else {
// Legacy path: classify via label sniffing for cached windows without metadata.
for (const window of windows) {
const kind = getCodexWindowKind(window.label);
switch (kind) {
case 'usage-5h':
if (!fiveHourWindow) fiveHourWindow = window;
nonCodeReviewWindows.push(window);
break;
case 'usage-weekly':
if (!weeklyWindow) weeklyWindow = window;
nonCodeReviewWindows.push(window);
break;
case 'code-review-5h':
case 'code-review-weekly':
case 'code-review':
codeReviewWindows.push(window);
break;
case 'additional-5h':
case 'additional-weekly':
// Unreachable from label-only kind, but kept for exhaustiveness.
additionalWindows.push(window);
break;
case 'unknown':
unknownWindows.push(window);
nonCodeReviewWindows.push(window);
break;
}
}
}
// Fallback for API label changes: infer 5h/weekly from reset horizon when explicit labels are absent.
// 'additional' windows are intentionally excluded from this pool to avoid leaking Spark windows
// into the core usage badges on a fresh Pro account.
if ((!fiveHourWindow || !weeklyWindow) && nonCodeReviewWindows.length > 0) {
const withReset = nonCodeReviewWindows
.filter((w) => typeof w.resetAfterSeconds === 'number' && w.resetAfterSeconds >= 0)
@@ -493,6 +589,7 @@ export function getCodexQuotaBreakdown(windows: CodexQuotaWindow[]): CodexQuotaB
fiveHourWindow,
weeklyWindow,
codeReviewWindows,
additionalWindows,
unknownWindows,
};
}
@@ -0,0 +1,316 @@
/**
* Tests for Codex quota breakdown logic.
*
* Covers the dashboard-side mirror of the server's CodexQuotaWindow handling:
* additional rate-limit windows (e.g. GPT-5.3 Codex Spark) must NOT pollute
* the core 5h/weekly buckets, and category metadata takes precedence over
* label sniffing when present.
*/
import { describe, it, expect } from 'vitest';
import {
getCodexQuotaBreakdown,
getCodexWindowKind,
getCodexWindowDisplayLabel,
prettifyCodexFeatureLabel,
} from '@/lib/utils';
import type { CodexQuotaWindow } from '@/lib/api-client';
describe('prettifyCodexFeatureLabel', () => {
it('strips "GPT-X.Y-Codex-" prefix and prepends "Codex"', () => {
expect(prettifyCodexFeatureLabel('GPT-5.3-Codex-Spark')).toBe('Codex Spark');
});
it('is case-insensitive when stripping the GPT prefix', () => {
expect(prettifyCodexFeatureLabel('gpt-5.3-codex-spark')).toBe('Codex spark');
});
it('returns labels without the GPT prefix verbatim', () => {
expect(prettifyCodexFeatureLabel('OmniReview')).toBe('OmniReview');
});
it('returns empty string for empty input', () => {
expect(prettifyCodexFeatureLabel('')).toBe('');
});
it('trims whitespace and handles whitespace-only input', () => {
expect(prettifyCodexFeatureLabel(' ')).toBe('');
expect(prettifyCodexFeatureLabel(' GPT-5.3-Codex-Spark ')).toBe('Codex Spark');
});
});
describe('getCodexWindowKind', () => {
it('uses category metadata when present (additional weekly)', () => {
const window: CodexQuotaWindow = {
label: 'GPT-5.3-Codex-Spark (Secondary)',
usedPercent: 10,
remainingPercent: 90,
resetAfterSeconds: 600000,
resetAt: '2026-05-04T00:00:00Z',
category: 'additional',
cadence: 'weekly',
featureLabel: 'GPT-5.3-Codex-Spark',
};
expect(getCodexWindowKind(window)).toBe('additional-weekly');
});
it('uses category metadata when present (additional 5h)', () => {
const window: CodexQuotaWindow = {
label: 'GPT-5.3-Codex-Spark (Primary)',
usedPercent: 5,
remainingPercent: 95,
resetAfterSeconds: 18000,
resetAt: '2026-04-28T00:00:00Z',
category: 'additional',
cadence: '5h',
featureLabel: 'GPT-5.3-Codex-Spark',
};
expect(getCodexWindowKind(window)).toBe('additional-5h');
});
it('uses category metadata when present (usage 5h)', () => {
const window: CodexQuotaWindow = {
label: 'Primary',
usedPercent: 25,
remainingPercent: 75,
resetAfterSeconds: 18000,
resetAt: '2026-04-28T00:00:00Z',
category: 'usage',
cadence: '5h',
};
expect(getCodexWindowKind(window)).toBe('usage-5h');
});
it('uses category metadata when present (code-review weekly)', () => {
const window: CodexQuotaWindow = {
label: 'Code Review (Secondary)',
usedPercent: 50,
remainingPercent: 50,
resetAfterSeconds: 604800,
resetAt: '2026-05-04T00:00:00Z',
category: 'code-review',
cadence: 'weekly',
featureLabel: 'Code Review',
};
expect(getCodexWindowKind(window)).toBe('code-review-weekly');
});
it('falls back to label sniffing for legacy windows without category', () => {
expect(getCodexWindowKind('Primary')).toBe('usage-5h');
expect(getCodexWindowKind('Secondary')).toBe('usage-weekly');
expect(getCodexWindowKind('Code Review (Primary)')).toBe('code-review-5h');
expect(getCodexWindowKind('Code Review (Secondary)')).toBe('code-review-weekly');
expect(getCodexWindowKind('Code Review')).toBe('code-review');
expect(getCodexWindowKind('Random Label')).toBe('unknown');
});
it('falls back to label sniffing when window has no category set', () => {
const window: CodexQuotaWindow = {
label: 'Primary',
usedPercent: 25,
remainingPercent: 75,
resetAfterSeconds: 18000,
resetAt: '2026-04-28T00:00:00Z',
};
expect(getCodexWindowKind(window)).toBe('usage-5h');
});
});
describe('getCodexQuotaBreakdown', () => {
it('returns empty buckets for empty windows', () => {
const result = getCodexQuotaBreakdown([]);
expect(result.fiveHourWindow).toBeNull();
expect(result.weeklyWindow).toBeNull();
expect(result.codeReviewWindows).toEqual([]);
expect(result.additionalWindows).toEqual([]);
expect(result.unknownWindows).toEqual([]);
});
it('routes Pro-account windows by category metadata (Spark stays out of core)', () => {
const windows: CodexQuotaWindow[] = [
{
label: 'Primary',
usedPercent: 0,
remainingPercent: 100,
resetAfterSeconds: 18000,
resetAt: '2026-04-28T05:00:00Z',
category: 'usage',
cadence: '5h',
},
{
label: 'Secondary',
usedPercent: 0,
remainingPercent: 100,
resetAfterSeconds: 604800,
resetAt: '2026-05-04T00:00:00Z',
category: 'usage',
cadence: 'weekly',
},
{
label: 'GPT-5.3-Codex-Spark (Primary)',
usedPercent: 12,
remainingPercent: 88,
// Intentionally short reset to verify Spark cannot displace core 5h via reset-horizon fallback.
resetAfterSeconds: 60,
resetAt: '2026-04-27T00:01:00Z',
category: 'additional',
cadence: '5h',
featureLabel: 'GPT-5.3-Codex-Spark',
},
{
label: 'GPT-5.3-Codex-Spark (Secondary)',
usedPercent: 30,
remainingPercent: 70,
resetAfterSeconds: 600000,
resetAt: '2026-05-04T00:00:00Z',
category: 'additional',
cadence: 'weekly',
featureLabel: 'GPT-5.3-Codex-Spark',
},
{
label: 'Code Review (Primary)',
usedPercent: 25,
remainingPercent: 75,
resetAfterSeconds: 17000,
resetAt: '2026-04-28T05:00:00Z',
category: 'code-review',
cadence: '5h',
featureLabel: 'Code Review',
},
];
const result = getCodexQuotaBreakdown(windows);
expect(result.fiveHourWindow?.label).toBe('Primary');
expect(result.fiveHourWindow?.category).toBe('usage');
expect(result.weeklyWindow?.label).toBe('Secondary');
expect(result.weeklyWindow?.category).toBe('usage');
expect(result.additionalWindows).toHaveLength(2);
expect(result.additionalWindows.map((w) => w.featureLabel)).toEqual([
'GPT-5.3-Codex-Spark',
'GPT-5.3-Codex-Spark',
]);
expect(result.additionalWindows.map((w) => w.cadence)).toEqual(['5h', 'weekly']);
expect(result.codeReviewWindows).toHaveLength(1);
expect(result.codeReviewWindows[0]?.featureLabel).toBe('Code Review');
expect(result.unknownWindows).toEqual([]);
});
it('returns empty additionalWindows for Plus-shape data without additional categories', () => {
const windows: CodexQuotaWindow[] = [
{
label: 'Primary',
usedPercent: 30,
remainingPercent: 70,
resetAfterSeconds: 18000,
resetAt: '2026-04-28T05:00:00Z',
category: 'usage',
cadence: '5h',
},
{
label: 'Secondary',
usedPercent: 60,
remainingPercent: 40,
resetAfterSeconds: 604800,
resetAt: '2026-05-04T00:00:00Z',
category: 'usage',
cadence: 'weekly',
},
];
const result = getCodexQuotaBreakdown(windows);
expect(result.fiveHourWindow?.label).toBe('Primary');
expect(result.weeklyWindow?.label).toBe('Secondary');
expect(result.additionalWindows).toEqual([]);
expect(result.codeReviewWindows).toEqual([]);
expect(result.unknownWindows).toEqual([]);
});
it('falls back to label sniffing for legacy cached windows without metadata', () => {
const windows: CodexQuotaWindow[] = [
{
label: 'Primary',
usedPercent: 25,
remainingPercent: 75,
resetAfterSeconds: 18000,
resetAt: '2026-04-28T05:00:00Z',
},
{
label: 'Secondary',
usedPercent: 65,
remainingPercent: 35,
resetAfterSeconds: 604800,
resetAt: '2026-05-04T00:00:00Z',
},
{
label: 'Code Review (Primary)',
usedPercent: 10,
remainingPercent: 90,
resetAfterSeconds: 17000,
resetAt: '2026-04-28T05:00:00Z',
},
{
label: 'Code Review (Secondary)',
usedPercent: 5,
remainingPercent: 95,
resetAfterSeconds: 600000,
resetAt: '2026-05-04T00:00:00Z',
},
];
const result = getCodexQuotaBreakdown(windows);
expect(result.fiveHourWindow?.label).toBe('Primary');
expect(result.weeklyWindow?.label).toBe('Secondary');
expect(result.codeReviewWindows).toHaveLength(2);
expect(result.additionalWindows).toEqual([]);
expect(result.unknownWindows).toEqual([]);
});
});
describe('getCodexWindowDisplayLabel for additional windows', () => {
it('renders additional 5h windows with prettified Codex feature name', () => {
const window: CodexQuotaWindow = {
label: 'GPT-5.3-Codex-Spark (Primary)',
usedPercent: 12,
remainingPercent: 88,
resetAfterSeconds: 18000,
resetAt: '2026-04-28T05:00:00Z',
category: 'additional',
cadence: '5h',
featureLabel: 'GPT-5.3-Codex-Spark',
};
expect(getCodexWindowDisplayLabel(window)).toBe('Codex Spark (5h)');
});
it('renders additional weekly windows with prettified Codex feature name', () => {
const window: CodexQuotaWindow = {
label: 'GPT-5.3-Codex-Spark (Secondary)',
usedPercent: 30,
remainingPercent: 70,
resetAfterSeconds: 600000,
resetAt: '2026-05-04T00:00:00Z',
category: 'additional',
cadence: 'weekly',
featureLabel: 'GPT-5.3-Codex-Spark',
};
expect(getCodexWindowDisplayLabel(window)).toBe('Codex Spark (weekly)');
});
it('falls back to raw label when featureLabel is missing', () => {
const window: CodexQuotaWindow = {
label: 'OmniReview (Primary)',
usedPercent: 0,
remainingPercent: 100,
resetAfterSeconds: 18000,
resetAt: '2026-04-28T05:00:00Z',
category: 'additional',
cadence: '5h',
};
expect(getCodexWindowDisplayLabel(window)).toBe('OmniReview (Primary) (5h)');
});
});