diff --git a/ui/src/components/ProcessDiagram/CompoundNode.tsx b/ui/src/components/ProcessDiagram/CompoundNode.tsx index eb5f7c33..f1180c3e 100644 --- a/ui/src/components/ProcessDiagram/CompoundNode.tsx +++ b/ui/src/components/ProcessDiagram/CompoundNode.tsx @@ -1,5 +1,6 @@ import type { DiagramNode as DiagramNodeType, DiagramEdge as DiagramEdgeType } from '../../api/queries/diagrams'; import type { NodeConfig } from './types'; +import type { NodeExecutionState } from '../ExecutionDiagram/types'; import { colorForType, isCompoundType } from './node-colors'; import { DiagramNode } from './DiagramNode'; import { DiagramEdge } from './DiagramEdge'; @@ -17,6 +18,8 @@ interface CompoundNodeProps { selectedNodeId?: string; hoveredNodeId: string | null; nodeConfigs?: Map; + /** Execution overlay for edge traversal coloring */ + executionOverlay?: Map; onNodeClick: (nodeId: string) => void; onNodeDoubleClick?: (nodeId: string) => void; onNodeEnter: (nodeId: string) => void; @@ -25,7 +28,7 @@ interface CompoundNodeProps { export function CompoundNode({ node, edges, parentX = 0, parentY = 0, - selectedNodeId, hoveredNodeId, nodeConfigs, + selectedNodeId, hoveredNodeId, nodeConfigs, executionOverlay, onNodeClick, onNodeDoubleClick, onNodeEnter, onNodeLeave, }: CompoundNodeProps) { const x = (node.x ?? 0) - parentX; @@ -78,15 +81,21 @@ export function CompoundNode({ {/* Internal edges (rendered after background, before children) */} - {internalEdges.map((edge, i) => ( - [p[0] - absX, p[1] - absY]), - }} - /> - ))} + {internalEdges.map((edge, i) => { + const isTraversed = executionOverlay + ? (executionOverlay.has(edge.sourceId) && executionOverlay.has(edge.targetId)) + : undefined; + return ( + [p[0] - absX, p[1] - absY]), + }} + traversed={isTraversed} + /> + ); + })} {/* Children — recurse into compound children, render leaves as DiagramNode */} @@ -102,6 +111,7 @@ export function CompoundNode({ selectedNodeId={selectedNodeId} hoveredNodeId={hoveredNodeId} nodeConfigs={nodeConfigs} + executionOverlay={executionOverlay} onNodeClick={onNodeClick} onNodeDoubleClick={onNodeDoubleClick} onNodeEnter={onNodeEnter} diff --git a/ui/src/components/ProcessDiagram/DiagramEdge.tsx b/ui/src/components/ProcessDiagram/DiagramEdge.tsx index 7c6c5a9c..3b0a8b41 100644 --- a/ui/src/components/ProcessDiagram/DiagramEdge.tsx +++ b/ui/src/components/ProcessDiagram/DiagramEdge.tsx @@ -3,9 +3,11 @@ import type { DiagramEdge as DiagramEdgeType } from '../../api/queries/diagrams' interface DiagramEdgeProps { edge: DiagramEdgeType; offsetY?: number; + /** undefined = no overlay (default gray solid), true = traversed (green solid), false = not traversed (gray dashed) */ + traversed?: boolean | undefined; } -export function DiagramEdge({ edge, offsetY = 0 }: DiagramEdgeProps) { +export function DiagramEdge({ edge, offsetY = 0, traversed }: DiagramEdgeProps) { const pts = edge.points; if (!pts || pts.length < 2) return null; @@ -29,9 +31,10 @@ export function DiagramEdge({ edge, offsetY = 0 }: DiagramEdgeProps) { {edge.label && pts.length >= 2 && ( ; + /** Execution overlay for edge traversal coloring */ + executionOverlay?: Map; onNodeClick: (nodeId: string) => void; onNodeDoubleClick?: (nodeId: string) => void; onNodeEnter: (nodeId: string) => void; @@ -28,7 +31,7 @@ const VARIANT_COLORS: Record = { }; export function ErrorSection({ - section, totalWidth, selectedNodeId, hoveredNodeId, nodeConfigs, + section, totalWidth, selectedNodeId, hoveredNodeId, nodeConfigs, executionOverlay, onNodeClick, onNodeDoubleClick, onNodeEnter, onNodeLeave, }: ErrorSectionProps) { const color = VARIANT_COLORS[section.variant ?? 'error'] ?? VARIANT_COLORS.error; @@ -82,9 +85,14 @@ export function ErrorSection({ {/* Edges */} - {section.edges.map((edge, i) => ( - - ))} + {section.edges.map((edge, i) => { + const isTraversed = executionOverlay + ? (executionOverlay.has(edge.sourceId) && executionOverlay.has(edge.targetId)) + : undefined; + return ( + + ); + })} {/* Nodes */} @@ -99,6 +107,7 @@ export function ErrorSection({ selectedNodeId={selectedNodeId} hoveredNodeId={hoveredNodeId} nodeConfigs={nodeConfigs} + executionOverlay={executionOverlay} onNodeClick={onNodeClick} onNodeDoubleClick={onNodeDoubleClick} onNodeEnter={onNodeEnter} diff --git a/ui/src/components/ProcessDiagram/ProcessDiagram.tsx b/ui/src/components/ProcessDiagram/ProcessDiagram.tsx index d9c6bdbc..f63b4ffe 100644 --- a/ui/src/components/ProcessDiagram/ProcessDiagram.tsx +++ b/ui/src/components/ProcessDiagram/ProcessDiagram.tsx @@ -53,6 +53,7 @@ export function ProcessDiagram({ knownRouteIds, className, diagramLayout, + executionOverlay, }: ProcessDiagramProps) { // Route stack for drill-down navigation const [routeStack, setRouteStack] = useState([routeId]); @@ -209,14 +210,29 @@ export function ProcessDiagram({ > + + + {/* Main section top-level edges (not inside compounds) */} - {mainSection.edges.filter(e => topLevelEdge(e, mainSection.nodes)).map((edge, i) => ( - - ))} + {mainSection.edges.filter(e => topLevelEdge(e, mainSection.nodes)).map((edge, i) => { + const isTraversed = executionOverlay + ? (executionOverlay.has(edge.sourceId) && executionOverlay.has(edge.targetId)) + : undefined; + return ( + + ); + })} {/* Main section nodes */} @@ -231,6 +247,7 @@ export function ProcessDiagram({ selectedNodeId={selectedNodeId} hoveredNodeId={toolbar.hoveredNodeId} nodeConfigs={nodeConfigs} + executionOverlay={executionOverlay} onNodeClick={handleNodeClick} onNodeDoubleClick={handleNodeDoubleClick} onNodeEnter={toolbar.onNodeEnter} @@ -265,6 +282,7 @@ export function ProcessDiagram({ selectedNodeId={selectedNodeId} hoveredNodeId={toolbar.hoveredNodeId} nodeConfigs={nodeConfigs} + executionOverlay={executionOverlay} onNodeClick={handleNodeClick} onNodeDoubleClick={handleNodeDoubleClick} onNodeEnter={toolbar.onNodeEnter}