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
4 changes: 2 additions & 2 deletions pages/auth-files/__tests__/AuthFilesPage.files-table.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5045,7 +5045,7 @@ describe("AuthFilesPage files table", () => {
expect(await screen.findByText("Code: 5h")).toBeInTheDocument();
expect(screen.getByText("Code: Weekly")).toBeInTheDocument();
expect(screen.queryByText("Review: Weekly")).not.toBeInTheDocument();
expect(screen.getByText("0%")).toHaveClass("text-rose-700");
expect(screen.getByText("0%")).toHaveClass("text-rose-900");
});

test("cards view shows codex team subscription quota instead of empty stable placeholders", async () => {
Expand Down Expand Up @@ -5169,7 +5169,7 @@ describe("AuthFilesPage files table", () => {
expect(metrics[3]).toHaveAttribute("data-layout", "wide");

const previewZero = within(row as HTMLElement).getByText("0%");
expect(previewZero).toHaveClass("text-rose-700");
expect(previewZero).toHaveClass("text-rose-900");

// Every chip carries its own countdown inline, so hovering opens nothing.
for (const metric of metrics) {
Expand Down
2 changes: 1 addition & 1 deletion pages/auth-files/components/AuthFilesFilesTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1727,7 +1727,7 @@ export function AuthFilesFilesTab({
successRate === null
? "text-slate-500 dark:text-white/45"
: successRate >= 90
? "text-slate-700 dark:text-white/70"
? "text-emerald-700 dark:text-emerald-200"
: successRate >= 50
? "text-amber-700 dark:text-amber-200"
: "text-rose-700 dark:text-rose-200";
Expand Down
67 changes: 46 additions & 21 deletions pages/auth-files/components/QuotaMetricChips.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,24 @@ import { clampPercent, type QuotaItem } from "@features/quota-preview/quota-help

export type QuotaVisualTone = {
normalized: number | null;
/**
* Bar fill: a light tint plus a 2px rule down its right edge. The rule is
* what marks the percentage, which frees the tint to stay light enough to
* read text over.
*/
fillClass: string;
percentClass: string;
fillHex: string;
/** Chip surface (border + background) mirroring the percent tone. */
chipClass: string;
/** Muted label color that stays legible on the chip surface. */
chipLabelClass: string;
/** Bar track: border, plus the background showing left of the fill. */
barTrackClass: string;
/** Bar label, which sits over the fill and has to read against it. */
barLabelClass: string;
/** Bar countdown: quieter than the label, still legible over the fill. */
barMetaClass: string;
};

export const resolveQuotaVisualTone = (percent: number | null | undefined): QuotaVisualTone => {
Expand All @@ -19,54 +30,68 @@ export const resolveQuotaVisualTone = (percent: number | null | undefined): Quot
if (normalized === null) {
return {
normalized,
fillClass: "bg-slate-300/50 dark:bg-white/10",
fillClass:
"border-r-2 border-slate-300 bg-slate-100 dark:border-white/20 dark:bg-white/[0.07]",
percentClass: "text-slate-900 dark:text-white",
fillHex: "#cbd5e1",
chipClass: "border-slate-900/8 bg-slate-50 dark:border-white/10 dark:bg-white/[0.06]",
chipLabelClass: "text-slate-600 dark:text-white/70",
barTrackClass: "border-slate-200 bg-white dark:border-white/10 dark:bg-white/[0.03]",
barLabelClass: "text-slate-700 dark:text-white/80",
barMetaClass: "text-slate-500 dark:text-white/50",
};
}

// A healthy quota is the common case, so it gets no colour beyond the ring:
// tinting the chip surface, its label and its percent all at once made every
// card on the page read as one flat green field, and a green that is always
// on cannot signal anything. Amber and rose below keep their full treatment —
// they are the states worth spotting from across the grid.
// The bar's fill is the row's own background, so at 100% it covers the entire
// line — and repeated down a grid of cards, a saturated fill becomes a wall of
// colour that is tiring to look at and, being always on, signals nothing.
//
// So the tint stays light and a 2px rule at the fill's right edge carries the
// percentage instead. The edge marks the value more precisely than a block
// boundary does, and the label keeps its contrast because it is no longer
// sitting on a saturated ground.
if (normalized >= 60) {
return {
normalized,
// The card view spends this on renderQuotaBarNode's fill, which is the
// row's own background rather than a slim track — at 100% it covers the
// whole line, so an emerald fill turned every healthy row into a green
// band. fillHex below stays green: it drives the 14px ring, where the
// colour costs nothing.
fillClass: "bg-slate-400 dark:bg-white/30",
percentClass: "text-slate-700 dark:text-white/80",
fillClass:
"border-r-2 border-emerald-400 bg-emerald-100 dark:border-emerald-400/70 dark:bg-emerald-500/20",
percentClass: "text-emerald-900 dark:text-emerald-100",
fillHex: "#10b981",
chipClass: "border-slate-900/8 bg-slate-50 dark:border-white/10 dark:bg-white/[0.06]",
chipLabelClass: "text-slate-600 dark:text-white/70",
chipClass:
"border-emerald-200/70 bg-emerald-50/70 dark:border-emerald-500/20 dark:bg-emerald-500/[0.08]",
chipLabelClass: "text-emerald-900 dark:text-emerald-100/80",
barTrackClass: "border-emerald-200 bg-white dark:border-emerald-500/20 dark:bg-white/[0.03]",
barLabelClass: "text-emerald-900 dark:text-emerald-50",
barMetaClass: "text-emerald-700 dark:text-emerald-200/70",
};
}

if (normalized >= 20) {
return {
normalized,
fillClass: "bg-amber-500",
percentClass: "text-amber-700 dark:text-amber-200",
fillClass:
"border-r-2 border-amber-400 bg-amber-100 dark:border-amber-400/70 dark:bg-amber-500/20",
percentClass: "text-amber-900 dark:text-amber-100",
fillHex: "#f59e0b",
chipClass:
"border-amber-200/70 bg-amber-50/70 dark:border-amber-500/20 dark:bg-amber-500/[0.08]",
chipLabelClass: "text-amber-900/70 dark:text-amber-100/70",
chipLabelClass: "text-amber-900 dark:text-amber-100/80",
barTrackClass: "border-amber-200 bg-white dark:border-amber-500/20 dark:bg-white/[0.03]",
barLabelClass: "text-amber-900 dark:text-amber-50",
barMetaClass: "text-amber-700 dark:text-amber-200/70",
};
}

return {
normalized,
fillClass: "bg-rose-500",
percentClass: "text-rose-700 dark:text-rose-200",
fillClass: "border-r-2 border-rose-400 bg-rose-100 dark:border-rose-400/70 dark:bg-rose-500/20",
percentClass: "text-rose-900 dark:text-rose-100",
fillHex: "#f43f5e",
chipClass: "border-rose-200/70 bg-rose-50/70 dark:border-rose-500/20 dark:bg-rose-500/[0.08]",
chipLabelClass: "text-rose-900/70 dark:text-rose-100/70",
chipLabelClass: "text-rose-900 dark:text-rose-100/80",
barTrackClass: "border-rose-200 bg-white dark:border-rose-500/20 dark:bg-white/[0.03]",
barLabelClass: "text-rose-900 dark:text-rose-50",
barMetaClass: "text-rose-700 dark:text-rose-200/70",
};
};

Expand Down
30 changes: 22 additions & 8 deletions pages/auth-files/hooks/quotaBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,16 @@ export const renderQuotaBarNode = (
const bar = (
<div
className={[
"relative flex w-full items-center overflow-hidden rounded-md",
"border border-slate-200/70 bg-slate-50/60 dark:border-white/5 dark:bg-white/[0.04]",
"relative flex w-full items-center overflow-hidden rounded-md border",
tone.barTrackClass,
compact ? "h-[22px] px-1.5" : "h-6 px-2",
].join(" ")}
>
{/* No opacity wrapper: the tone ships a tint already light enough to read
text over, and dimming it further was what made the fill edge — the
thing that actually encodes the percentage — impossible to locate. */}
<div
className={["absolute inset-y-0 left-0 opacity-20 dark:opacity-25", tone.fillClass].join(
" ",
)}
className={["absolute inset-y-0 left-0", tone.fillClass].join(" ")}
style={{ width: `${normalized ?? 0}%` }}
aria-hidden="true"
/>
Expand All @@ -64,12 +65,20 @@ export const renderQuotaBarNode = (
compact ? "text-2xs" : "text-xs",
].join(" ")}
>
<span className="inline-flex min-w-0 flex-1 items-center gap-1 font-medium text-slate-600 dark:text-white/70">
<span
className={[
"inline-flex min-w-0 flex-1 items-center gap-1 font-medium",
tone.barLabelClass,
].join(" ")}
>
<span className="min-w-0 truncate">{translatedLabel}</span>
{hint ? (
<HoverTooltip content={hint} placement="top" className="shrink-0">
<span
className="inline-flex shrink-0 cursor-help text-slate-400 transition-colors hover:text-slate-600 dark:text-white/40 dark:hover:text-white/70"
className={[
"inline-flex shrink-0 cursor-help transition-opacity hover:opacity-100",
tone.barMetaClass,
].join(" ")}
data-testid="quota-bar-hint"
aria-label={hint}
>
Expand All @@ -79,7 +88,12 @@ export const renderQuotaBarNode = (
) : null}
</span>
{detailText ? (
<span className="inline-flex max-w-[46%] shrink-0 items-center gap-0.5 truncate tabular-nums text-slate-400 dark:text-white/40">
<span
className={[
"inline-flex max-w-[46%] shrink-0 items-center gap-0.5 truncate tabular-nums",
tone.barMetaClass,
].join(" ")}
>
<Clock size={compact ? 9 : 10} className="shrink-0" aria-hidden />
{detailText}
</span>
Expand Down
2 changes: 1 addition & 1 deletion pages/auth-files/hooks/useAuthFilesFilesPresentation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ const KNOWN_QUOTA_TEXT_KEYS = new Set([

const SUBSCRIPTION_TONE_CLASSES = {
active:
"border-slate-900/8 bg-slate-50 text-slate-700 dark:border-white/10 dark:bg-white/[0.08] dark:text-white/70",
"border-emerald-200 bg-emerald-50 text-emerald-800 dark:border-emerald-500/20 dark:bg-emerald-500/15 dark:text-emerald-200",
warning:
"border-amber-200 bg-amber-50 text-amber-800 dark:border-amber-500/20 dark:bg-amber-500/15 dark:text-amber-200",
urgent:
Expand Down
Loading