diff --git a/apps/desktop-tauri/src/floatbar/FloatBar.test.tsx b/apps/desktop-tauri/src/floatbar/FloatBar.test.tsx index 3c03116d93..e91fe8b97b 100644 --- a/apps/desktop-tauri/src/floatbar/FloatBar.test.tsx +++ b/apps/desktop-tauri/src/floatbar/FloatBar.test.tsx @@ -41,6 +41,7 @@ function rateWindow( used: number, opts: { exhausted?: boolean; + informational?: boolean; resetsAt?: string | null; resetDescription?: string | null; } = {}, @@ -52,6 +53,7 @@ function rateWindow( resetsAt: opts.resetsAt ?? null, resetDescription: opts.resetDescription ?? null, isExhausted: opts.exhausted ?? false, + isInformational: opts.informational ?? false, reservePercent: null, reserveDescription: null, }; @@ -214,6 +216,51 @@ describe("FloatBar", () => { expect(titles[1]).toMatch(/Claude: 20% used/); }); + it("uses the secondary window when the primary window is informational", async () => { + const codex = snapshot("codex", "Codex", 0); + codex.primary = rateWindow(0, { + informational: true, + resetDescription: "No active 5h session", + }); + codex.secondary = rateWindow(26, { + resetDescription: "Weekly reset", + }); + tauriMocks.getCachedProviders.mockResolvedValue([ + snapshot("claude", "Claude", 20), + codex, + ]); + tauriMocks.getSettingsSnapshot.mockResolvedValue( + settings({ floatBarShowResetInline: true }), + ); + + const { container } = renderFloatBar( + bootstrap({ floatBarShowResetInline: true }), + ); + + await waitFor(() => { + const pills = container.querySelectorAll(".floatbar__pill"); + expect(pills.length).toBe(2); + expect(pills[0].getAttribute("title")).toContain("Codex: 26% used"); + expect(pills[0].getAttribute("title")).toContain("Weekly reset"); + expect(pills[0].textContent).toContain("26%"); + expect(pills[0].textContent).not.toContain("No active 5h session"); + }); + }); + + it("uses the secondary window when calculating the pill tone", async () => { + const codex = snapshot("codex", "Codex", 0); + codex.primary = rateWindow(0, { informational: true }); + codex.secondary = rateWindow(20, { exhausted: true }); + tauriMocks.getCachedProviders.mockResolvedValue([codex]); + tauriMocks.getSettingsSnapshot.mockResolvedValue(settings()); + + const { container } = renderFloatBar(bootstrap()); + + await waitFor(() => { + expect(container.querySelector(".floatbar__pill--crit")).not.toBeNull(); + }); + }); + it("loads local cost summaries without using the foreground chart endpoint", async () => { tauriMocks.getCachedProviders.mockResolvedValue([ snapshot("codex", "Codex", 75), diff --git a/apps/desktop-tauri/src/floatbar/FloatBar.tsx b/apps/desktop-tauri/src/floatbar/FloatBar.tsx index 8536fa3c39..993892da3e 100644 --- a/apps/desktop-tauri/src/floatbar/FloatBar.tsx +++ b/apps/desktop-tauri/src/floatbar/FloatBar.tsx @@ -23,6 +23,7 @@ import type { BootstrapState, ProviderLocalUsageSummary, ProviderUsageSnapshot, + RateWindowSnapshot, SettingsSnapshot, } from "../types/bridge"; import { FLOAT_BAR_CONFIG_CHANGED_EVENT, resizeFloatBar } from "./api"; @@ -84,6 +85,12 @@ function providerCostKey(provider: ProviderUsageSnapshot): string { return `${provider.providerId}:${provider.accountEmail ?? ""}`; } +function floatBarRateWindow(provider: ProviderUsageSnapshot): RateWindowSnapshot { + return provider.primary.isInformational && provider.secondary + ? provider.secondary + : provider.primary; +} + function hasLocalCost(summary: ProviderLocalUsageSummary | null): summary is ProviderLocalUsageSummary { return summary?.todayCost != null || summary?.thirtyDayCost != null; } @@ -178,11 +185,12 @@ function ProviderPill({ usedSuffix: string; remainingSuffix: string; }) { - const remaining = Math.max(0, Math.min(100, provider.primary.remainingPercent)); - const used = Math.max(0, Math.min(100, provider.primary.usedPercent)); + const rateWindow = floatBarRateWindow(provider); + const remaining = Math.max(0, Math.min(100, rateWindow.remainingPercent)); + const used = Math.max(0, Math.min(100, rateWindow.usedPercent)); const displayPercent = showAsUsed ? used : remaining; const displaySuffix = showAsUsed ? usedSuffix : remainingSuffix; - const exhausted = provider.primary.isExhausted || provider.error; + const exhausted = rateWindow.isExhausted || provider.error; let tone: "ok" | "warn" | "crit" = "ok"; if (exhausted || remaining <= critRemaining) tone = "crit"; else if (remaining <= highRemaining) tone = "warn"; @@ -190,8 +198,8 @@ function ProviderPill({ const brand = getProviderIcon(provider.providerId).brandColor; const label = provider.error ? "—" : `${Math.round(displayPercent)}%`; const resetText = useFormattedResetTime( - provider.primary.resetsAt, - provider.primary.resetDescription, + rateWindow.resetsAt, + rateWindow.resetDescription, resetRelative, ); const resetSuffix = resetText ? `\n${resetText}` : ""; @@ -308,7 +316,9 @@ export default function FloatBar({ state }: { state: BootstrapState }) { const wanted = new Set(filterIds); list = list.filter((p) => wanted.has(p.providerId)); } - return [...list].sort((a, b) => b.primary.usedPercent - a.primary.usedPercent); + return [...list].sort( + (a, b) => floatBarRateWindow(b).usedPercent - floatBarRateWindow(a).usedPercent, + ); }, [providers, settings.enabledProviders, filterIds]); const visibleCostTargets = useMemo(