PipelineCanvas (orchestrator)
├── ReactFlow (library wrapper)
│ ├── CustomNode ──────── Kedro function node (memo)
│ │ └── Handle (in/out connection points)
│ ├── DatasetNode ─────── Kedro dataset node (memo)
│ │ └── Handle (in/out connection points)
│ └── CustomEdge ──────── Bezier connection line (memo)
├── CanvasOverlay
│ ├── EmptyState ──────── "Drop components here"
│ └── BulkActionsToolbar ─ Multi-select actions
├── CanvasControls ──────── Zoom, fit-view, minimap
├── EdgeContextMenu ─────── Right-click on edges
└── GhostPreview ────────── Preview during connection drag
9 Canvas Hooks (all used by PipelineCanvas):
┌──────────────────────────┐
│ useCanvasState │ Redux ──▶ ReactFlow format
│ useNodeHandlers │ Drop, click, delete
│ useConnectionHandlers │ Connect, validate, cycle check
│ useSelectionHandlers │ Multi-select, bulk ops
│ useCanvasKeyboardShorts │ Del, Cmd+C/V, Escape, Space
│ useCopyPaste │ Clone with new IDs
│ useDragToCreate │ Drag connection → new node
│ useGhostPreview │ Visual drag feedback
│ useDeleteConfirmation │ Confirm before bulk delete
└──────────────────────────┘
Code Review Phase 10: Canvas System — The Heart of the App
Purpose: Review the visual pipeline canvas — custom ReactFlow nodes, edges, and the 9 canvas hooks that orchestrate drag, drop, connect, select, copy/paste, and keyboard shortcuts.
Prerequisites: Phases 2-4, 8 (Constants, Domain, Redux, UI Primitives)
Estimated time: 2.5 hours (largest phase — reviewers should be well-prepared by now)
Files: 29
Canvas Component Hierarchy
Files to Review (in order)
Canvas Hooks (read these first — they contain the logic)
src/components/Canvas/hooks/useCanvasState.ts— Converts Redux state to ReactFlow nodes/edgessrc/components/Canvas/hooks/utils/cycleDetection.ts— Real-time cycle detection during connection dragsrc/components/Canvas/hooks/useConnectionHandlers.ts— Connection creation with bipartite constraint and cycle detectionsrc/components/Canvas/hooks/useGhostPreview.ts— Ghost preview during connection dragsrc/components/Canvas/hooks/useNodeHandlers.ts— Node creation from palette, position sync, deletionsrc/components/Canvas/hooks/useDragToCreate.ts— Drag connection to empty space → create new componentsrc/components/Canvas/hooks/useSelectionHandlers.ts— Multi-select, bulk actions, focus-on-node eventssrc/components/Canvas/hooks/useDeleteConfirmation.ts— Delete confirmation statesrc/components/Canvas/hooks/useCopyPaste.ts— Copy/paste with ID regenerationsrc/components/Canvas/hooks/useCanvasKeyboardShortcuts.ts— Keyboard bindings (Del, Cmd+C/V, Escape, Space)Custom ReactFlow Components
src/components/Canvas/CustomNode/CustomNode.tsx+.scsssrc/components/Canvas/DatasetNode/DatasetNode.tsx+.scsssrc/components/Canvas/CustomEdge/CustomEdge.tsx+.scssCanvas Chrome (UI around the canvas)
src/components/Canvas/CanvasControls/CanvasControls.tsx+.scsssrc/components/Canvas/CanvasOverlay/CanvasOverlay.tsxsrc/components/Canvas/BulkActionsToolbar/BulkActionsToolbar.tsx+.scsssrc/components/Canvas/EdgeContextMenu/EdgeContextMenu.tsx+.scsssrc/components/Canvas/EmptyState/EmptyState.tsx+.scsssrc/components/Canvas/GhostPreview/GhostPreview.tsx+.scssOrchestrator (read last — ties everything together)
src/components/Canvas/PipelineCanvas.tsx+.scssTests
src/components/Canvas/CustomNode/CustomNode.test.tsx— 3 tests: renders name, 'Unnamed Node' fallback, error CSS classsrc/components/Canvas/DatasetNode/DatasetNode.test.tsx— 3 tests: renders name + extension, 'Unnamed Dataset' fallback, error CSS classsrc/components/Canvas/hooks/useConnectionHandlers.test.tsx— 2 tests: API shape, isValidConnection rejects node-to-nodesrc/components/Canvas/hooks/useSelectionHandlers.test.tsx— 2 tests: API shape (9 handlers), handlePaneClick clears selectionsrc/components/Canvas/hooks/useDragToCreate.test.tsx— 2 tests: API shape, handleConnectStart tracks source node IDKey Concepts
nodeTypesandedgeTypesmust be declared OUTSIDE the component to prevent recreation on every render (critical ReactFlow optimization)useCanvasStateconverts the normalized Redux state (byId/allIds) into the flat array format ReactFlow requiresuseConnectionHandlersenforces the bipartite graph constraint — connections can only go node↔datasetuseCopyPastemust regenerate IDs for pasted nodes/datasets usinggenerateCopyIdto avoid collisionsFocus Areas
useCanvasStatecorrectly memoize the Redux → ReactFlow conversion? Known issue:useLayoutEffectreplaces the nodes/edges arrays on every relevant Redux change, which defeatsReact.memo()onCustomNode/DatasetNode. This is a known architectural issue tracked for future optimization (ADR-002).useConnectionHandlersproperly prevent node-to-node and dataset-to-dataset connections? It now usesuseAppSelectorfor connection and node state (no longer usesstore.getState()anti-pattern).useCopyPastecorrectly regenerate all IDs AND update source/target references in pasted connections?CustomNodeandDatasetNodeusingReact.memo? (They are, but the memo is defeated by array replacement — see known issue above.)PipelineCanvaspassdeleteKeyCode={null}— disabling ReactFlow's built-in delete for custom handling?nodeTypes/edgeTypesobject declared outside the component?useCanvasStateno longer carries unusedthemefrom the canvas selector — verify theme-dependent functionality still works via its own selector.