From 3ad120e0f345ff4c589720cdef3240c629051c11 Mon Sep 17 00:00:00 2001 From: Florent Tapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Fri, 28 Aug 2026 21:52:42 +0200 Subject: [PATCH 1/3] fix(cache): strip heavy fields from aggregate blob to pass 2MB limit Hub pages only need card data. Drop seriesByRegion24h, metricPanels, editorial text (seoIntro/faq/findings/methodology/disclaimer/abstract/ perChainExplainer) from the blob aggregate slim. Cuts serialized size from ~4.3 MB to under 2 MB so unstable_cache actually persists the entry instead of silently dropping it on every render. --- src/lib/aggregate-blob.ts | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/src/lib/aggregate-blob.ts b/src/lib/aggregate-blob.ts index 0c2ff32e..f920fb1f 100644 --- a/src/lib/aggregate-blob.ts +++ b/src/lib/aggregate-blob.ts @@ -26,6 +26,37 @@ import type { Benchmark } from "@/types/benchmark"; import { loadSpecsUncached } from "@/lib/materialize/load"; import { overlayEditorial, slimBenchmarkForCache } from "@/lib/spec"; +// Aggressive slim for the aggregate blob. Hub pages (homepage, categories, +// chains, products) only need card data — they never render editorial text +// or metric panels. Stripping these fields drops the serialized aggregate +// from ~4.3 MB to well under the 2 MB unstable_cache ceiling. +// +// Fields stripped beyond slimBenchmarkForCache (which already removes +// 7d/30d series): +// - extras.seriesByRegion24h (only used on bench detail pages) +// - metricPanels (only used on bench detail pages) +// - seoIntro, faq, disclaimer (editorial, bench detail only) +// - perChainExplainer (bench detail + worker's sitemap.json handles sitemap) +// - findings, methodology (bench detail only; required fields → []) +// - abstract (bench detail only; required field → "") +function slimForBlobAggregate(b: Benchmark): Benchmark { + const base = slimBenchmarkForCache(b); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { seriesByRegion24h: _sbr, ...slimExtras } = base.extras; + return { + ...base, + extras: slimExtras, + metricPanels: undefined, + seoIntro: undefined, + faq: undefined, + perChainExplainer: undefined, + disclaimer: undefined, + findings: [], + methodology: [], + abstract: "", + }; +} + // On any Vercel deployment (production or preview), use the self-hosted // CDN proxy (/api/aggregate on openchainbench.com) so Vercel functions // pay ~1 ms (edge cache hit) instead of ~12 s fetching the 7.5 MB blob @@ -113,7 +144,7 @@ async function fetchAndProject(): Promise { for (const bench of raw.benches) { const spec = specBySlug.get(bench.slug); if (!spec) continue; // Bench in blob no longer has a spec — skip. - projected.push(slimBenchmarkForCache(overlayEditorial(bench, spec))); + projected.push(slimForBlobAggregate(overlayEditorial(bench, spec))); } return projected.sort((a, b) => (a.number ?? "").localeCompare(b.number ?? ""), @@ -130,6 +161,6 @@ async function fetchAndProject(): Promise { */ export const loadAggregateFromBlob = unstable_cache( fetchAndProject, - ["aggregate-blob-v2"], + ["aggregate-blob-v3"], { revalidate: 60, tags: ["bench-aggregate", "benchmarks"] }, ); From c5efd0269fe19764de866e71f7c61aa2f59618a8 Mon Sep 17 00:00:00 2001 From: Florent Tapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Sat, 29 Aug 2026 15:26:27 +0200 Subject: [PATCH 2/3] fix: include still-open Gains positions in carry projection --- src/app/api/fee-compare/route.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/app/api/fee-compare/route.ts b/src/app/api/fee-compare/route.ts index cd724592..bb3fb0e9 100644 --- a/src/app/api/fee-compare/route.ts +++ b/src/app/api/fee-compare/route.ts @@ -1121,16 +1121,19 @@ function reconstructGainsPositions(trades: GainsApiTrade[], cutoffMs: number): P else if (CLOSE_ACTIONS.has(t.action)) e.close = t; } + const now = Date.now(); const slices: PositionSlice[] = []; for (const { open, close } of byId.values()) { - if (!open || !close) continue; + if (!open) continue; const openMs = new Date(open.date).getTime(); if (openMs < cutoffMs) continue; + // Still-open positions use now as close time (same as reconstructHlPositions) + const closeMs = close ? new Date(close.date).getTime() : now; slices.push({ coin: open.pair.split("/")[0], notionalUsd: open.size * open.leverage, openMs, - closeMs: new Date(close.date).getTime(), + closeMs, isLong: open.buy !== false, }); } From faf478446fa3fa22709409958392a00ec932e0e9 Mon Sep 17 00:00:00 2001 From: Florent Tapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Sat, 29 Aug 2026 15:33:46 +0200 Subject: [PATCH 3/3] fix: update Gains open action names to v6 API, handle TradePosSizeIncrease --- src/app/api/fee-compare/route.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/app/api/fee-compare/route.ts b/src/app/api/fee-compare/route.ts index bb3fb0e9..b4f9e2e1 100644 --- a/src/app/api/fee-compare/route.ts +++ b/src/app/api/fee-compare/route.ts @@ -1110,7 +1110,10 @@ function augmentWithHlOpenPositions( } function reconstructGainsPositions(trades: GainsApiTrade[], cutoffMs: number): PositionSlice[] { - const OPEN_ACTIONS = new Set(["MarketOpened", "LimitOrderExecuted"]); + // v5 names: MarketOpened, LimitOrderExecuted — v6 names: TradeOpenedMarket, TradeOpenedLimit + const OPEN_ACTIONS = new Set(["MarketOpened", "LimitOrderExecuted", "TradeOpenedMarket", "TradeOpenedLimit"]); + // TradePosSizeIncrease updates the position size; use latest size as notional + const INCREASE_ACTIONS = new Set(["TradePosSizeIncrease"]); const CLOSE_ACTIONS = new Set(["TradeClosedMarket", "TradeClosedTP", "TradeClosedSL", "TradeClosedLIQ"]); const byId = new Map(); @@ -1118,6 +1121,9 @@ function reconstructGainsPositions(trades: GainsApiTrade[], cutoffMs: number): P if (!byId.has(t.id)) byId.set(t.id, {}); const e = byId.get(t.id)!; if (OPEN_ACTIONS.has(t.action)) e.open = t; + else if (INCREASE_ACTIONS.has(t.action) && e.open) { + e.open = { ...e.open, size: t.size, leverage: t.leverage }; + } else if (CLOSE_ACTIONS.has(t.action)) e.close = t; }