diff --git a/src/app/benchmarks/[slug]/page.tsx b/src/app/benchmarks/[slug]/page.tsx index eca94b4d..108c13cd 100644 --- a/src/app/benchmarks/[slug]/page.tsx +++ b/src/app/benchmarks/[slug]/page.tsx @@ -3,13 +3,13 @@ import { notFound } from "next/navigation"; import Link from "next/link"; import { ArrowLeft, ArrowUpRight, ChevronDown } from "lucide-react"; import { - formatLastRun, getBenchmark, getBenchmarks, getBenchmarkSlugs, } from "@/data/benchmarks"; import { Pill } from "@/components/pill"; import { BenchmarkBody } from "@/components/benchmark-body"; +import { LiveIndicator } from "@/components/live-indicator"; import { SectionLabel } from "@/components/summary-stat"; import { CATEGORY_COLOR } from "@/lib/category-colors"; import { SITE } from "@/data/site"; @@ -123,14 +123,16 @@ export default async function BenchmarkPage({ {/* Bench identifier — minimal mono line, no SaaS-style pills. */} -
+
{benchmark.category} {isDraft && draft} - - Updated {formatLastRun(benchmark.lastRunAt)} - + {!isDraft && ( + + + + )}
{/* Title */} diff --git a/src/components/benchmark-body.tsx b/src/components/benchmark-body.tsx index dc08675d..7bc0c0f5 100644 --- a/src/components/benchmark-body.tsx +++ b/src/components/benchmark-body.tsx @@ -56,11 +56,21 @@ export function BenchmarkBody({ return ( <> - {options.length > 0 && ( -
+
+ {options.length > 0 ? ( -
- )} + ) : ( + + )} + {!isDraft && ( + + )} +
{!isDraft && benchmark.unit === "count" && ( <> @@ -69,14 +79,6 @@ export function BenchmarkBody({ Provider ledger
-
- -
)} @@ -112,7 +114,6 @@ export function BenchmarkBody({
- {benchmark.metric} · last 24 hours {benchmark.results.length >= 5 || benchmark.unit === "bps" ? ( ) : ( @@ -131,15 +132,6 @@ export function BenchmarkBody({
)} - -
- -
)} diff --git a/src/components/live-dot.tsx b/src/components/live-dot.tsx new file mode 100644 index 00000000..7ec74e85 --- /dev/null +++ b/src/components/live-dot.tsx @@ -0,0 +1,16 @@ +/** + * Tiny pulsing green dot used inline in section labels to signal that + * the surrounding figure reflects live data. Discreet by design — when + * paired with the masthead `LiveIndicator`, this is just visual reinforcement. + */ +export function LiveDot({ className = "" }: { className?: string }) { + return ( + + + + + ); +} diff --git a/src/components/live-indicator.tsx b/src/components/live-indicator.tsx new file mode 100644 index 00000000..19c5fd1a --- /dev/null +++ b/src/components/live-indicator.tsx @@ -0,0 +1,53 @@ +"use client"; + +import { useEffect, useState } from "react"; + +/** + * Real-time freshness indicator for a bench: a pulsing green dot plus a + * "Live · updated Ns ago" counter that ticks client-side every second. + * Falls back to a muted "Stale" state if `lastRunAt` is older than 5 min, + * which usually means the harness is down or ISR is wedged — better to + * show staleness than to lie about freshness. + */ +export function LiveIndicator({ lastRunAt }: { lastRunAt: string }) { + const [now, setNow] = useState(() => Date.now()); + + useEffect(() => { + const id = setInterval(() => setNow(Date.now()), 1000); + return () => clearInterval(id); + }, []); + + const ageSec = Math.max(0, Math.floor((now - new Date(lastRunAt).getTime()) / 1000)); + const stale = ageSec > 300; + + return ( + + + {!stale && ( + + )} + + + + {stale ? "Stale" : "Live"} + + · + updated {formatAge(ageSec)} ago + + ); +} + +function formatAge(sec: number): string { + if (sec < 60) return `${sec}s`; + const min = Math.floor(sec / 60); + if (min < 60) return `${min}m`; + const h = Math.floor(min / 60); + if (h < 24) return `${h}h`; + return `${Math.floor(h / 24)}d`; +} diff --git a/src/components/ranked-bar-chart.tsx b/src/components/ranked-bar-chart.tsx index 5f96812e..c75e8cef 100644 --- a/src/components/ranked-bar-chart.tsx +++ b/src/components/ranked-bar-chart.tsx @@ -4,6 +4,7 @@ import { useMemo } from "react"; import type { Benchmark } from "@/types/benchmark"; import { fmtUnit } from "@/lib/format"; import { buildProviderColors } from "@/lib/series-colors"; +import { LiveDot } from "@/components/live-dot"; type Props = { benchmark: Benchmark }; @@ -45,6 +46,10 @@ export function RankedBarChart({ benchmark }: Props) { return (
+

+ + {benchmark.metric} · last 24 hours +

    {rows.map((r, idx) => { const w = project(r.value); diff --git a/src/components/share-section.tsx b/src/components/share-section.tsx index 37fa8e6e..550decd6 100644 --- a/src/components/share-section.tsx +++ b/src/components/share-section.tsx @@ -173,7 +173,7 @@ export function ShareSection({ slug, title, benchmark, chain }: Props) { <>