diff --git a/apps/web/src/components/pipeline/PipelinePhaseGroup.tsx b/apps/web/src/components/pipeline/PipelinePhaseGroup.tsx index 191c307..dfc7536 100644 --- a/apps/web/src/components/pipeline/PipelinePhaseGroup.tsx +++ b/apps/web/src/components/pipeline/PipelinePhaseGroup.tsx @@ -27,6 +27,7 @@ interface PipelinePhaseGroupProps { } export function PipelinePhaseGroup({ phase, tasks, taskDepsRaw, isBlocked, detailAgent }: PipelinePhaseGroupProps) { + const approvePhase = trpc.approvePhase.useMutation(); const queuePhase = trpc.queuePhase.useMutation(); // Sort tasks topologically by dependency order @@ -52,7 +53,12 @@ export function PipelinePhaseGroup({ phase, tasks, taskDepsRaw, isBlocked, detai return { sorted: sortedTasks, blockedByCountMap: countMap }; }, [tasks, taskDepsRaw]); - const canExecute = phase.status === "approved" && !isBlocked; + const hasNonDetailTasks = tasks.length > 0; + const canExecute = + (phase.status === "approved" || (phase.status === "pending" && hasNonDetailTasks)) && + !isBlocked; + const isPending = phase.status === "pending"; + const isMutating = approvePhase.isPending || queuePhase.isPending; const isDetailing = detailAgent?.status === "running" || detailAgent?.status === "waiting_for_input"; @@ -68,11 +74,21 @@ export function PipelinePhaseGroup({ phase, tasks, taskDepsRaw, isBlocked, detai {canExecute && ( )} diff --git a/apps/web/src/components/pipeline/PipelineTab.tsx b/apps/web/src/components/pipeline/PipelineTab.tsx index db8326d..25fd4ff 100644 --- a/apps/web/src/components/pipeline/PipelineTab.tsx +++ b/apps/web/src/components/pipeline/PipelineTab.tsx @@ -135,12 +135,25 @@ function PipelineTabInner({ initiativeId, phases, phasesLoading }: PipelineTabPr [phases, dependencyEdges], ); - // Count how many phases are approved and ready to execute - const approvedCount = useMemo( - () => phases.filter((p) => p.status === "approved").length, - [phases], - ); + // Count phases that can be executed: approved OR pending with non-detail tasks + const { actionableCount, pendingPhaseIds } = useMemo(() => { + const pending: string[] = []; + let count = 0; + for (const p of phases) { + if (p.status === "approved") { + count++; + } else if (p.status === "pending") { + const phaseTasks = tasksByPhase[p.id] ?? []; + if (phaseTasks.length > 0) { + count++; + pending.push(p.id); + } + } + } + return { actionableCount: count, pendingPhaseIds: pending }; + }, [phases, tasksByPhase]); + const approvePhase = trpc.approvePhase.useMutation(); const queueAll = trpc.queueAllPhases.useMutation(); // Register tasks with ExecutionContext for TaskSlideOver @@ -185,19 +198,24 @@ function PipelineTabInner({ initiativeId, phases, phasesLoading }: PipelineTabPr return (
- {approvedCount > 0 && ( + {actionableCount > 0 && (
)} diff --git a/docs/frontend.md b/docs/frontend.md index a5f122b..67a72f5 100644 --- a/docs/frontend.md +++ b/docs/frontend.md @@ -162,8 +162,9 @@ Configured in `src/lib/trpc.ts`. Uses `@trpc/react-query` with TanStack Query fo ### Pipeline Visualization 1. Execution tab → pipeline DAG shows phases as nodes 2. Drag to add dependencies between phases -3. Approve phases → queue for dispatch -4. Tasks auto-queued when phase starts +3. Per-phase Play button: if phase is `pending` (with non-detail tasks), approves then queues in one click; if already `approved`, just queues +4. "Execute N phases" top-level button: approves all `pending` phases that have tasks, then calls `queueAllPhases` — count includes both `pending` (with tasks) and `approved` phases +5. Tasks auto-queued when phase starts ### Detailing Phases 1. Select phase → "Detail" button