Skip to content
Closed
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
212 changes: 133 additions & 79 deletions apps/web/src/components/usage/UsageLimitsPooled.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,37 @@ function AccountName({
);
}

/**
* "<provider> · <instance>", so two accounts on one provider are told apart by
* the name their owner gave them. An account with no name of its own would
* render the provider label twice, so it keeps the provider label alone.
*/
function PoolRowName({
account,
providerLabel,
className,
}: {
readonly account: LimitAccount;
readonly providerLabel: string;
readonly className?: string;
}) {
// An unnamed instance carries the provider's own name, which would print twice.
const named = account.displayName
? account.displayName.toLowerCase() !== providerLabel.toLowerCase()
: account.email !== undefined;
return (
<span className={className}>
{providerLabel}
{named ? (
<>
<span className="text-muted-foreground"> · </span>
<AccountName account={account} className="min-w-0" />
</>
) : null}
</span>
);
}

function Row({ label, children }: { readonly label: string; readonly children: ReactNode }) {
return (
<div className="grid grid-cols-[4.5rem_minmax(0,1fr)] gap-x-3">
Expand Down Expand Up @@ -224,6 +255,7 @@ function SegmentPopover({
*/
function PoolSegment({
account,
providerLabel,
window,
reset,
color,
Expand All @@ -232,6 +264,7 @@ function PoolSegment({
showAccountName,
}: {
readonly account: LimitAccount;
readonly providerLabel: string;
readonly window: LimitPoolMember["window"];
readonly reset: LimitPoolWindow["resets"][number] | undefined;
readonly color: string;
Expand All @@ -246,68 +279,79 @@ function PoolSegment({
const credits = account.limits.resetCredits?.availableCount ?? 0;
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger
openOnHover
render={
<button
type="button"
style={{ gridColumn: index, gridRow: 1 }}
aria-label={`${account.displayName ?? (account.email ? accountInitials(account.email) : account.driver)}: ${remaining}% left${resetsIn ? `, ${resetsIn}` : ""}${credits ? `, ${credits} reset ${credits === 1 ? "credit" : "credits"} banked` : ""}`}
className="relative h-5 min-w-0 cursor-pointer overflow-hidden rounded-md bg-muted text-start outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background data-[popup-open]:ring-1 data-[popup-open]:ring-border @2xl/pool:h-8"
/>
}
>
{/* Translucent so the label reads over the fill for any provider colour and theme. */}
<div
aria-hidden
className="absolute inset-y-0 left-0 rounded-md opacity-35"
style={{ width: `${remaining}%`, backgroundColor: color }}
/>
{/* The spent share is hatched, not blank: it is what the countdown restores. */}
{remaining < 100 && reset ? (
<div className="flex min-w-0 flex-col">
<PopoverTrigger
openOnHover
render={
<button
type="button"
aria-label={`${account.displayName ?? (account.email ? accountInitials(account.email) : account.driver)}: ${remaining}% left${resetsIn ? `, ${resetsIn}` : ""}${credits ? `, ${credits} reset ${credits === 1 ? "credit" : "credits"} banked` : ""}`}
className="relative h-5 min-w-0 cursor-pointer overflow-hidden rounded-md bg-muted text-start outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background data-[popup-open]:ring-1 data-[popup-open]:ring-border @2xl/pool:h-8"
/>
}
>
{/* Translucent so the label reads over the fill for any provider colour and theme. */}
<div
aria-hidden
className="absolute inset-y-0 right-0 opacity-20"
style={{
width: `${100 - remaining}%`,
backgroundImage: `repeating-linear-gradient(135deg, ${color} 0 1px, transparent 1px 5px)`,
}}
className="absolute inset-y-0 left-0 rounded-md opacity-35"
style={{ width: `${remaining}%`, backgroundColor: color }}
/>
) : null}
<span
aria-hidden
className="absolute inset-0 flex items-center justify-center text-3xs leading-none font-semibold text-foreground/80 tabular-nums @2xl/pool:hidden"
>
{index}
</span>
<div className="relative hidden h-full min-w-0 items-center gap-1.5 px-2 text-xs @2xl/pool:flex">
{showAccountName ? (
<AccountName
account={account}
className="min-w-0 truncate font-medium text-foreground"
{/* The spent share is hatched, not blank: it is what the countdown restores. */}
{remaining < 100 && reset ? (
<div
aria-hidden
className="absolute inset-y-0 right-0 opacity-20"
style={{
width: `${100 - remaining}%`,
backgroundImage: `repeating-linear-gradient(135deg, ${color} 0 1px, transparent 1px 5px)`,
}}
/>
) : null}
<span className="shrink-0 font-semibold text-foreground tabular-nums">{remaining}%</span>
{/* Countdown and badge get their own plate: fill and hatching run under them otherwise. */}
<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="absolute inset-0 flex items-center justify-center text-3xs leading-none font-semibold text-foreground/80 tabular-nums @2xl/pool:hidden"
>
{index}
</span>
</div>
</PopoverTrigger>
<LegendRow account={account} window={window} color={color} now={now} index={index} />
<div className="relative hidden h-full min-w-0 items-center gap-1.5 px-2 text-xs @2xl/pool:flex">
{showAccountName ? (
<PoolRowName
account={account}
providerLabel={providerLabel}
className="min-w-0 truncate font-medium text-foreground"
/>
) : null}
<span className="shrink-0 font-semibold text-foreground tabular-nums">
{remaining}%
</span>
{/* Countdown and badge get their own plate: fill and hatching run under them otherwise. */}
<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>
</div>
</PopoverTrigger>
<LegendRow
account={account}
providerLabel={providerLabel}
window={window}
color={color}
now={now}
index={index}
/>
</div>
{account.redeem ? (
<RedeemableSegmentPopup
account={account}
Expand Down Expand Up @@ -340,12 +384,14 @@ function PoolSegment({
*/
function LegendRow({
account,
providerLabel,
window,
color,
now,
index,
}: {
readonly account: LimitAccount;
readonly providerLabel: string;
readonly window: LimitPoolMember["window"];
readonly color: string;
readonly now: number;
Expand All @@ -356,7 +402,6 @@ function LegendRow({
const credits = account.limits.resetCredits?.availableCount ?? 0;
return (
<PopoverTrigger
style={{ gridColumn: "1 / -1", gridRow: index + 1 }}
render={<Button variant="ghost" size="compact" />}
className="min-w-0 @2xl/pool:hidden"
>
Expand All @@ -369,7 +414,11 @@ function LegendRow({
<span className="sr-only">Segment </span>
<span className="relative">{index}</span>
</span>
<AccountName account={account} className="min-w-0 truncate font-medium text-foreground" />
<PoolRowName
account={account}
providerLabel={providerLabel}
className="min-w-0 truncate font-medium text-foreground"
/>
<span className="shrink-0 font-semibold text-foreground tabular-nums">{remaining}%</span>
<span className="ms-auto flex shrink-0 items-center gap-1.5 text-2xs text-muted-foreground tabular-nums">
{resetsIn?.replace("resets in ", "↻ ") ?? ""}
Expand Down Expand Up @@ -451,35 +500,37 @@ function RedeemableSegmentPopup({
*/
function PoolBar({
pool,
providerLabel,
color,
now,
}: {
readonly pool: LimitPoolWindow;
readonly providerLabel: string;
readonly color: string;
readonly now: number;
}) {
const restores = new Map(pool.resets.map((reset) => [reset.member.account.key, reset]));
return (
<div className="@container/pool min-w-0">
<div
className="grid gap-x-1 gap-y-1"
style={{ gridTemplateColumns: `repeat(${pool.columns.length}, minmax(0, 1fr))` }}
>
{pool.columns.map((member, position) =>
member.window ? (
<PoolSegment
key={member.account.key}
account={member.account}
window={member.window}
reset={restores.get(member.account.key)}
color={color}
now={now}
index={position + 1}
showAccountName={pool.columns.length > 1}
/>
) : null,
)}
</div>
<div className="@container/pool flex min-w-0 flex-col gap-1">
{pool.columns.map((member, position) =>
member.window ? (
<PoolSegment
key={member.account.key}
account={member.account}
providerLabel={providerLabel}
window={member.window}
reset={restores.get(member.account.key)}
color={color}
now={now}
index={position + 1}
showAccountName={pool.columns.length > 1}
/>
) : (
// The account reports nothing in this window, but keeps its place:
// a row holds the same account across every card of this provider.
<div key={member.account.key} aria-hidden className="h-5 min-w-0 @2xl/pool:h-8" />

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium usage/UsageLimitsPooled.tsx:531

At narrow widths, an empty account row is shorter than a populated PoolSegment, so subsequent accounts shift upward and no longer align across cards. The placeholder needs to reserve the segment, LegendRow, and wrapper-gap height before the @2xl/pool breakpoint.

Suggested change
<div key={member.account.key} aria-hidden className="h-5 min-w-0 @2xl/pool:h-8" />
<div key={member.account.key} aria-hidden className="h-13 min-w-0 @2xl/pool:h-8" />
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/usage/UsageLimitsPooled.tsx around line 531:

At narrow widths, an empty account row is shorter than a populated `PoolSegment`, so subsequent accounts shift upward and no longer align across cards. The placeholder needs to reserve the segment, `LegendRow`, and wrapper-gap height before the `@2xl/pool` breakpoint.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid. At narrow widths a populated row is the h-5 bar plus the h-7 compact legend button, and the placeholder only reserved the bar. Fixed in 11f9d7b by mirroring both parts. The suggested h-13 would overshoot: the gap-1 between rows already applies to the placeholder too.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I'm unable to act on this request because you do not have permissions within this repository.

),
)}
</div>
);
}
Expand All @@ -490,12 +541,14 @@ function PoolBar({
*/
function PoolWindowCard({
pool,
providerLabel,
color,
now,
label,
description,
}: {
readonly pool: LimitPoolWindow;
readonly providerLabel: string;
readonly color: string;
readonly now: number;
readonly label?: string | undefined;
Expand All @@ -520,7 +573,7 @@ function PoolWindowCard({
</span>
) : null}
</div>
<PoolBar pool={pool} color={color} now={now} />
<PoolBar pool={pool} providerLabel={providerLabel} color={color} now={now} />
{description ? (
<p className="text-xs text-muted-foreground md:col-span-2">{description}</p>
) : null}
Expand Down Expand Up @@ -550,6 +603,7 @@ function PoolSection({ pool, now }: { readonly pool: LimitPool; readonly now: nu
<PoolWindowCard
key={`${window.kind}:${window.id}`}
pool={window}
providerLabel={label}
color={color}
now={now}
label={details?.label}
Expand Down
Loading