The workflow builder follows a unidirectional data flow architecture with centralized state management through React Context API.
The application state is managed through WorkflowContext, which maintains:
interface WorkflowState {
nodes: WorkflowNode[] // All workflow nodes
edges: WorkflowEdge[] // Connections between nodes
selectedNode: string | null // Currently selected node ID
selectedEdge: string | null // Currently selected edge ID
}App
└── WorkflowProvider (Context)
└── WorkflowBuilder
└── ReactFlowProvider
└── WorkflowCanvas
├── WorkflowHeader
├── ReactFlow
│ ├── TriggerNode(s)
│ ├── ActionNode(s)
│ └── CustomEdge(s)
├── Controls
├── MiniMap
└── Background
- User Interaction → Component Event Handler
- Event Handler → Context Method Call
- Context Method → State Update
- State Update → Component Re-render
- Re-render → UI Update
Nodes are intentionally non-draggable to maintain a clean, organized layout:
- Fixed X-coordinate (400px)
- Automatic Y-positioning with 200px spacing
- Ensures consistent visual hierarchy
Custom edges provide:
- Inline node insertion via plus buttons
- Straight-line connections for clarity
- Consistent styling across the workflow
// Pseudo-code for node positioning
1. Add new node to array
2. Sort all nodes by current Y position
3. Starting from trigger node Y position:
- Position each node at currentY
- Increment currentY by 200px
- Update step numbers sequentiallyThe system prevents duplicates at multiple levels:
- Node IDs are checked before addition
- Edge connections verify uniqueness
- React StrictMode compatibility
// Node type variants
type NodeType = 'trigger' | 'action' | 'router' | 'branch'
// Node data structure
interface WorkflowNodeData {
label: string
type: NodeType
stepNumber?: string
integrationName?: string
integrationLogo?: string
[key: string]: any // Extensibility
}
// Full node type
type WorkflowNode = Node<WorkflowNodeData, NodeType>
// Edge type with optional data
type WorkflowEdge = Edge<{
label?: string
condition?: any
}>-
Component Memoization
- Node types defined outside components
- Prevent unnecessary re-renders
-
Batch Updates
- State updates grouped when possible
- Single re-render for multiple changes
-
Efficient Sorting
- Nodes sorted only when necessary
- O(n log n) complexity for positioning
- Define the type in
workflow.types.ts - Create component in
components/nodes/ - Register in
nodeTypesobject - Update positioning logic if needed
Hook into the context methods:
// Example: Connection validation
onConnect: (connection) => {
if (isValidConnection(connection)) {
// Proceed with connection
}
}Add save/load functionality:
// Serialize workflow
const saveWorkflow = () => {
return JSON.stringify({
nodes: nodes,
edges: edges,
metadata: { version: '1.0' },
})
}
// Deserialize workflow
const loadWorkflow = (json: string) => {
const data = JSON.parse(json)
// Validate and load nodes/edges
}-
Input Validation
- Sanitize node labels and data
- Validate integration URLs
-
ID Generation
- Use cryptographically secure methods for production
- Avoid predictable patterns
-
Data Isolation
- Context provider creates isolated scope
- No global state pollution
- Individual component behavior
- Context method logic
- Type safety verification
- Node addition/removal flows
- Edge connection scenarios
- State consistency checks
- Complete workflow creation
- User interaction sequences
- Visual regression tests