From 1a6deae48da011dcd3e0d7879b0b518d2a676b21 Mon Sep 17 00:00:00 2001 From: Shinwoo PARK Date: Sat, 9 May 2026 17:21:22 +0900 Subject: [PATCH] [verified] fix: separate account capacity panel --- apps/web/src/App.tsx | 132 ++++++++++++------ .../test/dashboard-account-capacity.test.js | 7 +- .../test/dashboard-capacity-windows.test.js | 1 + apps/web/test/dashboard-card-copy.test.js | 1 + apps/web/test/mobile-overflow-guards.test.js | 10 +- 5 files changed, 101 insertions(+), 50 deletions(-) diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index 4d7d39d..151ebf2 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -628,6 +628,17 @@ function Dashboard() { const fastestResetAt = getFastestResetAt( windowCards.map((item) => item.window.resetAt), ); + const accountCapacityItems = summary.accounts.map((account) => { + const accountWindows = extractUsageWindows(account.usage); + const accountCapacityBars = [accountWindows.primary, accountWindows.secondary].filter( + (window): window is NonNullable => window !== null, + ); + + return { + account, + accountCapacityBars, + }; + }); return (
@@ -663,13 +674,13 @@ function Dashboard() {
- + Unified capacity - + {windowCards.length > 0 ? ( -
+
{windowCards.map((item) => { const usedPercent = item.window.usedPercent; const progressValue = @@ -680,7 +691,7 @@ function Dashboard() { return (
@@ -695,6 +706,9 @@ function Dashboard() { {item.window.limitWindowSeconds !== null ? (
{formatDurationSeconds(item.window.limitWindowSeconds)}
) : null} +
+ Replenishes at {formatDate(item.window.resetAt)} +
{fastestResetAt ? ( - Replenishes at {formatDate(fastestResetAt)} + Earliest replenishment {formatDate(fastestResetAt)} ) : null} Accounts: {summary.totals.totalAccounts} Healthy: {summary.totals.activeAccounts} @@ -776,17 +790,7 @@ function Dashboard() { ))} - {summary.accounts.map((account) => { - const accountWindows = extractUsageWindows(account.usage); - const accountCapacityBars = [ - accountWindows.primary, - accountWindows.secondary, - ].filter( - (window): window is NonNullable => - window !== null, - ); - - return ( + {summary.accounts.map((account) => (
@@ -810,31 +814,6 @@ function Dashboard() { {account.status}
- {accountCapacityBars.length > 0 ? ( -
- {accountCapacityBars.map((window, index) => { - const progressValue = - window.usedPercent !== null - ? Math.max(0, Math.min(100, window.usedPercent)) - : 0; - - return ( -
- -
- {window.usedPercent !== null - ? `${window.usedPercent.toFixed(0)}%` - : '—'} -
-
- ); - })} -
- ) : null}
@@ -877,12 +856,79 @@ function Dashboard() {
- ); - })} + ))} )} + + + + Per-account capacity + Compact rate-limit bars for each connected OpenAI account. + + + {accountCapacityItems.length === 0 ? ( +
+ No account capacity data yet. +
+ ) : ( +
+ {accountCapacityItems.map(({ account, accountCapacityBars }) => ( +
+
+
+
{account.label}
+
+ {account.providerEmail || account.emailHint || 'No email available yet'} +
+
+ + {account.status} + +
+ {accountCapacityBars.length > 0 ? ( +
+ {accountCapacityBars.map((window, index) => { + const progressValue = + window.usedPercent !== null + ? Math.max(0, Math.min(100, window.usedPercent)) + : 0; + + return ( +
+ +
+ {window.usedPercent !== null + ? `${window.usedPercent.toFixed(0)}%` + : '—'} +
+
+ ); + })} +
+ ) : ( +
No window data yet.
+ )} +
+ ))} +
+ )} +
+
); } diff --git a/apps/web/test/dashboard-account-capacity.test.js b/apps/web/test/dashboard-account-capacity.test.js index 853d5e4..e1374df 100644 --- a/apps/web/test/dashboard-account-capacity.test.js +++ b/apps/web/test/dashboard-account-capacity.test.js @@ -5,13 +5,14 @@ import { join } from 'node:path'; const appSource = readFileSync(join(import.meta.dir, '../src/App.tsx'), 'utf8'); describe('dashboard account capacity bars', () => { - test('renders compact per-account capacity bars without verbose labels', () => { + test('renders compact per-account capacity bars in a dedicated panel', () => { + expect(appSource).toContain('Per-account capacity'); expect(appSource).toContain('const accountWindows = extractUsageWindows(account.usage);'); expect(appSource).toContain('const accountCapacityBars = ['); - expect(appSource).toContain("className=\"space-y-2\""); + expect(appSource).toContain("className=\"space-y-3\""); + expect(appSource).toContain("className=\"space-y-2 rounded-2xl border border-white/10 bg-white/4 p-4\""); expect(appSource).toContain('indicatorClassName={getUsageProgressTone(progressValue)}'); expect(appSource).toContain("className=\"min-w-10 text-right text-xs font-medium text-slate-400\""); - expect(appSource).not.toContain('Account capacity'); expect(appSource).not.toContain('Primary window used'); expect(appSource).not.toContain('Secondary window used'); }); diff --git a/apps/web/test/dashboard-capacity-windows.test.js b/apps/web/test/dashboard-capacity-windows.test.js index 522464e..43afcb1 100644 --- a/apps/web/test/dashboard-capacity-windows.test.js +++ b/apps/web/test/dashboard-capacity-windows.test.js @@ -14,6 +14,7 @@ describe('dashboard unified capacity windows', () => { expect(appSource).toContain('getUsageProgressTone(progressValue)'); expect(appSource).toContain('const fastestResetAt = getFastestResetAt('); expect(appSource).toContain('Replenishes at'); + expect(appSource).not.toContain('Replenish after'); expect(appSource).not.toContain('Window data from'); expect(appSource).not.toContain('Resets '); expect(appSource).not.toContain('Usage metrics'); diff --git a/apps/web/test/dashboard-card-copy.test.js b/apps/web/test/dashboard-card-copy.test.js index ca7330b..a29c189 100644 --- a/apps/web/test/dashboard-card-copy.test.js +++ b/apps/web/test/dashboard-card-copy.test.js @@ -11,6 +11,7 @@ describe('dashboard card copy', () => { expect(appSource).toContain('Primary window'); expect(appSource).toContain('Secondary window'); expect(appSource).toContain('Replenishes at'); + expect(appSource).not.toContain('Replenish after'); expect(appSource).not.toContain('Window data from'); expect(appSource).not.toContain('Usage metrics'); expect(appSource).toContain(">Merged by default. Inspect each account below.<"); diff --git a/apps/web/test/mobile-overflow-guards.test.js b/apps/web/test/mobile-overflow-guards.test.js index af61d5b..985f3cf 100644 --- a/apps/web/test/mobile-overflow-guards.test.js +++ b/apps/web/test/mobile-overflow-guards.test.js @@ -5,10 +5,12 @@ import { join } from 'node:path'; const appSource = readFileSync(join(import.meta.dir, '../src/App.tsx'), 'utf8'); describe('mobile overflow guards', () => { - test('unified capacity window cards stay in a responsive full-height two-column grid', () => { - expect(appSource).toContain('className="grid flex-1 gap-4 md:auto-rows-fr md:grid-cols-2"'); - expect(appSource).toContain('className="flex h-full flex-col justify-between rounded-2xl border border-white/10 bg-white/4 p-4"'); - expect(appSource).toContain('className="text-sm text-slate-400">{item.title}
'); + test('unified capacity window cards stay inside the card without h-full overflow', () => { + expect(appSource).toContain('className="flex h-full flex-col overflow-hidden"'); + expect(appSource).toContain('className="flex flex-1 flex-col gap-4"'); + expect(appSource).toContain('className="grid flex-1 items-stretch gap-4 md:grid-cols-2"'); + expect(appSource).toContain('className="flex min-h-0 flex-1 flex-col justify-between rounded-2xl border border-white/10 bg-white/4 p-4"'); + expect(appSource).toContain('Replenishes at {formatDate(item.window.resetAt)}'); }); test('connected account tabs no longer render a side-by-side payload column', () => {