mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-08-28 05:27:41 +00:00
fix(web): close run phase on terminal events (#427)
Run Events no longer leaves the pre-execution Initializing bar open when a run fails before `run.running`. This addresses the waterfall symptom in fabro-sh/fabro#426. The phase derivation now records terminal `run.completed` / `run.failed` events and uses them as fallback boundaries for Submitted, Pending, Runnable, and Initializing phases. The existing `run.running` handoff still takes precedence once execution actually starts. Tested: - `cd apps/fabro-web && bun test app/lib/run-phases.test.ts` - `cd apps/fabro-web && bun run typecheck` - `git diff --check` --- [](https://github.com/EveryInc/compound-engineering-plugin) 🤖 Generated with GPT-5 via [Codex](https://openai.com/codex)
This commit is contained in:
parent
5d6cd48e9e
commit
8bca376f35
2 changed files with 45 additions and 6 deletions
|
|
@ -9,6 +9,7 @@ const T_PENDING = "2026-05-23T12:00:02.000Z";
|
|||
const T_RUNNABLE = "2026-05-23T12:00:03.000Z";
|
||||
const T_STARTING = "2026-05-23T12:00:04.000Z";
|
||||
const T_RUNNING = "2026-05-23T12:00:10.000Z";
|
||||
const T_FAILED = "2026-05-23T12:00:12.000Z";
|
||||
|
||||
function makeEvent(name: string, ts: string, seq: number): EventEnvelope {
|
||||
return {
|
||||
|
|
@ -114,6 +115,38 @@ describe("deriveRunPhases", () => {
|
|||
expect(phases[1]!.endMs).toBe(Date.parse(T_RUNNING));
|
||||
});
|
||||
|
||||
test("closes initializing at run.failed when the run never reaches running", () => {
|
||||
const phases = deriveRunPhases(
|
||||
[
|
||||
makeEvent("run.start_requested", T_REQUESTED, 1),
|
||||
makeEvent("run.runnable", T_RUNNABLE, 2),
|
||||
makeEvent("run.starting", T_STARTING, 3),
|
||||
makeEvent("run.failed", T_FAILED, 4),
|
||||
],
|
||||
CREATED,
|
||||
);
|
||||
expect(phases).toEqual([
|
||||
{
|
||||
kind: "submitted",
|
||||
label: "Submitted",
|
||||
startMs: Date.parse(CREATED),
|
||||
endMs: Date.parse(T_REQUESTED),
|
||||
},
|
||||
{
|
||||
kind: "runnable",
|
||||
label: "Runnable",
|
||||
startMs: Date.parse(T_RUNNABLE),
|
||||
endMs: Date.parse(T_STARTING),
|
||||
},
|
||||
{
|
||||
kind: "initializing",
|
||||
label: "Initializing",
|
||||
startMs: Date.parse(T_STARTING),
|
||||
endMs: Date.parse(T_FAILED),
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("uses run.starting as fallback end for submitted when pre-execution events are missing", () => {
|
||||
const phases = deriveRunPhases(
|
||||
[makeEvent("run.starting", T_STARTING, 1)],
|
||||
|
|
|
|||
|
|
@ -29,11 +29,12 @@ export function deriveRunPhases(
|
|||
let runnableMs: number | null = null;
|
||||
let startingMs: number | null = null;
|
||||
let runningMs: number | null = null;
|
||||
let remaining = 5;
|
||||
let terminalMs: number | null = null;
|
||||
let remaining = 6;
|
||||
|
||||
for (const event of events ?? []) {
|
||||
if (remaining === 0) break;
|
||||
let target: "startRequested" | "pending" | "runnable" | "starting" | "running" | null = null;
|
||||
let target: "startRequested" | "pending" | "runnable" | "starting" | "running" | "terminal" | null = null;
|
||||
switch (event.event) {
|
||||
case "run.start_requested":
|
||||
if (startRequestedMs == null) target = "startRequested";
|
||||
|
|
@ -50,6 +51,10 @@ export function deriveRunPhases(
|
|||
case "run.running":
|
||||
if (runningMs == null) target = "running";
|
||||
break;
|
||||
case "run.completed":
|
||||
case "run.failed":
|
||||
if (terminalMs == null) target = "terminal";
|
||||
break;
|
||||
}
|
||||
if (target == null) continue;
|
||||
const ms = Date.parse(event.ts);
|
||||
|
|
@ -60,6 +65,7 @@ export function deriveRunPhases(
|
|||
case "runnable": runnableMs = ms; break;
|
||||
case "starting": startingMs = ms; break;
|
||||
case "running": runningMs = ms; break;
|
||||
case "terminal": terminalMs = ms; break;
|
||||
}
|
||||
remaining -= 1;
|
||||
}
|
||||
|
|
@ -70,7 +76,7 @@ export function deriveRunPhases(
|
|||
kind: "submitted",
|
||||
label: PHASE_LABEL.submitted,
|
||||
startMs: createdMs,
|
||||
endMs: startRequestedMs ?? pendingMs ?? runnableMs ?? startingMs ?? runningMs,
|
||||
endMs: startRequestedMs ?? pendingMs ?? runnableMs ?? startingMs ?? runningMs ?? terminalMs,
|
||||
});
|
||||
|
||||
if (pendingMs != null) {
|
||||
|
|
@ -78,7 +84,7 @@ export function deriveRunPhases(
|
|||
kind: "pending",
|
||||
label: PHASE_LABEL.pending,
|
||||
startMs: pendingMs,
|
||||
endMs: runnableMs ?? startingMs ?? runningMs,
|
||||
endMs: runnableMs ?? startingMs ?? runningMs ?? terminalMs,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -87,7 +93,7 @@ export function deriveRunPhases(
|
|||
kind: "runnable",
|
||||
label: PHASE_LABEL.runnable,
|
||||
startMs: runnableMs,
|
||||
endMs: startingMs ?? runningMs,
|
||||
endMs: startingMs ?? runningMs ?? terminalMs,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -96,7 +102,7 @@ export function deriveRunPhases(
|
|||
kind: "initializing",
|
||||
label: PHASE_LABEL.initializing,
|
||||
startMs: startingMs,
|
||||
endMs: runningMs,
|
||||
endMs: runningMs ?? terminalMs,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue