Run heartbeat
ProA run's state that never claims it's alive when nothing has been heard. Running and heard from recently, it says "Running · heard 1 minute ago". Running and silent past staleAfter (two minutes unless set), it says "No update since 10:42" instead, and that it may have stopped. A host the app can't reach says so, with when. A run that needs the person says what for, with a Respond the app handles. Before the page has a clock (server rendering) it gives the last update's time and claims nothing. Without lastHeardAt the app isn't tracking freshness, and the status is shown as given.
Preview
Install
First time? Get started sets up the registry and theme. Or with shadcn directly: npx shadcn@latest add @candor/run-heartbeat
Brings in: button, locale, relative-time, status, status-dot, utils · npm: lucide-react@^1.67.0
Usage
"use client"
import { RunHeartbeat } from "@/components/run-heartbeat"
const minutesAgo = (m: number) => new Date(Date.now() - m * 60_000).toISOString()
export default function Example() {
return (
<div className="flex max-w-md flex-col divide-y rounded-lg border bg-card">
{/* Heard from a minute ago: running. */}
<RunHeartbeat className="px-4 py-3" status="running" lastHeardAt={minutesAgo(1)} />
{/* Silent for 14 minutes: it no longer says running. */}
<RunHeartbeat className="px-4 py-3" status="running" lastHeardAt={minutesAgo(14)} />
{/* Its machine can't be reached. */}
<RunHeartbeat className="px-4 py-3" status="running" lastHeardAt={minutesAgo(7)} hostOffline={{ host: "Runner 3", since: minutesAgo(6) }} />
{/* Waiting on a person, with what for. */}
<RunHeartbeat className="px-4 py-3" status="waiting" needs="Approve dropping partner_tier_old" onRespond={() => {}} />
<RunHeartbeat className="px-4 py-3" status="succeeded" />
</div>
)
}<RunHeartbeat>
| Prop | Type | Default | Description |
|---|---|---|---|
| statusrequired | RunStatus | — | The run's status as the service last reported it. |
| lastHeardAt | Date | string | undefined | — | When the app last heard anything from the run: an event, a log line, a heartbeat. |
| hostOffline | HostOffline | undefined | — | Set when the machine or worker running it can't be reached. Shown ahead of everything but a finished run. |
| needs | string | undefined | — | What the run needs from the person, in plain words. Set, or with status "waiting", it shows as needing you. |
| label | string | undefined | — | Your word for the status ("Making"), used while the run is live or finished. |
| staleAfter | number | undefined | STALE_AFTER | How long a running run may be silent before it's no longer called running, in milliseconds. Defaults to two minutes. |
| onRespond | (() => void) | undefined | — | Called when the person picks Respond on a run that needs them. Without it, there's no button. |
| compact | boolean | undefined | false | One line, for lists: no hint and no button. |
| className | string | undefined | — | Extra classes for the outer element. |
Functions
useLivenessWords(staleAfter = STALE_AFTER)The words for a run that can't be called alive, for components that show many runs: const words = useLivenessWords(); words(run) gives its liveness and, when it's stale, offline or unchecked, the line to show instead of its status ("No update since 10:42"). For a live or finished run the text is null.
Strings
The fixed words, in English by default. Change any of them under runHeartbeat in <LocaleProvider strings={{ … }}>; the table is exported as runHeartbeatStrings.
| Key | English |
|---|---|
| heard | heard {time} |
| stale | No update since {time} |
| noUpdate | No update received |
| staleHint | It may have stopped. Nothing has been heard from it since. |
| offline | {host} is offline |
| offlineSince | {host} offline since {time} |
| host | The host |
| lastUpdate | Last update {time} |
| needsYou | Needs you |
| respond | Respond |