Compare commits

...

4 Commits

Author SHA1 Message Date
waleed
79621d788e fix(change-detection): add condition to check against duplicate edges 2026-01-13 15:51:35 -08:00
Waleed
67686eac07 fix(a2a): removed deployment constraint for redeploying a2a workflows (#2796)
* fix(a2a): removed deployment constraint for redeploying a2a workflows

* updated A2A tab copy state

* consolidated trigger types const
2026-01-13 15:51:35 -08:00
waleed
96a2979bee fix(change-detection): add condition to check against duplicate edges 2026-01-13 15:51:30 -08:00
waleed
c1a92d3be9 fix(dups): onConntext being called twice for a signle edge, once in onConnectEnd and onConnectExtended 2026-01-13 15:51:00 -08:00
4 changed files with 31 additions and 20 deletions

View File

@@ -356,6 +356,9 @@ const WorkflowContent = React.memo(() => {
/** Stores source node/handle info when a connection drag starts for drop-on-block detection. */
const connectionSourceRef = useRef<{ nodeId: string; handleId: string } | null>(null)
/** Tracks whether onConnect successfully handled the connection (ReactFlow pattern). */
const connectionCompletedRef = useRef(false)
/** Stores start positions for multi-node drag undo/redo recording. */
const multiNodeDragStartRef = useRef<Map<string, { x: number; y: number; parentId?: string }>>(
new Map()
@@ -2214,7 +2217,8 @@ const WorkflowContent = React.memo(() => {
)
/**
* Captures the source handle when a connection drag starts
* Captures the source handle when a connection drag starts.
* Resets connectionCompletedRef to track if onConnect handles this connection.
*/
const onConnectStart = useCallback((_event: any, params: any) => {
const handleId: string | undefined = params?.handleId
@@ -2223,6 +2227,7 @@ const WorkflowContent = React.memo(() => {
nodeId: params?.nodeId,
handleId: params?.handleId,
}
connectionCompletedRef.current = false
}, [])
/** Handles new edge connections with container boundary validation. */
@@ -2283,6 +2288,7 @@ const WorkflowContent = React.memo(() => {
isInsideContainer: true,
},
})
connectionCompletedRef.current = true
return
}
@@ -2311,6 +2317,7 @@ const WorkflowContent = React.memo(() => {
}
: undefined,
})
connectionCompletedRef.current = true
}
},
[addEdge, getNodes, blocks]
@@ -2319,8 +2326,9 @@ const WorkflowContent = React.memo(() => {
/**
* Handles connection drag end. Detects if the edge was dropped over a block
* and automatically creates a connection to that block's target handle.
* Only creates a connection if ReactFlow didn't already handle it (e.g., when
* dropping on the block body instead of a handle).
*
* Uses connectionCompletedRef to check if onConnect already handled this connection
* (ReactFlow pattern for distinguishing handle-to-handle vs handle-to-body drops).
*/
const onConnectEnd = useCallback(
(event: MouseEvent | TouchEvent) => {
@@ -2332,6 +2340,12 @@ const WorkflowContent = React.memo(() => {
return
}
// If onConnect already handled this connection, skip (handle-to-handle case)
if (connectionCompletedRef.current) {
connectionSourceRef.current = null
return
}
// Get cursor position in flow coordinates
const clientPos = 'changedTouches' in event ? event.changedTouches[0] : event
const flowPosition = screenToFlowPosition({
@@ -2342,12 +2356,7 @@ const WorkflowContent = React.memo(() => {
// Find node under cursor
const targetNode = findNodeAtPosition(flowPosition)
// Create connection if valid target found AND edge doesn't already exist
// ReactFlow's onConnect fires first when dropping on a handle, so we check
// if that connection already exists to avoid creating duplicates.
// IMPORTANT: We must read directly from the store (not React state) because
// the store update from ReactFlow's onConnect may not have triggered a
// React re-render yet when this callback runs (typically 1-2ms later).
// Create connection if valid target found (handle-to-body case)
if (targetNode && targetNode.id !== source.nodeId) {
const currentEdges = useWorkflowStore.getState().edges
const edgeAlreadyExists = currentEdges.some(

View File

@@ -24,7 +24,9 @@ export function hasWorkflowChanged(
deployedState: WorkflowState | null
): boolean {
// If no deployed state exists, then the workflow has changed
if (!deployedState) return true
if (!deployedState) {
return true
}
// 1. Compare edges (connections between blocks)
const currentEdges = currentState.edges || []

View File

@@ -197,9 +197,10 @@ export function normalizeEdge(edge: Edge): NormalizedEdge {
}
/**
* Sorts edges for consistent comparison
* Sorts and deduplicates edges for consistent comparison.
* Deduplication handles legacy data that may contain duplicate edges.
* @param edges - Array of edges to sort
* @returns Sorted array of normalized edges
* @returns Sorted array of unique normalized edges
*/
export function sortEdges(
edges: Array<{
@@ -214,7 +215,13 @@ export function sortEdges(
target: string
targetHandle?: string | null
}> {
return [...edges].sort((a, b) =>
const uniqueEdges = new Map<string, (typeof edges)[number]>()
for (const edge of edges) {
const key = `${edge.source}-${edge.sourceHandle ?? 'null'}-${edge.target}-${edge.targetHandle ?? 'null'}`
uniqueEdges.set(key, edge)
}
return Array.from(uniqueEdges.values()).sort((a, b) =>
`${a.source}-${a.sourceHandle}-${a.target}-${a.targetHandle}`.localeCompare(
`${b.source}-${b.sourceHandle}-${b.target}-${b.targetHandle}`
)

View File

@@ -498,8 +498,6 @@ export const useWorkflowStore = create<WorkflowStore>()(
const currentEdges = get().edges
const newEdges = [...currentEdges]
const existingEdgeIds = new Set(currentEdges.map((e) => e.id))
// Track existing connections to prevent duplicates (same source->target)
const existingConnections = new Set(currentEdges.map((e) => `${e.source}->${e.target}`))
for (const edge of edges) {
// Skip if edge ID already exists
@@ -508,10 +506,6 @@ export const useWorkflowStore = create<WorkflowStore>()(
// Skip self-referencing edges
if (edge.source === edge.target) continue
// Skip if connection already exists (same source and target)
const connectionKey = `${edge.source}->${edge.target}`
if (existingConnections.has(connectionKey)) continue
// Skip if would create a cycle
if (wouldCreateCycle([...newEdges], edge.source, edge.target)) continue
@@ -525,7 +519,6 @@ export const useWorkflowStore = create<WorkflowStore>()(
data: edge.data || {},
})
existingEdgeIds.add(edge.id)
existingConnections.add(connectionKey)
}
const blocks = get().blocks