@@ -472,6 +472,33 @@ const WorkflowContent = React.memo(() => {
472472 }
473473 } , [ debouncedAutoLayout , undo , redo ] )
474474
475+ /**
476+ * Removes all edges connected to a block, skipping individual edge recording for undo/redo.
477+ * Used when moving nodes between containers where edges would violate boundary constraints.
478+ */
479+ const removeEdgesForNode = useCallback (
480+ ( blockId : string , edgesToRemove : Edge [ ] ) : void => {
481+ if ( edgesToRemove . length === 0 ) return
482+
483+ // Skip individual edge recording - parent update will record as batch
484+ window . dispatchEvent ( new CustomEvent ( 'skip-edge-recording' , { detail : { skip : true } } ) )
485+
486+ try {
487+ edgesToRemove . forEach ( ( edge ) => {
488+ removeEdge ( edge . id )
489+ } )
490+
491+ logger . debug ( 'Removed edges for node' , {
492+ blockId,
493+ edgeCount : edgesToRemove . length ,
494+ } )
495+ } finally {
496+ window . dispatchEvent ( new CustomEvent ( 'skip-edge-recording' , { detail : { skip : false } } ) )
497+ }
498+ } ,
499+ [ removeEdge ]
500+ )
501+
475502 // Listen for explicit remove-from-subflow actions from ActionBar
476503 useEffect ( ( ) => {
477504 const handleRemoveFromSubflow = ( event : Event ) => {
@@ -490,18 +517,11 @@ const WorkflowContent = React.memo(() => {
490517 ( e ) => e . source === blockId || e . target === blockId
491518 )
492519
493- // Set flag to skip individual edge recording for undo/redo
494- window . dispatchEvent ( new CustomEvent ( 'skip-edge-recording' , { detail : { skip : true } } ) )
495-
496- // Remove edges first
497- edgesToRemove . forEach ( ( edge ) => {
498- removeEdge ( edge . id )
499- } )
520+ // Remove edges using shared helper
521+ removeEdgesForNode ( blockId , edgesToRemove )
500522
501- // Then update parent relationship
523+ // Update parent relationship (null = remove from parent)
502524 updateNodeParent ( blockId , null , edgesToRemove )
503-
504- window . dispatchEvent ( new CustomEvent ( 'skip-edge-recording' , { detail : { skip : false } } ) )
505525 } catch ( err ) {
506526 logger . error ( 'Failed to remove from subflow' , { err } )
507527 }
@@ -510,7 +530,7 @@ const WorkflowContent = React.memo(() => {
510530 window . addEventListener ( 'remove-from-subflow' , handleRemoveFromSubflow as EventListener )
511531 return ( ) =>
512532 window . removeEventListener ( 'remove-from-subflow' , handleRemoveFromSubflow as EventListener )
513- } , [ getNodes , updateNodeParent , removeEdge , edgesForDisplay ] )
533+ } , [ blocks , edgesForDisplay , removeEdgesForNode , updateNodeParent ] )
514534
515535 // Handle drops
516536 const findClosestOutput = useCallback (
@@ -1881,6 +1901,21 @@ const WorkflowContent = React.memo(() => {
18811901
18821902 // Update the node's parent relationship
18831903 if ( potentialParentId ) {
1904+ // Remove existing edges before moving into container
1905+ const edgesToRemove = edgesForDisplay . filter (
1906+ ( e ) => e . source === node . id || e . target === node . id
1907+ )
1908+
1909+ if ( edgesToRemove . length > 0 ) {
1910+ removeEdgesForNode ( node . id , edgesToRemove )
1911+
1912+ logger . info ( 'Removed edges when moving node into subflow' , {
1913+ blockId : node . id ,
1914+ targetParentId : potentialParentId ,
1915+ edgeCount : edgesToRemove . length ,
1916+ } )
1917+ }
1918+
18841919 // Compute relative position BEFORE updating parent to avoid stale state
18851920 // Account for header (50px), left padding (16px), and top padding (16px)
18861921 const containerAbsPosBefore = getNodeAbsolutePosition ( potentialParentId )
@@ -1954,8 +1989,9 @@ const WorkflowContent = React.memo(() => {
19541989 // Skip recording these edges separately since they're part of the parent update
19551990 window . dispatchEvent ( new CustomEvent ( 'skip-edge-recording' , { detail : { skip : true } } ) )
19561991
1957- // Moving to a new parent container - pass the edges that will be added
1958- updateNodeParent ( node . id , potentialParentId , edgesToAdd )
1992+ // Moving to a new parent container - pass both removed and added edges for undo/redo
1993+ const affectedEdges = [ ...edgesToRemove , ...edgesToAdd ]
1994+ updateNodeParent ( node . id , potentialParentId , affectedEdges )
19591995
19601996 // Now add the edges after parent update
19611997 edgesToAdd . forEach ( ( edge ) => addEdge ( edge ) )
@@ -1976,6 +2012,8 @@ const WorkflowContent = React.memo(() => {
19762012 addEdge ,
19772013 determineSourceHandle ,
19782014 blocks ,
2015+ edgesForDisplay ,
2016+ removeEdgesForNode ,
19792017 getNodeAbsolutePosition ,
19802018 getDragStartPosition ,
19812019 setDragStartPosition ,
0 commit comments