# Modly React Flow Workflow Editor Architecture and Backend Integration Explained

> Explore Modly's React Flow workflow editor architecture. Learn about its Zustand state management, undo/redo history, and Python backend integration via Electron IPC.

- Repository: [lightningpixel/modly](https://github.com/lightningpixel/modly)
- Tags: architecture
- Published: 2026-08-20

---

**Modly's workflow editor is built on React Flow for the visual canvas, uses Zustand for state management with undo/redo history, and communicates with its Python backend through Electron IPC and a dedicated Python bridge.**

Modly is an open-source AI workflow platform that combines a React-based visual editor with a Python execution backend. This article breaks down exactly how the **React Flow workflow editor architecture** works, how state flows through the application, and how the frontend integrates with the backend services that execute AI pipelines.

## React Flow Canvas and Node System

The visual editor centers on the **React Flow** library, which provides the draggable node canvas and edge connections. All workflow editing happens in [`src/areas/workflows/WorkflowsPage.tsx`](https://github.com/lightningpixel/modly/blob/main/src/areas/workflows/WorkflowsPage.tsx), the main editor component.

### Canvas Setup and Event Handlers

[`WorkflowsPage.tsx`](https://github.com/lightningpixel/modly/blob/main/WorkflowsPage.tsx) initializes the `<ReactFlow>` element with several critical callbacks:

- `onDrop` – handles dragging new nodes from the sidebar onto the canvas
- `onConnect` – creates edges between nodes when users draw connections
- `onKeyDown` – binds keyboard shortcuts (`Ctrl+Z` for undo, `Ctrl+Y` for redo)
- `isValidConnection` – validates connections before they're created

The `nodeTypes` prop registers all available node components, mapping type strings like `'extensionNode'` to their React component implementations in `src/areas/workflows/nodes/`.

### Node Definitions and Edge Rendering

Each node type is a self-contained React component. Custom edges are rendered by [`WorkflowEdge.tsx`](https://github.com/lightningpixel/modly/blob/main/WorkflowEdge.tsx), which handles styling and arrow indicators. Nodes support nested containers through a `parentId` field, enabling visual grouping of related operations.

### Coordinate Conversion and Layout

The `screenToFlowPosition` utility in `src/areas/workflows/utils` converts screen pixels to the canvas's internal coordinate system. This ensures nodes are placed accurately regardless of zoom level or pan position.

## Zustand State Management and History

Modly uses **Zustand** for state management, with the core store defined in [`src/shared/stores/workflowsStore.ts`](https://github.com/lightningpixel/modly/blob/main/src/shared/stores/workflowsStore.ts).

### Store Structure and Actions

The store maintains:

- `nodes` – array of React Flow node objects with positions and data
- `edges` – array of connection objects linking node handles
- `historyRef` – stack of past states for undo/redo functionality

Key actions include:

```typescript
setNodes: (updater: Node[] | ((nodes: Node[]) => Node[])) => void;
setEdges: (updater: Edge[] | ((edges: Edge[]) => Edge[])) => void;
undo: () => void;
redo: () => void;

```

The `useWorkflow()` selector provides convenient access throughout the component tree.

### Undo/Redo Implementation

History is managed as a stack of `{ nodes, edges }` snapshots. Before each state change, the current state is pushed to `historyRef.current.past`. The `undo` action replaces current state with `historyRef.current.past.pop()` and pushes the replaced state to `historyRef.current.future`. Redo reverses this flow.

## Connection Validation and Type Safety

Before any edge is created, `isValidConnection` runs two validation checks in [`WorkflowsPage.tsx`](https://github.com/lightningpixel/modly/blob/main/WorkflowsPage.tsx):

### Type Compatibility Check

```tsx
const srcType = getNodeOutputType(getNode(connection.source) as Node, allExtensions);
const tgtType = getNodeInputType(
  getNode(connection.target) as Node,
  connection.targetHandle,
  allExtensions,
);
if (srcType && tgtType && srcType !== tgtType) return false;

```

This prevents connecting incompatible data types between nodes.

### Cycle Detection

```tsx
const stack = [connection.target];
const seen = new Set<string>();
while (stack.length) {
  const id = stack.pop()!;
  if (id === connection.source) return false;
  if (seen.has(id)) continue;
  seen.add(id);
  for (const e of edges) if (e.source === id) stack.push(e.target);
}

```

This depth-first search ensures workflows remain directed acyclic graphs (DAGs), which is required for deterministic execution order.

## Frontend-Backend Integration via Electron IPC

The renderer process never accesses files or Python directly. All backend communication flows through a structured IPC layer.

### API Hook: useApi.ts

[`src/shared/hooks/useApi.ts`](https://github.com/lightningpixel/modly/blob/main/src/shared/hooks/useApi.ts) provides a thin wrapper around `window.electron?.api`, exposing methods like:

```typescript
saveWorkflow: (workflow: WorkflowDef) => Promise<ApiResult<void>>;
runWorkflow: (params: { workflowId: string }) => Promise<ApiResult<{ jobId: string }>>;
listModels: () => Promise<ApiResult<ModelInfo[]>>;

```

Usage in components:

```tsx
import useApi from '@/shared/hooks/useApi';

const runWorkflow = async (workflowId: string) => {
  const { data, error } = await useApi().runWorkflow({ workflowId });
  if (error) {
    console.error('Run failed:', error);
  } else {
    console.log('Run started, job id:', data.jobId);
  }
};

```

### Preload and IPC Handler Registration

[`electron/preload/electron-api.ts`](https://github.com/lightningpixel/modly/blob/main/electron/preload/electron-api.ts) exposes a safe API object to the renderer via `contextBridge.exposeInMainWorld('electron', { api: {...} })`. The actual implementations live in [`electron/main/ipc-handlers.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/ipc-handlers.ts), which registers listeners for each exposed method.

### Python Bridge Execution

[`electron/main/python-bridge.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/python-bridge.ts) spawns a child Python process and manages bidirectional JSON communication. When `runWorkflow` is invoked:

1. IPC handler receives the request
2. Workflow definition is serialized to JSON
3. Python bridge sends the workflow to the backend process
4. Backend executes the AI pipeline node by node
5. Status updates, logs, and progress stream back through the bridge
6. Results are returned to the renderer via the original Promise

### Supporting Services

- [`model-downloader.ts`](https://github.com/lightningpixel/modly/blob/main/model-downloader.ts) – downloads and caches AI models from Hugging Face and other sources
- [`artifact-registry-service.ts`](https://github.com/lightningpixel/modly/blob/main/artifact-registry-service.ts) – manages workflow inputs, outputs, and intermediate files
- [`settings-store.ts`](https://github.com/lightningpixel/modly/blob/main/settings-store.ts) – persists user preferences and workflow files to local filesystem

## Complete Example: Adding a Node Programmatically

```tsx
import { useStore } from '@/shared/stores/workflowsStore';
import { screenToFlowPosition } from '@/areas/workflows/utils';

const addExtensionNode = (extensionId: string, clientX: number, clientY: number) => {
  const position = screenToFlowPosition({ x: clientX, y: clientY });
  useStore.getState().setNodes((nodes) => [
    ...nodes,
    {
      id: crypto.randomUUID(),
      type: 'extensionNode',
      position,
      data: { extensionId, enabled: true, params: {} },
    },
  ]);
};

```

This pattern from [`WorkflowsPage.tsx`](https://github.com/lightningpixel/modly/blob/main/WorkflowsPage.tsx) shows how drag-drop operations translate screen coordinates into canvas state updates.

## Summary

- **React Flow** provides the visual canvas in [`WorkflowsPage.tsx`](https://github.com/lightningpixel/modly/blob/main/WorkflowsPage.tsx) with configurable node types and edge rendering
- **Zustand store** in [`workflowsStore.ts`](https://github.com/lightningpixel/modly/blob/main/workflowsStore.ts) manages nodes, edges, and undo/redo history as immutable snapshots
- **Connection validation** enforces type compatibility and prevents cycles before edges are created
- **Electron IPC layer** ([`useApi.ts`](https://github.com/lightningpixel/modly/blob/main/useApi.ts), preload, handlers) keeps the renderer sandboxed while enabling full backend access
- **Python bridge** executes workflows as child processes with streaming status updates
- **Supporting services** handle model downloads, artifact management, and filesystem persistence

## Frequently Asked Questions

### What state management library does Modly use for its workflow editor?

Modly uses **Zustand** for state management. The [`workflowsStore.ts`](https://github.com/lightningpixel/modly/blob/main/workflowsStore.ts) file defines the store with actions like `setNodes`, `setEdges`, `undo`, and `redo`, plus selectors like `useWorkflow()` that components consume.

### How does Modly prevent invalid connections between nodes?

The `isValidConnection` callback in [`WorkflowsPage.tsx`](https://github.com/lightningpixel/modly/blob/main/WorkflowsPage.tsx) runs two checks: type compatibility using `getNodeOutputType` and `getNodeInputType`, and cycle detection via depth-first search that traverses existing edges to ensure no path leads back to the source node.

### Why does Modly use Electron IPC instead of direct filesystem access?

The renderer process is sandboxed for security. All file operations, Python execution, and model management happen in the main process. The IPC layer in [`useApi.ts`](https://github.com/lightningpixel/modly/blob/main/useApi.ts) provides a controlled interface that validates requests and prevents unauthorized access to system resources.

### Can Modly workflows run without the Electron frontend?

The Python backend defined in [`python-bridge.ts`](https://github.com/lightningpixel/modly/blob/main/python-bridge.ts) accepts JSON workflow definitions and can execute them independently. However, the visual editor, undo/redo system, and interactive debugging features require the React Flow frontend.