"use client"; import Link from "next/link"; import { useEffect, useState } from "react"; import type { ActivityItem } from "@/lib/activity-types"; import { fmtAgo } from "@/lib/format"; // The running job, live. Polls /api/jobs every two seconds ONLY while something // is running: an idle dashboard makes no requests at all, and one that has just // watched a build finish stops the moment the state leaves "running". // // The per-entry boxes use the same vocabulary ReportBuildChain paints from // (fetching / cutting / done / failed), computed server-side in lib/activity.ts // so this file never re-parses events. const BOX = { fetching: "border-[var(--color-meter)] text-[var(--color-meter)]", cutting: "border-[var(--color-sel)] text-[var(--color-sel)]", done: "border-[var(--color-good)] text-[var(--color-good)]", failed: "border-[var(--color-bad)] text-[var(--color-bad)]", } as const; export default function NowLive({ initial }: { initial: ActivityItem | null }) { const [now, setNow] = useState(initial); // One fetch on mount, so the panel says what the registry says even if the // page render and the route disagreed; then a 2 s poll ONLY while running. useEffect(() => { let alive = true; void fetch("/api/jobs?n=1", { cache: "no-store" }) .then((r) => (r.ok ? r.json() : null)) .then((j: { running: ActivityItem | null } | null) => { if (alive && j && j.running) setNow(j.running); }) .catch(() => {}); return () => { alive = false; }; }, []); useEffect(() => { if (!now || now.state !== "running") return; const t = setInterval(async () => { try { const r = await fetch("/api/jobs?n=1", { cache: "no-store" }); if (!r.ok) return; const j = (await r.json()) as { running: ActivityItem | null; jobs: ActivityItem[] }; // When the running item has gone, show what it became rather than // blanking: the newest job in the list is the one that just finished. setNow(j.running ?? j.jobs.find((x) => x.id === now.id) ?? null); } catch { /* a missed poll is the next poll's problem */ } }, 2000); return () => clearInterval(t); }, [now]); if (!now) { return (

nothing running

); } const p = now.progress; const entries = Object.entries(p?.entries ?? {}); return (
{now.label} {now.source} {p && ( step {p.step}/{p.of} )} {p?.clipsOf !== undefined && ( {p.clipsDone ?? 0}/{p.clipsOf} entries )} {now.state === "running" ? `started ${fmtAgo(now.startedAt)}` : now.state} {now.error ? ` — ${now.error}` : ""}
{entries.length > 0 && (
{entries.map(([id, st]) => ( {id} ))}
)}
); }