Skip to content
Back to skills

Collection

ASecurity

XYFlow integration patterns for ChainGraph visual flow editor. Use when working on node rendering, edge rendering, drag-and-drop, selection handling, anchors, handles, or any XYFlow-related UI code. Covers custom nodes/edges, performance optimization, handle positioning. Triggers: xyflow, reactflow, node rendering, edge rendering, handle, anchor, drag drop, selection, viewport, canvas, flow editor UI.

  • 24 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 8, 2026
designtypescriptgoreactnodefrontendbackendperformance

Works with

  • cli

Security analysis

A100/100

Pro scans all 21 files and shows the line behind each finding

Scanned September 8, 2026

npx -y skills add mattnigh/skills_collection --skill collection --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Collection?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Collection
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/mattnigh-collection-81fd14b2/badge)](https://www.skillsdirectory.com/skills/mattnigh-collection-81fd14b2)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: xyflow-patterns
description: XYFlow integration patterns for ChainGraph visual flow editor. Use when working on node rendering, edge rendering, drag-and-drop, selection handling, anchors, handles, or any XYFlow-related UI code. Covers custom nodes/edges, performance optimization, handle positioning. Triggers: xyflow, reactflow, node rendering, edge rendering, handle, anchor, drag drop, selection, viewport, canvas, flow editor UI.
---

# XYFlow Patterns for ChainGraph

This skill covers XYFlow (React Flow) integration patterns used in the ChainGraph visual flow editor.

## XYFlow Overview

**Library**: `@xyflow/react` (React Flow v12+)
**Purpose**: Canvas-based flow editor with nodes, edges, zoom, pan
**ChainGraph Integration**: `apps/chaingraph-frontend/src/components/flow/Flow.tsx`

## Architecture

```
┌────────────────────────────────────────────────────────────┐
│                    Flow.tsx (Main Component)                │
│  ├─ ReactFlow                                               │
│  │   ├─ nodes (from useXYFlowNodes())                       │
│  │   ├─ edges (from useXYFlowEdges())                       │
│  │   ├─ nodeTypes (chaingraphNode, groupNode, anchorNode)   │
│  │   ├─ edgeTypes (flow, animated, default)                 │
│  │   └─ callbacks (onNodesChange, onEdgesChange, ...)       │
│  ├─ Background                                              │
│  ├─ StyledControls                                          │
│  └─ Custom UI Overlays (ContextMenu, ControlPanel)          │
└────────────────────────────────────────────────────────────┘
```

---

## Node Types

ChainGraph defines 3 custom node types:

**File**: `apps/chaingraph-frontend/src/components/flow/Flow.tsx:134-138`

```typescript
const nodeTypes = useMemo(() => ({
  chaingraphNode: ChaingraphNodeOptimized,  // Main computational node
  groupNode: memo(GroupNode),               // Container for grouping
  anchorNode: memo(AnchorNode),             // Edge anchor waypoints
}), [])
```

### ChaingraphNodeOptimized

**File**: `apps/chaingraph-frontend/src/components/flow/nodes/ChaingraphNode/ChaingraphNodeOptimized.tsx`

The main node component with heavy optimization via memoization:

```typescript
const ChaingraphNodeOptimized = memo(
  (props: NodeProps<ChaingraphNode>) => <ChaingraphNodeComponent {...props} />,
  (prevProps, nextProps) => {
    // Custom comparison for performance
    // Returns false (re-render) when id, selected, version, width, or height change
    return true // (no re-render) when everything matches
  },
)
```

### ChaingraphNode Component

**File**: `apps/chaingraph-frontend/src/components/flow/nodes/ChaingraphNode/ChaingraphNode.tsx`

Uses single consolidated render data subscription:

```typescript
function ChaingraphNodeComponent({ data, selected, id }: NodeProps<ChaingraphNode>) {
  // ✅ Single subscription for ALL render data (replaces 10 hooks)
  const renderData = useXYFlowNodeRenderData(id)

  // ✅ Flow metadata (keep separate - rarely changes, used for handlers)
  const activeFlow = useUnit($activeFlowMetadata)

  // ✅ Flow loaded (keep separate - simple guard)
  const isFlowLoaded = useUnit($isFlowLoaded)

  // ... component rendering
}
```

**Performance Result**: 97% fewer re-renders during drag operations (from 13 subscriptions to 4).

---

## Performance Optimization

### Consolidated Render Data Store

**Store**: `$xyflowNodeRenderMap` (NOT `$xyflowNodeRenderData`)
**File**: `apps/chaingraph-frontend/src/store/xyflow/stores/node-render-data.ts`
**Hook**: `useXYFlowNodeRenderData(nodeId)`

```typescript
// Hook usage (apps/chaingraph-frontend/src/store/xyflow/hooks/useXYFlowNodeRenderData.ts)
const renderData = useXYFlowNodeRenderData(nodeId)
```

### XYFlowNodeRenderData Interface

**File**: `apps/chaingraph-frontend/src/store/xyflow/types.ts:48-114`

```typescript
export interface XYFlowNodeRenderData {
  // Core identity
  nodeId: string
  version: number

  // Port ID arrays (pre-computed - no iteration in components!)
  inputPortIds: string[]
  outputPortIds: string[]
  passthroughPortIds: string[]

  // Specific system ports (pre-computed)
  flowInputPortId: string | null
  flowOutputPortId: string | null
  errorPortId: string | null
  errorMessagePortId: string | null

  // Metadata
  title: string
  status: 'idle' | 'running' | 'completed' | 'failed' | 'skipped'

  // Position & dimensions
  position: Position
  dimensions: { width: number, height: number }

  // Visual properties
  nodeType: 'chaingraphNode' | 'groupNode'
  categoryMetadata: CategoryMetadata
  zIndex: number

  // State flags
  isSelected: boolean
  isHidden: boolean
  isDraggable: boolean
  parentNodeId: string | undefined

  // Execution state
  executionStyle: string | undefined
  executionStatus: NodeExecutionStatus
  executionNode: ExecutionNodeData | null

  // Interaction state
  isHighlighted: boolean
  hasAnyHighlights: boolean
  pulseState: PulseState
  dropFeedback: DropFeedback | null

  // Debug state
  hasBreakpoint: boolean
  debugMode: boolean
}
```

### 8-Wire Delta Update System

The store uses 8 wires for surgical delta updates instead of full recalculation:

1. **Position updates** - High frequency (60fps during drag)
2. **Node data changes** - Version, dimensions, selection
3. **Execution state** - Execution events
4. **Highlight changes** - User highlights
5. **Pulse state** - Animation (200ms intervals)
6. **Drop feedback** - Drag operations
7. **Layer depth** - Parent structure changes
8. **Category metadata** - Theme changes

---

## Edge Types

**File**: `apps/chaingraph-frontend/src/components/flow/edges/index.ts`

```typescript
export const edgeTypes = {
  animated: AnimatedEdge,
  flow: FlowEdge,
  default: AnimatedEdge, // Fallback for edges without explicit type
} satisfies EdgeTypes
```

**Note**: Edge type keys are `flow`, `animated`, `default` - NOT `flowEdge`, `animatedEdge`.

### FlowEdge Component

**File**: `apps/chaingraph-frontend/src/components/flow/edges/FlowEdge.tsx`

Features:
- Catmull-Rom splines via `catmullRomToBezierPath()`
- Ghost anchors for adding new waypoints
- Selection highlighting
- Hover state feedback
- Animated particle effects (when `data.animated = true`)

```typescript
export const FlowEdge = memo(({
  id, sourceX, sourceY, targetX, targetY,
  sourcePosition, targetPosition, style, data,
}: EdgeProps) => {
  // Anchor support
  const selectedEdgeId = useUnit($selectedEdgeId)
  const isSelected = selectedEdgeId === id

  // Get anchor positions from anchor nodes store
  // PROTOTYPE: Anchors are now XYFlow nodes, positions come from their node positions
  const anchorPositions = useAnchorNodePositions(edgeId)

  // Path calculation with Catmull-Rom splines
  const pathData = useMemo(() => {
    return catmullRomToBezierPath(source, target, anchorPositions, sourcePosition, targetPosition)
  }, [source, target, anchorPositions, sourcePosition, targetPosition])

  // Ghost anchors (only when selected)
  const ghostAnchors = useMemo(() => {
    if (!isSelected) return []
    return calculateGhostAnchors(source, target, anchorPositions, sourcePosition, targetPosition)
  }, [isSelected, source, target, anchorPositions, sourcePosition, targetPosition])

  // ...
})
```

---

## Anchor System

**Key Insight**: Anchors are now XYFlow nodes (`anchorNode` type), NOT SVG circles rendered inside edges.

### Architecture

```
User clicks ghost anchor
    ↓
addAnchorNode event fires
    ↓
$anchorNodes store updates
    ↓
$anchorXYFlowNodes derived store creates XYFlow Node
    ↓
XYFlow handles drag/selection natively
    ↓
FlowEdge queries anchor positions for path calculation
    ↓
Changes sync to backend in EdgeMetadata.anchors[] format
```

### AnchorNodeState

**File**: `apps/chaingraph-frontend/src/store/edges/anchor-nodes.ts:46-56`

```typescript
export interface AnchorNodeState {
  id: string
  edgeId: string
  x: number // Flow position (top-left of node, not center)
  y: number
  index: number
  color?: string
  parentNodeId?: string // For XYFlow native parenting
  selected?: boolean
  version: number // Increments on any change to force XYFlow re-render
}
```

### EdgeAnchor Interface (Backend)

**File**: `packages/chaingraph-types/src/edge/types.ts:27-40`

```typescript
export interface EdgeAnchor {
  /** Unique identifier */
  id: string
  /** X coordinate (absolute if no parent, relative if parentNodeId is set) */
  x: number
  /** Y coordinate (absolute if no parent, relative if parentNodeId is set) */
  y: number
  /** Order index in path (0 = closest to source) */
  index: number
  /** Parent group node ID (if anchor is child of a group) */
  parentNodeId?: string
  /** Selection state (set by backend during paste operations) */
  selected?: boolean
}
```

### Anchor Events

```typescript
// Add anchor (from ghost anchor click)
export const addAnchorNode = edgesDomain.createEvent<{
  edgeId: string
  x: number
  y: number
  index: number
  color?: string
}>()

// Remove anchor (double-click or Delete key)
export const removeAnchorNode = edgesDomain.createEvent<{
  anchorNodeId: string
  edgeId?: string
}>()

// Update position (from XYFlow drag)
export const updateAnchorNodePosition = edgesDomain.createEvent<{
  anchorNodeId: string
  x: number
  y: number
}>()
```

### Ghost Anchors

Ghost anchors are SVG visual hints that appear when an edge is selected:

```typescript
// FlowEdge.tsx:268-272
const ghostAnchors = useMemo(() => {
  if (!isSelected) return []
  return calculateGhostAnchors(source, target, anchorPositions, sourcePosition, targetPosition)
}, [isSelected, source, target, anchorPositions, sourcePosition, targetPosition])

// Click handler creates real anchor node
const handleGhostClick = useCallback((insertIndex: number, x: number, y: number) => {
  addAnchorNode({
    edgeId,
    x,
    y,
    index: insertIndex,
    color: stroke,
  })
}, [edgeId, stroke])
```

---

## Handle Positioning

Handle positioning is delegated to XYFlow's automatic layout system.

**File**: `apps/chaingraph-frontend/src/components/flow/nodes/ChaingraphNode/ports/ui/PortHandle.tsx`

```typescript
const position = direction === 'input'
  ? Position.Left
  : Position.Right

<Handle
  type={direction === 'input' ? 'target' : 'source'}
  position={position}
  id={portId}
/>
```

**Note**: ChainGraph does NOT use custom `calculateHandlePosition()` functions. Vertical handle distribution is managed by the component layout, not explicit Y positioning.

---

## Custom Hooks

### Flow Interaction Hooks (18 hooks)

**Location**: `apps/chaingraph-frontend/src/components/flow/hooks/`

| Hook | Purpose |
|------|---------|
| `useBoxSelection` | Blender-style box selection with B key |
| `useCanvasHover` | Canvas hover detection for hotkeys |
| `useConnectionHandling` | Connection creation with cycle detection |
| `useEdgeAnchorKeyboard` | Keyboard shortcuts for anchor management |
| `useEdgeChanges` | Edge removal and selection handling |
| `useEdgeKeyboardShortcuts` | Edge-related keyboard shortcuts |
| `useEdgeReconnection` | Edge reconnection (onReconnectStart/onReconnect/onReconnectEnd) |
| `useFlowCallbacks` | Orchestrates all flow interaction callbacks |
| `useFlowCopyPaste` | Copy/paste and export/import operations |
| `useFlowUtils` | Utility functions (NOT a React hook - exports pure functions) |
| `useGrabMode` | Blender-style grab mode with G key |
| `useKeyboardShortcuts` | Unified shortcuts (Ctrl+C, Ctrl+V, Shift+D, A, F, X) |
| `useNodeChanges` | Node position, selection, and parent updates |
| `useNodeDragHandling` | Node drag with parent/group management |
| `useNodeDrop` | Node drop handling with position calculation |
| `useNodeSchemaDropEvents` | Node schema drop detection via event emitter |
| `useNodeSelection` | Node selection utilities (helper functions) |
| `useSelectionHotkeys` | Selection-related hotkeys |

### XYFlow Data Hooks

**Location**: `apps/chaingraph-frontend/src/store/xyflow/hooks/`

| Hook | Purpose |
|------|---------|
| `useXYFlowNodeRenderData` | Single subscription for all node render data |
| `useXYFlowNodeBodyPorts` | Body port IDs for node body rendering |
| `useXYFlowNodeErrorPorts` | Error port IDs for error section |
| `useXYFlowNodeFlowPorts` | Flow port IDs (input/output) |
| `useXYFlowNodeHeaderData` | Header data (title, category, etc.) |

### Store Data Hooks

**Location**: `apps/chaingraph-frontend/src/store/*/hooks/`

| Hook | Purpose |
|------|---------|
| `useXYFlowNodes` | XYFlow-compatible nodes from Effector stores |
| `useXYFlowEdges` | XYFlow-compatible edges from Effector stores |

---

## ReactFlow Configuration

**File**: `apps/chaingraph-frontend/src/components/flow/Flow.tsx:303-356`

```typescript
<ReactFlow
  nodes={nodes}
  nodeTypes={nodeTypes}
  edges={edges}
  edgeTypes={edgeTypes}

  // Callbacks
  onNodesChange={onNodesChange}
  onEdgesChange={onEdgesChange}
  onConnect={onConnect}
  onConnectStart={...}
  onConnectEnd={...}
  onNodeClick={handleNodeClick}
  onEdgeClick={handleEdgeClick}
  onPaneClick={handlePaneClick}
  onReconnect={onReconnect}
  onReconnectStart={onReconnectStart}
  onReconnectEnd={onReconnectEnd}
  onNodeDrag={onNodeDrag}
  onNodeDragStart={onNodeDragStart}
  onNodeDragStop={onNodeDragStop}
  onSelectionEnd={onSelectionEnd}
  onViewportChange={onViewportChange}

  // Selection & Pan
  panOnScroll={true}
  panOnDrag={panOnDrag}           // From useBoxSelection()
  selectionOnDrag={selectionOnDrag} // From useBoxSelection()
  selectionMode={selectionMode}   // From useBoxSelection()

  // Features
  zoomOnDoubleClick={true}
  connectOnClick={true}
  deleteKeyCode={['Delete', 'Backspace']}
  fitView={true}
  preventScrolling

  // Zoom limits
  minZoom={0.05}
  maxZoom={2}

  // Drag settings
  nodeDragThreshold={1}
  nodesDraggable={!isGrabMode}

  // Viewport
  defaultViewport={{ x: 0, y: 0, zoom: 0.2 }}
  defaultEdgeOptions={{ animated: true }}

  className="bg-background"
>
  <Background />
  <NodeInternalsSync />
  <StyledControls position="bottom-right" />
  {activeFlowId && <FlowControlPanel />}
</ReactFlow>
```

---

## Key Files

| File | Purpose |
|------|---------|
| `components/flow/Flow.tsx` | Main XYFlow container |
| `components/flow/nodes/ChaingraphNode/ChaingraphNodeOptimized.tsx` | Optimized node wrapper |
| `components/flow/nodes/ChaingraphNode/ChaingraphNode.tsx` | Main node component |
| `components/flow/nodes/AnchorNode/AnchorNode.tsx` | Anchor node component |
| `components/flow/edges/FlowEdge.tsx` | Custom edge with anchors |
| `components/flow/edges/index.ts` | Edge type registration |
| `store/xyflow/types.ts` | XYFlowNodeRenderData interface |
| `store/xyflow/stores/node-render-data.ts` | $xyflowNodeRenderMap store |
| `store/xyflow/hooks/useXYFlowNodeRenderData.ts` | Render data hook |
| `store/nodes/hooks/useXYFlowNodes.ts` | Node data transformation |
| `store/edges/hooks/useXYFlowEdges.ts` | Edge data transformation |
| `store/edges/anchor-nodes.ts` | Anchor node store and events |
| `components/flow/hooks/` | 18 interaction hooks |

---

## Common Patterns

### Adding a Custom Node Type

```typescript
// 1. Create node component
function MyCustomNode({ id, data }: NodeProps<MyData>) {
  return (
    <div className="my-custom-node">
      <Handle type="target" position={Position.Left} />
      {data.label}
      <Handle type="source" position={Position.Right} />
    </div>
  )
}

// 2. Register in nodeTypes (Flow.tsx)
const nodeTypes = useMemo(() => ({
  chaingraphNode: ChaingraphNodeOptimized,
  groupNode: memo(GroupNode),
  anchorNode: memo(AnchorNode),
  myCustomNode: memo(MyCustomNode),  // Add here
}), [])

// 3. Use in node data
addNode({
  id: 'node-1',
  type: 'myCustomNode',
  position: { x: 100, y: 100 },
  data: { label: 'Custom' },
})
```

### Custom Edge Styling

```typescript
function StyledEdge({ id, ...props }: EdgeProps) {
  const selectedEdgeId = useUnit($selectedEdgeId)
  const isActive = selectedEdgeId === id

  return (
    <path
      {...props}
      style={{
        stroke: isActive ? '#3b82f6' : '#6b7280',
        strokeWidth: isActive ? 3 : 2,
      }}
    />
  )
}
```

---

## Related Skills

- `frontend-architecture` - Overall frontend structure
- `effector-patterns` - Store patterns used
- `subscription-sync` - Real-time node/edge updates
- `optimistic-updates` - Position interpolation
- `chaingraph-concepts` - Node/edge domain concepts

Files in this skill

  • 0Chan-smc__claude-code-workflow-lab__claude__skills__frontend-dev-guidelines__SKILL.md15.1 KB
  • 17hz__nextjs-template__claude__skills__example-skill__SKILL.md316 B
  • 1ambda__dataops-platform__claude__skills__context-synthesis__SKILL.md3.5 KB
  • 1natsu172__dotfiles__claude__skills__git-analysis__SKILL.md5.4 KB
  • 1natsu172__dotfiles__claude__skills__github-pr-best-practices__SKILL.md7.7 KB
  • 23Maestro__prospect-pipeline__claude__skills__npid-fastapi-skill.md26.1 KB
  • 360AYA25__ClaudeN8N__claude__skills__n8n-code-javascript__SKILL.md15.7 KB
  • 360AYA25__ClaudeN8N__claude__skills__n8n-code-python__SKILL.md17.5 KB
  • 360AYA25__ClaudeN8N__claude__skills__n8n-expression-syntax__SKILL.md9.4 KB
  • 360AYA25__ClaudeN8N__claude__skills__n8n-mcp-tools-expert__SKILL.md12.5 KB
  • 360AYA25__ClaudeN8N__claude__skills__n8n-node-configuration__SKILL.md16.6 KB
  • 360AYA25__ClaudeN8N__claude__skills__n8n-workflow-patterns__SKILL.md11.2 KB
  • 3x-Projetos__claude-memory-framework__claude__skills__scientist__SKILL.md14.8 KB
  • 5MinFutures__futures-arena__claude__skills__migration-tracker__SKILL.md16.2 KB
  • 5MinFutures__futures-arena__claude__skills__planning-guidelines__SKILL.md11.8 KB
  • 92Bilal26__TaskPilotAI__claude__skills__assessment-builder__SKILL.md17.5 KB
  • 92Bilal26__TaskPilotAI__claude__skills__book-scaffolding__SKILL.md19.1 KB
  • 92Bilal26__TaskPilotAI__claude__skills__code-validation-sandbox__SKILL.md6.2 KB
  • 92Bilal26__TaskPilotAI__claude__skills__exercise-designer__SKILL.md18.1 KB
  • 92Bilal26__TaskPilotAI__claude__skills__learning-objectives__SKILL.md24.5 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…