# State Management

Zustand store patterns for managing application state.

## Core Principles

### Single Source of Truth
- **Store Only**: Never use React Flow's internal state (`useNodesState`, `useEdgesState`)
- **Centralized State**: All application state lives in Zustand store
- **No Dual State**: Eliminate synchronization between multiple state sources

### Selector Pattern
```typescript
// ✅ CORRECT: Individual selectors
const nodes = useStore(state => state.nodes);
const selectedNode = useStore(state => state.selectedNode);
const setNodes = useStore(state => state.setNodes);

// ❌ WRONG: Store destructuring
const { nodes, selectedNode, setNodes } = useStore();
```

## Store Interface

### State Structure
```typescript
interface StoreState {
  // Core data
  nodes: Node[];
  edges: Edge[];
  
  // Selection state (single element for property panel)
  selectedNode: Node | null;
  selectedEdge: Edge | null;
  
  // Selection state (multi-select for bulk operations)
  selectedNodes: string[];    // Array of selected node IDs
  selectedEdges: string[];    // Array of selected edge IDs
  
  // Selection setters
  setSelectedNode: (node: Node | null) => void;
  setSelectedEdge: (edge: Edge | null) => void;
  setSelectedNodes: (ids: string[]) => void;
  setSelectedEdges: (ids: string[]) => void;
  
  // UI state
  isReplayMode: boolean;
  darkMode: boolean;
  isTokenDragging: boolean;        // True while a token is being dragged from replay panel
  setTokenDragging: (isDragging: boolean) => void;
  
  // Start flow filter
  visibleStartNodeIds: string[] | null;  // IDs of visible start nodes (null = all visible)
  setVisibleStartNodeIds: (ids: string[] | null) => void;
  
  // Context state
  contexts: ModelerContext[];          // Available contexts from Drupal
  setContexts: (contexts: ModelerContext[]) => void;
  selectedContextId: string | null;    // Active context (null = no filter)
  setSelectedContextId: (id: string | null) => void;
  
  // Store methods
  setNodes: (nodes: Node[] | ((nodes: Node[]) => Node[])) => void;
  setEdges: (edges: Edge[] | ((edges: Edge[]) => Edge[])) => void;
  updateNode: (nodeId: string, updates: Partial<Node>) => void;
  // ... other methods
}
```

### Selection Architecture

ReactFlow is the **sole selection authority**. The selection flow:

```
User action (click/quick-add/drag-drop)
  → Hook sets `selected: true/false` on node/edge objects via setNodes/setEdges
  → ReactFlow detects `.selected` changes on elements
  → ReactFlow fires `onSelectionChange({ nodes, edges })`
  → useFlowEventHandlers handler writes to store:
      setSelectedNode / setSelectedEdge / setSelectedNodes / setSelectedEdges
  → PropertyPanel receives selection from store
  → useConfigurationLoader fetches config form for the selected element
```

**Key rules:**
- Hooks that add elements (useQuickAdd, useNodeEdgeActions, useDragAndDrop) must
  set `selected: true` on the new element and `selected: false` on all others
  via `setNodes`/`setEdges` updater functions. They must NOT call `setSelectedNode`
  or `setSelectedEdge` directly -- this avoids race conditions with ReactFlow's
  `onSelectionChange` handler.
- `useFlowEventHandlers.ts` defines the canonical `onSelectionChange` handler
  that bridges ReactFlow's internal selection to the Zustand store. It uses a
  `paneClickedRef` guard to prevent a stale-selection race: when the user clicks
  the empty canvas, `onPaneClick` fires first and clears the store, but ReactFlow
  may then fire `onSelectionChange` with the previously selected node still
  present. The guard causes `onSelectionChange` to ignore any non-empty selection
  events that arrive after a pane click, until the confirming empty event arrives.
- `useSelectionSync.ts` keeps `selectedNode`/`selectedEdge` object references
  fresh when `nodes`/`edges` arrays update (e.g., after label edits).
- `useConfigurationLoader.ts` uses stable primitive identifiers
  (`node.id + node.data.plugin`) in its effect dependencies to avoid
  spurious re-triggers from object reference changes.

## Modal State Management

### useModalState Hook
The `useModalState` hook (`src/hooks/useModalState.ts`) manages metadata modal and confirmation dialog state. Its `showConfirmationDialog` method supports an optional 6th `options` bag for button customization:

