Skip to content

Commit e576443

Browse files
committed
style(codex): split usage page theme colors
Make the Codex usage/reset credits page maintain explicit light and dark color sets for each surface instead of relying on a single visual palette plus scattered dark overrides. Add USAGE_PAGE_COLORS and READING_HINT_COLORS so header, guide, chips, cards, insets, warning states, progress track, and reset summary each declare paired light/dark backgrounds, borders, and foregrounds. Update project memory to preserve the product rule that this page must keep separate light/dark surface tokens while matching the CCSwitchMulti Codex workbench style. Verification: pnpm exec prettier --write src/components/codex/CodexUsagePage.tsx memory.md; pnpm exec prettier --check src/components/codex/CodexUsagePage.tsx memory.md; pnpm typecheck; pnpm vitest run src/components/codex/CodexUsagePage.test.tsx tests/integration/App.test.tsx. Browser QA used temporary Vite on 127.0.0.1:3000, opened Codex usage page through the Codex toolbar, captured dark and light screenshots, checked page text/overlay state, and restored localStorage theme to dark. Browser DOM snapshot API failed with incrementalAriaSnapshot incompatibility, so visual checks used Browser evaluate/screenshot and CDP for temporary localStorage setup. Expected Tauri invoke/listen errors appeared because renderer was running outside Tauri.
1 parent 9b3f457 commit e576443

2 files changed

Lines changed: 90 additions & 34 deletions

File tree

memory.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
- 参考 `jordan-edai/codex-reset-watcher` 时,真正可复用的产品边界不是 macOS 菜单栏,而是跨平台的只读 Codex 用量面板:5 小时窗口、每周窗口、banked reset credits、到期紧迫度、部分明细失败提示和手动刷新。CCSwitchMulti 里不要把这类信息只藏在 provider footer。
2222
- 前端已新增 Codex 专属工具页 `src/components/codex/CodexUsagePage.tsx`,数据源复用 `useSubscriptionQuota("codex", true, true, 5)`,仍读取本机 Codex 登录状态,不兑换 reset、不修改账号、不新增平台限定逻辑。
2323
- 入口在主界面切到 Codex 后的顶部工具栏:`Codex 多模型路由` 图标旁边的 `Codex 用量与重置额度`(柱状图)按钮。`src/App.tsx``codexUsage` view 已加入 `VALID_VIEWS`,但切到非 Codex app 时会回退 providers,避免 Codex 专属工具跨 app 残留。
24-
- 该页面必须保留显式引导和 CCSwitchMulti UI 匹配:顶部使用和 `CodexRouterWorkspacePage` 一致的渐变工作台 header,正文用蓝色引导卡说明“从 Codex 工具栏进入 -> 刷新当前登录 -> 按窗口和 reset 到期决策”,普通区块使用 `border border-border bg-card` / dark slate 边界。不要退回成外部 dashboard 风格或只展示数字
24+
- 该页面必须保留显式引导和 CCSwitchMulti UI 匹配:顶部使用和 `CodexRouterWorkspacePage` 一致的渐变工作台 header,正文用蓝色引导卡说明“从 Codex 工具栏进入 -> 刷新当前登录 -> 按窗口和 reset 到期决策”。配色必须维护浅色/深色两套 surface token(当前集中在 `USAGE_PAGE_COLORS` / `READING_HINT_COLORS`),不要只用单套颜色叠 `dark:` 或退回成外部 dashboard 风格
2525
- 回归覆盖:`tests/integration/App.test.tsx` 固定 Codex 工具栏入口能打开 `CodexUsagePage``src/components/codex/CodexUsagePage.test.tsx` 固定成功数据、banked reset credits、reset 明细部分失败和无凭据状态不会空白。
2626

2727
## 2026-07-06 Codex 127.0.0.1:15721 502 VPN/Proxy Boundary

src/components/codex/CodexUsagePage.tsx

Lines changed: 89 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,35 @@ interface UsageReadingHintProps {
4747
tone: "ok" | "warn" | "info";
4848
}
4949

