@@ -90,9 +90,10 @@ export function applyTargetedLayout(
9090
9191/**
9292 * Selects the best anchor block for offset computation.
93- * Prefers an unchanged block that is a direct edge-neighbor of a block
94- * that needs layout, so the offset aligns new blocks relative to their
95- * actual graph neighbors rather than an arbitrary/outlier block.
93+ * Prefers an upstream (predecessor) anchor over a downstream one because
94+ * upstream blocks keep their layer assignment when new blocks are inserted
95+ * after them, giving a stable offset. Downstream blocks shift to later
96+ * layers in the ideal layout, producing a large incorrect offset.
9697 */
9798function selectBestAnchor (
9899 eligibleIds : string [ ] ,
@@ -107,14 +108,17 @@ function selectBestAnchor(
107108 const candidateSet = new Set ( candidates )
108109
109110 for ( const edge of edges ) {
110- if ( needsLayoutSet . has ( edge . source ) && candidateSet . has ( edge . target ) ) {
111- return edge . target
112- }
113111 if ( needsLayoutSet . has ( edge . target ) && candidateSet . has ( edge . source ) ) {
114112 return edge . source
115113 }
116114 }
117115
116+ for ( const edge of edges ) {
117+ if ( needsLayoutSet . has ( edge . source ) && candidateSet . has ( edge . target ) ) {
118+ return edge . target
119+ }
120+ }
121+
118122 return candidates [ 0 ]
119123}
120124
@@ -213,11 +217,77 @@ function layoutGroup(
213217 block . position = snapPositionToGrid ( { x : newPos . x + offsetX , y : newPos . y + offsetY } , gridSize )
214218 }
215219
220+ shiftDownstreamFrozenBlocks (
221+ needsLayoutSet ,
222+ layoutEligibleChildIds ,
223+ blocks ,
224+ edges ,
225+ horizontalSpacing ,
226+ gridSize
227+ )
228+
216229 if ( parentBlock ) {
217230 updateContainerDimensions ( parentBlock , childIds , blocks )
218231 }
219232}
220233
234+ /**
235+ * Shifts frozen (unchanged) blocks rightward when a newly placed block
236+ * overlaps with them in the X-axis. Traverses the DAG forward from changed
237+ * blocks via BFS, cascading shifts through downstream frozen blocks so that
238+ * insertions between existing layers push everything after them to the right.
239+ *
240+ * Only considers edges within the current layout group (scoped to subflow).
241+ */
242+ function shiftDownstreamFrozenBlocks (
243+ needsLayoutSet : Set < string > ,
244+ eligibleIds : string [ ] ,
245+ blocks : Record < string , BlockState > ,
246+ edges : Edge [ ] ,
247+ horizontalSpacing : number ,
248+ gridSize ?: number
249+ ) : void {
250+ const eligibleSet = new Set ( eligibleIds )
251+
252+ const downstreamMap = new Map < string , string [ ] > ( )
253+ for ( const edge of edges ) {
254+ if ( ! eligibleSet . has ( edge . source ) || ! eligibleSet . has ( edge . target ) ) continue
255+ if ( ! downstreamMap . has ( edge . source ) ) downstreamMap . set ( edge . source , [ ] )
256+ downstreamMap . get ( edge . source ) ! . push ( edge . target )
257+ }
258+
259+ const shifted = new Set < string > ( )
260+ const queue : string [ ] = Array . from ( needsLayoutSet )
261+
262+ while ( queue . length > 0 ) {
263+ const sourceId = queue . shift ( ) !
264+ const sourceBlock = blocks [ sourceId ]
265+ if ( ! sourceBlock ) continue
266+
267+ const sourceMetrics = getBlockMetrics ( sourceBlock )
268+ const sourceRight = sourceBlock . position . x + sourceMetrics . width
269+
270+ const successors = downstreamMap . get ( sourceId ) || [ ]
271+ for ( const targetId of successors ) {
272+ if ( needsLayoutSet . has ( targetId ) ) continue
273+ if ( shifted . has ( targetId ) ) continue
274+
275+ const targetBlock = blocks [ targetId ]
276+ if ( ! targetBlock ) continue
277+
278+ if ( targetBlock . position . x < sourceRight + horizontalSpacing ) {
279+ const shiftX = sourceRight + horizontalSpacing - targetBlock . position . x
280+ targetBlock . position = snapPositionToGrid (
281+ { x : targetBlock . position . x + shiftX , y : targetBlock . position . y } ,
282+ gridSize
283+ )
284+ shifted . add ( targetId )
285+ queue . push ( targetId )
286+ }
287+ }
288+ }
289+ }
290+
221291/**
222292 * Computes layout positions for a subset of blocks using the core layout function
223293 */
0 commit comments