From 72b83aae47bb3ed9228bb2c7d2c5bceefc27acd7 Mon Sep 17 00:00:00 2001 From: Test User Date: Tue, 24 Mar 2026 16:08:09 -0700 Subject: [PATCH 1/2] feat(web-ui): task dependency graph visualization in detail modal (#476) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - TaskCard: replace title attr with Tooltip explaining dependency semantics - TaskDetailModal: full dependency list with task names, status badges, and navigation via onOpenTask prop (closes + opens dependency's modal) - Incomplete dependencies (not DONE/MERGED) highlighted in amber - TaskBoardView: passes onOpenTask={handleTaskClick} to TaskDetailModal - useSWR for all tasks in modal (reuses TaskBoardView's cache — no extra request) --- web-ui/src/components/tasks/TaskBoardView.tsx | 1 + web-ui/src/components/tasks/TaskCard.tsx | 18 ++++-- .../src/components/tasks/TaskDetailModal.tsx | 56 ++++++++++++++++--- 3 files changed, 63 insertions(+), 12 deletions(-) diff --git a/web-ui/src/components/tasks/TaskBoardView.tsx b/web-ui/src/components/tasks/TaskBoardView.tsx index 7b7a8fd7..56c7898b 100644 --- a/web-ui/src/components/tasks/TaskBoardView.tsx +++ b/web-ui/src/components/tasks/TaskBoardView.tsx @@ -376,6 +376,7 @@ export function TaskBoardView({ workspacePath }: TaskBoardViewProps) { onClose={handleCloseDetail} onExecute={handleExecute} onStatusChange={handleStatusChange} + onOpenTask={handleTaskClick} /> {/* Bulk action confirmation */} diff --git a/web-ui/src/components/tasks/TaskCard.tsx b/web-ui/src/components/tasks/TaskCard.tsx index 9b46f26b..70b6075a 100644 --- a/web-ui/src/components/tasks/TaskCard.tsx +++ b/web-ui/src/components/tasks/TaskCard.tsx @@ -6,6 +6,7 @@ import { Card, CardContent } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Checkbox } from '@/components/ui/checkbox'; +import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from '@/components/ui/tooltip'; import type { Task, TaskStatus, ProofRequirement } from '@/types'; /** Map backend TaskStatus to badge variant name. */ @@ -97,10 +98,19 @@ export function TaskCard({ {task.depends_on.length > 0 && ( - - - {task.depends_on.length} - + + + + + + {task.depends_on.length} + + + + Depends on {task.depends_on.length} task{task.depends_on.length !== 1 ? 's' : ''}. This task will become READY when all dependencies complete. + + + )} diff --git a/web-ui/src/components/tasks/TaskDetailModal.tsx b/web-ui/src/components/tasks/TaskDetailModal.tsx index db283c74..0b243c11 100644 --- a/web-ui/src/components/tasks/TaskDetailModal.tsx +++ b/web-ui/src/components/tasks/TaskDetailModal.tsx @@ -24,9 +24,10 @@ import { } from '@/components/ui/dialog'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; +import useSWR from 'swr'; import { tasksApi } from '@/lib/api'; import { useRequirementsLookup } from '@/hooks/useRequirementsLookup'; -import type { Task, TaskStatus, ApiError } from '@/types'; +import type { Task, TaskStatus, ApiError, TaskListResponse } from '@/types'; const STATUS_BADGE_VARIANT: Record = { BACKLOG: 'backlog', @@ -55,6 +56,7 @@ interface TaskDetailModalProps { onClose: () => void; onExecute: (taskId: string) => void; onStatusChange: () => void; + onOpenTask?: (taskId: string) => void; } export function TaskDetailModal({ @@ -64,6 +66,7 @@ export function TaskDetailModal({ onClose, onExecute, onStatusChange, + onOpenTask, }: TaskDetailModalProps) { const router = useRouter(); const [task, setTask] = useState(null); @@ -72,6 +75,13 @@ export function TaskDetailModal({ const [isUpdating, setIsUpdating] = useState(false); const { requirementsMap, isLoading: reqsLoading } = useRequirementsLookup(workspacePath); + // Fetch all tasks (reuses TaskBoardView's SWR cache — same key, no extra network call) + const { data: allTasksData } = useSWR( + `/api/v2/tasks?path=${workspacePath}`, + () => tasksApi.getAll(workspacePath) + ); + const tasksById = new Map(allTasksData?.tasks.map((t) => [t.id, t]) ?? []); + useEffect(() => { if (!open || !taskId) { setTask(null); @@ -172,13 +182,6 @@ export function TaskDetailModal({ {/* Metadata */}
- {task.depends_on.length > 0 && ( - - - {task.depends_on.length} dependenc{task.depends_on.length === 1 ? 'y' : 'ies'}: - {' '}{task.depends_on.map((id) => id.slice(0, 8)).join(', ')} - - )} {task.estimated_hours != null && ( @@ -187,6 +190,43 @@ export function TaskDetailModal({ )}
+ {/* Dependencies — full list with status highlights and navigation */} + {task.depends_on.length > 0 && ( +
+
+ + Dependencies ({task.depends_on.length}) +
+
    + {task.depends_on.map((depId) => { + const dep = tasksById.get(depId); + const isIncomplete = dep && !['DONE', 'MERGED'].includes(dep.status); + return ( +
  • + {dep && ( + + {dep.status} + + )} + {onOpenTask ? ( + + ) : ( + + {dep?.title ?? depId.slice(0, 12)} + + )} +
  • + ); + })} +
+
+ )} + {/* Requirements */} {(task.requirement_ids ?? []).length > 0 && (
From 67012d4f1a04d65bb1ec9fb8fa623cc3bd1371cd Mon Sep 17 00:00:00 2001 From: Test User Date: Tue, 24 Mar 2026 16:17:25 -0700 Subject: [PATCH 2/2] fix: add focus-visible ring to dependency nav buttons (CodeRabbit #498) --- web-ui/src/components/tasks/TaskDetailModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web-ui/src/components/tasks/TaskDetailModal.tsx b/web-ui/src/components/tasks/TaskDetailModal.tsx index 0b243c11..c64ef7a5 100644 --- a/web-ui/src/components/tasks/TaskDetailModal.tsx +++ b/web-ui/src/components/tasks/TaskDetailModal.tsx @@ -210,7 +210,7 @@ export function TaskDetailModal({ )} {onOpenTask ? (