11import { createLogger } from '@/lib/logs/console/logger'
22import type { BlockState } from '@/stores/workflows/workflow/types'
33import type { Edge , GraphNode } from './types'
4- import { getBlockDimensions , isStarterBlock } from './utils'
4+ import { getBlockMetrics } from './utils'
55
66const logger = createLogger ( 'AutoLayout:Layering' )
77
@@ -15,7 +15,7 @@ export function assignLayers(
1515 nodes . set ( id , {
1616 id,
1717 block,
18- dimensions : getBlockDimensions ( block ) ,
18+ metrics : getBlockMetrics ( block ) ,
1919 incoming : new Set ( ) ,
2020 outgoing : new Set ( ) ,
2121 layer : 0 ,
@@ -33,45 +33,60 @@ export function assignLayers(
3333 }
3434 }
3535
36- const starterNodes = Array . from ( nodes . values ( ) ) . filter (
37- ( node ) => node . incoming . size === 0 || isStarterBlock ( node . block )
38- )
36+ // Only treat blocks as starters if they have no incoming edges
37+ // This prevents triggers that are mid-flow from being forced to layer 0
38+ const starterNodes = Array . from ( nodes . values ( ) ) . filter ( ( node ) => node . incoming . size === 0 )
3939
4040 if ( starterNodes . length === 0 && nodes . size > 0 ) {
4141 const firstNode = Array . from ( nodes . values ( ) ) [ 0 ]
4242 starterNodes . push ( firstNode )
4343 logger . warn ( 'No starter blocks found, using first block as starter' , { blockId : firstNode . id } )
4444 }
4545
46- const visited = new Set < string > ( )
47- const queue : Array < { nodeId : string ; layer : number } > = [ ]
46+ // Use topological sort to ensure proper layering based on dependencies
47+ // Each node's layer = max(all incoming nodes' layers) + 1
48+ const inDegreeCount = new Map < string , number > ( )
4849
49- for ( const starter of starterNodes ) {
50- starter . layer = 0
51- queue . push ( { nodeId : starter . id , layer : 0 } )
50+ for ( const node of nodes . values ( ) ) {
51+ inDegreeCount . set ( node . id , node . incoming . size )
52+ if ( starterNodes . includes ( node ) ) {
53+ node . layer = 0
54+ }
5255 }
5356
54- while ( queue . length > 0 ) {
55- const { nodeId, layer } = queue . shift ( ) !
56-
57- if ( visited . has ( nodeId ) ) {
58- continue
59- }
57+ const queue : string [ ] = starterNodes . map ( ( n ) => n . id )
58+ const processed = new Set < string > ( )
6059
61- visited . add ( nodeId )
60+ while ( queue . length > 0 ) {
61+ const nodeId = queue . shift ( ) !
6262 const node = nodes . get ( nodeId ) !
63- node . layer = Math . max ( node . layer , layer )
63+ processed . add ( nodeId )
64+
65+ // Calculate this node's layer based on all incoming edges
66+ if ( node . incoming . size > 0 ) {
67+ let maxIncomingLayer = - 1
68+ for ( const incomingId of node . incoming ) {
69+ const incomingNode = nodes . get ( incomingId )
70+ if ( incomingNode ) {
71+ maxIncomingLayer = Math . max ( maxIncomingLayer , incomingNode . layer )
72+ }
73+ }
74+ node . layer = maxIncomingLayer + 1
75+ }
6476
77+ // Add outgoing nodes to queue when all their dependencies are processed
6578 for ( const targetId of node . outgoing ) {
66- const targetNode = nodes . get ( targetId )
67- if ( targetNode ) {
68- queue . push ( { nodeId : targetId , layer : layer + 1 } )
79+ const currentCount = inDegreeCount . get ( targetId ) || 0
80+ inDegreeCount . set ( targetId , currentCount - 1 )
81+
82+ if ( inDegreeCount . get ( targetId ) === 0 && ! processed . has ( targetId ) ) {
83+ queue . push ( targetId )
6984 }
7085 }
7186 }
7287
7388 for ( const node of nodes . values ( ) ) {
74- if ( ! visited . has ( node . id ) ) {
89+ if ( ! processed . has ( node . id ) ) {
7590 logger . debug ( 'Isolated node detected, assigning to layer 0' , { blockId : node . id } )
7691 node . layer = 0
7792 }
0 commit comments