diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx
index 6183d2c..44b52e8 100644
--- a/apps/web/src/App.tsx
+++ b/apps/web/src/App.tsx
@@ -1,4 +1,4 @@
-import { useEffect, useMemo, useRef, useState } from 'react';
+import { useEffect, useRef, useState } from 'react';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { zodResolver } from '@hookform/resolvers/zod';
@@ -30,7 +30,7 @@ import {
import { toast, Toaster } from 'sonner';
import { api } from '@/lib/api';
import { clearToken, getToken, setToken } from '@/lib/storage';
-import { flattenNumericMetrics, formatDate, titleizeMetric } from '@/lib/utils';
+import { summarizeUsageWindows, formatDate, formatDurationSeconds } from '@/lib/utils';
import { Button } from '@/components/ui/button';
import {
Card,
@@ -568,10 +568,6 @@ function Dashboard() {
onError: (error: Error) => toast.error(error.message),
});
- const metricCards = useMemo(() => {
- return flattenNumericMetrics(summaryQuery.data?.aggregatedUsage).slice(0, 6);
- }, [summaryQuery.data?.aggregatedUsage]);
-
if (summaryQuery.isLoading || userQuery.isLoading) {
return (
@@ -601,12 +597,27 @@ function Dashboard() {
const summary = summaryQuery.data!;
const user = userQuery.data!;
- const firstMetric = metricCards[0]?.value ?? 0;
- const secondMetric = metricCards[1]?.value ?? 0;
- const progressValue =
- firstMetric + secondMetric > 0
- ? (firstMetric / (firstMetric + secondMetric)) * 100
- : 0;
+ const usageWindows = summarizeUsageWindows(
+ summary.accounts.map((account) => account.usage),
+ );
+ const windowCards = [
+ {
+ title: 'Primary window',
+ tone: 'text-sky-300',
+ window: usageWindows.primary,
+ },
+ {
+ title: 'Secondary window',
+ tone: 'text-violet-300',
+ window: usageWindows.secondary,
+ },
+ ].filter(
+ (item): item is {
+ title: string;
+ tone: string;
+ window: NonNullable
;
+ } => item.window !== null,
+ );
return (
@@ -647,25 +658,53 @@ function Dashboard() {
Unified capacity
-
-
-
- {firstMetric.toLocaleString()}
-
-
- {titleizeMetric(metricCards[0]?.label ?? 'Primary metric')}
-
+ {windowCards.length > 0 ? (
+
+ {windowCards.map((item) => {
+ const usedPercent = item.window.usedPercent;
+ const progressValue =
+ usedPercent !== null && usedPercent !== undefined
+ ? Math.max(0, Math.min(100, usedPercent))
+ : 0;
+
+ return (
+
+
+
+
{item.title}
+
+ {usedPercent !== null && usedPercent !== undefined
+ ? `${usedPercent.toFixed(0)}%`
+ : '—'}
+
+
+ {item.window.accountCount
+ ? `Window data from ${item.window.accountCount} account${item.window.accountCount === 1 ? '' : 's'}`
+ : 'Used'}
+
+
+
+ {item.window.limitWindowSeconds !== null ? (
+
{formatDurationSeconds(item.window.limitWindowSeconds)}
+ ) : null}
+
+ Resets {formatDate(item.window.resetAt)}
+
+
+
+
+
+ );
+ })}
-
-
- {secondMetric.toLocaleString()}
-
-
- {titleizeMetric(metricCards[1]?.label ?? 'Secondary metric')}
-
+ ) : (
+
+ No rate-limit window data yet. Connect an OpenAI account and refresh to load Codex usage windows.
-
-
+ )}
Accounts: {summary.totals.totalAccounts}
Healthy: {summary.totals.activeAccounts}
@@ -705,35 +744,6 @@ function Dashboard() {
-
-
- Usage metrics
-
-
- {metricCards.length === 0 ? (
-
- No usage data yet. Connect an OpenAI account and complete the sign-in flow to start refreshing.
-
- ) : (
-
- {metricCards.map((metric) => (
-
-
- {titleizeMetric(metric.label)}
-
-
- {metric.value.toLocaleString()}
-
-
- ))}
-
- )}
-
-
-
Connected OpenAI accounts
diff --git a/apps/web/src/lib/utils.ts b/apps/web/src/lib/utils.ts
index ae6ea3e..3f6df84 100644
--- a/apps/web/src/lib/utils.ts
+++ b/apps/web/src/lib/utils.ts
@@ -13,6 +13,181 @@ export function formatDate(value: string | null) {
}).format(new Date(value));
}
+type UsageWindowSummary = {
+ usedPercent: number | null;
+ limitWindowSeconds: number | null;
+ resetAt: string | null;
+};
+
+type AggregatedUsageWindowSummary = UsageWindowSummary & {
+ accountCount: number;
+};
+
+function isRecord(value: unknown): value is Record {
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
+}
+
+function toFiniteNumber(value: unknown) {
+ return typeof value === 'number' && Number.isFinite(value) ? value : null;
+}
+
+function getPath(value: unknown, path: string[]) {
+ let current: unknown = value;
+ for (const segment of path) {
+ if (!isRecord(current) || !(segment in current)) {
+ return null;
+ }
+ current = current[segment];
+ }
+ return current;
+}
+
+function normalizeResetAt(value: unknown) {
+ if (typeof value === 'string' && value.trim()) {
+ return value;
+ }
+
+ if (typeof value === 'number' && Number.isFinite(value)) {
+ return new Date(value * 1000).toISOString();
+ }
+
+ return null;
+}
+
+function extractWindow(value: unknown, paths: string[][]): UsageWindowSummary | null {
+ for (const path of paths) {
+ const windowValue = getPath(value, path);
+
+ if (!isRecord(windowValue)) {
+ continue;
+ }
+
+ const summary = {
+ usedPercent: toFiniteNumber(windowValue.used_percent),
+ limitWindowSeconds:
+ toFiniteNumber(windowValue.limit_window_seconds) ??
+ (toFiniteNumber(windowValue.window_minutes) !== null
+ ? toFiniteNumber(windowValue.window_minutes)! * 60
+ : null),
+ resetAt: normalizeResetAt(windowValue.reset_at ?? windowValue.resets_at),
+ };
+
+ if (
+ summary.usedPercent === null &&
+ summary.limitWindowSeconds === null &&
+ summary.resetAt === null
+ ) {
+ continue;
+ }
+
+ return summary;
+ }
+
+ return null;
+}
+
+export function extractUsageWindows(value: unknown) {
+ return {
+ primary: extractWindow(value, [
+ ['rate_limit', 'primary_window'],
+ ['rate_limits', 'primary'],
+ ['primary_window'],
+ ]),
+ secondary: extractWindow(value, [
+ ['rate_limit', 'secondary_window'],
+ ['rate_limits', 'secondary'],
+ ['secondary_window'],
+ ]),
+ };
+}
+
+function average(values: number[]) {
+ if (values.length === 0) {
+ return null;
+ }
+
+ return values.reduce((sum, value) => sum + value, 0) / values.length;
+}
+
+function earliestDate(values: Array) {
+ const validDates = values
+ .filter((value): value is string => Boolean(value))
+ .map((value) => ({ value, timestamp: Date.parse(value) }))
+ .filter((value) => Number.isFinite(value.timestamp))
+ .sort((left, right) => left.timestamp - right.timestamp);
+
+ return validDates[0]?.value ?? null;
+}
+
+function summarizeWindowCollection(
+ windows: Array,
+): AggregatedUsageWindowSummary | null {
+ const presentWindows = windows.filter(
+ (window): window is UsageWindowSummary => window !== null,
+ );
+
+ if (presentWindows.length === 0) {
+ return null;
+ }
+
+ const usedPercentValues = presentWindows
+ .map((window) => window.usedPercent)
+ .filter((value): value is number => value !== null);
+
+ const limitWindowValues = presentWindows
+ .map((window) => window.limitWindowSeconds)
+ .filter((value): value is number => value !== null);
+
+ const uniqueLimitWindowValues = [...new Set(limitWindowValues)];
+
+ return {
+ accountCount: presentWindows.length,
+ usedPercent: average(usedPercentValues),
+ limitWindowSeconds:
+ limitWindowValues.length === presentWindows.length &&
+ uniqueLimitWindowValues.length === 1
+ ? uniqueLimitWindowValues[0]
+ : null,
+ resetAt: earliestDate(presentWindows.map((window) => window.resetAt)),
+ };
+}
+
+export function summarizeUsageWindows(values: Array) {
+ const extractedWindows = values.map((value) => extractUsageWindows(value));
+
+ return {
+ primary: summarizeWindowCollection(
+ extractedWindows.map((windows) => windows.primary),
+ ),
+ secondary: summarizeWindowCollection(
+ extractedWindows.map((windows) => windows.secondary),
+ ),
+ };
+}
+
+export function formatDurationSeconds(value: number | null) {
+ if (!value || value <= 0) {
+ return 'Unknown window';
+ }
+
+ if (value % 86_400 === 0) {
+ const days = value / 86_400;
+ return `${days}d window`;
+ }
+
+ if (value % 3_600 === 0) {
+ const hours = value / 3_600;
+ return `${hours}h window`;
+ }
+
+ if (value % 60 === 0) {
+ const minutes = value / 60;
+ return `${minutes}m window`;
+ }
+
+ return `${value}s window`;
+}
+
export function flattenNumericMetrics(
value: unknown,
path: string[] = [],
diff --git a/apps/web/test/dashboard-capacity-windows.test.js b/apps/web/test/dashboard-capacity-windows.test.js
new file mode 100644
index 0000000..0be1c3d
--- /dev/null
+++ b/apps/web/test/dashboard-capacity-windows.test.js
@@ -0,0 +1,20 @@
+import { describe, expect, test } from 'bun:test';
+import { readFileSync } from 'node:fs';
+import { join } from 'node:path';
+
+const appSource = readFileSync(join(import.meta.dir, '../src/App.tsx'), 'utf8');
+
+describe('dashboard unified capacity windows', () => {
+ test('shows primary and secondary windows inside unified capacity and removes usage metrics card', () => {
+ expect(appSource).toContain('Unified capacity');
+ expect(appSource).toContain('Primary window');
+ expect(appSource).toContain('Secondary window');
+ expect(appSource).toContain('const windowCards = [');
+ expect(appSource).toContain("} => item.window !== null,");
+ expect(appSource).toContain('item.window.limitWindowSeconds !== null ? (');
+ expect(appSource).not.toContain('Usage metrics');
+ expect(appSource).not.toContain('flattenNumericMetrics(summaryQuery.data?.aggregatedUsage).slice(0, 6)');
+ expect(appSource).not.toContain('const firstMetric = metricCards[0]?.value ?? 0;');
+ expect(appSource).not.toContain('const secondMetric = metricCards[1]?.value ?? 0;');
+ });
+});
diff --git a/apps/web/test/dashboard-card-copy.test.js b/apps/web/test/dashboard-card-copy.test.js
index 60266bf..38dfad9 100644
--- a/apps/web/test/dashboard-card-copy.test.js
+++ b/apps/web/test/dashboard-card-copy.test.js
@@ -7,8 +7,10 @@ const appSource = readFileSync(join(import.meta.dir, '../src/App.tsx'), 'utf8');
describe('dashboard card copy', () => {
test('removes verbose dashboard card descriptions and keeps concise labels', () => {
expect(appSource).toContain('Unified capacity');
- expect(appSource).toContain('Usage metrics');
expect(appSource).toContain('Connected OpenAI accounts');
+ expect(appSource).toContain('Primary window');
+ expect(appSource).toContain('Secondary window');
+ expect(appSource).not.toContain('Usage metrics');
expect(appSource).toContain(">Merged by default. Inspect each account below.<");
expect(appSource).not.toContain(
'Fast glance card for the first two numeric metrics extracted from the merged usage payload.',
diff --git a/apps/web/test/mobile-overflow-guards.test.js b/apps/web/test/mobile-overflow-guards.test.js
index 92d0333..762399d 100644
--- a/apps/web/test/mobile-overflow-guards.test.js
+++ b/apps/web/test/mobile-overflow-guards.test.js
@@ -5,11 +5,10 @@ import { join } from 'node:path';
const appSource = readFileSync(join(import.meta.dir, '../src/App.tsx'), 'utf8');
describe('mobile overflow guards', () => {
- test('usage metrics cards allow long metric labels to wrap on mobile', () => {
- expect(appSource).toContain('className="mt-6 min-w-0"');
- expect(appSource).toContain('className="grid gap-3 sm:grid-cols-2"');
- expect(appSource).toContain('className="min-w-0 rounded-2xl border border-white/10 bg-white/4 p-4"');
- expect(appSource).toContain('className="text-sm text-slate-400 break-words"');
+ test('unified capacity window cards stay in a responsive two-column grid', () => {
+ expect(appSource).toContain('className="grid gap-4 md:grid-cols-2"');
+ expect(appSource).toContain('className="rounded-2xl border border-white/10 bg-white/4 p-4"');
+ expect(appSource).toContain('className="text-sm text-slate-400">{item.title} ');
});
test('connected account tabs no longer render a side-by-side payload column', () => {
diff --git a/apps/web/test/usage-window-helpers.test.js b/apps/web/test/usage-window-helpers.test.js
new file mode 100644
index 0000000..c2a4e1c
--- /dev/null
+++ b/apps/web/test/usage-window-helpers.test.js
@@ -0,0 +1,175 @@
+import { describe, expect, test } from 'bun:test';
+import {
+ extractUsageWindows,
+ formatDurationSeconds,
+ summarizeUsageWindows,
+} from '../src/lib/utils';
+
+describe('usage window helpers', () => {
+ test('extracts primary and secondary windows from codex rate_limit payloads', () => {
+ const windows = extractUsageWindows({
+ rate_limit: {
+ primary_window: {
+ used_percent: 14,
+ limit_window_seconds: 18_000,
+ reset_at: '2026-05-09T12:00:00.000Z',
+ },
+ secondary_window: {
+ used_percent: 19,
+ limit_window_seconds: 604_800,
+ reset_at: '2026-05-16T12:00:00.000Z',
+ },
+ },
+ });
+
+ expect(windows).toEqual({
+ primary: {
+ usedPercent: 14,
+ limitWindowSeconds: 18_000,
+ resetAt: '2026-05-09T12:00:00.000Z',
+ },
+ secondary: {
+ usedPercent: 19,
+ limitWindowSeconds: 604_800,
+ resetAt: '2026-05-16T12:00:00.000Z',
+ },
+ });
+ });
+
+ test('formats canonical codex limit windows into readable labels', () => {
+ expect(formatDurationSeconds(18_000)).toBe('5h window');
+ expect(formatDurationSeconds(604_800)).toBe('7d window');
+ expect(formatDurationSeconds(null)).toBe('Unknown window');
+ });
+
+ test('summarizes windows across multiple account payloads without summing percentages', () => {
+ const windows = summarizeUsageWindows([
+ {
+ rate_limit: {
+ primary_window: {
+ used_percent: 20,
+ limit_window_seconds: 18_000,
+ reset_at: '2026-05-09T12:00:00.000Z',
+ },
+ secondary_window: {
+ used_percent: 40,
+ limit_window_seconds: 604_800,
+ reset_at: '2026-05-16T12:00:00.000Z',
+ },
+ },
+ },
+ {
+ rate_limit: {
+ primary_window: {
+ used_percent: 60,
+ limit_window_seconds: 18_000,
+ reset_at: '2026-05-09T10:00:00.000Z',
+ },
+ secondary_window: {
+ used_percent: 80,
+ limit_window_seconds: 604_800,
+ reset_at: '2026-05-15T10:00:00.000Z',
+ },
+ },
+ },
+ ]);
+
+ expect(windows).toEqual({
+ primary: {
+ accountCount: 2,
+ usedPercent: 40,
+ limitWindowSeconds: 18_000,
+ resetAt: '2026-05-09T10:00:00.000Z',
+ },
+ secondary: {
+ accountCount: 2,
+ usedPercent: 60,
+ limitWindowSeconds: 604_800,
+ resetAt: '2026-05-15T10:00:00.000Z',
+ },
+ });
+ });
+
+ test('keeps window summaries null-safe for partial or inconsistent account data', () => {
+ const windows = summarizeUsageWindows([
+ {
+ rate_limit: {
+ primary_window: {
+ limit_window_seconds: 18_000,
+ reset_at: '2026-05-09T12:00:00.000Z',
+ },
+ },
+ },
+ {
+ rate_limit: {
+ primary_window: {
+ used_percent: 50,
+ reset_at: '2026-05-09T11:00:00.000Z',
+ },
+ secondary_window: {
+ used_percent: 22,
+ limit_window_seconds: 604_800,
+ reset_at: '2026-05-16T12:00:00.000Z',
+ },
+ },
+ },
+ {
+ rate_limit: {
+ secondary_window: {
+ used_percent: 44,
+ limit_window_seconds: 86_400,
+ reset_at: '2026-05-15T12:00:00.000Z',
+ },
+ },
+ },
+ ]);
+
+ expect(windows).toEqual({
+ primary: {
+ accountCount: 2,
+ usedPercent: 50,
+ limitWindowSeconds: null,
+ resetAt: '2026-05-09T11:00:00.000Z',
+ },
+ secondary: {
+ accountCount: 2,
+ usedPercent: 33,
+ limitWindowSeconds: null,
+ resetAt: '2026-05-15T12:00:00.000Z',
+ },
+ });
+ });
+
+ test('ignores empty window shells with no meaningful values', () => {
+ expect(
+ extractUsageWindows({
+ rate_limit: {
+ primary_window: {},
+ secondary_window: null,
+ },
+ }),
+ ).toEqual({ primary: null, secondary: null });
+ });
+
+ test('falls back to later candidate paths when earlier window shells are empty', () => {
+ expect(
+ extractUsageWindows({
+ rate_limit: {
+ primary_window: {},
+ },
+ primary_window: {
+ used_percent: 42,
+ limit_window_seconds: 18_000,
+ reset_at: '2026-05-09T12:00:00.000Z',
+ },
+ }),
+ ).toEqual({
+ primary: {
+ usedPercent: 42,
+ limitWindowSeconds: 18_000,
+ resetAt: '2026-05-09T12:00:00.000Z',
+ },
+ secondary: null,
+ });
+ });
+ });