From bf635375aff8ab36017cea5b1ab673b0d554c569 Mon Sep 17 00:00:00 2001 From: Lukas May Date: Wed, 4 Mar 2026 13:11:12 +0100 Subject: [PATCH] refactor: Replace PipelineTaskCard with TaskGraph in pipeline phases Pipeline phase cards now use the same TaskGraph component from the plan tab's phase detail panel. This gives tasks a two-line layout with rail dot, status badge, category badge, priority, and dependency layer connectors instead of the flat single-line display. Removes PipelineTaskCard (no longer imported anywhere). --- .../pipeline/PipelinePhaseGroup.tsx | 80 ++++++------------- .../components/pipeline/PipelineTaskCard.tsx | 55 ------------- docs/frontend.md | 3 +- 3 files changed, 24 insertions(+), 114 deletions(-) delete mode 100644 apps/web/src/components/pipeline/PipelineTaskCard.tsx diff --git a/apps/web/src/components/pipeline/PipelinePhaseGroup.tsx b/apps/web/src/components/pipeline/PipelinePhaseGroup.tsx index 1f12d31..ea9f3d5 100644 --- a/apps/web/src/components/pipeline/PipelinePhaseGroup.tsx +++ b/apps/web/src/components/pipeline/PipelinePhaseGroup.tsx @@ -1,13 +1,10 @@ -import { useMemo, useState } from "react"; -import { ChevronDown, Loader2, Play } from "lucide-react"; +import { useMemo } from "react"; +import { Loader2, Play } from "lucide-react"; import { StatusDot, mapEntityStatus } from "@/components/StatusDot"; import { trpc } from "@/lib/trpc"; import { cn } from "@/lib/utils"; -import { - topologicalSortPhases, - type DependencyEdge, -} from "@codewalk-district/shared"; -import { PipelineTaskCard } from "./PipelineTaskCard"; +import { TaskGraph } from "@/components/execution/TaskGraph"; +import { useExecutionContext } from "@/components/execution"; import type { SerializedTask } from "@/components/TaskRow"; export interface DetailAgentInfo { @@ -27,8 +24,6 @@ interface PipelinePhaseGroupProps { detailAgent?: DetailAgentInfo | null; } -const COLLAPSE_THRESHOLD = 5; - const statusBorderColor: Record = { active: "border-l-status-active-dot", success: "border-l-status-success-dot", @@ -38,22 +33,15 @@ const statusBorderColor: Record = { urgent: "border-l-status-urgent-dot", }; +const emptyAgentMap = new Map(); + export function PipelinePhaseGroup({ phase, tasks, taskDepsRaw, isBlocked, detailAgent }: PipelinePhaseGroupProps) { const approvePhase = trpc.approvePhase.useMutation(); const queuePhase = trpc.queuePhase.useMutation(); - const [expanded, setExpanded] = useState(false); + const { setSelectedTaskId } = useExecutionContext(); - // Sort tasks topologically by dependency order - const { sorted, blockedByCountMap } = useMemo(() => { - const edges: DependencyEdge[] = []; - for (const raw of taskDepsRaw) { - for (const depId of raw.dependsOn) { - edges.push({ phaseId: raw.taskId, dependsOnPhaseId: depId }); - } - } - const sortedTasks = topologicalSortPhases(tasks, edges); - - // Compute blocked-by counts (incomplete dependencies) + // Compute blocked-by counts for TaskGraph + const blockedByCountMap = useMemo(() => { const countMap = new Map(); for (const raw of taskDepsRaw) { const incomplete = raw.dependsOn.filter((depId) => { @@ -62,8 +50,7 @@ export function PipelinePhaseGroup({ phase, tasks, taskDepsRaw, isBlocked, detai }).length; if (incomplete > 0) countMap.set(raw.taskId, incomplete); } - - return { sorted: sortedTasks, blockedByCountMap: countMap }; + return countMap; }, [tasks, taskDepsRaw]); const completedCount = tasks.filter((t) => t.status === "completed").length; @@ -84,13 +71,6 @@ export function PipelinePhaseGroup({ phase, tasks, taskDepsRaw, isBlocked, detai const variant = mapEntityStatus(phase.status); const borderClass = statusBorderColor[variant] ?? statusBorderColor.neutral; - // Collapsible logic - const needsCollapse = sorted.length > COLLAPSE_THRESHOLD; - const visibleTasks = needsCollapse && !expanded - ? sorted.slice(0, COLLAPSE_THRESHOLD) - : sorted; - const hiddenCount = sorted.length - COLLAPSE_THRESHOLD; - return (
+
{isDetailing ? ( -

+

Detailing…

- ) : detailDone && sorted.length === 0 ? ( -

Review changes

- ) : sorted.length === 0 ? ( -

No tasks

+ ) : detailDone && tasks.length === 0 ? ( +

Review changes

+ ) : tasks.length === 0 ? ( +

No tasks

) : ( - <> - {visibleTasks.map((task) => ( - - ))} - {needsCollapse && ( - - )} - + )}
diff --git a/apps/web/src/components/pipeline/PipelineTaskCard.tsx b/apps/web/src/components/pipeline/PipelineTaskCard.tsx deleted file mode 100644 index 4d1a2b6..0000000 --- a/apps/web/src/components/pipeline/PipelineTaskCard.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { CheckCircle2, Loader2, Clock, Ban, Play, AlertTriangle } from "lucide-react"; -import { cn } from "@/lib/utils"; -import { trpc } from "@/lib/trpc"; -import { useExecutionContext } from "@/components/execution"; -import type { SerializedTask } from "@/components/TaskRow"; - -const statusConfig: Record = { - pending: { icon: Clock, color: "text-status-neutral-fg" }, - pending_approval: { icon: AlertTriangle, color: "text-status-warning-dot" }, - in_progress: { icon: Loader2, color: "text-status-active-dot", spin: true }, - completed: { icon: CheckCircle2, color: "text-status-success-dot" }, - blocked: { icon: Ban, color: "text-status-error-dot" }, -}; - -interface PipelineTaskCardProps { - task: SerializedTask; - blockedByCount?: number; -} - -export function PipelineTaskCard({ task, blockedByCount = 0 }: PipelineTaskCardProps) { - const { setSelectedTaskId } = useExecutionContext(); - const queueTask = trpc.queueTask.useMutation(); - - const config = statusConfig[task.status] ?? statusConfig.pending; - const Icon = config.icon; - - return ( -
setSelectedTaskId(task.id)} - > - - {task.name} - {blockedByCount > 0 && ( - - {blockedByCount} dep{blockedByCount === 1 ? "" : "s"} - - )} - {task.status === "pending" && ( - - )} -
- ); -} diff --git a/docs/frontend.md b/docs/frontend.md index 67a72f5..6828b53 100644 --- a/docs/frontend.md +++ b/docs/frontend.md @@ -106,8 +106,7 @@ The initiative detail page has three tabs managed via local state (not URL param | `PipelineTab` | Execution tab entry — fetches tasks, phase deps, task deps | | `PipelineGraph` | Horizontal DAG of phase columns with connectors | | `PipelineStageColumn` | Single depth column containing phase groups | -| `PipelinePhaseGroup` | Phase card with topologically-sorted task list | -| `PipelineTaskCard` | Individual task row with status icon, blocked-by count | +| `PipelinePhaseGroup` | Phase card with status border accent, progress bar, TaskGraph | ### Review Components (`src/components/review/`) | Component | Purpose |