"use client"; import Link from "next/link"; import { useEffect, useRef, useState } from "react"; import type { Stats } from "@/lib/api"; import { fmtInt, relTime } from "@/lib/format"; import { publicFetch } from "@/lib/owner"; /** * Live system strip (spec §65): SOURCES · SENSORS · CHECKS/MIN · EVENTS 24H · BREAKING · SILENT · STATUS. * Server-rendered with real values, then refreshed every 10 s. Numbers tick when they change. */ export function LiveStrip({ initial }: { initial: Stats }) { const [s, setS] = useState(initial); const [now, setNow] = useState(() => Date.now()); useEffect(() => { const tick = (): void => { publicFetch("/api/v1/stats").then(setS).catch(() => undefined); setNow(Date.now()); }; const t = setInterval(tick, 10_000); const n = setInterval(() => setNow(Date.now()), 1000); return () => { clearInterval(t); clearInterval(n); }; }, []); const lastCheckAge = s.last_check_at ? now - new Date(s.last_check_at).getTime() : Infinity; const live = lastCheckAge < 120_000; // `now` differs between server and client renders; the status word is re-rendered right after mount. const items: { label: string; value: number | undefined; href?: string; tone?: string; hint?: string }[] = [ { label: "Sources", value: s.sources, href: "/sources", hint: `${fmtInt(s.sources_first_party)} first-party` }, { label: "Sensors", value: s.sensors, href: "/health", hint: s.sensors_degraded ? `${fmtInt(s.sensors_degraded)} degraded` : "all healthy" }, { label: "Checks / min", value: s.checks_per_min, href: "/health", hint: s.not_modified_ratio_5m !== null && s.not_modified_ratio_5m !== undefined ? `${Math.round(s.not_modified_ratio_5m * 100)}% 304` : undefined }, { label: "Events 24h", value: s.events_24h, href: "/live", hint: s.events_per_min !== undefined ? `${s.events_per_min}/min` : undefined }, { label: "Breaking", value: s.breaking_now ?? s.breaking_24h, href: "/breaking", tone: "text-hot", hint: s.developing_now ? `${s.developing_now} developing` : undefined }, { label: "Silent 24h", value: s.silent_24h, href: "/silent", tone: "text-silent" }, ]; return (
{items.map((it) => ( ))}
Status {live ? "LIVE" : "STALE"} {s.last_check_at ? `last check ${relTime(s.last_check_at, now)}` : "—"}
); } function Cell({ label, value, href, tone, hint }: { label: string; value: number | undefined; href?: string; tone?: string; hint?: string }) { const prev = useRef(value); const [tick, setTick] = useState(false); useEffect(() => { if (prev.current !== value) { prev.current = value; setTick(true); const t = setTimeout(() => setTick(false), 600); return () => clearTimeout(t); } }, [value]); const body = ( <> {label} {fmtInt(value)} {hint && {hint}} ); const cls = "flex min-w-[7.25rem] flex-col gap-0.5 border-r border-line px-3 py-2 last:border-r-0"; return href ? {body} :
{body}
; }