@@ -1800,37 +1800,32 @@ const WorkflowContent = React.memo(() => {
18001800 )
18011801 } , [ screenToFlowPosition , handleToolbarDrop ] )
18021802
1803- /**
1804- * Focus canvas on changed blocks when diff appears.
1805- */
1803+ /** Tracks blocks to pan to after diff updates. */
18061804 const pendingZoomBlockIdsRef = useRef < Set < string > | null > ( null )
1807- const prevDiffReadyRef = useRef ( false )
1805+ const seenDiffBlocksRef = useRef < Set < string > > ( new Set ( ) )
18081806
1809- // Phase 1: When diff becomes ready, record which blocks we want to zoom to
1810- // Phase 2 effect is located after displayNodes is defined (search for "Phase 2")
1807+ /** Queues newly changed blocks for viewport panning. */
18111808 useEffect ( ( ) => {
1812- if ( isDiffReady && ! prevDiffReadyRef . current && diffAnalysis ) {
1813- // Diff just became ready - record blocks to zoom to
1814- const changedBlockIds = [
1815- ...( diffAnalysis . new_blocks || [ ] ) ,
1816- ...( diffAnalysis . edited_blocks || [ ] ) ,
1817- ]
1818-
1819- if ( changedBlockIds . length > 0 ) {
1820- pendingZoomBlockIdsRef . current = new Set ( changedBlockIds )
1821- } else {
1822- // No specific blocks to focus on, fit all after a frame
1823- pendingZoomBlockIdsRef . current = null
1824- requestAnimationFrame ( ( ) => {
1825- fitViewToBounds ( { padding : 0.1 , duration : 600 } )
1826- } )
1827- }
1828- } else if ( ! isDiffReady && prevDiffReadyRef . current ) {
1829- // Diff was cleared (accepted/rejected) - cancel any pending zoom
1809+ if ( ! isDiffReady || ! diffAnalysis ) {
18301810 pendingZoomBlockIdsRef . current = null
1811+ seenDiffBlocksRef . current . clear ( )
1812+ return
1813+ }
1814+
1815+ const newBlocks = new Set < string > ( )
1816+ const allBlocks = [ ...( diffAnalysis . new_blocks || [ ] ) , ...( diffAnalysis . edited_blocks || [ ] ) ]
1817+
1818+ for ( const id of allBlocks ) {
1819+ if ( ! seenDiffBlocksRef . current . has ( id ) ) {
1820+ newBlocks . add ( id )
1821+ }
1822+ seenDiffBlocksRef . current . add ( id )
18311823 }
1832- prevDiffReadyRef . current = isDiffReady
1833- } , [ isDiffReady , diffAnalysis , fitViewToBounds ] )
1824+
1825+ if ( newBlocks . size > 0 ) {
1826+ pendingZoomBlockIdsRef . current = newBlocks
1827+ }
1828+ } , [ isDiffReady , diffAnalysis ] )
18341829
18351830 /** Displays trigger warning notifications. */
18361831 useEffect ( ( ) => {
@@ -2238,18 +2233,12 @@ const WorkflowContent = React.memo(() => {
22382233 } )
22392234 } , [ derivedNodes , blocks , pendingSelection , clearPendingSelection ] )
22402235
2241- // Phase 2: When displayNodes updates, check if pending zoom blocks are ready
2242- // (Phase 1 is located earlier in the file where pendingZoomBlockIdsRef is defined)
2236+ /** Pans viewport to pending blocks once they have valid dimensions. */
22432237 useEffect ( ( ) => {
22442238 const pendingBlockIds = pendingZoomBlockIdsRef . current
2245- if ( ! pendingBlockIds || pendingBlockIds . size === 0 ) {
2246- return
2247- }
2239+ if ( ! pendingBlockIds || pendingBlockIds . size === 0 ) return
22482240
2249- // Find the nodes we're waiting for
22502241 const pendingNodes = displayNodes . filter ( ( node ) => pendingBlockIds . has ( node . id ) )
2251-
2252- // Check if all expected nodes are present with valid dimensions
22532242 const allNodesReady =
22542243 pendingNodes . length === pendingBlockIds . size &&
22552244 pendingNodes . every (
@@ -2261,24 +2250,28 @@ const WorkflowContent = React.memo(() => {
22612250 )
22622251
22632252 if ( allNodesReady ) {
2264- logger . info ( 'Diff ready - focusing on changed blocks' , {
2253+ logger . info ( 'Focusing on changed blocks' , {
22652254 changedBlockIds : Array . from ( pendingBlockIds ) ,
22662255 foundNodes : pendingNodes . length ,
22672256 } )
2268- // Clear pending state before zooming to prevent re-triggers
22692257 pendingZoomBlockIdsRef . current = null
2270- // Use requestAnimationFrame to ensure React has finished rendering
2258+
2259+ const nodesWithAbsolutePositions = pendingNodes . map ( ( node ) => ( {
2260+ ...node ,
2261+ position : getNodeAbsolutePosition ( node . id ) ,
2262+ } ) )
2263+
22712264 requestAnimationFrame ( ( ) => {
22722265 fitViewToBounds ( {
2273- nodes : pendingNodes ,
2266+ nodes : nodesWithAbsolutePositions ,
22742267 duration : 600 ,
22752268 padding : 0.1 ,
22762269 minZoom : 0.5 ,
22772270 maxZoom : 1.0 ,
22782271 } )
22792272 } )
22802273 }
2281- } , [ displayNodes , fitViewToBounds ] )
2274+ } , [ displayNodes , fitViewToBounds , getNodeAbsolutePosition ] )
22822275
22832276 /** Handles ActionBar remove-from-subflow events. */
22842277 useEffect ( ( ) => {
0 commit comments