50+
/** 页面级配色:每个 surface 都显式维护 light 与 dark 两套颜色,避免主题切换时只继承单套颜色。 */
51+
const USAGE_PAGE_COLORS = {
52+
shell:
53+
"border-slate-200 bg-white text-slate-950 dark:border-slate-700/80 dark:bg-slate-950/30 dark:text-slate-100",
54+
header:
55+
"bg-gradient-to-r from-sky-50 via-white to-emerald-50 dark:from-blue-950/45 dark:via-slate-900 dark:to-emerald-950/30",
56+
chip: "border-slate-200 bg-white/80 text-slate-600 dark:border-slate-700/80 dark:bg-slate-950/40 dark:text-slate-300",
57+
guide:
58+
"border-sky-200 bg-sky-50/80 text-sky-950 dark:border-blue-700/40 dark:bg-blue-950/15 dark:text-blue-100",
59+
guideStep:
60+
"border-sky-200 bg-white/90 text-slate-950 dark:border-blue-700/40 dark:bg-slate-950/40 dark:text-slate-100",
61+
card: "border-slate-200 bg-white text-slate-950 dark:border-slate-700/80 dark:bg-slate-950/30 dark:text-slate-100",
62+
inset:
63+
"border-slate-200 bg-slate-50 text-slate-950 dark:border-slate-700/70 dark:bg-slate-950/40 dark:text-slate-100",
64+
progressTrack: "bg-slate-200 dark:bg-slate-800",
65+
warning:
66+
"border-amber-200 bg-amber-50 text-amber-950 dark:border-amber-800 dark:bg-amber-950/30 dark:text-amber-100",
67+
warningButton:
68+
"border-amber-300 bg-white text-amber-950 hover:bg-amber-100 dark:border-amber-700 dark:bg-slate-950/40 dark:text-amber-100 dark:hover:bg-amber-900/40",
69+
resetSummary: "bg-sky-100 text-sky-800 dark:bg-sky-500/10 dark:text-sky-300",
70+
};
71+
72+
/** 读数提示卡配色:ok/warn/info 各自维护浅色和深色配色。 */
73+
const READING_HINT_COLORS: Record<UsageReadingHintProps["tone"], string> = {
74+
ok: "border-emerald-200 bg-emerald-50 text-emerald-950 dark:border-emerald-700/40 dark:bg-emerald-950/20 dark:text-emerald-100",
75+
warn: "border-amber-200 bg-amber-50 text-amber-950 dark:border-amber-700/40 dark:bg-amber-950/20 dark:text-amber-100",
76+
info: "border-sky-200 bg-sky-50 text-sky-950 dark:border-blue-700/40 dark:bg-blue-950/20 dark:text-blue-100",
77+
};
78+
5079
/** 将百分比限制在进度条可安全渲染的 0-100 区间。 */
5180
function clampPercent(value: number): number {
5281
if (!Number.isFinite(value)) return 0;
@@ -164,8 +193,10 @@ function getVisibleTiers(quota: SubscriptionQuota | undefined): QuotaTier[] {
164193

165194
/** 引导步骤条目:说明从哪里进入页面以及读数顺序。 */
166195
const GuideStep: React.FC<GuideStepProps> = ({ index, title, detail }) => (
167-
<div className="flex gap-3 rounded-lg border border-blue-200 bg-card p-3 text-sm dark:border-blue-700/40 dark:bg-slate-950/40">
168-
<div className="flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-blue-600 text-xs font-semibold text-white">
196+
<div
197+
className={`flex gap-3 rounded-lg border p-3 text-sm ${USAGE_PAGE_COLORS.guideStep}`}
198+
>
199+
<div className="flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-sky-600 text-xs font-semibold text-white dark:bg-blue-500">
169200
{index}
170201
</div>
171202
<div className="min-w-0">
@@ -183,15 +214,8 @@ const UsageReadingHint: React.FC<UsageReadingHintProps> = ({
183214
detail,
184215
tone,
185216
}) => {
186-
const toneClass =
187-
tone === "ok"
188-
? "border-emerald-200 bg-emerald-50/70 text-emerald-900 dark:border-emerald-700/40 dark:bg-emerald-950/20 dark:text-emerald-100"
189-
: tone === "warn"
190-
? "border-amber-200 bg-amber-50/70 text-amber-900 dark:border-amber-700/40 dark:bg-amber-950/20 dark:text-amber-100"
191-
: "border-blue-200 bg-blue-50/70 text-blue-900 dark:border-blue-700/40 dark:bg-blue-950/20 dark:text-blue-100";
192-
193217
return (
194-
<div className={`rounded-lg border px-3 py-2 ${toneClass}`}>
218+
<div className={`rounded-lg border px-3 py-2 ${READING_HINT_COLORS[tone]}`}>
195219
<div className="text-xs font-semibold">{title}</div>
196220
<div className="mt-1 text-xs leading-5 opacity-80">{detail}</div>
197221
</div>
@@ -200,8 +224,8 @@ const UsageReadingHint: React.FC<UsageReadingHintProps> = ({
200224

201225
/** 页面引导区:补齐入口、刷新和读数判断,避免用户只看到一组静态数字。 */
202226
const UsageGuidePanel: React.FC = () => (
203-
<section className="rounded-lg border border-blue-200 bg-blue-50/70 p-4 dark:border-blue-700/40 dark:bg-blue-950/15">
204-
<div className="mb-3 flex items-center gap-2 text-sm font-semibold text-blue-900 dark:text-blue-100">
227+
<section className={`rounded-lg border p-4 ${USAGE_PAGE_COLORS.guide}`}>
228+
<div className="mb-3 flex items-center gap-2 text-sm font-semibold">
205229
<Info className="h-4 w-4" />
206230
使用引导
207231
</div>
@@ -249,7 +273,7 @@ const UsageWindowCard: React.FC<UsageWindowCardProps> = ({ tier }) => {
249273
const label = TIER_LABELS[tier.name] ?? tier.name;
250274

251275
return (
252-
<section className="rounded-lg border border-border bg-card p-4 dark:border-slate-700/80 dark:bg-slate-950/30">
276+
<section className={`rounded-lg border p-4 ${USAGE_PAGE_COLORS.card}`}>
253277
<div className="mb-4 flex items-start justify-between gap-3">
254278
<div>
255279
<div className="text-sm font-semibold text-foreground">{label}</div>
@@ -265,7 +289,9 @@ const UsageWindowCard: React.FC<UsageWindowCardProps> = ({ tier }) => {
265289
</div>
266290
</div>
267291

268-
<div className="h-2 overflow-hidden rounded-full bg-muted dark:bg-slate-800">
292+
<div
293+
className={`h-2 overflow-hidden rounded-full ${USAGE_PAGE_COLORS.progressTrack}`}
294+
>
269295
<div
270296
className={`h-full rounded-full transition-all ${capacityBarTone(used)}`}
271297
style={{ width: `${used}%` }}
@@ -287,7 +313,9 @@ const ResetCreditRow: React.FC<ResetCreditRowProps> = ({ credit, index }) => {
287313
const urgency = resetCreditUrgency(credit.expiresAt);
288314

289315
return (
290-
<div className="grid grid-cols-[minmax(0,1fr)_auto_auto] items-center gap-3 rounded-lg border border-border bg-background px-3 py-2 text-sm dark:border-slate-700/70 dark:bg-slate-950/40">
316+
<div
317+
className={`grid grid-cols-[minmax(0,1fr)_auto_auto] items-center gap-3 rounded-lg border px-3 py-2 text-sm ${USAGE_PAGE_COLORS.inset}`}
318+
>
291319
<div className="min-w-0">
292320
<div className="truncate font-medium text-foreground">
293321
{credit.title || `Reset ${index + 1}`}
@@ -314,7 +342,9 @@ function renderQuotaProblem(
314342
): React.ReactNode {
315343
if (loading && !quota) {
316344
return (
317-
<div className="rounded-lg border border-border bg-card p-6 text-sm text-muted-foreground dark:border-slate-700/80 dark:bg-slate-950/30">
345+
<div
346+
className={`rounded-lg border p-6 text-sm text-muted-foreground ${USAGE_PAGE_COLORS.card}`}
347+
>
318348
正在读取本机 Codex 登录状态...
319349
</div>
320350
);
@@ -336,19 +366,21 @@ function renderQuotaProblem(
336366
"请确认 Codex Desktop / CLI 已登录,然后刷新。";
337367

338368
return (
339-
<section className="rounded-lg border border-amber-200 bg-amber-50 p-5 text-sm text-amber-900 shadow-sm dark:border-amber-800 dark:bg-amber-950/30 dark:text-amber-100">
369+
<section
370+
className={`rounded-lg border p-5 text-sm shadow-sm ${USAGE_PAGE_COLORS.warning}`}
371+
>
340372
<div className="mb-3 flex items-center gap-2 font-semibold">
341373
<AlertCircle className="h-4 w-4" />
342374
{title}
343375
</div>
344-
<p className="mb-4 text-amber-800 dark:text-amber-200">{detail}</p>
376+
<p className="mb-4 opacity-90">{detail}</p>
345377
<Button
346378
type="button"
347379
onClick={refetch}
348380
disabled={loading}
349381
size="sm"
350382
variant="outline"
351-
className="gap-2 border-amber-300 hover:bg-amber-100 dark:border-amber-700 dark:hover:bg-amber-900/40"
383+
className={`gap-2 ${USAGE_PAGE_COLORS.warningButton}`}
352384
>
353385
<RefreshCw className={`h-3.5 w-3.5 ${loading ? "animate-spin" : ""}`} />
354386
重新刷新
@@ -377,11 +409,15 @@ export const CodexUsagePage: React.FC = () => {
377409

378410
return (
379411
<div className="mx-auto flex w-full max-w-6xl flex-col gap-4 px-6 py-6">
380-
<section className="overflow-hidden rounded-lg border border-border bg-card dark:border-slate-700/80 dark:bg-slate-950/30">
381-
<div className="flex flex-col gap-4 bg-gradient-to-r from-blue-50 via-background to-emerald-50 px-4 py-3 dark:from-blue-950/45 dark:via-slate-900 dark:to-emerald-950/30 lg:flex-row lg:items-center lg:justify-between">
412+
<section
413+
className={`overflow-hidden rounded-lg border ${USAGE_PAGE_COLORS.shell}`}
414+
>
415+
<div
416+
className={`flex flex-col gap-4 px-4 py-3 lg:flex-row lg:items-center lg:justify-between ${USAGE_PAGE_COLORS.header}`}
417+
>
382418
<div className="min-w-0 space-y-2">
383419
<div className="flex items-center gap-2 text-base font-semibold">
384-
<Gauge className="h-4 w-4 text-blue-600 dark:text-blue-300" />
420+
<Gauge className="h-4 w-4 text-sky-600 dark:text-blue-300" />
385421
Codex 用量与重置额度
386422
</div>
387423
<p className="max-w-4xl text-xs leading-5 text-muted-foreground dark:text-slate-400">
@@ -390,22 +426,30 @@ export const CodexUsagePage: React.FC = () => {
390426
配置。
391427
</p>
392428
<div className="flex flex-wrap items-center gap-2 text-xs text-muted-foreground">
393-
<span className="inline-flex items-center gap-1 rounded-md border border-border bg-background px-2 py-1 dark:border-slate-700/80 dark:bg-slate-950/40">
429+
<span
430+
className={`inline-flex items-center gap-1 rounded-md border px-2 py-1 ${USAGE_PAGE_COLORS.chip}`}
431+
>
394432
Codex 工具栏
395433
<ArrowRight className="h-3 w-3" />
396434
柱状图按钮
397435
</span>
398-
<span className="inline-flex items-center gap-1 rounded-md border border-border bg-background px-2 py-1 dark:border-slate-700/80 dark:bg-slate-950/40">
436+
<span
437+
className={`inline-flex items-center gap-1 rounded-md border px-2 py-1 ${USAGE_PAGE_COLORS.chip}`}
438+
>
399439
只读查询
400440
</span>
401-
<span className="inline-flex items-center gap-1 rounded-md border border-border bg-background px-2 py-1 dark:border-slate-700/80 dark:bg-slate-950/40">
441+
<span
442+
className={`inline-flex items-center gap-1 rounded-md border px-2 py-1 ${USAGE_PAGE_COLORS.chip}`}
443+
>
402444
自动 5 分钟刷新
403445
</span>
404446
</div>
405447
</div>
406448

407449
<div className="flex flex-wrap items-center gap-3 text-sm">
408-
<div className="inline-flex items-center gap-2 rounded-lg border border-border bg-background px-3 py-2 text-muted-foreground dark:border-slate-700/80 dark:bg-slate-950/40">
450+
<div
451+
className={`inline-flex items-center gap-2 rounded-lg border px-3 py-2 ${USAGE_PAGE_COLORS.chip}`}
452+
>
409453
<Clock className="h-4 w-4" />
410454
{formatCheckedAt(quota?.queriedAt)}
411455
</div>
@@ -414,7 +458,7 @@ export const CodexUsagePage: React.FC = () => {
414458
onClick={() => void refetch()}
415459
disabled={isFetching}
416460
size="sm"
417-
className="gap-2 bg-blue-600 hover:bg-blue-500"
461+
className="gap-2 bg-sky-600 hover:bg-sky-500 dark:bg-blue-600 dark:hover:bg-blue-500"
418462
>
419463
<RefreshCw
420464
className={`h-4 w-4 ${isFetching ? "animate-spin" : ""}`}
@@ -436,21 +480,27 @@ export const CodexUsagePage: React.FC = () => {
436480
<UsageWindowCard key={tier.name} tier={tier} />
437481
))}
438482
{visibleTiers.length === 0 && (
439-
<section className="rounded-lg border border-border bg-card p-5 text-sm text-muted-foreground dark:border-slate-700/80 dark:bg-slate-950/30 lg:col-span-2">
483+
<section
484+
className={`rounded-lg border p-5 text-sm text-muted-foreground lg:col-span-2 ${USAGE_PAGE_COLORS.card}`}
485+
>
440486
Codex 没有返回 5 小时或每周用量窗口。
441487
</section>
442488
)}
443489
</div>
444490

445-
<section className="rounded-lg border border-border bg-card p-5 dark:border-slate-700/80 dark:bg-slate-950/30">
491+
<section
492+
className={`rounded-lg border p-5 ${USAGE_PAGE_COLORS.card}`}
493+
>
446494
<div className="mb-4 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
447495
<div className="flex items-center gap-2">
448496
<RotateCcw className="h-4 w-4 text-sky-600 dark:text-sky-400" />
449497
<h3 className="text-base font-semibold text-foreground">
450498
已存 reset 额度
451499
</h3>
452500
</div>
453-
<div className="inline-flex items-center gap-2 rounded-lg bg-sky-500/10 px-3 py-2 text-sm font-semibold text-sky-700 dark:text-sky-300">
501+
<div
502+
className={`inline-flex items-center gap-2 rounded-lg px-3 py-2 text-sm font-semibold ${USAGE_PAGE_COLORS.resetSummary}`}
503+
>
454504
<TimerReset className="h-4 w-4" />
455505
{availableCount} 个可用
456506
</div>
@@ -466,21 +516,27 @@ export const CodexUsagePage: React.FC = () => {
466516
/>
467517
))}
468518
{missingExpiryCount > 0 && (
469-
<div className="rounded-lg border border-dashed border-border bg-background px-3 py-2 text-sm text-muted-foreground dark:border-slate-700/70 dark:bg-slate-950/40">
519+
<div
520+
className={`rounded-lg border border-dashed px-3 py-2 text-sm text-muted-foreground ${USAGE_PAGE_COLORS.inset}`}
521+
>
470522
还有 {missingExpiryCount} 个可用 reset
471523
没有返回可展示的到期明细。
472524
</div>
473525
)}
474526
</div>
475527
) : (
476-
<div className="flex items-center gap-2 rounded-lg border border-border bg-background px-3 py-3 text-sm text-muted-foreground dark:border-slate-700/70 dark:bg-slate-950/40">
528+
<div
529+
className={`flex items-center gap-2 rounded-lg border px-3 py-3 text-sm text-muted-foreground ${USAGE_PAGE_COLORS.inset}`}
530+
>
477531
<CheckCircle2 className="h-4 w-4 text-emerald-600 dark:text-emerald-400" />
478532
当前没有已存 reset 额度。
479533
</div>
480534
)}
481535

482536
{quota.resetCreditsError && (
483-
<div className="mt-3 rounded-lg border border-amber-200 bg-amber-50 px-3 py-2 text-sm text-amber-800 dark:border-amber-800 dark:bg-amber-950/30 dark:text-amber-200">
537+
<div
538+
className={`mt-3 rounded-lg border px-3 py-2 text-sm ${USAGE_PAGE_COLORS.warning}`}
539+
>
484540
Reset credit 明细读取不完整:{quota.resetCreditsError}
485541
</div>
486542
)}

0 commit comments

Comments
 (0)