One elapsed-time notation: ago() reads dur's units app-wide, health runs cell widened

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WgBfkm4rCmtd1T4vHWjEPx
This commit is contained in:
2026-10-15 17:10:36 +02:00
co-authored by Claude Opus 5.5
parent 59f069022f
commit 70af80f6f6
10 changed files with 22 additions and 41 deletions
@@ -1,9 +1,9 @@
import { useEffect, useSyncExternalStore } from "react"
import { ago } from "@/components/Health/queries"
import { connectionStore } from "@/lib/connectionStore"
import { dismiss, notify } from "@/lib/notificationStore"
import { isPortal } from "@/lib/portal"
import { ago } from "@/lib/utils"
/** One card, replaced in place, rather than one per poll that confirms it. */
const ID = "connection-offline"
@@ -6,6 +6,7 @@ import { Marquee } from "@/components/Common/Marquee"
import { Button } from "@/components/ui/button"
import { Popover, PopoverAnchor, PopoverContent } from "@/components/ui/popover"
import useCustomToast from "@/hooks/useCustomToast"
import { ago } from "@/lib/utils"
import { displayName } from "./deriveEdges"
import { useLiveValue } from "./liveStore"
import { MessageSparkline } from "./MessageSparkline"
@@ -13,11 +14,7 @@ import { ValuePreview } from "./ValuePreview"
export function relativeTime(ts: number | null | undefined): string {
if (!ts) return "not seen yet"
const seconds = Math.max(0, Math.round(Date.now() / 1000 - ts))
if (seconds < 5) return "just now"
if (seconds < 60) return `${seconds}s ago`
if (seconds < 3600) return `${Math.round(seconds / 60)}m ago`
return `${Math.round(seconds / 3600)}h ago`
return Date.now() / 1000 - ts < 5 ? "just now" : ago(ts)
}
/**
@@ -8,9 +8,8 @@ import { UplotChart } from "@/components/Common/UplotChart"
import { useEngineEventMinute } from "@/components/Flow/liveStore"
import { PANEL_SECTION } from "@/components/Flow/SidePanel"
import { Button } from "@/components/ui/button"
import { cn, dur, si } from "@/lib/utils"
import { ago, cn, dur, si } from "@/lib/utils"
import {
ago,
auditQueryOptions,
CARD,
clock,
@@ -358,7 +357,7 @@ export function HealthActivity({ range }: { range: Range }) {
<span className="w-20 shrink-0 whitespace-nowrap text-right text-muted-foreground">
{dur(run.duration_ms)}
</span>
<span className="w-16 shrink-0 text-right text-xs text-muted-foreground">
<span className="w-24 shrink-0 whitespace-nowrap text-right text-xs text-muted-foreground">
{ago(run.started_at)}
</span>
</div>
@@ -5,9 +5,8 @@ import { type Range, RangePicker } from "@/components/Common/RangePicker"
import { PANEL_SECTION } from "@/components/Flow/SidePanel"
import { runOverviewQueryOptions } from "@/components/Runs/queries"
import { Badge } from "@/components/ui/badge"
import { cn, dur, si } from "@/lib/utils"
import { ago, cn, dur, si } from "@/lib/utils"
import {
ago,
CARD,
failuresQueryOptions,
flowRollupsQueryOptions,
-11
View File
@@ -122,17 +122,6 @@ export const deadLetterQueryOptions = () => ({
refetchInterval: REFRESH,
})
/** How long ago something happened, in the coarsest unit that still says it. */
export function ago(ts: string | number | null | undefined): string {
if (!ts) return "—"
const stamp = typeof ts === "number" ? ts * 1000 : Date.parse(ts)
const seconds = Math.max(0, (Date.now() - stamp) / 1000)
if (seconds < 90) return `${Math.round(seconds)}s ago`
if (seconds < 5400) return `${Math.round(seconds / 60)}m ago`
if (seconds < 172800) return `${Math.round(seconds / 3600)}h ago`
return `${Math.round(seconds / 86400)}d ago`
}
/**
* The minute a timestamp belongs to, as unix seconds.
*
+1 -2
View File
@@ -5,7 +5,6 @@ import { useState } from "react"
import type { ArtifactRow, RunNodeRow } from "@/client"
import { ValuePreview } from "@/components/Flow/ValuePreview"
import { ago } from "@/components/Health/queries"
import { Button } from "@/components/ui/button"
import { Skeleton } from "@/components/ui/skeleton"
import {
@@ -17,7 +16,7 @@ import {
TableRow,
} from "@/components/ui/table"
import useCustomToast from "@/hooks/useCustomToast"
import { cn, dur, si } from "@/lib/utils"
import { ago, cn, dur, si } from "@/lib/utils"
import { MetricPicker, RunMetricChart, useMetricNames } from "./MetricChart"
import { OpenInDashboard } from "./OpenInDashboard"
import {
+2 -8
View File
@@ -39,7 +39,7 @@ import {
TableHeader,
TableRow,
} from "@/components/ui/table"
import { cn, dur } from "@/lib/utils"
import { ago, cn, dur } from "@/lib/utils"
import {
AxisPicker,
MetricPicker,
@@ -425,12 +425,6 @@ function ParamValue({ value }: { value: unknown }) {
)
}
/** How long ago a moment was, in `dur`'s units so a row's two times agree. */
function since(ts: unknown): string {
if (!ts) return "—"
return `${dur(Date.now() - Date.parse(String(ts)))} ago`
}
function RunsTable({
runs,
search,
@@ -611,7 +605,7 @@ function RunsTable({
`${dur(run.duration_ms)} ago`
) : (
<>
{since(run.created_at)}
{ago(String(run.created_at ?? ""))}
<span className="px-1.5 text-border">|</span>
<span>{run.duration_ms ? dur(run.duration_ms) : "—"}</span>
</>
+10
View File
@@ -81,3 +81,13 @@ export function dur(ms: number, digits = 3): string {
]
return `${Number((rounded / factor).toPrecision(digits))} ${unit}`
}
/**
* How long ago a moment was, in `dur`'s notation: "4.52 min ago". A number is
* unix seconds, a string an ISO timestamp.
*/
export function ago(ts: string | number | null | undefined): string {
if (!ts) return "—"
const stamp = typeof ts === "number" ? ts * 1000 : Date.parse(ts)
return `${dur(Math.max(0, Date.now() - stamp))} ago`
}
+2 -8
View File
@@ -23,6 +23,7 @@ import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import useAuth from "@/hooks/useAuth"
import useCustomToast from "@/hooks/useCustomToast"
import { ago } from "@/lib/utils"
import { handleError } from "@/utils"
export const Route = createFileRoute("/_layout/workers")({
@@ -40,13 +41,6 @@ const emptyFlavor = { name: "", cpus: 1, gpus: 0, ram: 2048, description: "" }
/** Fresh enough to watch a node take a machine, slow enough to be free. */
const REFETCH_MS = 5000
function ago(seconds: number): string {
const since = Date.now() / 1000 - seconds
if (since < 60) return `${Math.max(0, Math.round(since))}s ago`
if (since < 3600) return `${Math.round(since / 60)}m ago`
return `${Math.round(since / 3600)}h ago`
}
type Level = { total: number; free: number }
/** "cpu 2/18" — what is in use, out of what there is. */
@@ -203,7 +197,7 @@ function Attached() {
{worker.protocol}
</span>
<span>
{worker.behind ? "refused" : "seen"} {ago(worker.last_seen ?? 0)}
{worker.behind ? "refused" : "seen"} {ago(worker.last_seen)}
</span>
</span>
</div>
+1 -1
View File
@@ -1,7 +1,7 @@
import { AxiosError } from "axios"
import type { ApiError } from "./client"
import { ago } from "./components/Health/queries"
import { offlineDetail } from "./lib/connectionStore"
import { ago } from "./lib/utils"
function extractErrorMessage(err: ApiError): string {
// An unreachable instance is not a rejected action: say plainly that