Give a run a timestamp for when it last moved

The collection had 'created' and nothing else, so the card read a field that was
not there, got NaN, and called every live run stalled. status alone cannot answer
'is it still alive' -- a killed process leaves its record saying running for ever
-- so the honest signal is when it last updated.

And PocketBase hands back '2026-09-10 09:42:49.664Z': a space instead of the T,
with the zone already on the end. Appending another Z made every parse NaN.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Claude 2026-09-10 11:44:07 +02:00
parent 5a4d0e1c91
commit 886e2643cf
2 changed files with 36 additions and 3 deletions

View file

@ -0,0 +1,25 @@
/// <reference path="../pb_data/types.d.ts" />
// When a run last moved.
//
// The collection had `created` and nothing else, so "is this run still alive or
// did it die" had no answer: the dashboard read a field that was not there, got
// NaN, and called every live run stalled. `status` alone cannot answer it -- a
// killed process leaves its record saying `running` for ever -- so the honest
// signal is the timestamp of the last update.
migrate(
(app) => {
const runs = app.findCollectionByNameOrId("e2e_runs");
runs.fields.add(
new AutodateField({ name: "updated", onCreate: true, onUpdate: true }),
);
app.save(runs);
},
(app) => {
const runs = app.findCollectionByNameOrId("e2e_runs");
const f = runs.fields.getByName("updated");
if (f) runs.fields.removeById(f.id);
app.save(runs);
},
);

View file

@ -714,9 +714,17 @@ function outcomePill(o){
// deliberately generous and only ever says "stalled", never "failed". // deliberately generous and only ever says "stalled", never "failed".
var STALL_MS = 300000; var STALL_MS = 300000;
// PocketBase hands back "2026-09-10 09:42:49.664Z" — a space instead of the T
// and the zone already on the end. Appending another Z made every parse NaN,
// which made every live run look stalled.
function pbDate(s){
var iso = String(s || "").replace(" ", "T");
return Date.parse(/[Z+]/.test(iso.slice(10)) ? iso : iso + "Z");
}
function isLive(r){ function isLive(r){
return r.status === "running" && var at = pbDate(r.updated || r.created);
(Date.now() - Date.parse(String(r.updated).replace(" ", "T") + "Z")) < STALL_MS; return r.status === "running" && !isNaN(at) && (Date.now() - at) < STALL_MS;
} }
function isStalled(r){ function isStalled(r){
@ -742,7 +750,7 @@ function progressBar(r){
'<div class="runningnow"><span>' + done + " z " + total + "</span>" + '<div class="runningnow"><span>' + done + " z " + total + "</span>" +
(r.current ? "<code>" + esc(r.current) + "</code>" : "") + (r.current ? "<code>" + esc(r.current) + "</code>" : "") +
(isStalled(r) ? "<span>naposledy se pohnul " + (isStalled(r) ? "<span>naposledy se pohnul " +
esc(String(r.updated).substring(11, 16)) + "</span>" : "") + esc(String(r.updated || r.created).substring(11, 16)) + "</span>" : "") +
"</div>"; "</div>";
} }