diff --git a/src/lib/chain-aliases.ts b/src/lib/chain-aliases.ts new file mode 100644 index 00000000..9250575e --- /dev/null +++ b/src/lib/chain-aliases.ts @@ -0,0 +1,28 @@ +/** + * Chain-slug alias helpers, isolated from the chain registry so that + * `src/lib/spec.ts` can import them without pulling in the registry's + * data-layer dependency (`@/data/benchmarks` → `@/lib/spec` is a cycle + * that fails with a TDZ "Cannot access 'X' before initialization" + * crash at build time when both modules touch each other during ESM + * evaluation). + * + * Used during chain rebrand transitions (e.g. TON → Gram, June 2026) + * where stored snapshots + harness Prom labels still carry the legacy + * slug while the YAMLs + chain registry have moved to the canonical + * one. The site reads through these helpers to absorb the lag. + * + * Add an alias when a chain rebrands; remove an alias once the caches + * and harnesses have rotated past it. + */ + +export const CHAIN_SLUG_ALIASES: Record = { + ton: "gram", +}; + +/** Map any slug to its canonical chain slug. Identity for known slugs, + * resolves a legacy slug to its current canonical via CHAIN_SLUG_ALIASES, + * returns the input lowercased for anything unknown. */ +export function canonicalChainSlug(slug: string): string { + const lc = slug.toLowerCase(); + return CHAIN_SLUG_ALIASES[lc] ?? lc; +} diff --git a/src/lib/chains.ts b/src/lib/chains.ts index af34e3fb..b27e2b6d 100644 --- a/src/lib/chains.ts +++ b/src/lib/chains.ts @@ -14,6 +14,12 @@ import { cache } from "react"; import { getBenchmarksSafe } from "@/data/benchmarks"; import type { Benchmark } from "@/types/benchmark"; +import { + CHAIN_SLUG_ALIASES, + canonicalChainSlug, +} from "@/lib/chain-aliases"; + +export { CHAIN_SLUG_ALIASES, canonicalChainSlug }; export type ChainCategory = "L1" | "L2"; @@ -207,43 +213,14 @@ export const CHAINS: ChainEntry[] = [ export const CHAIN_BY_SLUG = new Map(CHAINS.map((c) => [c.slug, c])); -/** - * Legacy chain slugs that should resolve to a current canonical chain. - * - * Necessary because a slug rename (e.g. TON token rebrand to Gram in - * June 2026) leaves stale identifiers in three places that don't all - * roll over at the same time: - * - YAML provider entries that haven't been edited yet - * - Cached Benchmark snapshots in Upstash KV (populated by the - * materialize worker before the rename, results[].slug = old) - * - Harness Prom labels still emitting the old chain label - * - * `getBenchmarksForChain` honours these aliases so the new canonical - * /chains/ URL still surfaces benches whose results[].slug is the - * old "ton" until everything downstream catches up. ChainHeadingsSummary - * and any other component reading r.slug uses `chainLabelForSlug` to - * resolve the alias to the registry's display label, so a stale "TON" - * row reads as "Gram" anywhere the chain registry can override it. - * - * Add a new alias when a chain rebrands; remove an alias once the - * caches and harnesses have rotated past it. - */ -export const CHAIN_SLUG_ALIASES: Record = { - ton: "gram", -}; - -/** Map any slug to its canonical chain slug. Identity for known slugs, - * resolves a legacy slug to its current canonical via CHAIN_SLUG_ALIASES, - * returns the input lowercased for anything unknown. */ -export function canonicalChainSlug(slug: string): string { - const lc = slug.toLowerCase(); - return CHAIN_SLUG_ALIASES[lc] ?? lc; -} - /** Display label for a slug, resolving aliases against the chain * registry. Returns null when neither the canonical nor the raw slug * is registered, so callers can fall back to whatever local data they - * have (e.g. the bench result's own name field). */ + * have (e.g. the bench result's own name field). Lives here (not in + * chain-aliases.ts) because it depends on the CHAIN_BY_SLUG registry + * which IS in this module. Callers inside the spec/data layer that + * would create a circular import should use `canonicalChainSlug` + * from `@/lib/chain-aliases` directly. */ export function chainLabelForSlug(slug: string): string | null { const canon = canonicalChainSlug(slug); return CHAIN_BY_SLUG.get(canon)?.label ?? null; diff --git a/src/lib/spec.ts b/src/lib/spec.ts index d5ea0e90..e9369440 100644 --- a/src/lib/spec.ts +++ b/src/lib/spec.ts @@ -13,7 +13,11 @@ import { cache } from "react"; import { unstable_cache } from "next/cache"; import type { Benchmark } from "@/types/benchmark"; import type { Spec } from "@/lib/spec-schema"; -import { canonicalChainSlug } from "@/lib/chains"; +// Imported from the isolated alias module (NOT @/lib/chains) to avoid +// a circular import: chains.ts → @/data/benchmarks → @/lib/spec. The +// cycle fails at module-eval time with "Cannot access 'X' before +// initialization" because both ends touch each other during ESM load. +import { canonicalChainSlug } from "@/lib/chain-aliases"; import { renderBenchmarkText } from "@/lib/bench-template"; import { buildEditorial,