diff --git a/frontend/src/components/Runs/RunsScreen.tsx b/frontend/src/components/Runs/RunsScreen.tsx
index 5156c89..a2f2110 100644
--- a/frontend/src/components/Runs/RunsScreen.tsx
+++ b/frontend/src/components/Runs/RunsScreen.tsx
@@ -6,7 +6,6 @@ import { useRef } from "react"
import type { fluksio__api__routes__runs__RunRow as RunRow } from "@/client"
import { MAX_SERIES } from "@/components/Common/UplotChart"
import { ValuePreview } from "@/components/Flow/ValuePreview"
-import { ago } from "@/components/Health/queries"
import { Button } from "@/components/ui/button"
import { Checkbox } from "@/components/ui/checkbox"
import {
@@ -296,6 +295,12 @@ function ParamValue({ value }: { value: unknown }) {
return {paramText(value)}
}
+/** 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,
@@ -456,14 +461,20 @@ function RunsTable({
only the store's, which is then the whole answer. */}
{shortCommit(run.origin_commit || run.commit || "") || "—"}
-
+
{/* When it started, and how long it then took. Two readings of
- one thing, so one column rather than two. */}
- {ago(String(run.created_at ?? ""))}
- |
-
- {run.duration_ms ? dur(run.duration_ms) : "—"}
-
+ one thing, so one column rather than two — and while a run is
+ still going they are the same reading, so it is written once:
+ how long it has been running is also how long ago it began. */}
+ {run.status === "running" ? (
+ `${dur(run.duration_ms)} ago`
+ ) : (
+ <>
+ {since(run.created_at)}
+ |
+ {run.duration_ms ? dur(run.duration_ms) : "—"}
+ >
+ )}
))}
diff --git a/frontend/src/lib/utils.check.ts b/frontend/src/lib/utils.check.ts
index a0c7d84..5e46fc5 100644
--- a/frontend/src/lib/utils.check.ts
+++ b/frontend/src/lib/utils.check.ts
@@ -38,6 +38,8 @@ assert.equal(dur(0), "0 ms")
assert.equal(dur(203.63), "204 ms")
assert.equal(dur(1240), "1.24 s")
assert.equal(dur(90_000), "1.5 min")
+assert.equal(dur(12_600_000), "3.5 h")
+assert.equal(dur(259_200_000), "3 d")
// The plain window down to 0.01 ms, so a fast run keeps one unit across polls.
assert.equal(dur(0.4), "0.4 ms")
diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts
index acaca2a..3969708 100644
--- a/frontend/src/lib/utils.ts
+++ b/frontend/src/lib/utils.ts
@@ -44,6 +44,8 @@ export function si(value: number, digits = 3): string {
/** Floor, divisor and unit per step, largest first. */
const DUR_STEPS: [number, number, string][] = [
+ [86_400_000, 86_400_000, "d"],
+ [3_600_000, 3_600_000, "h"],
[60_000, 60_000, "min"],
[1_000, 1_000, "s"],
[0.01, 1, "ms"],
@@ -63,6 +65,9 @@ const DUR_STEPS: [number, number, string][] = [
* window transposed onto units. That floor is deliberate: without it a tile
* polled every ten seconds flips between "400 µs" and "1.2 ms", and a reading
* that changes unit on every poll reads as broken rather than as fast.
+ *
+ * It keeps stepping past the minute into hours and days, so that an elapsed
+ * time is one notation wherever it is written — "3.5 h", never "210 min".
*/
export function dur(ms: number, digits = 3): string {
if (!Number.isFinite(ms)) return "--"