/** * @file The lane pipeline map: a horizontal chain of stage nodes coloured by * state. Layout is computed from the node list (flex + connectors), never from * hardcoded coordinates, so a lane can use a longer or shorter template without * touching this component. Every state but `current` shares one visual * language — coloured border, coloured text, a translucent wash of the same * colour — so status colour means the same thing everywhere in the app, not * a different shade per component. `current` is the sole solid fill, in the * app's own accent colour: the one state that gets to look bolder than the * rest, because it answers "where am I right now". "passed without * evidence" is its own colour: a stage the agent claimed but left no * artifact for is not the same as a stage that is genuinely done. A * `detected` node (the server's heuristic saw tool-event evidence but the * agent never declared it) gets a FOURTH treatment — thin dashed warning, * no fill — because it must never be mistaken for a real declaration. * @author Nguyễn Ngọc Trí Vĩ */ import type { LaneNode } from "../../lib/types"; const STATE_CLASS: Record = { done: "border-status-success/60 text-status-success bg-status-success/10", current: "border-accent bg-accent text-white ring-2 ring-accent/30 shadow-sm", "passed-no-evidence": "border-status-warning/60 text-status-warning bg-status-warning/10", failed: "border-status-danger/60 text-status-danger bg-status-danger/10", pending: "border-border-light text-fg-muted bg-transparent", }; // Thin dashed border and no fill keep an inference visually lighter than // every outlined state above — so it never reads as more certain than a claim. const DETECTED_CLASS = "border-dashed border-status-warning/50 text-status-warning bg-transparent"; export default function PipelineMap({ nodes, detectedSignal, }: { nodes: LaneNode[]; detectedSignal?: string | null; }) { if (!nodes.length) return
no pipeline
; return ( // The map spans the panel: every node takes an equal share and the // connectors absorb the slack, so the pipeline reads as one track across // the width rather than a short cluster hugging the left edge.
{nodes.map((n, i) => (
{n.icon} {n.label}
{i < nodes.length - 1 &&
}
))}
); }