diff --git a/client/src/components/workflows/LanePipelinePanel.tsx b/client/src/components/workflows/LanePipelinePanel.tsx index 97da2187..22571cc3 100644 --- a/client/src/components/workflows/LanePipelinePanel.tsx +++ b/client/src/components/workflows/LanePipelinePanel.tsx @@ -4,8 +4,10 @@ * gets passed, aggregated over every lane and every archived feature by * `GET /api/lanes/pipeline-stats`. One row per node: a bar split into passed * with evidence / passed without / failed, plus the raw counts, so a stage that - * everyone claims but nobody proves is visible at a glance. Read-only — the - * live per-lane view and every lane action stay on the Workspace page. + * everyone claims but nobody proves is visible at a glance. A lane picker + * narrows the same aggregation to one lane and its archived features. + * Read-only — the live per-lane view and every lane action stay on the + * Workspace page. * @author Nguyễn Ngọc Trí Vĩ */ @@ -28,16 +30,17 @@ export function LanePipelinePanel() { const { t } = useTranslation("workflows"); const [data, setData] = useState(null); const [selectedId, setSelectedId] = useState(null); + const [laneId, setLaneId] = useState(null); const [error, setError] = useState(null); const refresh = useCallback(async () => { try { - setData(await api.lanes.pipelineStats()); + setData(await api.lanes.pipelineStats(laneId)); setError(null); } catch (err) { setError(err instanceof Error ? err.message : t("failedLoad")); } - }, [t]); + }, [t, laneId]); useEffect(() => { void refresh(); @@ -62,11 +65,39 @@ export function LanePipelinePanel() { // Follow the list rather than pinning an id: templates can come and go. const selected = data.pipelines.find((p) => p.id === selectedId) ?? data.pipelines[0] ?? null; + // The lane picker sits above the empty branch, not inside the populated one: + // narrowing to a lane with no stage records yet must still leave a way back. + const lanePicker = data.lanes.length > 1 && ( + + ); + if (!selected) { return ( -

- {t("lanes.empty")} ccam lanes add --cwd $(pwd) -

+
+ {lanePicker} +

+ {laneId ? ( + t("lanes.emptyLane") + ) : ( + <> + {t("lanes.empty")} ccam lanes add --cwd $(pwd) + + )} +

+
); } @@ -75,6 +106,7 @@ export function LanePipelinePanel() { return (
+ {lanePicker} {data.pipelines.length > 1 && (