diff --git a/src/app/api/badge/[slug]/[provider]/route.ts b/src/app/api/badge/[slug]/[provider]/route.ts index 08bd7f51..e5e6009f 100644 --- a/src/app/api/badge/[slug]/[provider]/route.ts +++ b/src/app/api/badge/[slug]/[provider]/route.ts @@ -30,6 +30,7 @@ import { fmtUnit } from "@/lib/format"; import { readBestPerChain } from "@/lib/per-chain-contract"; import { clientKey, rateLimit, tooManyRequests } from "@/lib/rate-limit"; import { PROVIDER_RE, SLUG_RE } from "@/lib/slug"; +import { matchesChainSlug } from "@/lib/chain-aliases"; import type { Benchmark, ProviderResult } from "@/types/benchmark"; export const revalidate = 300; @@ -144,9 +145,14 @@ function truncate(s: string, max: number): string { return s.slice(0, max - 1).trimEnd() + "…"; } -/** Returns the human label for a chain value from the bench's spec. */ +/** Returns the human label for a chain value from the bench's spec. + * Canonical-aware so a request with the new slug ("gram") still finds + * the dimension entry whose value is the legacy "ton". */ function chainLabel(b: Benchmark, chain: string): string { - return b.dimensions?.chain?.find((c) => c.value === chain)?.label ?? chain; + return ( + b.dimensions?.chain?.find((c) => matchesChainSlug(c.value, chain))?.label ?? + chain + ); } /** Returns the human label for a region value from the bench's spec. */ @@ -185,8 +191,12 @@ export async function GET( const url = new URL(req.url); const rawChain = url.searchParams.get("chain")?.toLowerCase().trim() || null; const rawRegion = url.searchParams.get("region")?.toLowerCase().trim() || null; + // Resolve via the alias-aware matcher so /api/badge?chain=gram lands + // on the dimension whose YAML value is "ton" (kept to match the + // harness's Prom labels). Returns the actual dimension value so the + // downstream cell lookup hits the snapshot's storage key. const chainParam = rawChain - ? (b.dimensions?.chain?.find((c) => c.value.toLowerCase() === rawChain) + ? (b.dimensions?.chain?.find((c) => matchesChainSlug(c.value, rawChain)) ?.value ?? null) : null; const regionParam = rawRegion diff --git a/src/app/api/bench/[slug]/variant/route.ts b/src/app/api/bench/[slug]/variant/route.ts index 37bcabe8..4935a465 100644 --- a/src/app/api/bench/[slug]/variant/route.ts +++ b/src/app/api/bench/[slug]/variant/route.ts @@ -15,6 +15,7 @@ import { type NextRequest, NextResponse } from "next/server"; import { getBenchmark } from "@/data/benchmarks"; import { clientKey, rateLimit, tooManyRequests } from "@/lib/rate-limit"; import { SLUG_RE } from "@/lib/slug"; +import { matchesChainSlug } from "@/lib/chain-aliases"; export const revalidate = 60; @@ -49,8 +50,13 @@ export async function GET( for (const dim of ["chain", "region", "kind"] as const) { const raw = url.searchParams.get(dim)?.toLowerCase().trim(); if (!raw || raw === "all") continue; - const known = (aggregate.dimensions?.[dim] ?? []).find( - (d) => d.value.toLowerCase() === raw, + // Canonical-aware matching: the chain dimension may still hold the + // legacy slug ("ton") even though clients now request the canonical + // ("gram"). The matcher resolves both sides to canonical. + const known = (aggregate.dimensions?.[dim] ?? []).find((d) => + dim === "chain" + ? matchesChainSlug(d.value, raw) + : d.value.toLowerCase() === raw, ); if (!known) { return new NextResponse(`unknown ${dim}`, { diff --git a/src/app/benchmarks/[slug]/[chain]/page.tsx b/src/app/benchmarks/[slug]/[chain]/page.tsx index 5bfd036c..320c9686 100644 --- a/src/app/benchmarks/[slug]/[chain]/page.tsx +++ b/src/app/benchmarks/[slug]/[chain]/page.tsx @@ -10,6 +10,10 @@ import { capDescription } from "@/lib/seo-text"; import { SITE } from "@/data/site"; import { buildBreadcrumbJsonLd, safeJsonLd } from "@/lib/jsonld"; import { CATEGORY_COLOR } from "@/lib/category-colors"; +import { + canonicalChainSlug, + matchesChainSlug, +} from "@/lib/chain-aliases"; import type { Benchmark, ProviderResult } from "@/types/benchmark"; // Dedicated per-chain landing pages. Only chains that have a hand-written @@ -119,8 +123,12 @@ async function loadChainPage( ): Promise { const benchmark = await getBenchmark(slug); if (!benchmark) return null; - const found = (benchmark.perChainExplainer ?? []).find( - (e) => e.slug === chain, + // Canonical-aware lookups: a URL like /benchmarks//gram must + // resolve when the bench's perChainExplainer / results / dimensions + // still carry the legacy slug ("ton") because YAMLs and the harness + // haven't all rotated past the rename yet. + const found = (benchmark.perChainExplainer ?? []).find((e) => + matchesChainSlug(e.slug, chain), ); if (!found) return null; const explainer = { @@ -130,19 +138,24 @@ async function loadChainPage( }; // Shape 1: the chain is a leaderboard row (l1-finality). - const result = benchmark.results.find((r) => r.slug === chain); + const result = benchmark.results.find((r) => matchesChainSlug(r.slug, chain)); if (result) { const sorted = sortLive(benchmark.results, benchmark.higherIsBetter); - const rank = sorted.findIndex((r) => r.slug === chain) + 1; + const rank = sorted.findIndex((r) => matchesChainSlug(r.slug, chain)) + 1; return { shape: "row", benchmark, explainer, result, sorted, rank }; } // Shape 2: the chain is a filter dimension (rpc-capabilities). const chainOption = (benchmark.dimensions?.chain ?? []).find( - (c) => c.value === chain && c.value.toLowerCase() !== "all", + (c) => matchesChainSlug(c.value, chain) && c.value.toLowerCase() !== "all", ); if (!chainOption) return null; - const scoped = (await getBenchmark(slug, { chain })) ?? benchmark; + // Use the actual YAML value (not the canonical) when fetching the + // scoped variant — the Prom-label injection downstream expects the + // raw dimension value (e.g. "ton") so the regex straddle keeps + // matching the harness's current label. + const scoped = + (await getBenchmark(slug, { chain: chainOption.value })) ?? benchmark; const providers = sortLive(scoped.results, benchmark.higherIsBetter); const leader = providers[0] ?? null; @@ -157,7 +170,10 @@ async function loadChainPage( const variants = await Promise.all( regions.map(async (r) => ({ label: r.label, - bench: await getBenchmark(slug, { chain, region: r.value }), + bench: await getBenchmark(slug, { + chain: chainOption.value, + region: r.value, + }), })), ); for (const v of variants) { diff --git a/src/app/benchmarks/[slug]/opengraph-image.tsx b/src/app/benchmarks/[slug]/opengraph-image.tsx index dc917d7b..361d9c93 100644 --- a/src/app/benchmarks/[slug]/opengraph-image.tsx +++ b/src/app/benchmarks/[slug]/opengraph-image.tsx @@ -3,6 +3,7 @@ import { getBenchmark } from "@/data/benchmarks"; import { headlineSentence, leader } from "@/lib/citation"; import { fmtUnit } from "@/lib/format"; import { CATEGORY_COLOR } from "@/lib/category-colors"; +import { matchesChainSlug } from "@/lib/chain-aliases"; import { loadBenchmark } from "@/lib/spec"; export const runtime = "nodejs"; @@ -76,7 +77,8 @@ export default async function OG({ const sentence = headlineSentence(b); const catColor = CATEGORY_COLOR[b.category] ?? "#7a2e1f"; const chainLabel = chainId - ? b.dimensions?.chain?.find((c) => c.value === chainId)?.label ?? chainId + ? b.dimensions?.chain?.find((c) => matchesChainSlug(c.value, chainId)) + ?.label ?? chainId : null; const titleText = chainLabel ? `${b.title} on ${chainLabel}` : b.title; diff --git a/src/app/benchmarks/[slug]/share-card/route.tsx b/src/app/benchmarks/[slug]/share-card/route.tsx index be8f7e7b..655da40a 100644 --- a/src/app/benchmarks/[slug]/share-card/route.tsx +++ b/src/app/benchmarks/[slug]/share-card/route.tsx @@ -5,6 +5,7 @@ import { getBenchmark } from "@/data/benchmarks"; import { buildProviderColors } from "@/lib/series-colors"; import { fmtUnit, fmtValue, unitSuffix } from "@/lib/format"; import { logoPath } from "@/lib/logo-manifest"; +import { matchesChainSlug } from "@/lib/chain-aliases"; import { chipBackground, chipTextColor, initials } from "@/lib/brand"; import type { Benchmark, ProviderResult } from "@/types/benchmark"; import { clientKey, rateLimit, tooManyRequests } from "@/lib/rate-limit"; @@ -527,7 +528,7 @@ export async function GET( const isAll = chainParam === "all"; const chainOption = isAll ? null - : chainOptions.find((c) => c.value === chainParam) ?? null; + : chainOptions.find((c) => matchesChainSlug(c.value, chainParam)) ?? null; const benchmark = chainOption ? (await getBenchmark(slug, { chain: chainOption.value })) ?? aggregate : aggregate; diff --git a/src/app/benchmarks/[slug]/twitter-image.tsx b/src/app/benchmarks/[slug]/twitter-image.tsx index f0c5e96c..f720ed0e 100644 --- a/src/app/benchmarks/[slug]/twitter-image.tsx +++ b/src/app/benchmarks/[slug]/twitter-image.tsx @@ -3,6 +3,7 @@ import { getBenchmark } from "@/data/benchmarks"; import { headlineSentence, leader } from "@/lib/citation"; import { fmtUnit } from "@/lib/format"; import { CATEGORY_COLOR } from "@/lib/category-colors"; +import { matchesChainSlug } from "@/lib/chain-aliases"; import { loadBenchmark } from "@/lib/spec"; export const runtime = "nodejs"; @@ -66,7 +67,8 @@ export default async function TwitterImage({ const sentence = headlineSentence(b); const catColor = CATEGORY_COLOR[b.category] ?? "#7a2e1f"; const chainLabel = chainId - ? b.dimensions?.chain?.find((c) => c.value === chainId)?.label ?? chainId + ? b.dimensions?.chain?.find((c) => matchesChainSlug(c.value, chainId)) + ?.label ?? chainId : null; const titleText = chainLabel ? `${b.title} on ${chainLabel}` : b.title; diff --git a/src/app/chains/[slug]/page.tsx b/src/app/chains/[slug]/page.tsx index 1a745ffd..48f33ab5 100644 --- a/src/app/chains/[slug]/page.tsx +++ b/src/app/chains/[slug]/page.tsx @@ -20,6 +20,7 @@ import { ProviderLogo } from "@/components/provider-logo"; import { SITE } from "@/data/site"; import { buildBreadcrumbJsonLd, safeJsonLd } from "@/lib/jsonld"; import { capDescription } from "@/lib/seo-text"; +import { matchesChainSlug } from "@/lib/chain-aliases"; import type { Benchmark } from "@/types/benchmark"; export const revalidate = 60; @@ -200,12 +201,19 @@ export default async function ChainPage({