Render live durations immediately instead of leaving them to the ticker

The 10s table poll rebuilt the rows with an empty duration cell for
running/pending builds (durationSeconds is null until a build finishes),
which the once-per-second ticker then filled back in — a visible flicker.
Rows and current-build cards now compute the elapsed time at render time.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
mhoennig
2026-08-10 15:45:33 +02:00
co-authored by Claude Fable 5
parent 9e7982ce34
commit 2cb6b1598a
+20 -2
View File
@@ -56,6 +56,24 @@ function statusCssClass(status) {
return "status status-" + (KNOWN_STATUSES.has(status) ? status : "unknown"); return "status status-" + (KNOWN_STATUSES.has(status) ? status : "unknown");
} }
/** Seconds elapsed since `startedAtIso`, or null when it is missing/invalid. */
function elapsedSeconds(startedAtIso) {
const startedAt = new Date(startedAtIso || "").getTime();
return Number.isNaN(startedAt) ? null : (Date.now() - startedAt) / 1000;
}
/**
* The duration to display for a build: the recorded duration once finished, the
* live elapsed time while running or pending — so re-rendered rows never show an
* empty cell that the once-per-second ticker fills back in (visible flicker).
*/
function displayDurationSeconds(build) {
if (build.durationSeconds != null) {
return build.durationSeconds;
}
return build.status === "running" || build.status === "pending" ? elapsedSeconds(build.startedAt) : null;
}
// ---- shared infrastructure ------------------------------------------------- // ---- shared infrastructure -------------------------------------------------
const FETCH_TIMEOUT_MS = 8000; const FETCH_TIMEOUT_MS = 8000;
@@ -216,7 +234,7 @@ function renderBuildRow(build, allowRestart) {
startedCell.dataset.label = "Started"; startedCell.dataset.label = "Started";
row.appendChild(startedCell); row.appendChild(startedCell);
const durationCell = elem("td", "duration-cell", formatDuration(build.durationSeconds)); const durationCell = elem("td", "duration-cell", formatDuration(displayDurationSeconds(build)));
durationCell.dataset.label = "Duration"; durationCell.dataset.label = "Duration";
row.appendChild(durationCell); row.appendChild(durationCell);
@@ -309,7 +327,7 @@ function renderBuildCard(build) {
); );
header.appendChild(commitCode); header.appendChild(commitCode);
header.appendChild(elem("span", "muted", "started " + formatTimestamp(build.startedAt))); header.appendChild(elem("span", "muted", "started " + formatTimestamp(build.startedAt)));
header.appendChild(elem("span", "duration-cell running-duration")); header.appendChild(elem("span", "duration-cell running-duration", formatDuration(displayDurationSeconds(build))));
const cardActions = elem("span", "build-card-actions"); const cardActions = elem("span", "build-card-actions");
cardActions.appendChild( cardActions.appendChild(
actionButton("× Cancel", "Cancel build", "cancel-button", { actionButton("× Cancel", "Cancel build", "cancel-button", {