Skip to content

Commit 2b49d15

Browse files
authored
fix(comparison): add condition to prevent duplicate identical edges (#2799)
* fix)comparison): add condition to prevent duplicate identical edges, ignore from workflow change detection * fix failing test * added back store check
1 parent 3d037c9 commit 2b49d15

File tree

2 files changed

+33
-27
lines changed

2 files changed

+33
-27
lines changed

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,9 @@ const WorkflowContent = React.memo(() => {
356356
/** Stores source node/handle info when a connection drag starts for drop-on-block detection. */
357357
const connectionSourceRef = useRef<{ nodeId: string; handleId: string } | null>(null)
358358

359+
/** Tracks whether onConnect successfully handled the connection (ReactFlow pattern). */
360+
const connectionCompletedRef = useRef(false)
361+
359362
/** Stores start positions for multi-node drag undo/redo recording. */
360363
const multiNodeDragStartRef = useRef<Map<string, { x: number; y: number; parentId?: string }>>(
361364
new Map()
@@ -2214,7 +2217,8 @@ const WorkflowContent = React.memo(() => {
22142217
)
22152218

22162219
/**
2217-
* Captures the source handle when a connection drag starts
2220+
* Captures the source handle when a connection drag starts.
2221+
* Resets connectionCompletedRef to track if onConnect handles this connection.
22182222
*/
22192223
const onConnectStart = useCallback((_event: any, params: any) => {
22202224
const handleId: string | undefined = params?.handleId
@@ -2223,6 +2227,7 @@ const WorkflowContent = React.memo(() => {
22232227
nodeId: params?.nodeId,
22242228
handleId: params?.handleId,
22252229
}
2230+
connectionCompletedRef.current = false
22262231
}, [])
22272232

22282233
/** Handles new edge connections with container boundary validation. */
@@ -2283,6 +2288,7 @@ const WorkflowContent = React.memo(() => {
22832288
isInsideContainer: true,
22842289
},
22852290
})
2291+
connectionCompletedRef.current = true
22862292
return
22872293
}
22882294

@@ -2311,6 +2317,7 @@ const WorkflowContent = React.memo(() => {
23112317
}
23122318
: undefined,
23132319
})
2320+
connectionCompletedRef.current = true
23142321
}
23152322
},
23162323
[addEdge, getNodes, blocks]
@@ -2319,8 +2326,9 @@ const WorkflowContent = React.memo(() => {
23192326
/**
23202327
* Handles connection drag end. Detects if the edge was dropped over a block
23212328
* and automatically creates a connection to that block's target handle.
2322-
* Only creates a connection if ReactFlow didn't already handle it (e.g., when
2323-
* dropping on the block body instead of a handle).
2329+
*
2330+
* Uses connectionCompletedRef to check if onConnect already handled this connection
2331+
* (ReactFlow pattern for distinguishing handle-to-handle vs handle-to-body drops).
23242332
*/
23252333
const onConnectEnd = useCallback(
23262334
(event: MouseEvent | TouchEvent) => {
@@ -2332,6 +2340,12 @@ const WorkflowContent = React.memo(() => {
23322340
return
23332341
}
23342342

2343+
// If onConnect already handled this connection, skip (handle-to-handle case)
2344+
if (connectionCompletedRef.current) {
2345+
connectionSourceRef.current = null
2346+
return
2347+
}
2348+
23352349
// Get cursor position in flow coordinates
23362350
const clientPos = 'changedTouches' in event ? event.changedTouches[0] : event
23372351
const flowPosition = screenToFlowPosition({
@@ -2342,25 +2356,14 @@ const WorkflowContent = React.memo(() => {
23422356
// Find node under cursor
23432357
const targetNode = findNodeAtPosition(flowPosition)
23442358

2345-
// Create connection if valid target found AND edge doesn't already exist
2346-
// ReactFlow's onConnect fires first when dropping on a handle, so we check
2347-
// if that connection already exists to avoid creating duplicates.
2348-
// IMPORTANT: We must read directly from the store (not React state) because
2349-
// the store update from ReactFlow's onConnect may not have triggered a
2350-
// React re-render yet when this callback runs (typically 1-2ms later).
2359+
// Create connection if valid target found (handle-to-body case)
23512360
if (targetNode && targetNode.id !== source.nodeId) {
2352-
const currentEdges = useWorkflowStore.getState().edges
2353-
const edgeAlreadyExists = currentEdges.some(
2354-
(e) => e.source === source.nodeId && e.target === targetNode.id
2355-
)
2356-
if (!edgeAlreadyExists) {
2357-
onConnect({
2358-
source: source.nodeId,
2359-
sourceHandle: source.handleId,
2360-
target: targetNode.id,
2361-
targetHandle: 'target',
2362-
})
2363-
}
2361+
onConnect({
2362+
source: source.nodeId,
2363+
sourceHandle: source.handleId,
2364+
target: targetNode.id,
2365+
targetHandle: 'target',
2366+
})
23642367
}
23652368

23662369
connectionSourceRef.current = null

apps/sim/stores/workflows/workflow/store.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -498,8 +498,6 @@ export const useWorkflowStore = create<WorkflowStore>()(
498498
const currentEdges = get().edges
499499
const newEdges = [...currentEdges]
500500
const existingEdgeIds = new Set(currentEdges.map((e) => e.id))
501-
// Track existing connections to prevent duplicates (same source->target)
502-
const existingConnections = new Set(currentEdges.map((e) => `${e.source}->${e.target}`))
503501

504502
for (const edge of edges) {
505503
// Skip if edge ID already exists
@@ -508,9 +506,15 @@ export const useWorkflowStore = create<WorkflowStore>()(
508506
// Skip self-referencing edges
509507
if (edge.source === edge.target) continue
510508

511-
// Skip if connection already exists (same source and target)
512-
const connectionKey = `${edge.source}->${edge.target}`
513-
if (existingConnections.has(connectionKey)) continue
509+
// Skip if identical connection already exists (same ports)
510+
const connectionExists = newEdges.some(
511+
(e) =>
512+
e.source === edge.source &&
513+
e.sourceHandle === edge.sourceHandle &&
514+
e.target === edge.target &&
515+
e.targetHandle === edge.targetHandle
516+
)
517+
if (connectionExists) continue
514518

515519
// Skip if would create a cycle
516520
if (wouldCreateCycle([...newEdges], edge.source, edge.target)) continue
@@ -525,7 +529,6 @@ export const useWorkflowStore = create<WorkflowStore>()(
525529
data: edge.data || {},
526530
})
527531
existingEdgeIds.add(edge.id)
528-
existingConnections.add(connectionKey)
529532
}
530533

531534
const blocks = get().blocks

0 commit comments

Comments
 (0)