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
79 changes: 71 additions & 8 deletions src/app/benchmarks/[slug]/share-card/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -529,12 +529,67 @@ export async function GET(
const chainOption = isAll
? null
: chainOptions.find((c) => matchesChainSlug(c.value, chainParam)) ?? null;
const benchmark = chainOption
? (await getBenchmark(slug, { chain: chainOption.value })) ?? aggregate
: aggregate;

// Additional dimension filters. Symmetric with `chain`: each declared
// dimension gets a URL param, and when the value matches a spec option
// it gets forwarded to the materialize loader so the exported PNG
// renders the exact scope the user is looking at on the page. `all`
// means "no filter for this dimension" (same convention as chain).
const regionParam = url.searchParams.get("region");
const regionOptions = aggregate.dimensions?.region ?? [];
const regionOption =
!regionParam || regionParam === "all"
? null
: regionOptions.find((r) => r.value === regionParam) ?? null;

const kindParam = url.searchParams.get("kind");
const kindOptions = aggregate.dimensions?.kind ?? [];
const kindOption =
!kindParam || kindParam === "all"
? null
: kindOptions.find((k) => k.value === kindParam) ?? null;

const venueParam = url.searchParams.get("venue");
const venueOptions = aggregate.dimensions?.venue ?? [];
const venueOption =
!venueParam || venueParam === "all"
? null
: venueOptions.find((v) => v.value === venueParam) ?? null;

const filters: {
chain?: string;
region?: string;
kind?: string;
venue?: string;
} = {};
if (chainOption) filters.chain = chainOption.value;
if (regionOption) filters.region = regionOption.value;
if (kindOption) filters.kind = kindOption.value;
if (venueOption) filters.venue = venueOption.value;

const benchmark =
Object.keys(filters).length > 0
? (await getBenchmark(slug, filters)) ?? aggregate
: aggregate;
// No pill for `all` either - it's the unfiltered default view, calling
// it out as a "chain" reads awkward.
const chainLabel = chainOption?.label ?? null;
const regionLabel = regionOption?.label ?? null;
const kindLabel = kindOption?.label ?? null;
const venueLabel = venueOption?.label ?? null;
// Composed context suffix for the card title. Appends the active
// filters after the bench title so a card exported from
// `/benchmarks/rpc-capabilities?chain=ethereum&region=sgp` renders
// "Fastest free public RPC ... · Ethereum · Singapore" instead of just
// the aggregate title. Empty when no filter is active - avoids a
// trailing separator on the default view.
const contextParts = [chainLabel, regionLabel, kindLabel, venueLabel].filter(
(p): p is string => Boolean(p),
);
const displayTitle =
contextParts.length > 0
? `${aggregate.title} · ${contextParts.join(" · ")}`
: aggregate.title;

const rawTemplate = url.searchParams.get("template");
const template: "ranking" | "snapshot" | "headline" | "compare" | "leaderboard" =
Expand Down Expand Up @@ -583,18 +638,26 @@ export async function GET(

const colors = buildProviderColors(benchmark.results);

// Overlay the composed title onto the benchmark that each render
// receives, so the existing `{benchmark.title}` slot in every template
// picks up the filter context without touching every render signature.
// Display-only overlay: keeps the underlying benchmark object intact
// for data purposes (results, dimensions, unit, higherIsBetter etc).
const filteredWithTitle = { ...filteredSafe, title: displayTitle };
const benchmarkWithTitle = { ...benchmark, title: displayTitle };

switch (template) {
case "snapshot":
return renderSnapshot(filteredSafe, colors, chainLabel);
return renderSnapshot(filteredWithTitle, colors, chainLabel);
case "headline":
return renderHeadline(benchmark, colors, headlineProvider, chainLabel);
return renderHeadline(benchmarkWithTitle, colors, headlineProvider, chainLabel);
case "compare":
return renderCompare(benchmark, colors, compareA, compareB, chainLabel);
return renderCompare(benchmarkWithTitle, colors, compareA, compareB, chainLabel);
case "leaderboard":
return renderLeaderboard(benchmark, colors, chainLabel);
return renderLeaderboard(benchmarkWithTitle, colors, chainLabel);
case "ranking":
default:
return renderRanking(benchmark, colors, chainLabel);
return renderRanking(benchmarkWithTitle, colors, chainLabel);
}
}

Expand Down
28 changes: 21 additions & 7 deletions src/components/share-section-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,22 +139,36 @@ export default function ShareSectionModal({
// Build the URL with the right params per template.
const cardSrc = (templateId: string) => {
const tpl = TEMPLATES.find((t) => t.id === templateId);
// Read the chain from the live URL so the share-card stays in sync
// when the user flips chain tabs client-side. `chain` prop is the
// server-rendered fallback for the very first render.
const liveChain =
// Read every dimension filter from the live URL so the share-card
// stays in sync when the user flips a chain / region / kind / venue
// tab client-side. `chain` prop is the server-rendered fallback for
// the very first render; the other dimensions are read from the URL
// only (they're not passed as props today, and the pattern reads
// whatever the page's state has serialised).
const liveUrl =
typeof window !== "undefined"
? new URL(window.location.href).searchParams.get("chain")
: chain ?? null;
? new URL(window.location.href)
: null;
const liveChain = liveUrl
? liveUrl.searchParams.get("chain")
: chain ?? null;
const chainParam = liveChain ? `&chain=${encodeURIComponent(liveChain)}` : "";
const liveRegion = liveUrl ? liveUrl.searchParams.get("region") : null;
const regionParam = liveRegion
? `&region=${encodeURIComponent(liveRegion)}`
: "";
const liveKind = liveUrl ? liveUrl.searchParams.get("kind") : null;
const kindParam = liveKind ? `&kind=${encodeURIComponent(liveKind)}` : "";
const liveVenue = liveUrl ? liveUrl.searchParams.get("venue") : null;
const venueParam = liveVenue ? `&venue=${encodeURIComponent(liveVenue)}` : "";
// Mirror the active site theme so the exported PNG matches what the
// user is looking at. SSR can't read the dark state - default to light
// server-side, the client re-renders with `dark` once mounted.
const isDark =
typeof window !== "undefined" &&
document.documentElement.classList.contains("dark");
const themeParam = isDark ? "&theme=dark" : "";
const base = `/benchmarks/${slug}/share-card?template=${templateId}${chainParam}${themeParam}`;
const base = `/benchmarks/${slug}/share-card?template=${templateId}${chainParam}${regionParam}${kindParam}${venueParam}${themeParam}`;
if (!tpl) return base;
if (tpl.pick === "multi") {
if (
Expand Down
25 changes: 18 additions & 7 deletions src/components/share-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -125,22 +125,33 @@ export function ShareSection({ slug, title, benchmark, chain }: Props) {
// Build the URL with the right params per template.
const cardSrc = (templateId: string) => {
const tpl = TEMPLATES.find((t) => t.id === templateId);
// Read the chain from the live URL so the share-card stays in sync
// when the user flips chain tabs client-side. `chain` prop is the
// server-rendered fallback for the very first render.
const liveChain =
// Read every dimension filter from the live URL so the share-card
// stays in sync when the user flips a chain / region / kind / venue
// tab client-side.
const liveUrl =
typeof window !== "undefined"
? new URL(window.location.href).searchParams.get("chain")
: chain ?? null;
? new URL(window.location.href)
: null;
const liveChain = liveUrl
? liveUrl.searchParams.get("chain")
: chain ?? null;
const chainParam = liveChain ? `&chain=${encodeURIComponent(liveChain)}` : "";
const liveRegion = liveUrl ? liveUrl.searchParams.get("region") : null;
const regionParam = liveRegion
? `&region=${encodeURIComponent(liveRegion)}`
: "";
const liveKind = liveUrl ? liveUrl.searchParams.get("kind") : null;
const kindParam = liveKind ? `&kind=${encodeURIComponent(liveKind)}` : "";
const liveVenue = liveUrl ? liveUrl.searchParams.get("venue") : null;
const venueParam = liveVenue ? `&venue=${encodeURIComponent(liveVenue)}` : "";
// Mirror the active site theme so the exported PNG matches what the
// user is looking at. SSR can't read the dark state - default to light
// server-side, the client re-renders with `dark` once mounted.
const isDark =
typeof window !== "undefined" &&
document.documentElement.classList.contains("dark");
const themeParam = isDark ? "&theme=dark" : "";
const base = `/benchmarks/${slug}/share-card?template=${templateId}${chainParam}${themeParam}`;
const base = `/benchmarks/${slug}/share-card?template=${templateId}${chainParam}${regionParam}${kindParam}${venueParam}${themeParam}`;
if (!tpl) return base;
if (tpl.pick === "multi") {
if (
Expand Down
Loading