mission control’s network tab shows scheduled-task health: who runs on what cadence, when each last fired, whether the last run was green or red. the data is regenerated by a cron every fifteen minutes through the day. but the ui never knew. i had to refresh the page to see the latest.
the existing global watcher (chokidar + sse) has an exclusion list
that skips Network/data/* because that data churns on every cron
tick and would refresh every tab in the browser, mostly for no
reason. the right thing was not to remove the exclusion globally.
per-tab refresh instead
the cleaner version is: each tab that needs near-live freshness mounts its own tiny “refresh while i’m here” component:
'use client';
import { useEffect } from 'react';
import { useRouter } from 'next/navigation';
export function NetworkLiveRefresh() {
const router = useRouter();
useEffect(() => {
const id = setInterval(() => router.refresh(), 60_000);
return () => clearInterval(id);
}, [router]);
return null;
}mounted at the top of /network, ticks every sixty seconds, cleans up
when you leave the tab. no other tab pays any cost. if i ever want
the same behavior on the home tab or a future “live deploys” page, i
drop the same component in. the global watcher continues to do what
it was originally designed for: vault edits that humans care about
seeing immediately.
the rule that fell out of this: don’t centralize freshness. let the tabs that need it ask for it. the central infrastructure is for the class of changes that affect everything.
sortable columns, while i was in there
the automations table grew enough rows that scanning for “what
failed” was annoying. extracted the table into a 'use client'
component, added sort state on each header. status sorts by traffic-
light severity (red → yellow → unknown → green) so failures rise
to the top automatically; everything else is alphabetical or
chronological.
clicking a header cycles asc → desc → unsorted. unsorted falls back to the page’s source order. the third click is the one nobody remembers exists, but it’s the only way to undo without a refresh.
the bite that keeps biting
third project in a row hit the same next.js error: passing a function as a prop into a client component fails at runtime with “Functions cannot be passed directly to Client Components.” the fix is consistent: pre-compute the strings server-side and ship plain data:
// server page
const rows: AutomationRowView[] = automations.map((a) => ({
data: a,
displayStatus: STATUS_LABEL[a.status],
lastRunRelative: formatRelativeTime(a.lastRun),
}));
return <AutomationsTable rows={rows} />;the client gets displayStatus: "ok" already as a string, not a
formatter that can produce one. once you’re disciplined about it,
client components only care about display, not derivation.
none of this changes what the dashboard does. it changes how the data feels. going from “this number is from when i loaded the page” to “this number is from a minute ago, and it’ll be a minute newer in a minute.” that’s most of the difference between a dashboard you check and one you trust.