feat: add dependency graph view for task visualization

Add a new interactive graph view alongside the kanban board for visualizing
task dependencies. The graph view uses React Flow with dagre auto-layout to
display tasks as nodes connected by dependency edges.

Key features:
- Toggle between kanban and graph view via new control buttons
- Custom TaskNode component matching existing card styling/themes
- Animated edges that flow when tasks are in progress
- Status-aware node colors (backlog, in-progress, waiting, verified)
- Blocked tasks show lock icon with dependency count tooltip
- MiniMap for navigation in large graphs
- Zoom, pan, fit-view, and lock controls
- Horizontal/vertical layout options via dagre
- Click node to view details, double-click to edit
- Respects all 32 themes via CSS variables
- Reduced motion support for animations

New dependencies: @xyflow/react, dagre
This commit is contained in:
Claude
2025-12-22 19:10:32 +00:00
parent a85dec6dbb
commit b930091c42
17 changed files with 1540 additions and 35 deletions

View File

@@ -889,3 +889,162 @@
.xterm-viewport::-webkit-scrollbar-thumb:hover {
background: var(--muted-foreground);
}
/* ========================================
DEPENDENCY GRAPH STYLES
Theme-aware styling for React Flow graph
======================================== */
/* React Flow base theme overrides */
.graph-canvas {
--xy-background-color: transparent;
--xy-node-background-color: var(--card);
--xy-node-border-color: var(--border);
--xy-node-border-radius: 0.75rem;
--xy-edge-stroke-default: var(--border);
--xy-edge-stroke-selected: var(--brand-500);
--xy-minimap-background-color: var(--popover);
--xy-minimap-mask-background-color: rgba(0, 0, 0, 0.2);
--xy-controls-background-color: var(--popover);
--xy-controls-border-color: var(--border);
}
/* MiniMap styling */
.graph-canvas .react-flow__minimap {
background-color: var(--popover) !important;
border: 1px solid var(--border) !important;
border-radius: 0.5rem;
}
.graph-canvas .react-flow__minimap-mask {
fill: var(--background);
fill-opacity: 0.8;
}
/* Edge animations */
@keyframes flow-dash {
to {
stroke-dashoffset: -20;
}
}
@keyframes edge-glow {
0%, 100% {
filter: drop-shadow(0 0 2px var(--status-in-progress));
}
50% {
filter: drop-shadow(0 0 6px var(--status-in-progress));
}
}
.graph-canvas .animated-edge path {
animation: flow-dash 0.5s linear infinite;
}
.graph-canvas .edge-flowing path {
animation:
flow-dash 0.5s linear infinite,
edge-glow 2s ease-in-out infinite;
}
/* Edge particle animation */
.edge-particle {
pointer-events: none;
}
/* Node animations */
@keyframes pulse-subtle {
0%, 100% {
box-shadow: 0 0 0 0 var(--status-in-progress);
}
50% {
box-shadow: 0 0 15px 3px var(--status-in-progress);
}
}
.animate-pulse-subtle {
animation: pulse-subtle 2s ease-in-out infinite;
}
/* Progress bar indeterminate animation */
@keyframes progress-indeterminate {
0% {
transform: translateX(-100%);
width: 50%;
}
50% {
transform: translateX(50%);
width: 30%;
}
100% {
transform: translateX(200%);
width: 50%;
}
}
.animate-progress-indeterminate {
animation: progress-indeterminate 1.5s ease-in-out infinite;
}
/* Handle styling */
.graph-canvas .react-flow__handle {
width: 12px;
height: 12px;
border-radius: 50%;
background-color: var(--border);
border: 2px solid var(--background);
transition: all 0.2s ease;
}
.graph-canvas .react-flow__handle:hover {
background-color: var(--brand-500);
transform: scale(1.2);
}
.graph-canvas .react-flow__handle-left {
left: -6px;
}
.graph-canvas .react-flow__handle-right {
right: -6px;
}
/* Selection styles */
.graph-canvas .react-flow__node.selected {
outline: none;
}
.graph-canvas .react-flow__edge.selected path {
stroke: var(--brand-500);
stroke-width: 3;
}
/* Attribution removal (requires pro license) */
.graph-canvas .react-flow__attribution {
display: none;
}
/* Panel styling */
.graph-canvas .react-flow__panel {
margin: 12px;
}
/* Retro theme overrides */
.retro .graph-canvas .react-flow__handle,
.retro .graph-canvas .react-flow__minimap {
border-radius: 0 !important;
}
.retro .graph-canvas .react-flow__node {
border-radius: 0 !important;
}
/* Reduce motion preference */
@media (prefers-reduced-motion: reduce) {
.graph-canvas .animated-edge path,
.graph-canvas .edge-flowing path,
.animate-pulse-subtle,
.animate-progress-indeterminate {
animation: none;
}
}