Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions src/app/benchmarks/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -123,14 +123,16 @@ export default async function BenchmarkPage({
</Link>

{/* Bench identifier — minimal mono line, no SaaS-style pills. */}
<div className="mt-6 flex flex-wrap items-baseline gap-3 font-mono text-[11px] uppercase tracking-[0.18em] text-ink-muted">
<div className="mt-6 flex flex-wrap items-center gap-3 font-mono text-[11px] uppercase tracking-[0.18em] text-ink-muted">
<span style={{ color: catColor ?? "var(--color-ink-soft)" }}>
{benchmark.category}
</span>
{isDraft && <span className="text-ink-faint">draft</span>}
<span className="ml-auto tabular text-ink-muted normal-case tracking-normal text-xs">
Updated {formatLastRun(benchmark.lastRunAt)}
</span>
{!isDraft && (
<span className="ml-auto">
<LiveIndicator lastRunAt={benchmark.lastRunAt} />
</span>
)}
</div>

{/* Title */}
Expand Down
36 changes: 14 additions & 22 deletions src/components/benchmark-body.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,21 @@ export function BenchmarkBody({

return (
<>
{options.length > 0 && (
<div className="mt-8">
<div className="mt-8 flex flex-wrap items-center justify-between gap-3">
{options.length > 0 ? (
<ChainTabs options={options} selected={chain} onSelect={setChain} />
</div>
)}
) : (
<span />
)}
{!isDraft && (
<ShareSection
slug={benchmark.slug}
title={benchmark.title}
benchmark={benchmark}
chain={chain}
/>
)}
</div>

{!isDraft && benchmark.unit === "count" && (
<>
Expand All @@ -69,14 +79,6 @@ export function BenchmarkBody({
<SectionLabel>Provider ledger</SectionLabel>
<LedgerTable benchmark={benchmark} />
</div>
<div className="mt-10">
<ShareSection
slug={benchmark.slug}
title={benchmark.title}
benchmark={benchmark}
chain={chain}
/>
</div>
</>
)}

Expand Down Expand Up @@ -112,7 +114,6 @@ export function BenchmarkBody({
</dl>

<div className="mt-12">
<SectionLabel>{benchmark.metric} · last 24 hours</SectionLabel>
{benchmark.results.length >= 5 || benchmark.unit === "bps" ? (
<RankedBarChart benchmark={benchmark} />
) : (
Expand All @@ -131,15 +132,6 @@ export function BenchmarkBody({
<RegionGrid benchmark={benchmark} />
</div>
)}

<div className="mt-10">
<ShareSection
slug={benchmark.slug}
title={benchmark.title}
benchmark={benchmark}
chain={chain}
/>
</div>
</>
)}
</>
Expand Down
16 changes: 16 additions & 0 deletions src/components/live-dot.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<span
className={`relative inline-flex h-1.5 w-1.5 items-center justify-center align-middle ${className}`}
aria-hidden
>
<span className="absolute inset-0 rounded-full bg-good opacity-60 animate-ping" />
<span className="relative h-1.5 w-1.5 rounded-full bg-good" />
</span>
);
}
53 changes: 53 additions & 0 deletions src/components/live-indicator.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<span className="inline-flex items-center gap-2 normal-case tracking-normal text-xs tabular text-ink-muted">
<span className="relative flex h-2 w-2 items-center justify-center">
{!stale && (
<span
className="absolute inset-0 rounded-full bg-good opacity-60 animate-ping"
aria-hidden
/>
)}
<span
className={`relative h-2 w-2 rounded-full ${stale ? "bg-ink-faint" : "bg-good"}`}
aria-hidden
/>
</span>
<span className="font-medium" style={{ color: stale ? undefined : "var(--color-good)" }}>
{stale ? "Stale" : "Live"}
</span>
<span className="text-ink-faint">·</span>
<span>updated {formatAge(ageSec)} ago</span>
</span>
);
}

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`;
}
5 changes: 5 additions & 0 deletions src/components/ranked-bar-chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 };

Expand Down Expand Up @@ -45,6 +46,10 @@ export function RankedBarChart({ benchmark }: Props) {

return (
<figure className="my-2">
<p className="mb-3 inline-flex items-center gap-2 text-[10px] font-medium uppercase tracking-[0.18em] text-ink-muted">
<LiveDot />
<span>{benchmark.metric} · last 24 hours</span>
</p>
<ul className="space-y-2">
{rows.map((r, idx) => {
const w = project(r.value);
Expand Down
2 changes: 1 addition & 1 deletion src/components/share-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@
setPairB(s);
}

const activeTemplate = TEMPLATES.find((t) => t.id === activeId);

Check warning on line 125 in src/components/share-section.tsx

View workflow job for this annotation

GitHub Actions / check

'activeTemplate' is assigned a value but never used

// Build the URL with the right params per template.
const cardSrc = (templateId: string) => {
Expand Down Expand Up @@ -173,7 +173,7 @@
<>
<button
onClick={() => setOpen(true)}
className="inline-flex items-center gap-2 rounded-md border border-ink bg-paper px-3.5 py-2 text-[11px] font-medium uppercase tracking-[0.18em] text-ink hover:bg-ink hover:text-paper transition-colors"
className="inline-flex items-center gap-2 rounded-md border border-ink bg-ink px-3.5 py-2 text-[11px] font-medium uppercase tracking-[0.18em] text-paper hover:bg-paper hover:text-ink transition-colors shadow-sm"
>
<ImageIcon size={14} strokeWidth={2} />
Export as image
Expand Down
14 changes: 14 additions & 0 deletions src/components/time-series-chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
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;
Expand All @@ -19,6 +20,13 @@
"7d": 168,
};

const RANGE_LABEL: Record<Range, string> = {
"1h": "last hour",
"6h": "last 6 hours",
"24h": "last 24 hours",
"7d": "last 7 days",
};

const REGION_LABEL: Record<string, string> = {
"us-east": "US-East",
"eu-west": "EU-West",
Expand Down Expand Up @@ -70,6 +78,12 @@

return (
<figure className="my-2">
<p className="mb-3 inline-flex items-center gap-2 text-[10px] font-medium uppercase tracking-[0.18em] text-ink-muted">
<LiveDot />
<span>
{benchmark.metric} · {RANGE_LABEL[range]}
</span>
</p>
<div className="mb-4 flex flex-wrap items-center justify-between gap-3">
<div className="flex items-center gap-1">
{RANGES.map((r) => {
Expand Down Expand Up @@ -286,7 +300,7 @@
const lastY = lastDrawn ? lastDrawn.y : padT + innerH;
return { ...l, color, pts, linePath, fillPath, lastX, lastY, last };
});
}, [lines, padL, padR, padT, padB, innerW, innerH, lo, yRange]);

Check warning on line 303 in src/components/time-series-chart.tsx

View workflow job for this annotation

GitHub Actions / check

React Hook useMemo has unnecessary dependencies: 'padB' and 'padR'. Either exclude them or remove the dependency array

const hoverX = hover ? padL + innerW * (hover.idx / Math.max(1, numPoints - 1)) : null;
const hoverFraction = hover ? hover.idx / Math.max(1, numPoints - 1) : 0;
Expand Down
13 changes: 0 additions & 13 deletions src/data/benchmarks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,16 +67,3 @@ export async function getBenchmarkSlugs(): Promise<string[]> {
}
}

export function formatLastRun(iso: string) {
const d = new Date(iso);
return d.toLocaleString("en-US", {
month: "short",
day: "numeric",
year: "numeric",
hour: "numeric",
minute: "2-digit",
timeZone: "UTC",
timeZoneName: "short",
});
}

Loading