```typescript
// Original 5 positional args + optional options bag
showConfirmationDialog(
  title: string,
  message: string,
  type: 'danger' | 'warning' | 'info',
  onSaveAndCloseCallback?: () => void,
  onCloseWithoutSaveCallback?: () => void,
  options?: {
    primaryLabel?: string;           // Custom primary button text
    secondaryLabel?: string | false; // Custom text or `false` to hide
    cancelLabel?: string;            // Custom cancel button text
    primaryVariant?: 'primary' | 'danger'; // Button styling variant
  }
);

// Example: 2-button danger dialog for bulk delete
showConfirmationDialog(
  'Delete Selected?',
  'This will permanently delete the selected elements.',
  'danger',
  handleDeleteSelected,
  undefined,
  { primaryLabel: 'Delete', secondaryLabel: false, cancelLabel: 'Cancel', primaryVariant: 'danger' }
);
```

The hook exposes the customization state as `confirmDialogPrimaryLabel`, `confirmDialogSecondaryLabel`, `confirmDialogCancelLabel`, and `confirmDialogPrimaryVariant`, which `Flow.tsx` passes through `Modals` to `ConfirmDialog`.

**Key design choice**: `secondaryLabel: false` (not `undefined`) is the sentinel to hide the secondary button, because `undefined` would be replaced by the ConfirmDialog component's destructuring default.

## Common Agent Tasks

### Reading State
```typescript
// Get current state
const currentNodes = useStore(state => state.nodes);
const isDarkMode = useStore(state => state.darkMode);

// Derived state
const selectedNodeIds = useStore(state => 
  state.nodes.filter(n => n.selected).map(n => n.id)
);
```

### Updating State
```typescript
// Simple updates
const setNodes = useStore(state => state.setNodes);
setNodes(prevNodes => prevNodes.filter(n => n.id !== nodeId));

// Complex updates
const updateNode = useStore(state => state.updateNode);
updateNode(nodeId, { label: 'New Label', locked: true });
```

### Batch Operations
```typescript
// Atomic updates
const { setNodes, setEdges, setSelectedNode } = useStore(s => ({
  setNodes: s.setNodes,
  setEdges: s.setEdges, 
  setSelectedNode: s.setSelectedNode
}));

// Use together for atomic changes
setNodes(newNodes);
setEdges(newEdges);
setSelectedNode(null);
```

## Performance Patterns

### Avoid Re-renders
```typescript
// ✅ CORRECT: Specific selector
const nodeCount = useStore(state => state.nodes.length);

// ❌ WRONG: Causes re-renders
const { nodes } = useStore();
const nodeCount = nodes.length;
```

### Effect Dependencies
```typescript
// ✅ CORRECT: Minimal dependencies
useEffect(() => {
  // Side effect using current values
  console.log('Current count:', nodes.length);
}, [selectedNodeId]); // Only depends on selection

// ❌ WRONG: Expensive re-renders
useEffect(() => {
  console.log('Current count:', nodes.length);
}, [nodes]); // Fires on every node change
```

## File Locations

### Core Store Files
- **Store Definition**: `src/store/store.ts`
- **Shared Types**: `src/types/settings.ts`
- **Constants**: `src/constants/dimensions.ts`

### Hook Integration
- **State Hooks**: `src/hooks/use*.ts` (35 specialized hooks)
- **Component Usage**: Individual selectors in all components

## Testing State Management

### Unit Tests
```typescript
// Test store operations
test('should add node', () => {
  const initialState = useStore.getState();
  
  useStore.getState().addNode(mockNode);
  
  const newState = useStore.getState();
  expect(newState.nodes).toHaveLength(initialState.nodes.length + 1);
});
```

### Integration Tests
```typescript
// Test component-store integration
test('component updates store correctly', () => {
  const { rerender } = render(<MyComponent />);
  
  act(() => {
    fireEvent.click(screen.getByTestId('add-node'));
  });
  
  const storeState = useStore.getState();
  expect(storeState.nodes).toHaveLength(1);
});
```

## Common Issues to Avoid

### Store Destructuring
Never destructure the store - this creates subscriptions to all properties.

### Direct Mutation
Never mutate state directly - always use store methods.

### Circular Dependencies
Avoid hooks that depend on each other in a circular manner.

### Race Conditions
Use proper cleanup for async operations that update store.

## Advanced Patterns

### Store Computed Values
```typescript
// Computed selector
const selectedNodeIds = useStore(state => 
  state.nodes.filter(n => n.selected).map(n => n.id)
);
```

### Store Actions
```typescript
// Complex actions as store methods
const deleteNodeWithEdges = (nodeId: string) => {
  useStore.setState(state => ({
    nodes: state.nodes.filter(n => n.id !== nodeId),
    edges: state.edges.filter(e => e.source !== nodeId && e.target !== nodeId)
  }));
};
```

