Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 75 additions & 41 deletions apps/web/src/components/usage/UsageLimitsPooled.browser.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type { LimitPresentations } from "@t3tools/shared/usageLimits";
import { act } from "react";
import { createRoot, type Root } from "react-dom/client";
import { afterEach, beforeEach, expect, it, vi } from "vite-plus/test";
import { userEvent } from "vite-plus/test/browser";
import { page, userEvent } from "vite-plus/test/browser";

// Real pointer clicks need the dialog's stacking styles to land above Base UI's modal overlay.
import "~/index.css";
Expand All @@ -26,47 +26,48 @@ vi.mock("../settings/RedactedSensitiveText", () => ({ RedactedSensitiveText: ()

import { UsageLimitsPooled } from "./UsageLimitsPooled";

const presentations: LimitPresentations = new Map([
[
EnvironmentId.make("env-a"),
{
entry: { target: { label: "Test" } },
serverConfig: {
usageLimitSources: [],
providers: [
{
instanceId: ProviderInstanceId.make("alpha"),
driver: ProviderDriverKind.make("codex"),
displayName: "Alpha",
enabled: true,
installed: true,
version: null,
status: "ready",
auth: { status: "authenticated", email: "alpha@example.com" },
checkedAt: "2026-09-03T11:00:00Z",
models: [],
slashCommands: [],
skills: [],
usageLimits: {
const presentations = (resetsAt: string | undefined, availableCount: number): LimitPresentations =>
new Map([
[
EnvironmentId.make("env-a"),
{
entry: { target: { label: "Test" } },
serverConfig: {
usageLimitSources: [],
providers: [
{
instanceId: ProviderInstanceId.make("alpha"),
driver: ProviderDriverKind.make("codex"),
displayName: "Alpha",
enabled: true,
installed: true,
version: null,
status: "ready",
auth: { status: "authenticated", email: "alpha@example.com" },
checkedAt: "2026-09-03T11:00:00Z",
resetCredits: { availableCount: 1 },
windows: [
{
id: "five_hour",
kind: "session",
label: "Session",
usedPercent: 40,
windowDurationMins: 300,
resetsAt: "2026-09-03T14:00:00Z",
},
],
},
} satisfies ServerProvider,
],
models: [],
slashCommands: [],
skills: [],
usageLimits: {
checkedAt: "2026-09-03T11:00:00Z",
resetCredits: { availableCount },
windows: [
{
id: "five_hour",
kind: "session",
label: "Session",
usedPercent: resetsAt ? 40 : 0,
windowDurationMins: 300,
...(resetsAt ? { resetsAt } : {}),
},
],
},
} satisfies ServerProvider,
],
},
},
},
],
]);
],
]);

let root: Root;
let container: HTMLDivElement;
Expand All @@ -77,7 +78,11 @@ beforeEach(async () => {
container = document.createElement("div");
document.body.append(container);
root = createRoot(container);
await act(() => root.render(<UsageLimitsPooled presentations={presentations} now={0} />));
await act(() =>
root.render(
<UsageLimitsPooled presentations={presentations("2026-09-03T14:00:00Z", 1)} now={0} />,
),
);
});

afterEach(async () => {
Expand Down Expand Up @@ -130,3 +135,32 @@ it.each([
await act(async () => answer(result));
expect(status?.textContent).toBe(`Alpha ${text}`);
});

it("omits an empty reset plate but keeps countdowns and banked credits visible", async () => {
await page.viewport(1440, 900);
const now = Date.parse("2026-09-03T11:00:00Z");
const segment = page.getByRole("button", { name: /^Alpha:/ });
// The empty plate has no accessible text; select its painted background inside the real bar.
const plate = () =>
segment.element().querySelector<HTMLSpanElement>('[class~="bg-background/85"]');

await act(() =>
root.render(<UsageLimitsPooled presentations={presentations(undefined, 0)} now={now} />),
);
await expect.element(segment.getByText("100%", { exact: true })).toBeVisible();
expect(plate()).toBeNull();

await act(() =>
root.render(
<UsageLimitsPooled presentations={presentations("2026-09-03T14:00:00Z", 0)} now={now} />,
),
);
await expect.element(plate()).toBeVisible();
expect(plate()?.textContent).toBe("↻ 3h 0m");

await act(() =>
root.render(<UsageLimitsPooled presentations={presentations(undefined, 1)} now={now} />),
);
await expect.element(plate()).toBeVisible();
expect(plate()?.textContent).toBe("1");
});
32 changes: 17 additions & 15 deletions apps/web/src/components/usage/UsageLimitsPooled.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -268,22 +268,24 @@ function PoolSegment({
/>
) : null}
<span className="shrink-0 font-semibold text-foreground tabular-nums">{remaining}%</span>
<span className="ms-auto flex shrink-0 items-center gap-1.5 rounded-sm bg-background/85 px-1.5 py-0.5 text-2xs text-foreground tabular-nums">
{resetsIn?.replace("resets in ", "↻ ") ?? ""}
{credits ? (
<>
{resetsIn ? (
<span aria-hidden className="text-muted-foreground">
·
{resetsIn || credits ? (
<span className="ms-auto flex shrink-0 items-center gap-1.5 rounded-sm bg-background/85 px-1.5 py-0.5 text-2xs text-foreground tabular-nums">
{resetsIn?.replace("resets in ", "↻ ") ?? ""}
{credits ? (
<>
{resetsIn ? (
<span aria-hidden className="text-muted-foreground">
·
</span>
) : null}
<span aria-hidden className="inline-flex items-center gap-0.5 font-semibold">
<TicketIcon className="size-3" aria-hidden />
{credits}
</span>
) : null}
<span aria-hidden className="inline-flex items-center gap-0.5 font-semibold">
<TicketIcon className="size-3" aria-hidden />
{credits}
</span>
</>
) : null}
</span>
</>
) : null}
</span>
) : null}
</div>
</PopoverTrigger>
<LegendRow account={account} window={window} color={color} now={now} index={index} />
Expand Down
Loading