Modly React Flow Workflow Editor Architecture and Backend Integration Explained
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, the main editor component.
Canvas Setup and Event Handlers
WorkflowsPage.tsx initializes the <ReactFlow> element with several critical callbacks:
onDrop– handles dragging new nodes from the sidebar onto the canvasonConnect– creates edges between nodes when users draw connectionsonKeyDown– binds keyboard shortcuts (Ctrl+Zfor undo,Ctrl+Yfor 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, 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.
Store Structure and Actions
The store maintains:
nodes– array of React Flow node objects with positions and dataedges– array of connection objects linking node handleshistoryRef– stack of past states for undo/redo functionality
Key actions include:
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:
Type Compatibility Check
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
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 provides a thin wrapper around window.electron?.api, exposing methods like:
saveWorkflow: (workflow: WorkflowDef) => Promise<ApiResult<void>>;
runWorkflow: (params: { workflowId: string }) => Promise<ApiResult<{ jobId: string }>>;
listModels: () => Promise<ApiResult<ModelInfo[]>>;
Usage in components:
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 exposes a safe API object to the renderer via contextBridge.exposeInMainWorld('electron', { api: {...} }). The actual implementations live in electron/main/ipc-handlers.ts, which registers listeners for each exposed method.
Python Bridge Execution
electron/main/python-bridge.ts spawns a child Python process and manages bidirectional JSON communication. When runWorkflow is invoked:
- IPC handler receives the request
- Workflow definition is serialized to JSON
- Python bridge sends the workflow to the backend process
- Backend executes the AI pipeline node by node
- Status updates, logs, and progress stream back through the bridge
- Results are returned to the renderer via the original Promise
Supporting Services
model-downloader.ts– downloads and caches AI models from Hugging Face and other sourcesartifact-registry-service.ts– manages workflow inputs, outputs, and intermediate filessettings-store.ts– persists user preferences and workflow files to local filesystem
Complete Example: Adding a Node Programmatically
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 shows how drag-drop operations translate screen coordinates into canvas state updates.
Summary
- React Flow provides the visual canvas in
WorkflowsPage.tsxwith configurable node types and edge rendering - Zustand store in
workflowsStore.tsmanages 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, 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 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 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 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 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →