@@ -1641,51 +1641,36 @@ const WorkflowContent = React.memo(() => {
16411641 } , [ screenToFlowPosition , handleToolbarDrop ] )
16421642
16431643 /**
1644- * Focus canvas on changed blocks when diff appears
1645- * Focuses on new/edited blocks rather than fitting the entire workflow
1644+ * Focus canvas on changed blocks when diff appears.
16461645 */
1646+ const pendingZoomBlockIdsRef = useRef < Set < string > | null > ( null )
16471647 const prevDiffReadyRef = useRef ( false )
1648+
1649+ // Phase 1: When diff becomes ready, record which blocks we want to zoom to
1650+ // Phase 2 effect is located after displayNodes is defined (search for "Phase 2")
16481651 useEffect ( ( ) => {
1649- // Only focus when diff transitions from not ready to ready
16501652 if ( isDiffReady && ! prevDiffReadyRef . current && diffAnalysis ) {
1653+ // Diff just became ready - record blocks to zoom to
16511654 const changedBlockIds = [
16521655 ...( diffAnalysis . new_blocks || [ ] ) ,
16531656 ...( diffAnalysis . edited_blocks || [ ] ) ,
16541657 ]
16551658
16561659 if ( changedBlockIds . length > 0 ) {
1657- const allNodes = getNodes ( )
1658- const changedNodes = allNodes . filter ( ( node ) => changedBlockIds . includes ( node . id ) )
1659-
1660- if ( changedNodes . length > 0 ) {
1661- logger . info ( 'Diff ready - focusing on changed blocks' , {
1662- changedBlockIds,
1663- foundNodes : changedNodes . length ,
1664- } )
1665- requestAnimationFrame ( ( ) => {
1666- fitViewToBounds ( {
1667- nodes : changedNodes ,
1668- duration : 600 ,
1669- padding : 0.1 ,
1670- minZoom : 0.5 ,
1671- maxZoom : 1.0 ,
1672- } )
1673- } )
1674- } else {
1675- logger . info ( 'Diff ready - no changed nodes found, fitting all' )
1676- requestAnimationFrame ( ( ) => {
1677- fitViewToBounds ( { padding : 0.1 , duration : 600 } )
1678- } )
1679- }
1660+ pendingZoomBlockIdsRef . current = new Set ( changedBlockIds )
16801661 } else {
1681- logger . info ( 'Diff ready - no changed blocks, fitting all' )
1662+ // No specific blocks to focus on, fit all after a frame
1663+ pendingZoomBlockIdsRef . current = null
16821664 requestAnimationFrame ( ( ) => {
16831665 fitViewToBounds ( { padding : 0.1 , duration : 600 } )
16841666 } )
16851667 }
1668+ } else if ( ! isDiffReady && prevDiffReadyRef . current ) {
1669+ // Diff was cleared (accepted/rejected) - cancel any pending zoom
1670+ pendingZoomBlockIdsRef . current = null
16861671 }
16871672 prevDiffReadyRef . current = isDiffReady
1688- } , [ isDiffReady , diffAnalysis , fitViewToBounds , getNodes ] )
1673+ } , [ isDiffReady , diffAnalysis , fitViewToBounds ] )
16891674
16901675 /** Displays trigger warning notifications. */
16911676 useEffect ( ( ) => {
@@ -2093,6 +2078,48 @@ const WorkflowContent = React.memo(() => {
20932078 } )
20942079 } , [ derivedNodes , blocks , pendingSelection , clearPendingSelection ] )
20952080
2081+ // Phase 2: When displayNodes updates, check if pending zoom blocks are ready
2082+ // (Phase 1 is located earlier in the file where pendingZoomBlockIdsRef is defined)
2083+ useEffect ( ( ) => {
2084+ const pendingBlockIds = pendingZoomBlockIdsRef . current
2085+ if ( ! pendingBlockIds || pendingBlockIds . size === 0 ) {
2086+ return
2087+ }
2088+
2089+ // Find the nodes we're waiting for
2090+ const pendingNodes = displayNodes . filter ( ( node ) => pendingBlockIds . has ( node . id ) )
2091+
2092+ // Check if all expected nodes are present with valid dimensions
2093+ const allNodesReady =
2094+ pendingNodes . length === pendingBlockIds . size &&
2095+ pendingNodes . every (
2096+ ( node ) =>
2097+ typeof node . width === 'number' &&
2098+ typeof node . height === 'number' &&
2099+ node . width > 0 &&
2100+ node . height > 0
2101+ )
2102+
2103+ if ( allNodesReady ) {
2104+ logger . info ( 'Diff ready - focusing on changed blocks' , {
2105+ changedBlockIds : Array . from ( pendingBlockIds ) ,
2106+ foundNodes : pendingNodes . length ,
2107+ } )
2108+ // Clear pending state before zooming to prevent re-triggers
2109+ pendingZoomBlockIdsRef . current = null
2110+ // Use requestAnimationFrame to ensure React has finished rendering
2111+ requestAnimationFrame ( ( ) => {
2112+ fitViewToBounds ( {
2113+ nodes : pendingNodes ,
2114+ duration : 600 ,
2115+ padding : 0.1 ,
2116+ minZoom : 0.5 ,
2117+ maxZoom : 1.0 ,
2118+ } )
2119+ } )
2120+ }
2121+ } , [ displayNodes , fitViewToBounds ] )
2122+
20962123 /** Handles ActionBar remove-from-subflow events. */
20972124 useEffect ( ( ) => {
20982125 const handleRemoveFromSubflow = ( event : Event ) => {
0 commit comments