diff --git a/src/app/products/[slug]/page.tsx b/src/app/products/[slug]/page.tsx index 1404fe50d..1992396b1 100644 --- a/src/app/products/[slug]/page.tsx +++ b/src/app/products/[slug]/page.tsx @@ -782,11 +782,13 @@ export default async function ProviderPage({ const catColor = CATEGORY_COLOR[a.benchmark.category]; const hasData = a.rank > 0 && a.result.ms.p50 !== 0; const value = hasData ? fmtUnit(a.result.ms.p50, a.benchmark.unit) : null; - // Per-chain rank chips. Rendered alongside the aggregate rank - // when the bench declares chain dimensions and the provider has - // per-chain ranks populated. Surface text reads e.g. "#1 on - // Solana · #4 on Base · #4 on BNB" so a chain-restricted - // provider can't be passed off as a free cross-chain #1. + // Per-chain leadership chips, rendered alongside the aggregate + // rank when the bench declares chain dimensions. Leaders only: + // a chip means "leads this chain", and its absence means "does + // not lead", never "ranks lower". Reads e.g. "#1 of 4 on + // Ethereum" so a chain-restricted provider can't be passed off + // as a free cross-chain #1, and so a win on a two-provider + // chain isn't dressed up as a win on a crowded one. const chainRanks = a.rankPerChain && a.benchmark.chainDimensions ? a.benchmark.chainDimensions @@ -840,13 +842,10 @@ export default async function ProviderPage({ {chainRanks.map(({ chain, entry }) => ( - #{entry.rank} on {chain.label} + #1{entry.totalRanked > 0 ? ` of ${entry.totalRanked}` : ""} on{" "} + {chain.label} ))}

diff --git a/src/components/bench-appearances-section.tsx b/src/components/bench-appearances-section.tsx index c50c7fc38..de5582033 100644 --- a/src/components/bench-appearances-section.tsx +++ b/src/components/bench-appearances-section.tsx @@ -82,13 +82,10 @@ export async function BenchAppearancesSection({ providerSlug }: Props) { {chainRanks.map(({ chain, entry }) => ( - #{entry.rank} on {chain.label} + #1{entry.totalRanked > 0 ? ` of ${entry.totalRanked}` : ""} on{" "} + {chain.label} ))}

diff --git a/src/lib/providers.ts b/src/lib/providers.ts index 8588a77d4..0301b7333 100644 --- a/src/lib/providers.ts +++ b/src/lib/providers.ts @@ -200,11 +200,12 @@ export type ProviderAppearance = { result: ProviderResult; rank: number; totalRanked: number; - /** Per-chain rank for this provider on this bench. Only populated when - * the bench declares chain dimensions AND bestPerChain has at least one - * entry. Key = chain slug (matching dimensions.chain[].value), value = - * { rank, totalRanked } computed within the providers present on that - * chain. Renderers can fall back to `rank` when this is empty. */ + /** Per-chain leadership for this provider on this bench. Populated only + * for chains this provider *leads*, so `rank` is always 1 and an absent + * key means "does not lead here", never "ranked lower here". Key = chain + * slug (matching dimensions.chain[].value). `totalRanked` is how many + * providers were measured on that chain, or 0 when that set is unknown. + * Renderers fall back to the aggregate `rank` when this is empty. */ rankPerChain?: Record; }; @@ -245,18 +246,23 @@ function rankProviders(b: Benchmark): ProviderResult[] { } /** - * Compute per-chain rank for every provider on a bench. Bench must declare - * `dimensions.chain` and have a non-empty `bestPerChain` for any rank to be + * Compute per-chain leadership for a bench. Bench must declare + * `dimensions.chain` and have a non-empty `bestPerChain` for anything to be * recorded. * - * Approximation: spec.ts only stashes the *leader* per chain (one extra Prom - * roundtrip per chain). To express other providers' rank-per-chain, we use a - * coarse fallback: anyone present in the unfiltered `results` is ranked by - * the bench's standard direction (lower-is-better or higher-is-better) - * within the live result set, and the leader's slot is forcibly overridden - * with rank 1 for that chain. This is a soft signal — the bench page chain - * tabs are authoritative — but it is enough to flag chain-restricted - * providers like GMGN as "#1 on Solana only" on /products/[slug]. + * Leaders only, deliberately. spec.ts stashes the *leader* per chain (one + * extra Prom roundtrip per chain), and that is the only per-chain fact we + * actually hold. An earlier version also emitted ranks for non-leaders by + * reusing the unfiltered aggregate order shifted by one slot; those chips + * rendered as "#3 on Solana" while being the provider's *global* rank with a + * chain label stuck on it, identical across every chain of the bench. That + * reads as a measurement and is not one, so it is gone: a provider now gets a + * chip for a chain only when it leads that chain. + * + * `totalRanked` is the number of providers actually measured on the chain, + * taken from `providersPerChain`. It is 0 when the bench has not stashed that + * set (older cached entries), and renderers must then omit the denominator + * rather than substitute the global count. */ function rankPerChainForBench( b: Benchmark, @@ -280,24 +286,14 @@ function rankPerChainForBench( const presentSet = providersPerChain?.[chain.value] ? new Set(providersPerChain[chain.value].map((s) => s.toLowerCase())) : undefined; - const scoped = presentSet - ? liveSorted.filter((r) => presentSet.has(r.slug.toLowerCase())) - : liveSorted; + // Only claim a denominator when we know who was measured on this + // chain; scoped.length over the global list would be a different + // number wearing the same label. + const totalRanked = presentSet + ? liveSorted.filter((r) => presentSet.has(r.slug.toLowerCase())).length + : 0; const perProvider = new Map(); - const leaderLc = leader.slug.toLowerCase(); - const leaderIdx = scoped.findIndex((r) => r.slug.toLowerCase() === leaderLc); - scoped.forEach((r, idx) => { - const lc = r.slug.toLowerCase(); - if (lc === leaderLc) { - perProvider.set(lc, { rank: 1, totalRanked: scoped.length }); - return; - } - // Anyone ranked above the leader in the unfiltered set drops by one - // slot here (since the leader skips ahead of them on this chain). - const rankOnChain = - leaderIdx !== -1 && idx < leaderIdx ? idx + 2 : idx + 1; - perProvider.set(lc, { rank: rankOnChain, totalRanked: scoped.length }); - }); + perProvider.set(leader.slug.toLowerCase(), { rank: 1, totalRanked }); out[chain.value] = perProvider; } return out;