@runtypelabs/react-flow 1.0.40 → 1.0.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,9 +12,6 @@ import type {
12
12
  SendEmailStepConfig,
13
13
  } from '../types'
14
14
 
15
- // ============================================================================
16
- // Validation Rules
17
- // ============================================================================
18
15
 
19
16
  interface ValidationRule {
20
17
  validate: (step: FlowStep, allSteps: FlowStep[]) => ValidationError[]
@@ -24,9 +21,6 @@ interface WarningRule {
24
21
  check: (step: FlowStep, allSteps: FlowStep[]) => ValidationWarning[]
25
22
  }
26
23
 
27
- // ============================================================================
28
- // Prompt Step Validation
29
- // ============================================================================
30
24
 
31
25
  const promptValidation: ValidationRule = {
32
26
  validate: (step) => {
@@ -61,9 +55,6 @@ const promptValidation: ValidationRule = {
61
55
  },
62
56
  }
63
57
 
64
- // ============================================================================
65
- // Fetch URL Step Validation
66
- // ============================================================================
67
58
 
68
59
  const fetchUrlValidation: ValidationRule = {
69
60
  validate: (step) => {
@@ -77,7 +68,6 @@ const fetchUrlValidation: ValidationRule = {
77
68
  message: 'URL is required',
78
69
  })
79
70
  } else {
80
- // Validate URL format (allow template variables)
81
71
  const url = config.http.url
82
72
  if (!url.startsWith('http://') && !url.startsWith('https://') && !url.includes('{{')) {
83
73
  errors.push({
@@ -100,9 +90,6 @@ const fetchUrlValidation: ValidationRule = {
100
90
  },
101
91
  }
102
92
 
103
- // ============================================================================
104
- // Transform Data Step Validation
105
- // ============================================================================
106
93
 
107
94
  const transformDataValidation: ValidationRule = {
108
95
  validate: (step) => {
@@ -129,9 +116,6 @@ const transformDataValidation: ValidationRule = {
129
116
  },
130
117
  }
131
118
 
132
- // ============================================================================
133
- // Conditional Step Validation
134
- // ============================================================================
135
119
 
136
120
  const conditionalValidation: ValidationRule = {
137
121
  validate: (step, allSteps) => {
@@ -146,7 +130,6 @@ const conditionalValidation: ValidationRule = {
146
130
  })
147
131
  }
148
132
 
149
- // Validate nested steps
150
133
  if (config.trueSteps && config.trueSteps.length > 0) {
151
134
  for (const nestedStep of config.trueSteps) {
152
135
  const nestedErrors = validateStep(nestedStep, allSteps)
@@ -175,9 +158,6 @@ const conditionalValidation: ValidationRule = {
175
158
  },
176
159
  }
177
160
 
178
- // ============================================================================
179
- // Send Email Step Validation
180
- // ============================================================================
181
161
 
182
162
  const sendEmailValidation: ValidationRule = {
183
163
  validate: (step) => {
@@ -191,7 +171,6 @@ const sendEmailValidation: ValidationRule = {
191
171
  message: 'Recipient (To) is required',
192
172
  })
193
173
  } else {
194
- // Validate email format (allow template variables)
195
174
  const to = config.to
196
175
  if (!to.includes('@') && !to.includes('{{')) {
197
176
  errors.push({
@@ -230,9 +209,6 @@ const sendEmailValidation: ValidationRule = {
230
209
  },
231
210
  }
232
211
 
233
- // ============================================================================
234
- // Warning Rules
235
- // ============================================================================
236
212
 
237
213
  const outputVariableWarning: WarningRule = {
238
214
  check: (step, allSteps) => {
@@ -240,7 +216,6 @@ const outputVariableWarning: WarningRule = {
240
216
  const config = step.config as { outputVariable?: string }
241
217
 
242
218
  if (config.outputVariable) {
243
- // Check for duplicate output variables
244
219
  const duplicates = allSteps.filter(
245
220
  (s) =>
246
221
  s.id !== step.id &&
@@ -288,9 +263,6 @@ const emptyBranchWarning: WarningRule = {
288
263
  },
289
264
  }
290
265
 
291
- // ============================================================================
292
- // Validation Logic
293
- // ============================================================================
294
266
 
295
267
  const validationRules: Record<string, ValidationRule> = {
296
268
  prompt: promptValidation,
@@ -305,7 +277,6 @@ const warningRules: WarningRule[] = [outputVariableWarning, emptyBranchWarning]
305
277
  function validateStep(step: FlowStep, allSteps: FlowStep[]): ValidationError[] {
306
278
  const errors: ValidationError[] = []
307
279
 
308
- // Common validation
309
280
  if (!step.name?.trim()) {
310
281
  errors.push({
311
282
  stepId: step.id,
@@ -314,7 +285,6 @@ function validateStep(step: FlowStep, allSteps: FlowStep[]): ValidationError[] {
314
285
  })
315
286
  }
316
287
 
317
- // Type-specific validation
318
288
  const rule = validationRules[step.type]
319
289
  if (rule) {
320
290
  errors.push(...rule.validate(step, allSteps))
@@ -333,9 +303,6 @@ function checkWarnings(step: FlowStep, allSteps: FlowStep[]): ValidationWarning[
333
303
  return warnings
334
304
  }
335
305
 
336
- // ============================================================================
337
- // Hook
338
- // ============================================================================
339
306
 
340
307
  export interface UseFlowValidationOptions {
341
308
  /** Steps to validate */
@@ -360,14 +327,12 @@ export interface UseFlowValidationReturn {
360
327
  export function useFlowValidation(options: UseFlowValidationOptions): UseFlowValidationReturn {
361
328
  const { steps: providedSteps, nodes } = options
362
329
 
363
- // Extract steps from nodes if not provided directly
364
330
  const steps = useMemo(() => {
365
331
  if (providedSteps) return providedSteps
366
332
  if (nodes) return nodes.map((n) => n.data.step)
367
333
  return []
368
334
  }, [providedSteps, nodes])
369
335
 
370
- // Perform validation
371
336
  const result = useMemo((): FlowValidationResult => {
372
337
  const errors: ValidationError[] = []
373
338
  const warnings: ValidationWarning[] = []
@@ -384,7 +349,6 @@ export function useFlowValidation(options: UseFlowValidationOptions): UseFlowVal
384
349
  }
385
350
  }, [steps])
386
351
 
387
- // Helper functions
388
352
  const validateStepFn = useCallback((step: FlowStep) => validateStep(step, steps), [steps])
389
353
 
390
354
  const isStepValid = useCallback(
@@ -17,9 +17,6 @@ import {
17
17
  import { autoLayout } from '../utils/layout'
18
18
  import type { FlowStepType } from '../flow-step-types'
19
19
 
20
- // ============================================================================
21
- // Hook Options
22
- // ============================================================================
23
20
 
24
21
  export interface UseRuntypeFlowOptions {
25
22
  /** Runtype API client instance */
@@ -38,9 +35,6 @@ export interface UseRuntypeFlowOptions {
38
35
  autoLayoutOnLoad?: boolean
39
36
  }
40
37
 
41
- // ============================================================================
42
- // Main Hook
43
- // ============================================================================
44
38
 
45
39
  export function useRuntypeFlow(options: UseRuntypeFlowOptions): UseRuntypeFlowReturn {
46
40
  const {
@@ -53,7 +47,6 @@ export function useRuntypeFlow(options: UseRuntypeFlowOptions): UseRuntypeFlowRe
53
47
  autoLayoutOnLoad = true,
54
48
  } = options
55
49
 
56
- // State
57
50
  const [flowId, setFlowId] = useState<string | null>(initialFlowId || null)
58
51
  const [flowName, setFlowName] = useState(initialName)
59
52
  const [flowDescription, setFlowDescription] = useState(initialDescription)
@@ -62,16 +55,11 @@ export function useRuntypeFlow(options: UseRuntypeFlowOptions): UseRuntypeFlowRe
62
55
  const [error, setError] = useState<Error | null>(null)
63
56
  const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false)
64
57
 
65
- // React Flow state
66
58
  const [nodes, setNodes, onNodesChange] = useNodesState<RuntypeNode>([])
67
59
  const [edges, setEdges, onEdgesChange] = useEdgesState<RuntypeEdge>([])
68
60
 
69
- // Track last saved state for change detection
70
61
  const lastSavedState = useRef<string>('')
71
62
 
72
- // ============================================================================
73
- // Handlers
74
- // ============================================================================
75
63
 
76
64
  const handleStepChange = useCallback(
77
65
  (stepId: string, updates: Partial<FlowStep>) => {
@@ -117,9 +105,6 @@ export function useRuntypeFlow(options: UseRuntypeFlowOptions): UseRuntypeFlowRe
117
105
  [setEdges]
118
106
  )
119
107
 
120
- // ============================================================================
121
- // API Operations
122
- // ============================================================================
123
108
 
124
109
  /**
125
110
  * Load a flow by ID
@@ -130,10 +115,8 @@ export function useRuntypeFlow(options: UseRuntypeFlowOptions): UseRuntypeFlowRe
130
115
  setError(null)
131
116
 
132
117
  try {
133
- // Get flow with embedded steps
134
118
  const flow = await client.flows.get(id)
135
119
 
136
- // Use flow steps from the flow response (consolidated endpoint)
137
120
  const steps: FlowStep[] = Array.isArray(flow.flowSteps)
138
121
  ? flow.flowSteps.map((s) => ({
139
122
  id: s.id,
@@ -145,13 +128,11 @@ export function useRuntypeFlow(options: UseRuntypeFlowOptions): UseRuntypeFlowRe
145
128
  }))
146
129
  : []
147
130
 
148
- // Convert to React Flow nodes
149
131
  let newNodes = flowStepsToNodes(steps, {
150
132
  onChange: handleStepChange,
151
133
  onDelete: handleStepDelete,
152
134
  })
153
135
 
154
- // Auto-layout if enabled
155
136
  if (autoLayoutOnLoad) {
156
137
  const newEdges = createEdgesFromNodes(newNodes)
157
138
  newNodes = autoLayout(newNodes, newEdges)
@@ -161,14 +142,11 @@ export function useRuntypeFlow(options: UseRuntypeFlowOptions): UseRuntypeFlowRe
161
142
 
162
143
  setFlowId(id)
163
144
  setFlowName(flow.name || '')
164
- // The flow GET response carries no `description` (the handler never
165
- // selects it), so there is nothing to load into the editor here.
166
145
  setFlowDescription('')
167
146
  setNodes(newNodes)
168
147
  setEdges(newEdges)
169
148
  setHasUnsavedChanges(false)
170
149
 
171
- // Store last saved state
172
150
  lastSavedState.current = JSON.stringify(nodesToFlowSteps(newNodes))
173
151
  } catch (err) {
174
152
  setError(err instanceof Error ? err : new Error('Failed to load flow'))
@@ -194,11 +172,6 @@ export function useRuntypeFlow(options: UseRuntypeFlowOptions): UseRuntypeFlowRe
194
172
  try {
195
173
  const steps = nodesToFlowSteps(nodes)
196
174
 
197
- // Update flow with metadata and steps in one atomic operation.
198
- // `description` is intentionally omitted: the flow update endpoint does
199
- // not persist it (the GET response carries no description either), so
200
- // sending the local value here would be a no-op at best and could
201
- // overwrite a description with an empty string if the schema ever changes.
202
175
  await client.flows.update(flowId, {
203
176
  name: flowName,
204
177
  flowSteps: steps.map((step) => ({
@@ -308,7 +281,6 @@ export function useRuntypeFlow(options: UseRuntypeFlowOptions): UseRuntypeFlowRe
308
281
 
309
282
  setNodes((nds) => [...nds, newNode])
310
283
 
311
- // Add edge from previous node if exists
312
284
  if (nodes.length > 0) {
313
285
  const lastNode = nodes[nodes.length - 1]
314
286
  setEdges((eds) => [
@@ -327,16 +299,11 @@ export function useRuntypeFlow(options: UseRuntypeFlowOptions): UseRuntypeFlowRe
327
299
  [nodes, setNodes, setEdges, handleStepChange, handleStepDelete]
328
300
  )
329
301
 
330
- // ============================================================================
331
- // Effects
332
- // ============================================================================
333
302
 
334
- // Load initial flow if flowId is provided
335
303
  useEffect(() => {
336
304
  if (initialFlowId) {
337
305
  loadFlow(initialFlowId)
338
306
  } else if (initialSteps.length > 0) {
339
- // Initialize with provided steps
340
307
  let newNodes = flowStepsToNodes(initialSteps, {
341
308
  onChange: handleStepChange,
342
309
  onDelete: handleStepDelete,
@@ -354,12 +321,10 @@ export function useRuntypeFlow(options: UseRuntypeFlowOptions): UseRuntypeFlowRe
354
321
  }
355
322
  }, []) // Only run on mount
356
323
 
357
- // Notify onChange when nodes/edges change
358
324
  useEffect(() => {
359
325
  onChange?.(nodes, edges)
360
326
  }, [nodes, edges, onChange])
361
327
 
362
- // Detect unsaved changes
363
328
  useEffect(() => {
364
329
  const currentState = JSON.stringify(nodesToFlowSteps(nodes))
365
330
  if (lastSavedState.current && currentState !== lastSavedState.current) {
@@ -367,26 +332,20 @@ export function useRuntypeFlow(options: UseRuntypeFlowOptions): UseRuntypeFlowRe
367
332
  }
368
333
  }, [nodes])
369
334
 
370
- // ============================================================================
371
- // Return
372
- // ============================================================================
373
335
 
374
336
  return {
375
- // React Flow state
376
337
  nodes,
377
338
  edges,
378
339
  onNodesChange: onNodesChange as unknown as (changes: unknown) => void,
379
340
  onEdgesChange: onEdgesChange as unknown as (changes: unknown) => void,
380
341
  onConnect: handleConnect as unknown as (connection: unknown) => void,
381
342
 
382
- // Flow metadata
383
343
  flowName,
384
344
  flowDescription,
385
345
  flowId,
386
346
  setFlowName,
387
347
  setFlowDescription,
388
348
 
389
- // API operations
390
349
  loadFlow,
391
350
  saveFlow,
392
351
  createFlow,
@@ -394,7 +353,6 @@ export function useRuntypeFlow(options: UseRuntypeFlowOptions): UseRuntypeFlowRe
394
353
  updateStep,
395
354
  addStep,
396
355
 
397
- // Status
398
356
  isLoading,
399
357
  isSaving,
400
358
  error,
package/src/index.ts CHANGED
@@ -1,7 +1,5 @@
1
- // Types
2
1
  export * from './types'
3
2
 
4
- // Components
5
3
  export { RuntypeFlowEditor } from './components/RuntypeFlowEditor'
6
4
  export { BaseNode } from './components/nodes/BaseNode'
7
5
  export { PromptNode } from './components/nodes/PromptNode'
@@ -10,11 +8,9 @@ export { CodeNode } from './components/nodes/CodeNode'
10
8
  export { ConditionalNode } from './components/nodes/ConditionalNode'
11
9
  export { SendEmailNode } from './components/nodes/SendEmailNode'
12
10
 
13
- // Hooks
14
11
  export { useRuntypeFlow } from './hooks/useRuntypeFlow'
15
12
  export { useFlowValidation } from './hooks/useFlowValidation'
16
13
 
17
- // Utilities
18
14
  export {
19
15
  flowStepsToNodes,
20
16
  nodesToFlowSteps,
@@ -2,9 +2,6 @@ import type { Node, Edge, NodeProps } from '@xyflow/react'
2
2
  import type { FlowStepType, PromptStepMode } from '../flow-step-types'
3
3
  import type { RuntypeClient } from '@runtypelabs/sdk'
4
4
 
5
- // ============================================================================
6
- // Flow Step Types (matching Runtype API)
7
- // ============================================================================
8
5
 
9
6
  /**
10
7
  * Core FlowStep interface matching the Runtype API structure
@@ -124,9 +121,6 @@ export interface GenericStepConfig {
124
121
  outputVariable?: string
125
122
  }
126
123
 
127
- // ============================================================================
128
- // React Flow Node Types
129
- // ============================================================================
130
124
 
131
125
  /**
132
126
  * Base data structure for all Runtype nodes
@@ -154,9 +148,6 @@ export type RuntypeEdge = Edge<{ stepOrder?: number }>
154
148
  */
155
149
  export type RuntypeNodeProps = NodeProps
156
150
 
157
- // ============================================================================
158
- // Node Type Registry
159
- // ============================================================================
160
151
 
161
152
  /**
162
153
  * Supported node types that have custom components
@@ -178,9 +169,6 @@ export function isSupportedNodeType(type: FlowStepType): type is SupportedNodeTy
178
169
  return SUPPORTED_NODE_TYPES.includes(type as SupportedNodeType)
179
170
  }
180
171
 
181
- // ============================================================================
182
- // Editor Configuration
183
- // ============================================================================
184
172
 
185
173
  /**
186
174
  * Configuration for the RuntypeFlowEditor component
@@ -233,9 +221,6 @@ export interface SavedFlow {
233
221
  updatedAt?: string
234
222
  }
235
223
 
236
- // ============================================================================
237
- // Hook Types
238
- // ============================================================================
239
224
 
240
225
  /**
241
226
  * Return type for useRuntypeFlow hook
@@ -302,9 +287,6 @@ export interface ValidationWarning {
302
287
  message: string
303
288
  }
304
289
 
305
- // ============================================================================
306
- // Event Types
307
- // ============================================================================
308
290
 
309
291
  /**
310
292
  * Event emitted when a step is updated
@@ -1,21 +1,15 @@
1
1
  import type { FlowStep, RuntypeNode, RuntypeEdge, RuntypeNodeData } from '../types'
2
2
  import type { FlowStepType } from '../flow-step-types'
3
3
 
4
- // ============================================================================
5
- // Constants
6
- // ============================================================================
7
4
 
8
5
  const NODE_WIDTH = 280
9
6
  const NODE_HEIGHT = 200 // Increased for better card visibility
10
7
  const NODE_SPACING_X = 350 // Horizontal space between main steps
11
8
  const NODE_SPACING_Y = 80 // Vertical space between branch steps
12
- const BRANCH_OFFSET_X = 350 // Horizontal offset for branches to the right of conditional
9
+ const BRANCH_OFFSET_X = 350
13
10
  const BRANCH_OFFSET_Y = -80 // Vertical offset for true branch (above center)
14
11
  const FALSE_BRANCH_GAP = 100 // Gap between true and false branches
15
12
 
16
- // ============================================================================
17
- // Flow Steps to React Flow Nodes
18
- // ============================================================================
19
13
 
20
14
  interface FlowStepsToNodesOptions {
21
15
  onChange?: (stepId: string, updates: Partial<FlowStep>) => void
@@ -43,7 +37,6 @@ export function flowStepsToNodes(
43
37
  parentId,
44
38
  } = options ?? {}
45
39
 
46
- // Sort steps by order
47
40
  const sortedSteps = [...steps].sort((a, b) => a.order - b.order)
48
41
 
49
42
  const nodes: RuntypeNode[] = []
@@ -75,23 +68,18 @@ export function flowStepsToNodes(
75
68
 
76
69
  nodes.push(node)
77
70
 
78
- // DEBUG: Log node positions
79
71
  console.log(`[flowStepsToNodes] Placed "${step.name}" (${step.type}) at x=${currentX}`)
80
72
 
81
- // Handle conditional branches - position to the right with true above, false below
82
73
  if (step.type === 'conditional' && step.config) {
83
74
  const config = step.config as { trueSteps?: FlowStep[]; falseSteps?: FlowStep[] }
84
75
  const branchX = currentX + BRANCH_OFFSET_X
85
76
 
86
- // Calculate branch lengths (horizontal extent)
87
77
  const trueBranchLength = config.trueSteps?.length || 0
88
78
  const falseBranchLength = config.falseSteps?.length || 0
89
79
  const maxBranchLength = Math.max(trueBranchLength, falseBranchLength)
90
80
 
91
- // Calculate true branch height (vertical extent for stacking)
92
81
  const trueBranchHeight = trueBranchLength * (NODE_HEIGHT + NODE_SPACING_Y)
93
82
 
94
- // True branch nodes (positioned to the right, above center)
95
83
  if (config.trueSteps && config.trueSteps.length > 0) {
96
84
  const trueBranchY = startPosition.y + BRANCH_OFFSET_Y
97
85
  const trueBranchNodes = flowStepsToNodes(config.trueSteps, {
@@ -109,9 +97,7 @@ export function flowStepsToNodes(
109
97
  maxBranchY = Math.max(maxBranchY, trueBranchY + trueBranchHeight)
110
98
  }
111
99
 
112
- // False branch nodes (positioned to the right, below center)
113
100
  if (config.falseSteps && config.falseSteps.length > 0) {
114
- // Position false branch below true branch (or at center + gap if no true branch)
115
101
  const falseBranchY =
116
102
  trueBranchLength > 0
117
103
  ? startPosition.y + BRANCH_OFFSET_Y + trueBranchHeight + FALSE_BRANCH_GAP
@@ -133,10 +119,6 @@ export function flowStepsToNodes(
133
119
  maxBranchY = Math.max(maxBranchY, falseBranchY + falseBranchHeight)
134
120
  }
135
121
 
136
- // Skip past the conditional AND all its branch steps
137
- // Branches start at branchX, so we need to account for:
138
- // - The offset from conditional to first branch step (BRANCH_OFFSET_X)
139
- // - All the branch steps (maxBranchLength * (NODE_WIDTH + NODE_SPACING_X))
140
122
  if (maxBranchLength > 0) {
141
123
  const advance = BRANCH_OFFSET_X + maxBranchLength * (NODE_WIDTH + NODE_SPACING_X)
142
124
  console.log(
@@ -148,7 +130,6 @@ export function flowStepsToNodes(
148
130
  currentX += NODE_WIDTH + NODE_SPACING_X
149
131
  }
150
132
  } else {
151
- // Move to the next X position for horizontal layout (non-conditional steps)
152
133
  currentX += NODE_WIDTH + NODE_SPACING_X
153
134
  }
154
135
  }
@@ -156,26 +137,20 @@ export function flowStepsToNodes(
156
137
  return nodes
157
138
  }
158
139
 
159
- // ============================================================================
160
- // React Flow Nodes to Flow Steps
161
- // ============================================================================
162
140
 
163
141
  /**
164
142
  * Convert React Flow Node array back to Runtype FlowStep array
165
143
  */
166
144
  export function nodesToFlowSteps(nodes: RuntypeNode[]): FlowStep[] {
167
- // Filter out branch nodes (handled within conditional steps)
168
145
  const topLevelNodes = nodes.filter(
169
146
  (n) => !n.parentId && !n.id.includes('-true-') && !n.id.includes('-false-')
170
147
  )
171
148
 
172
- // Sort by X position to determine order (horizontal layout)
173
149
  const sortedNodes = [...topLevelNodes].sort((a, b) => a.position.x - b.position.x)
174
150
 
175
151
  return sortedNodes.map((node, index) => {
176
152
  const step = node.data.step
177
153
 
178
- // Handle conditional steps - extract nested steps
179
154
  if (step.type === 'conditional') {
180
155
  const trueSteps = extractBranchSteps(nodes, node.id, 'true')
181
156
  const falseSteps = extractBranchSteps(nodes, node.id, 'false')
@@ -206,11 +181,9 @@ function extractBranchSteps(
206
181
  parentId: string,
207
182
  branch: 'true' | 'false'
208
183
  ): FlowStep[] {
209
- // Match nodes with the branch prefix pattern
210
184
  const branchPrefix = `${parentId}-${branch}-`
211
185
  const branchNodes = nodes.filter((n) => n.id.startsWith(branchPrefix))
212
186
 
213
- // Sort by X position (horizontal layout within branches)
214
187
  const sortedBranchNodes = [...branchNodes].sort((a, b) => a.position.x - b.position.x)
215
188
 
216
189
  return sortedBranchNodes.map((node, index) => ({
@@ -219,9 +192,6 @@ function extractBranchSteps(
219
192
  }))
220
193
  }
221
194
 
222
- // ============================================================================
223
- // Create Edges from Nodes
224
- // ============================================================================
225
195
 
226
196
  /**
227
197
  * Create edges connecting nodes in sequence
@@ -229,22 +199,17 @@ function extractBranchSteps(
229
199
  export function createEdgesFromNodes(nodes: RuntypeNode[]): RuntypeEdge[] {
230
200
  const edges: RuntypeEdge[] = []
231
201
 
232
- // Helper to check if a node is a branch node
233
202
  const isBranchNode = (n: RuntypeNode) =>
234
203
  n.parentId || n.id.includes('-true-') || n.id.includes('-false-')
235
204
 
236
- // Get top-level nodes sorted by X position (horizontal layout)
237
205
  const topLevelNodes = nodes
238
206
  .filter((n) => !isBranchNode(n))
239
207
  .sort((a, b) => a.position.x - b.position.x)
240
208
 
241
- // Create sequential edges for top-level nodes
242
- // Skip conditionals - their branches connect to the next step instead
243
209
  for (let i = 0; i < topLevelNodes.length - 1; i++) {
244
210
  const sourceNode = topLevelNodes[i]
245
211
  const targetNode = topLevelNodes[i + 1]
246
212
 
247
- // Skip edge from conditional - branches will connect to next step
248
213
  if (sourceNode.data.step.type === 'conditional') {
249
214
  continue
250
215
  }
@@ -260,7 +225,6 @@ export function createEdgesFromNodes(nodes: RuntypeNode[]): RuntypeEdge[] {
260
225
  })
261
226
  }
262
227
 
263
- // Create edges for conditional branches
264
228
  const conditionalNodes = nodes.filter(
265
229
  (n) => n.data.step.type === 'conditional' && !isBranchNode(n)
266
230
  )
@@ -271,19 +235,14 @@ export function createEdgesFromNodes(nodes: RuntypeNode[]): RuntypeEdge[] {
271
235
  const nextMainStep =
272
236
  conditionalIndex < topLevelNodes.length - 1 ? topLevelNodes[conditionalIndex + 1] : null
273
237
 
274
- // Find true branch nodes (contain -true- after the conditional ID)
275
- // Sort by X position since branches flow horizontally
276
238
  const trueBranchNodes = nodes
277
239
  .filter((n) => n.id.startsWith(`${conditionalId}-true-`))
278
240
  .sort((a, b) => a.position.x - b.position.x)
279
241
 
280
- // Find false branch nodes (contain -false- after the conditional ID)
281
- // Sort by X position since branches flow horizontally
282
242
  const falseBranchNodes = nodes
283
243
  .filter((n) => n.id.startsWith(`${conditionalId}-false-`))
284
244
  .sort((a, b) => a.position.x - b.position.x)
285
245
 
286
- // Connect conditional to first true branch node (branch goes right)
287
246
  if (trueBranchNodes.length > 0) {
288
247
  edges.push({
289
248
  id: `edge-${conditionalId}-to-true-branch`,
@@ -300,7 +259,6 @@ export function createEdgesFromNodes(nodes: RuntypeNode[]): RuntypeEdge[] {
300
259
  style: { stroke: '#22c55e', strokeWidth: 2 },
301
260
  })
302
261
 
303
- // Connect true branch nodes sequentially (horizontal connections)
304
262
  for (let i = 0; i < trueBranchNodes.length - 1; i++) {
305
263
  edges.push({
306
264
  id: `edge-true-${trueBranchNodes[i].id}-${trueBranchNodes[i + 1].id}`,
@@ -313,7 +271,6 @@ export function createEdgesFromNodes(nodes: RuntypeNode[]): RuntypeEdge[] {
313
271
  })
314
272
  }
315
273
 
316
- // Connect last true branch node to next main step (convergence)
317
274
  if (nextMainStep) {
318
275
  const lastTrueNode = trueBranchNodes[trueBranchNodes.length - 1]
319
276
  edges.push({
@@ -327,7 +284,6 @@ export function createEdgesFromNodes(nodes: RuntypeNode[]): RuntypeEdge[] {
327
284
  })
328
285
  }
329
286
  } else if (nextMainStep) {
330
- // No true branch steps - connect conditional directly to next step via true handle
331
287
  edges.push({
332
288
  id: `edge-${conditionalId}-true-to-${nextMainStep.id}`,
333
289
  source: conditionalId,
@@ -344,7 +300,6 @@ export function createEdgesFromNodes(nodes: RuntypeNode[]): RuntypeEdge[] {
344
300
  })
345
301
  }
346
302
 
347
- // Connect conditional to first false branch node (branch goes right, below true)
348
303
  if (falseBranchNodes.length > 0) {
349
304
  edges.push({
350
305
  id: `edge-${conditionalId}-to-false-branch`,
@@ -361,7 +316,6 @@ export function createEdgesFromNodes(nodes: RuntypeNode[]): RuntypeEdge[] {
361
316
  style: { stroke: '#ef4444', strokeWidth: 2 },
362
317
  })
363
318
 
364
- // Connect false branch nodes sequentially (horizontal connections)
365
319
  for (let i = 0; i < falseBranchNodes.length - 1; i++) {
366
320
  edges.push({
367
321
  id: `edge-false-${falseBranchNodes[i].id}-${falseBranchNodes[i + 1].id}`,
@@ -374,7 +328,6 @@ export function createEdgesFromNodes(nodes: RuntypeNode[]): RuntypeEdge[] {
374
328
  })
375
329
  }
376
330
 
377
- // Connect last false branch node to next main step (convergence)
378
331
  if (nextMainStep) {
379
332
  const lastFalseNode = falseBranchNodes[falseBranchNodes.length - 1]
380
333
  edges.push({
@@ -388,7 +341,6 @@ export function createEdgesFromNodes(nodes: RuntypeNode[]): RuntypeEdge[] {
388
341
  })
389
342
  }
390
343
  } else if (nextMainStep) {
391
- // No false branch steps - connect conditional directly to next step via false handle
392
344
  edges.push({
393
345
  id: `edge-${conditionalId}-false-to-${nextMainStep.id}`,
394
346
  source: conditionalId,
@@ -405,7 +357,6 @@ export function createEdgesFromNodes(nodes: RuntypeNode[]): RuntypeEdge[] {
405
357
  })
406
358
  }
407
359
 
408
- // Handle case where conditional has no branches - connect directly to next step
409
360
  if (trueBranchNodes.length === 0 && falseBranchNodes.length === 0 && nextMainStep) {
410
361
  edges.push({
411
362
  id: `edge-${conditionalId}-to-${nextMainStep.id}`,
@@ -421,9 +372,6 @@ export function createEdgesFromNodes(nodes: RuntypeNode[]): RuntypeEdge[] {
421
372
  return edges
422
373
  }
423
374
 
424
- // ============================================================================
425
- // Utility Functions
426
- // ============================================================================
427
375
 
428
376
  /**
429
377
  * Get default name for a step type