feat(ui): put the auto-router savings hero on a spend rail and a four-tile row (#38470)

The savings card carried four numbers in two stacked halves: the headline
saving with its delta on the left over the two spend rows, and avg saved per
session on the right. Give the headline the whole left half, move the two
spend rows into a rail on the right, and drop avg saved per session into the
metric row below as its first tile, with the session count as an inline hint.

Each spend row stays a description list so assistive tech keeps the label to
value association, with the shadcn Separator between the two rows. Both hero
columns are minmax(0,1fr) so a large total wraps instead of overflowing the
card, which also fixes the clipping the old 1fr columns already had. Metric
grows one optional hint slot so the new tile reuses the same presenter as its
three siblings.
This commit is contained in:
tin-berri 2026-08-27 00:22:38 -07:00 committed by GitHub
parent 192ccaaf02
commit cd63c7e5a7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 62 additions and 31 deletions

View file

@ -1,5 +1,5 @@
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { fireEvent, render, screen } from "@testing-library/react";
import { fireEvent, render, screen, within } from "@testing-library/react";
import React from "react";
import { beforeEach, describe, expect, it, vi } from "vitest";
@ -151,15 +151,18 @@ describe("AutoRouterBenchmarksTab", () => {
mockAutoRouters();
});
it("leads with total estimated savings, before the three session-shape metrics", () => {
it("leads with total estimated savings, before the four session-shape metrics", () => {
mockHook({ data: response([group(), group({ router_name: "gpt-auto" })]) });
renderTab();
const labels = screen
.getAllByText(/Total estimated savings|Avg turns per session|Avg session length|Avg tokens per session/)
.getAllByText(
/Total estimated savings|Avg saved per session|Avg turns per session|Avg session length|Avg tokens per session/,
)
.map((node) => node.textContent);
expect(labels).toEqual([
"Total estimated savings",
"Avg saved per session",
"Avg turns per session",
"Avg session length",
"Avg tokens per session",
@ -181,13 +184,35 @@ describe("AutoRouterBenchmarksTab", () => {
expect(screen.getByText("5.3M")).toBeInTheDocument();
});
it("pairs the savings with the session count it was earned over", () => {
it("pairs the savings with the session count it was earned over, in its own tile", () => {
mockHook({ data: response([group(), group({ router_name: "gpt-auto" })]) });
renderTab();
expect(screen.getByText("Avg saved per session")).toBeInTheDocument();
expect(screen.getByText("$23.13")).toBeInTheDocument();
expect(screen.getByText("across 94 sessions")).toBeInTheDocument();
const tile = screen.getByText("Avg saved per session").closest('[data-slot="card"]');
if (!tile) throw new Error("expected avg saved per session to render as a metric tile");
expect(within(tile).getByText("$23.13")).toBeInTheDocument();
expect(within(tile).getByText("· 94 sessions")).toBeInTheDocument();
});
it("exposes each spend row as a term and its value, not as loose text", () => {
mockHook({ data: response([group()]) });
renderTab();
const terms = screen.getAllByRole("term").map((node) => node.textContent);
const values = screen.getAllByRole("definition").map((node) => node.textContent);
expect(terms).toEqual(["Actual auto-router spend", "Estimated spend at highest-tier model"]);
expect(values).toEqual(["$359.86", "$2,534.45"]);
});
it("lets both hero columns shrink below their content so a large total cannot clip", () => {
const huge = totals({ saved_spend: 123_456_789_012.34 });
mockHook({ data: response([group(huge)], huge) });
renderTab();
const figure = screen.getByText("$123,456,789,012.34");
const grid = figure.closest('[data-slot="card"]')?.firstElementChild;
expect(grid).toHaveClass("md:grid-cols-[minmax(0,1fr)_minmax(0,1fr)]");
});
it("shows a cost increase as a positive delta rather than a saving", () => {
@ -315,7 +340,7 @@ describe("AutoRouterBenchmarksTab", () => {
expect(screen.getByText("Total estimated savings")).toBeInTheDocument();
expect(screen.getAllByText("$0.00")).toHaveLength(4);
expect(screen.getByText("across 0 sessions")).toBeInTheDocument();
expect(screen.getByText("· 0 sessions")).toBeInTheDocument();
expect(screen.getByText("0s")).toBeInTheDocument();
expect(screen.getByText(/turns measured/)).toBeInTheDocument();
expect(screen.getAllByText("0.0%").length).toBeGreaterThan(0);

View file

@ -8,6 +8,7 @@ import AdvancedDatePicker from "@/components/shared/advanced_date_picker";
import { Badge } from "@/components/ui/badge";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Separator } from "@/components/ui/separator";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
@ -39,51 +40,51 @@ const Message: React.FC<{ children: React.ReactNode }> = ({ children }) => (
<p className="py-8 text-center text-sm text-muted-foreground">{children}</p>
);
const Metric: React.FC<{ label: string; value: string }> = ({ label, value }) => (
const Metric: React.FC<{ label: string; value: string; hint?: string }> = ({ label, value, hint }) => (
<Card size="sm">
<CardHeader>
<CardTitle className="text-sm font-normal text-muted-foreground">{label}</CardTitle>
</CardHeader>
<CardContent>
<CardContent className="flex flex-wrap items-baseline gap-2">
<p className="text-3xl font-semibold text-foreground">{value}</p>
{hint && <p className="text-sm text-muted-foreground">{hint}</p>}
</CardContent>
</Card>
);
const SpendRow: React.FC<{ label: string; value: string }> = ({ label, value }) => (
<dl className="flex items-baseline justify-between gap-6 py-3">
<dt className="text-sm text-muted-foreground">{label}</dt>
<dd className="text-base font-semibold tabular-nums text-foreground">{value}</dd>
</dl>
);
const HeroCard: React.FC<{ view: BenchmarkView }> = ({ view }) => {
const stats = view.stats;
const cheaper = stats.saved_spend >= 0;
return (
<Card className="overflow-hidden py-0">
<div className="grid md:grid-cols-[1fr_1fr]">
<div className="flex flex-col justify-center gap-3 p-6">
<p className="text-sm text-muted-foreground">Total estimated savings</p>
<div className="flex flex-wrap items-center gap-3">
<p className="text-5xl font-semibold tracking-tight text-foreground">{usd(stats.saved_spend)}</p>
<div className="grid md:grid-cols-[minmax(0,1fr)_minmax(0,1fr)]">
<div className="flex flex-col items-center justify-center gap-2 p-6">
<p className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
Total estimated savings
</p>
<div className="flex flex-wrap items-center justify-center gap-3">
<p className="text-6xl font-semibold tracking-tight text-foreground">{usd(stats.saved_spend)}</p>
<Badge
variant="secondary"
className={cheaper ? "bg-success/10 text-success" : "bg-destructive/10 text-destructive"}
className={`h-6 px-2.5 text-sm ${cheaper ? "bg-success/10 text-success" : "bg-destructive/10 text-destructive"}`}
>
{stats.saved_spend !== 0 && (cheaper ? "-" : "+")}
{Math.abs(stats.saved_pct).toFixed(0)}%
</Badge>
</div>
<dl className="divide-y text-sm">
<div className="flex items-baseline justify-between gap-6 py-3">
<dt className="text-muted-foreground">Actual auto-router spend</dt>
<dd className="font-medium tabular-nums text-foreground">{usd(stats.spend)}</dd>
</div>
<div className="flex items-baseline justify-between gap-6 py-3">
<dt className="text-muted-foreground">Estimated spend at highest-tier model</dt>
<dd className="font-medium tabular-nums text-foreground">{usd(stats.baseline_spend)}</dd>
</div>
</dl>
</div>
<div className="flex flex-col items-center justify-center gap-2 border-t p-6 md:border-t-0 md:border-l">
<p className="text-[11px] uppercase tracking-wide text-muted-foreground">Avg saved per session</p>
<p className="text-5xl font-semibold tracking-tight text-foreground">{usd(stats.saved_per_session)}</p>
<p className="text-sm text-muted-foreground">across {stats.sessions.toLocaleString()} sessions</p>
<div className="flex flex-col justify-center border-t p-6 md:border-t-0 md:border-l">
<SpendRow label="Actual auto-router spend" value={usd(stats.spend)} />
<Separator />
<SpendRow label="Estimated spend at highest-tier model" value={usd(stats.baseline_spend)} />
</div>
</div>
</Card>
@ -239,7 +240,12 @@ const BenchmarksBody: React.FC<BenchmarksBodyProps> = ({ isPending, error, data,
<TierTurnsChart view={view} autoRouters={autoRouters} />
<div className="grid grid-cols-1 gap-4 sm:grid-cols-3">
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
<Metric
label="Avg saved per session"
value={usd(stats.saved_per_session)}
hint={`· ${stats.sessions.toLocaleString()} sessions`}
/>
<Metric label="Avg turns per session" value={stats.avg_turns_per_session.toFixed(1)} />
<Metric label="Avg session length" value={durationLabel(stats.avg_session_seconds)} />
<Metric label="Avg tokens per session" value={formatNumberWithCommas(stats.avg_tokens_per_session, 1, true)} />