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;