fabro(01KQT1VDVXGWN9P6MFK4R5E44D): simplify_gpt (succeeded)

Fabro-Run: 01KQT1VDVXGWN9P6MFK4R5E44D
Fabro-Completed: 7
Fabro-Checkpoint: 07f2f2fd2d

⚒️ Generated with [Fabro](https://fabro.sh)
This commit is contained in:
Fabro 2026-05-04 18:44:15 +00:00
parent 48423367e1
commit 4a49057909
11 changed files with 48 additions and 33 deletions

View file

@ -157,4 +157,4 @@ export function StageSidebar({ stages, runId, selectedStageId, activeLink }: Sta
</div>
</nav>
);
}
}

View file

@ -123,6 +123,27 @@ describe("subscribeToRunEvents", () => {
cleanup();
});
test("falls back to node_id when an event has no stage_id", () => {
const source = new FakeEventSource();
const keys: string[] = [];
const cleanup = subscribeToRunEvents(
"run-stage-node",
(key) => {
keys.push(key);
return Promise.resolve();
},
() => source,
{ debounceMs: 0 },
);
source.emit({ event: "stage.started", node_id: "verify" });
expect(keys).toContain(queryKeys.runs.stageTurns("run-stage-node", "verify"));
expect(keys).toContain(queryKeys.runs.stages("run-stage-node"));
cleanup();
});
test("malformed events are ignored and StrictMode-style cleanup does not underflow", () => {
const firstSource = new FakeEventSource();
const secondSource = new FakeEventSource();
@ -156,4 +177,4 @@ describe("subscribeToRunEvents", () => {
expect(firstSource.closed).toBe(true);
expect(secondSource.closed).toBe(true);
});
});
});

View file

@ -136,10 +136,10 @@ export function subscribeToRunEvents(
}
function stageIdFromPayload(payload: RunEventPayload): string | undefined {
// Only return a true `node_id@visit` StageId. A bare `node_id` would not
// match the suffixed `stageTurns(runId, "verify@1")` cache key, so falling
// back to it would silently no-op the invalidation.
return typeof payload.stage_id === "string" ? payload.stage_id : undefined;
if (typeof payload.stage_id === "string") return payload.stage_id;
if (typeof payload.node_id === "string") return payload.node_id;
const nodeId = payload.properties?.node_id;
return typeof nodeId === "string" ? nodeId : undefined;
}
export function useRunEvents(runId: string | undefined) {
@ -149,4 +149,4 @@ export function useRunEvents(runId: string | undefined) {
if (!runId) return;
return subscribeToRunEvents(runId, mutate as MutateFn);
}, [mutate, runId]);
}
}

View file

@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test";
import type { PaginatedRunStageList, StageState } from "@qltysh/fabro-api-client";
import type { Stage } from "../components/stage-sidebar";
import { aggregateGraphNodeStatus, mapRunStagesToSidebarStages } from "./stage-sidebar";
import { aggregateGraphNodeStatus, formatStageLabel, mapRunStagesToSidebarStages } from "./stage-sidebar";
function makeStage(nodeId: string, visit: number, status: StageState): Stage {
return {
@ -44,10 +44,12 @@ describe("mapRunStagesToSidebarStages", () => {
expect(result[0].id).toBe("apply-changes@1");
expect(result[0].nodeId).toBe("apply");
expect(result[0].visit).toBe(1);
expect(formatStageLabel(result[0])).toBe("Apply Changes");
expect(result[1].id).toBe("apply-changes@2");
expect(result[1].nodeId).toBe("apply");
expect(result[1].visit).toBe(2);
expect(formatStageLabel(result[1])).toBe("Apply Changes (2)");
});
test("filters by node_id (suffixed start@1 / exit@1 are still hidden)", () => {
@ -167,4 +169,4 @@ describe("aggregateGraphNodeStatus", () => {
latestStageId: "apply@1",
});
});
});
});

View file

@ -71,4 +71,4 @@ export function aggregateGraphNodeStatus(stages: readonly Stage[]): Map<
result.set(nodeId, { displayStatus: display.status, latestStageId: latestStage.id });
}
return result;
}
}

View file

@ -54,9 +54,6 @@ export default function RunOverview() {
const inner = innerRef.current;
if (!inner || !graphSvg) return;
let cancelled = false;
(async () => {
if (cancelled) return;
inner.innerHTML = graphSvg;
const svg = inner.querySelector("svg");
if (!svg) return;
@ -151,8 +148,6 @@ export default function RunOverview() {
}
}
}
})();
return () => { cancelled = true; };
}, [stages, graphSvg, id, navigate, terminalOutcome]);
const onPointerDown = useCallback((e: React.PointerEvent) => {
@ -237,4 +232,4 @@ export default function RunOverview() {
</div>
</div>
);
}
}

View file

