fix: Persist and expose task dependencies from detail output
Detail agents define task dependencies in YAML frontmatter but they were silently dropped — never written to the task_dependencies table. This caused all tasks to dispatch in parallel regardless of intended ordering, and the frontend showed no dependency information. - Add fileIdToDbId mapping and second-pass dependency creation in output-handler.ts (mirrors existing phase dependency pattern) - Add task_dependency to changeset entry entityType enum - Add listPhaseTaskDependencies tRPC procedure for batch querying - Wire blockedBy in PhaseDetailPanel and PhaseWithTasks from real data - Clarify dependency semantics in detail prompt
This commit is contained in:
@@ -119,6 +119,23 @@ export function PhaseDetailPanel({
|
||||
const depsQuery = trpc.getPhaseDependencies.useQuery({ phaseId: phase.id });
|
||||
const dependencyIds = depsQuery.data?.dependencies ?? [];
|
||||
|
||||
// Task-level dependencies
|
||||
const taskDepsQuery = trpc.listPhaseTaskDependencies.useQuery({ phaseId: phase.id });
|
||||
const taskDepsMap = useMemo(() => {
|
||||
const map = new Map<string, Array<{ name: string; status: string }>>();
|
||||
const edges = taskDepsQuery.data ?? [];
|
||||
for (const edge of edges) {
|
||||
const blockers = edge.dependsOn
|
||||
.map((depId) => {
|
||||
const t = tasks.find((tk) => tk.id === depId);
|
||||
return t ? { name: t.name, status: t.status } : null;
|
||||
})
|
||||
.filter(Boolean) as Array<{ name: string; status: string }>;
|
||||
if (blockers.length > 0) map.set(edge.taskId, blockers);
|
||||
}
|
||||
return map;
|
||||
}, [taskDepsQuery.data, tasks]);
|
||||
|
||||
// Resolve dependency IDs to phase objects
|
||||
const resolvedDeps = dependencyIds
|
||||
.map((depId) => phases.find((p) => p.id === depId))
|
||||
@@ -139,11 +156,11 @@ export function PhaseDetailPanel({
|
||||
task,
|
||||
phaseName: `Phase ${displayIndex}: ${phase.name}`,
|
||||
agentName: null,
|
||||
blockedBy: [],
|
||||
blockedBy: taskDepsMap.get(task.id) ?? [],
|
||||
dependents: [],
|
||||
}));
|
||||
handleRegisterTasks(phase.id, entries);
|
||||
}, [tasks, phase.id, displayIndex, phase.name, handleTaskCounts, handleRegisterTasks]);
|
||||
}, [tasks, phase.id, displayIndex, phase.name, handleTaskCounts, handleRegisterTasks, taskDepsMap]);
|
||||
|
||||
// --- Change sets for detail agent ---
|
||||
const changeSetsQuery = trpc.listChangeSets.useQuery(
|
||||
@@ -379,7 +396,7 @@ export function PhaseDetailPanel({
|
||||
key={task.id}
|
||||
task={task}
|
||||
agentName={null}
|
||||
blockedBy={[]}
|
||||
blockedBy={taskDepsMap.get(task.id) ?? []}
|
||||
isLast={idx === sortedTasks.length - 1}
|
||||
onClick={() => setSelectedTaskId(task.id)}
|
||||
onDelete={() => deleteTask.mutate({ id: task.id })}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect } from "react";
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { trpc } from "@/lib/trpc";
|
||||
import { PhaseAccordion } from "@/components/PhaseAccordion";
|
||||
import type { SerializedTask } from "@/components/TaskRow";
|
||||
@@ -30,6 +30,7 @@ export function PhaseWithTasks({
|
||||
}: PhaseWithTasksProps) {
|
||||
const tasksQuery = trpc.listPhaseTasks.useQuery({ phaseId: phase.id });
|
||||
const depsQuery = trpc.getPhaseDependencies.useQuery({ phaseId: phase.id });
|
||||
const taskDepsQuery = trpc.listPhaseTaskDependencies.useQuery({ phaseId: phase.id });
|
||||
|
||||
const tasks = tasksQuery.data ?? [];
|
||||
|
||||
@@ -39,6 +40,7 @@ export function PhaseWithTasks({
|
||||
tasks={tasks}
|
||||
tasksLoaded={tasksQuery.isSuccess}
|
||||
phaseDependencyIds={depsQuery.data?.dependencies ?? []}
|
||||
taskDependencyEdges={taskDepsQuery.data ?? []}
|
||||
defaultExpanded={defaultExpanded}
|
||||
onTaskClick={onTaskClick}
|
||||
onTaskCounts={onTaskCounts}
|
||||
@@ -52,6 +54,7 @@ interface PhaseWithTasksInnerProps {
|
||||
tasks: SerializedTask[];
|
||||
tasksLoaded: boolean;
|
||||
phaseDependencyIds: string[];
|
||||
taskDependencyEdges: Array<{ taskId: string; dependsOn: string[] }>;
|
||||
defaultExpanded: boolean;
|
||||
onTaskClick: (taskId: string) => void;
|
||||
onTaskCounts: (phaseId: string, counts: TaskCounts) => void;
|
||||
@@ -63,11 +66,27 @@ function PhaseWithTasksInner({
|
||||
tasks,
|
||||
tasksLoaded,
|
||||
phaseDependencyIds: _phaseDependencyIds,
|
||||
taskDependencyEdges,
|
||||
defaultExpanded,
|
||||
onTaskClick,
|
||||
onTaskCounts,
|
||||
registerTasks,
|
||||
}: PhaseWithTasksInnerProps) {
|
||||
// Build task dependency map
|
||||
const taskDepsMap = useMemo(() => {
|
||||
const map = new Map<string, Array<{ name: string; status: string }>>();
|
||||
for (const edge of taskDependencyEdges) {
|
||||
const blockers = edge.dependsOn
|
||||
.map((depId) => {
|
||||
const t = tasks.find((tk) => tk.id === depId);
|
||||
return t ? { name: t.name, status: t.status } : null;
|
||||
})
|
||||
.filter(Boolean) as Array<{ name: string; status: string }>;
|
||||
if (blockers.length > 0) map.set(edge.taskId, blockers);
|
||||
}
|
||||
return map;
|
||||
}, [taskDependencyEdges, tasks]);
|
||||
|
||||
// Propagate task counts and entries
|
||||
useEffect(() => {
|
||||
const complete = tasks.filter(
|
||||
@@ -79,17 +98,17 @@ function PhaseWithTasksInner({
|
||||
task,
|
||||
phaseName: phase.name,
|
||||
agentName: null,
|
||||
blockedBy: [],
|
||||
blockedBy: taskDepsMap.get(task.id) ?? [],
|
||||
dependents: [],
|
||||
}));
|
||||
registerTasks(phase.id, entries);
|
||||
}, [tasks, phase.id, phase.name, onTaskCounts, registerTasks]);
|
||||
}, [tasks, phase.id, phase.name, onTaskCounts, registerTasks, taskDepsMap]);
|
||||
|
||||
const sortedTasks = sortByPriorityAndQueueTime(tasks);
|
||||
const taskEntries = sortedTasks.map((task) => ({
|
||||
task,
|
||||
agentName: null as string | null,
|
||||
blockedBy: [] as Array<{ name: string; status: string }>,
|
||||
blockedBy: taskDepsMap.get(task.id) ?? [] as Array<{ name: string; status: string }>,
|
||||
}));
|
||||
|
||||
const phaseDeps: Array<{ name: string; status: string }> = [];
|
||||
|
||||
Reference in New Issue
Block a user