@@ -1961,27 +1961,37 @@ const WorkflowContent = React.memo(() => {
19611961
19621962 const movingNodeIds = new Set ( validBlockIds )
19631963
1964+ // Find boundary edges (edges that cross the subflow boundary)
19641965 const boundaryEdges = edgesForDisplay . filter ( ( e ) => {
19651966 const sourceInSelection = movingNodeIds . has ( e . source )
19661967 const targetInSelection = movingNodeIds . has ( e . target )
19671968 return sourceInSelection !== targetInSelection
19681969 } )
19691970
1970- // Collect absolute positions BEFORE updating parents
1971+ // Collect absolute positions BEFORE any mutations
19711972 const absolutePositions = new Map < string , { x : number ; y : number } > ( )
19721973 for ( const blockId of validBlockIds ) {
19731974 absolutePositions . set ( blockId , getNodeAbsolutePosition ( blockId ) )
19741975 }
19751976
1976- for ( const blockId of validBlockIds ) {
1977+ // Build batch update with all blocks and their affected edges
1978+ const updates = validBlockIds . map ( ( blockId ) => {
1979+ const absolutePosition = absolutePositions . get ( blockId ) !
19771980 const edgesForThisNode = boundaryEdges . filter (
19781981 ( e ) => e . source === blockId || e . target === blockId
19791982 )
1980- removeEdgesForNode ( blockId , edgesForThisNode )
1981- updateNodeParent ( blockId , null , edgesForThisNode )
1982- }
1983+ return {
1984+ blockId,
1985+ newParentId : null ,
1986+ newPosition : absolutePosition ,
1987+ affectedEdges : edgesForThisNode ,
1988+ }
1989+ } )
19831990
1984- // Immediately update displayNodes to prevent React Flow from using stale parent data
1991+ // Single atomic batch update (handles edge removal + parent update + undo/redo)
1992+ collaborativeBatchUpdateParent ( updates )
1993+
1994+ // Update displayNodes once to prevent React Flow from using stale parent data
19851995 setDisplayNodes ( ( nodes ) =>
19861996 nodes . map ( ( n ) => {
19871997 const absPos = absolutePositions . get ( n . id )
@@ -1996,6 +2006,8 @@ const WorkflowContent = React.memo(() => {
19962006 return n
19972007 } )
19982008 )
2009+
2010+ // Note: Container resize happens automatically via the derivedNodes effect
19992011 } catch ( err ) {
20002012 logger . error ( 'Failed to remove from subflow' , { err } )
20012013 }
@@ -2004,7 +2016,7 @@ const WorkflowContent = React.memo(() => {
20042016 window . addEventListener ( 'remove-from-subflow' , handleRemoveFromSubflow as EventListener )
20052017 return ( ) =>
20062018 window . removeEventListener ( 'remove-from-subflow' , handleRemoveFromSubflow as EventListener )
2007- } , [ blocks , edgesForDisplay , removeEdgesForNode , updateNodeParent , getNodeAbsolutePosition ] )
2019+ } , [ blocks , edgesForDisplay , getNodeAbsolutePosition , collaborativeBatchUpdateParent ] )
20082020
20092021 /** Handles node position changes - updates local state for smooth drag, syncs to store only on drag end. */
20102022 const onNodesChange = useCallback ( ( changes : NodeChange [ ] ) => {
0 commit comments