diff --git a/src/app/benchmarks/[slug]/page.tsx b/src/app/benchmarks/[slug]/page.tsx index c4f48bb1..c82a7755 100644 --- a/src/app/benchmarks/[slug]/page.tsx +++ b/src/app/benchmarks/[slug]/page.tsx @@ -181,9 +181,11 @@ export default async function BenchmarkPage({ (r) => r.value === "all" || regionsWithData.has(r.value), ); const kindOptions = aggregate.dimensions?.kind ?? []; + const venueOptions = aggregate.dimensions?.venue ?? []; const chain = chainOptions[0]?.value ?? null; const region = regionOptions[0]?.value ?? null; const kind = kindOptions[0]?.value ?? null; + const venue = venueOptions[0]?.value ?? null; // Variants (chain × region × kind) are NOT embedded anymore. The old // pre-fetch awaited every variant (rpc-capabilities: 39 full provider @@ -524,9 +526,11 @@ export default async function BenchmarkPage({ chainOptions={chainOptions} regionOptions={regionOptions} kindOptions={kindOptions} + venueOptions={venueOptions} initialChain={chain ?? null} initialRegion={region ?? null} initialKind={kind ?? null} + initialVenue={venue ?? null} hasLongHistory={benchmark.slug === "hyperliquid-frontends"} /> diff --git a/src/components/benchmark-body.tsx b/src/components/benchmark-body.tsx index d317e9e2..c302dfd6 100644 --- a/src/components/benchmark-body.tsx +++ b/src/components/benchmark-body.tsx @@ -123,8 +123,9 @@ function variantKey( chain: string | null, region: string | null, kind: string | null, + venue: string | null = null, ): string { - return `${chain ?? "__none"}|${region ?? "__none"}|${kind ?? "__none"}`; + return `${chain ?? "__none"}|${region ?? "__none"}|${kind ?? "__none"}|${venue ?? "__none"}`; } /** Region values that appear in extras.seriesByRegion24h. Used when the @@ -152,18 +153,22 @@ export function BenchmarkBody({ chainOptions, regionOptions, kindOptions = [], + venueOptions = [], initialChain, initialRegion, initialKind = null, + initialVenue = null, hasLongHistory = false, }: { variants: Record; chainOptions: ChainOption[]; regionOptions: ChainOption[]; kindOptions?: ChainOption[]; + venueOptions?: ChainOption[]; initialChain: string | null; initialRegion: string | null; initialKind?: string | null; + initialVenue?: string | null; /** When true, render the long-window archive toggle (24h..All time) * below the main ledger. Only set on benches whose harness ships a * long-window archive blob (currently: hyperliquid-frontends). */ @@ -178,6 +183,7 @@ export function BenchmarkBody({ const urlChain = searchParams.get("chain"); const urlRegion = searchParams.get("region"); const urlKind = searchParams.get("kind"); + const urlVenue = searchParams.get("venue"); const urlLayer = searchParams.get("layer"); // Canonical-aware lookup: a URL with the new slug ("?chain=gram") // still selects the dimension whose YAML value is the legacy "ton". @@ -189,12 +195,15 @@ export function BenchmarkBody({ (urlRegion && regionOptions.find((r) => r.value === urlRegion)?.value) ?? initialRegion; const resolvedInitialKind = (urlKind && kindOptions.find((k) => k.value === urlKind)?.value) ?? initialKind; + const resolvedInitialVenue = + (urlVenue && venueOptions.find((v) => v.value === urlVenue)?.value) ?? initialVenue; const resolvedInitialLayer: ProviderLayer = urlLayer === "l2" ? "l2" : "l1"; const [chain, setChain] = useState(resolvedInitialChain); const [region, setRegion] = useState(resolvedInitialRegion); const [kind, setKind] = useState(resolvedInitialKind); + const [venue, setVenue] = useState(resolvedInitialVenue); const [layer, setLayer] = useState(resolvedInitialLayer); useEffect(() => { @@ -202,6 +211,7 @@ export function BenchmarkBody({ syncParam(url, "chain", chain, chainOptions); syncParam(url, "region", region, regionOptions); syncParam(url, "kind", kind, kindOptions); + syncParam(url, "venue", venue, venueOptions); // Layer param: drop when default ("l1"), keep when user picked l2. if (layer === "l1") url.searchParams.delete("layer"); else url.searchParams.set("layer", layer); @@ -209,14 +219,16 @@ export function BenchmarkBody({ if (next !== window.location.pathname + window.location.search) { window.history.replaceState(null, "", next); } - }, [chain, region, kind, layer, chainOptions, regionOptions, kindOptions]); + }, [chain, region, kind, venue, layer, chainOptions, regionOptions, kindOptions, venueOptions]); const fallbackChain = chainOptions[0]?.value ?? null; const fallbackRegion = regionOptions[0]?.value ?? null; const fallbackKind = kindOptions[0]?.value ?? null; + const fallbackVenue = venueOptions[0]?.value ?? null; const effectiveChain = chainOptions.length > 0 ? (chain ?? fallbackChain) : null; const effectiveRegion = regionOptions.length > 0 ? (region ?? fallbackRegion) : null; const effectiveKind = kindOptions.length > 0 ? (kind ?? fallbackKind) : null; + const effectiveVenue = venueOptions.length > 0 ? (venue ?? fallbackVenue) : null; // The page ships ONLY the aggregate view (embedding every variant made // ISR regenerations take 30-60 s). Filtered variants are fetched here @@ -225,13 +237,14 @@ export function BenchmarkBody({ // tab still works, numbers stay cross-dimension) and may retry on the // next flip. const [variantMap, setVariantMap] = useState>(variants); - const activeKey = variantKey(effectiveChain, effectiveRegion, effectiveKind); + const activeKey = variantKey(effectiveChain, effectiveRegion, effectiveKind, effectiveVenue); const aggregateBench = - variants[variantKey(null, null, null)] ?? Object.values(variants)[0]; + variants[variantKey(null, null, null, null)] ?? Object.values(variants)[0]; const isAllSelection = (!effectiveChain || effectiveChain === "all") && (!effectiveRegion || effectiveRegion === "all") && - (!effectiveKind || effectiveKind === "all"); + (!effectiveKind || effectiveKind === "all") && + (!effectiveVenue || effectiveVenue === "all"); useEffect(() => { if (variantMap[activeKey] || !aggregateBench) return; // The all/all/all selection IS the aggregate: derived at render time, @@ -242,6 +255,7 @@ export function BenchmarkBody({ if (!isAll(effectiveChain)) qs.set("chain", effectiveChain!); if (!isAll(effectiveRegion)) qs.set("region", effectiveRegion!); if (!isAll(effectiveKind)) qs.set("kind", effectiveKind!); + if (!isAll(effectiveVenue)) qs.set("venue", effectiveVenue!); let cancelled = false; fetch(`/api/bench/${aggregateBench.slug}/variant?${qs.toString()}`) .then((r) => (r.ok ? r.json() : null)) @@ -267,25 +281,24 @@ export function BenchmarkBody({ useEffect(() => { if (!aggregateBench) return; const isAll = (v: string | null) => !v || v === "all"; - const combos: [string | null, string | null, string | null][] = [ - ...chainOptions.map( - (c) => [c.value, effectiveRegion, effectiveKind] as [string | null, string | null, string | null], - ), - ...regionOptions.map( - (r) => [effectiveChain, r.value, effectiveKind] as [string | null, string | null, string | null], - ), + type Combo = [string | null, string | null, string | null, string | null]; + const combos: Combo[] = [ + ...chainOptions.map((c) => [c.value, effectiveRegion, effectiveKind, effectiveVenue] as Combo), + ...regionOptions.map((r) => [effectiveChain, r.value, effectiveKind, effectiveVenue] as Combo), + ...venueOptions.map((v) => [effectiveChain, effectiveRegion, effectiveKind, v.value] as Combo), ]; let cancelled = false; const timers: ReturnType[] = []; let i = 0; - for (const [c, r, k] of combos) { - if (isAll(c) && isAll(r) && isAll(k)) continue; - const key = variantKey(c, r, k); + for (const [c, r, k, vn] of combos) { + if (isAll(c) && isAll(r) && isAll(k) && isAll(vn)) continue; + const key = variantKey(c, r, k, vn); if (variantMap[key]) continue; const qs = new URLSearchParams(); if (!isAll(c)) qs.set("chain", c!); if (!isAll(r)) qs.set("region", r!); if (!isAll(k)) qs.set("kind", k!); + if (!isAll(vn)) qs.set("venue", vn!); timers.push( setTimeout(() => { if (cancelled) return; @@ -528,7 +541,8 @@ export function BenchmarkBody({ {(hasLayerSplit || chainOptions.length > 0 || regionOptions.length > 0 || - kindOptions.length > 0) && ( + kindOptions.length > 0 || + venueOptions.length > 0) && (
{hasLayerSplit && ( setLayer(v as ProviderLayer)} /> )} + {venueOptions.length > 0 && ( + [ + o.value, + summarize( + variantMap[variantKey(effectiveChain, effectiveRegion, effectiveKind, o.value)], + ), + ]) + .filter(([, v]) => v !== null) as [string, ChainMeta][] + )} + /> + )} {kindOptions.length > 0 && ( [ o.value, summarize( - variantMap[variantKey(effectiveChain, effectiveRegion, o.value)], + variantMap[variantKey(effectiveChain, effectiveRegion, o.value, effectiveVenue)], ), ]) .filter(([, v]) => v !== null) as [string, ChainMeta][] @@ -569,7 +601,7 @@ export function BenchmarkBody({ chainOptions .map((o) => [ o.value, - summarize(variantMap[variantKey(o.value, effectiveRegion, effectiveKind)]), + summarize(variantMap[variantKey(o.value, effectiveRegion, effectiveKind, effectiveVenue)]), ]) .filter(([, v]) => v !== null) as [string, ChainMeta][] )} @@ -585,7 +617,7 @@ export function BenchmarkBody({ regionOptions .map((o) => [ o.value, - summarize(variantMap[variantKey(effectiveChain, o.value, effectiveKind)]), + summarize(variantMap[variantKey(effectiveChain, o.value, effectiveKind, effectiveVenue)]), ]) .filter(([, v]) => v !== null) as [string, ChainMeta][] )} diff --git a/src/types/benchmark.ts b/src/types/benchmark.ts index db13e88d..5cff01e3 100644 --- a/src/types/benchmark.ts +++ b/src/types/benchmark.ts @@ -207,6 +207,7 @@ export type Benchmark = { chain?: { value: string; label: string }[]; region?: { value: string; label: string }[]; kind?: { value: string; label: string }[]; + venue?: { value: string; label: string }[]; }; category: "Aggregators" | "Bridges" | "Blockchains" | "Trading" | "Wallets" | "RPCs" | "NFT APIs" | "Explorers"; results: ProviderResult[];