From 8b4442096d012687a9843de490f4e29693b07587 Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Sat, 9 May 2026 19:16:27 -0400 Subject: [PATCH] refactor(web): polish stage renderer designs after visual review Tighten the meta bar (drop redundant labels, fold timestamp into the duration tooltip), elevate the wait card to a centered hero clock, strengthen the conditional view with the actual chosen edge and condition expression sourced from run-level edge.selected events, upgrade the fan-in selected card with a trophy badge and gradient, and redesign the parallel stat strip with toned numbers and a duration column. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../stage-renderers/conditional-decision.tsx | 124 +++++++++++++++--- .../stage-renderers/fan-in-results.tsx | 49 ++++--- .../app/components/stage-renderers/helpers.ts | 51 ++++++- .../components/stage-renderers/meta-bar.tsx | 28 ++-- .../stage-renderers/parallel-children.tsx | 48 +++++-- .../stage-renderers/wait-status.tsx | 43 +++--- apps/fabro-web/app/routes/run-stages.tsx | 13 +- 7 files changed, 269 insertions(+), 87 deletions(-) diff --git a/apps/fabro-web/app/components/stage-renderers/conditional-decision.tsx b/apps/fabro-web/app/components/stage-renderers/conditional-decision.tsx index 3ff6c8581..748f46853 100644 --- a/apps/fabro-web/app/components/stage-renderers/conditional-decision.tsx +++ b/apps/fabro-web/app/components/stage-renderers/conditional-decision.tsx @@ -1,39 +1,127 @@ +import { useMemo } from "react"; +import { Link } from "react-router"; import { + ArrowLongRightIcon, ArrowsRightLeftIcon, - InformationCircleIcon, } from "@heroicons/react/20/solid"; +import type { EventEnvelope } from "@qltysh/fabro-api-client"; import type { Stage } from "../stage-sidebar"; import { StageMetaBar } from "./meta-bar"; +import { findEdgeForNode } from "./helpers"; -// TODO: render the evaluated condition expression and the chosen edge once -// `ConditionalHandler` emits a `conditional.evaluated` event with that data. -// Right now the handler is a passthrough — engine-level edge selection is the -// only signal — so we only have notes/duration to display. +const REASON_LABEL: Record = { + condition: "Matched condition", + unconditional: "Default edge", + jump: "Jumped", + preferred_label: "Preferred label", +}; + +function reasonLabel(reason: string): string { + return REASON_LABEL[reason] ?? reason; +} + +export function ConditionalDecision({ + stage, + runEvents, + allStages, + runId, +}: { + stage: Stage; + runEvents: EventEnvelope[]; + allStages: Stage[]; + runId: string; +}) { + const edge = useMemo( + () => findEdgeForNode(runEvents, stage.nodeId), + [runEvents, stage.nodeId], + ); + const targetStage = useMemo(() => { + if (!edge) return null; + let pick: Stage | null = null; + for (const s of allStages) { + if (s.nodeId !== edge.toNode) continue; + if (!pick || s.visit > pick.visit) pick = s; + } + return pick; + }, [allStages, edge]); -export function ConditionalDecision({ stage }: { stage: Stage }) { return (
-
+
-

- The conditional {stage.nodeId}{" "} - evaluated and the workflow continued along its chosen edge. -

-

- +

+
+ From +
+
+ {stage.nodeId} +
+
+
+ + {edge && ( +
+
Reason
+
{reasonLabel(edge.reason)}
+ {edge.condition && ( + <> +
Condition
+
+ {edge.condition} +
+ + )} + {edge.isJump && ( + <> +
Edge type
+
Jump
+ + )} +
+ )} ); diff --git a/apps/fabro-web/app/components/stage-renderers/fan-in-results.tsx b/apps/fabro-web/app/components/stage-renderers/fan-in-results.tsx index ec650a863..a7d4b7f5c 100644 --- a/apps/fabro-web/app/components/stage-renderers/fan-in-results.tsx +++ b/apps/fabro-web/app/components/stage-renderers/fan-in-results.tsx @@ -1,6 +1,7 @@ import { useMemo } from "react"; import { CpuChipIcon, + SparklesIcon, TrophyIcon, } from "@heroicons/react/20/solid"; import type { EventEnvelope } from "@qltysh/fabro-api-client"; @@ -111,26 +112,36 @@ export function FanInResults({ } /> -
-
-