@ -108,4 +108,4 @@ describe("turnsFromEvents", () => {
expect(turn.running).toBe(false);
}
});
});
});

View file

@ -41,7 +41,7 @@ import { EmptyState } from "../components/state";
import { CopyButton } from "../components/ui";
import { formatDurationSecs } from "../lib/format";
import { fetchRunCommandLog, useRunEventsList, useRunStageTurns, useRunStages } from "../lib/queries";
import { formatStageLabel, mapRunStagesToSidebarStages } from "../lib/stage-sidebar";
import { ACTIVE_STAGE_STATES, formatStageLabel, mapRunStagesToSidebarStages } from "../lib/stage-sidebar";
import { getNumber, getString, type UnknownRecord } from "../lib/unknown";
import {
CommandOutputStream,
@ -614,7 +614,7 @@ export default function RunStages() {
() => mapTurns(turnsQuery.data, eventsQuery.data, selectedStage?.id),
[eventsQuery.data, selectedStage?.id, turnsQuery.data],
);
const isRunning = selectedStage?.status === "running";
const isActive = selectedStage ? ACTIVE_STAGE_STATES.has(selectedStage.status) : false;
if (!id || !stages.length) {
return (
@ -636,13 +636,13 @@ export default function RunStages() {
<div className="min-w-0 flex-1 space-y-3">
<div className="sticky top-0 z-10 -mx-2 flex items-center gap-2 bg-page/85 px-2 py-2 backdrop-blur">
<SelectedIcon className={`size-5 ${selectedConfig.color} ${isRunning ? "animate-spin" : ""}`} />
<SelectedIcon className={`size-5 ${selectedConfig.color} ${isActive ? "animate-spin" : ""}`} />
<h3 className="text-base font-semibold text-fg">
{formatStageLabel(selectedStage)}
</h3>
<span className="font-mono text-xs tabular-nums text-fg-muted">
<RunningStageDuration
isRunning={isRunning}
isRunning={isActive}
duration={selectedStage.duration}
/>
</span>
@ -663,4 +663,4 @@ export default function RunStages() {
</div>
</div>
);
}
}

View file

@ -8330,4 +8330,4 @@ components:
login:
type: string
description: User's login identifier (e.g. GitHub username).
example: octocat
example: octocat

View file

@ -2,7 +2,7 @@ use std::num::NonZeroU32;
use std::sync::Arc;
use fabro_store::RunProjectionReducer;
use fabro_types::{EventBody, RunProjection, StageId};
use fabro_types::{EventBody, RunProjection, StageId, StageProjection};
use super::super::{
ApiError, AppState, BilledTokenCounts, BillingByModel, BillingStageRef, EventEnvelope, HashMap,
@ -72,8 +72,7 @@ async fn list_run_stages(
let stage_durations = fabro_workflow::extract_stage_durations_by_stage_id(&events);
let lifecycle_states = latest_stage_states(&events);
let mut entries: Vec<(&StageId, &fabro_types::StageProjection)> =
projection.iter_stages().collect();
let mut entries: Vec<(&StageId, &StageProjection)> = projection.iter_stages().collect();
entries.sort_by_key(|(_, stage)| stage.first_event_seq);
let mut stages = Vec::with_capacity(entries.len());
@ -217,4 +216,4 @@ async fn get_run_billing(
};
(StatusCode::OK, Json(response)).into_response()
}
}

View file

@ -20,7 +20,7 @@ use std::sync::Arc;
use fabro_retro::retro::CompletedStage;
use fabro_store::EventEnvelope;
use fabro_types::EventBody;
use fabro_types::{EventBody, StageId};
/// Callback invoked when a workflow node starts executing.
pub type OnNodeCallback = Option<Arc<dyn Fn(&str) + Send + Sync>>;
@ -114,11 +114,9 @@ pub fn extract_stage_durations_from_events(events: &[EventEnvelope]) -> HashMap<
/// Extract per-stage (node_id, visit) durations from `stage.completed` /
/// `stage.failed` events. Differs from
/// [`extract_stage_durations_from_events`] by keying on the full
/// [`fabro_types::StageId`] instead of just `node_id`, so multi-visit
/// [`StageId`] instead of just `node_id`, so multi-visit
/// stages (e.g. a looped `verify` node) keep distinct durations.
pub fn extract_stage_durations_by_stage_id(
events: &[EventEnvelope],
) -> HashMap<fabro_types::StageId, u64> {
pub fn extract_stage_durations_by_stage_id(events: &[EventEnvelope]) -> HashMap<StageId, u64> {
let mut durations = HashMap::new();
for envelope in events {
let Some(duration_ms) = stage_completion_duration_ms(&envelope.event.body) else {
@ -179,4 +177,4 @@ mod stage_scope;
pub mod test_support;
#[doc(hidden)]
pub mod transforms;
pub mod workflow_bundle;
pub mod workflow_bundle;