From c03c5998cecb8a1f7fb8c61c1f87ebbc724c1e44 Mon Sep 17 00:00:00 2001 From: Florent Tapponnier Date: Fri, 10 Jul 2026 12:19:33 +0200 Subject: [PATCH] products: signed p50 sentinel (negative funding rendered as warming up) + degraded-store-read tripwire --- src/app/answers/[slug]/page.tsx | 2 +- src/app/benchmarks/[slug]/[chain]/page.tsx | 2 +- src/app/benchmarks/[slug]/share-card/route.tsx | 4 ++-- src/app/compare/[slug]/page.tsx | 2 +- src/app/products/[slug]/page.tsx | 14 ++++++++++++-- 5 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/app/answers/[slug]/page.tsx b/src/app/answers/[slug]/page.tsx index c804399a..1483f6bd 100644 --- a/src/app/answers/[slug]/page.tsx +++ b/src/app/answers/[slug]/page.tsx @@ -103,7 +103,7 @@ export default async function AnswerPage({ // crawlers concrete provider names + numbers in the SSR HTML. const sortedResults = [...bench.results] .filter((r) => !isRegion(r.slug)) - .filter((r) => r.ms.p50 > 0 || r.ms.p99 > 0) + .filter((r) => r.ms.p50 !== 0 || r.ms.p99 !== 0) .sort((a, b) => bench.higherIsBetter ? b.ms.p50 - a.ms.p50 : a.ms.p50 - b.ms.p50, ); diff --git a/src/app/benchmarks/[slug]/[chain]/page.tsx b/src/app/benchmarks/[slug]/[chain]/page.tsx index 9cba3fdc..203b14b1 100644 --- a/src/app/benchmarks/[slug]/[chain]/page.tsx +++ b/src/app/benchmarks/[slug]/[chain]/page.tsx @@ -276,7 +276,7 @@ function buildKeyFacts(data: ChainPageData): string { function pageTitle(data: ChainPageData): string { const b = data.benchmark; if (data.shape === "row") { - return data.result.ms.p50 > 0 + return data.result.ms.p50 !== 0 ? `${data.explainer.h2}: ${fmtUnit(data.result.ms.p50, b.unit)}` : data.explainer.h2; } diff --git a/src/app/benchmarks/[slug]/share-card/route.tsx b/src/app/benchmarks/[slug]/share-card/route.tsx index 655da40a..d462c280 100644 --- a/src/app/benchmarks/[slug]/share-card/route.tsx +++ b/src/app/benchmarks/[slug]/share-card/route.tsx @@ -23,7 +23,7 @@ function sortByP50(b: Benchmark): ProviderResult[] { !!r && !!r.ms && Number.isFinite(r.ms.p50) && - r.ms.p50 > 0, + r.ms.p50 !== 0, ) .sort( b.higherIsBetter @@ -1238,7 +1238,7 @@ async function renderCompare( const bRank = sorted.findIndex((r) => r.slug === b.slug) + 1; const delta = b.ms.p50 - a.ms.p50; - const deltaPct = a.ms.p50 > 0 ? (delta / a.ms.p50) * 100 : 0; + const deltaPct = a.ms.p50 !== 0 ? (delta / a.ms.p50) * 100 : 0; return new ImageResponse( ( diff --git a/src/app/compare/[slug]/page.tsx b/src/app/compare/[slug]/page.tsx index 7129e594..fccc0a52 100644 --- a/src/app/compare/[slug]/page.tsx +++ b/src/app/compare/[slug]/page.tsx @@ -702,7 +702,7 @@ export default async function ComparePage({ // shared is empty (never actually reached because notFound() short- // circuits above, but defensive). const faqEntries: Array<{ q: string; a: string }> = []; - const aWinsBench = shared.find((s) => s.aggregateWinner === "a" && s.aResult.p50 > 0 && s.bResult.p50 > 0); + const aWinsBench = shared.find((s) => s.aggregateWinner === "a" && s.aResult.p50 !== 0 && s.bResult.p50 !== 0); const bWinsBench = shared.find((s) => s.aggregateWinner === "b" && s.aResult.p50 > 0 && s.bResult.p50 > 0); faqEntries.push({ q: `${a.name} vs ${b.name}: which one is better?`, diff --git a/src/app/products/[slug]/page.tsx b/src/app/products/[slug]/page.tsx index e1716aa4..53d458f7 100644 --- a/src/app/products/[slug]/page.tsx +++ b/src/app/products/[slug]/page.tsx @@ -157,6 +157,16 @@ export default async function ProviderPage({ if (!p) notFound(); const reg = getProviderRegistry(p.slug); + // Degraded-read tripwire: a provider listed on several benches never + // loses EVERY rank in the same cycle — that signature means the store + // read failed mid-render (srh timeout, snapshot swap), not that data + // is warming up. Throwing here makes the ISR revalidation fail, so + // Vercel keeps serving the last good render instead of caching a page + // full of "data warming up" for the next 5 minutes. + if (p.appearances.length >= 3 && p.appearances.every((a) => a.rank === 0)) { + throw new Error(`degraded store read for /products/${slug}: ${p.appearances.length} appearances, all unranked`); + } + // HyperTracker-parity dashboard for the 104 Hyperliquid frontends. The // strip renders inline between the product header and the bench // appearances list, only when the slug actually maps to a builder the @@ -186,7 +196,7 @@ export default async function ProviderPage({ // 2026-07-05: only 2 of ~5000 pages indexed). Each sentence is derived // from live measurements — no editorial claim. const rankedAppearances = sorted.filter( - (a) => a.rank > 0 && a.result.ms.p50 > 0, + (a) => a.rank > 0 && a.result.ms.p50 !== 0, ); const topLines: string[] = []; for (const a of rankedAppearances.slice(0, 4)) { @@ -624,7 +634,7 @@ export default async function ProviderPage({
    {sorted.map((a) => { const catColor = CATEGORY_COLOR[a.benchmark.category]; - const hasData = a.rank > 0 && a.result.ms.p50 > 0; + 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