@@ -2188,51 +2188,126 @@ <h3>Phases</h3>
21882188        return  arr [ Symbol . iterator ] ( ) ; 
21892189      } 
21902190
2191-       // Derive array of operations used for graph layout with pruning of noop leaves. 
2191+       // Derive array of operations used for graph layout with pruning of noop nodes. 
2192+       // New behavior: skip noop nodes that are leaves (no dependents) OR that have exactly 
2193+       // one incoming edge OR exactly one outgoing edge. When pruning a noop, we will 
2194+       // rewire dependencies so remaining ops connect directly (i.e., bypass removed noops). 
21922195      function  computeGraphOperations ( )  { 
21932196        // Stable layout: always all operations; filtering only adjusts classes 
21942197        const  base  =  Array . from ( operations . values ( ) ) ; 
21952198        computeFilterSets ( ) ; 
21962199        if  ( ! base . length )  return  base ; 
2197-         // Build dependency reverse  map (dependents list) inside the filtered set.  
2200+         // Build lookup and static dependents  map (dependents = incoming adjacency)  
21982201        const  byName  =  new  Map ( ) ; 
21992202        base . forEach ( ( o )  =>  byName . set ( o . name ,  o ) ) ; 
2200-         const  dependents  =  new  Map ( ) ;  // name -> Set of dependent op names 
2203+         const  dependents  =  new  Map ( ) ;  // name -> Set of direct  dependent op names 
22012204        base . forEach ( ( o )  =>  { 
22022205          ( o . dependencies  ||  [ ] ) . forEach ( ( d )  =>  { 
22032206            if  ( ! byName . has ( d ) )  return ;  // dependency outside filtered set not considered for pruning 
22042207            ( dependents . get ( d )  ||  dependents . set ( d ,  new  Set ( ) ) . get ( d ) ) . add ( o . name ) ; 
22052208          } ) ; 
22062209        } ) ; 
2207-         // We want to remove noop operations that have no dependents OR whose dependents are all pruned noop as well. 
2208-         // Algorithm: iterative pruning queue starting with noop nodes with zero dependents. 
2210+ 
2211+         // Active set initially contains all nodes; we'll iteratively remove noop nodes 
2212+         // that meet the pruning criteria. 
22092213        const  active  =  new  Set ( base . map ( ( o )  =>  o . name ) ) ; 
22102214        const  noopSet  =  new  Set ( base . filter ( ( o )  =>  o . noop ) . map ( ( o )  =>  o . name ) ) ; 
2211-         const  dependentsCount  =  new  Map ( ) ; 
2212-         active . forEach ( ( n )  =>  dependentsCount . set ( n ,  ( dependents . get ( n )  ||  new  Set ( ) ) . size ) ) ; 
2215+ 
2216+         // Compute initial counts (incoming = number of dependents, outgoing = number of deps) 
2217+         const  incomingCount  =  new  Map ( ) ; 
2218+         const  outgoingCount  =  new  Map ( ) ; 
2219+         base . forEach ( ( o )  =>  { 
2220+           incomingCount . set ( o . name ,  ( dependents . get ( o . name )  ||  new  Set ( ) ) . size ) ; 
2221+           const  out  =  ( o . dependencies  ||  [ ] ) . filter ( ( d )  =>  byName . has ( d ) ) . length ; 
2222+           outgoingCount . set ( o . name ,  out ) ; 
2223+         } ) ; 
2224+ 
2225+         // Queue noop nodes that are leaves or have exactly one incoming or one outgoing edge. 
22132226        const  queue  =  [ ] ; 
22142227        active . forEach ( ( n )  =>  { 
2215-           if  ( noopSet . has ( n )  &&  dependentsCount . get ( n )  ===  0 )  queue . push ( n ) ; 
2228+           if  ( ! noopSet . has ( n ) )  return ; 
2229+           const  inc  =  incomingCount . get ( n )  ||  0 ; 
2230+           const  out  =  outgoingCount . get ( n )  ||  0 ; 
2231+           if  ( inc  ===  0  ||  inc  ===  1  ||  out  ===  1 )  queue . push ( n ) ; 
22162232        } ) ; 
2233+ 
22172234        while  ( queue . length )  { 
22182235          const  name  =  queue . pop ( ) ; 
22192236          if  ( ! active . has ( name ) )  continue ; 
2220-           // Remove this noop leaf  
2237+           // Remove this noop node  
22212238          active . delete ( name ) ; 
2222-           // Decrement dependency counts for its dependencies (making them closer to leaves) 
22232239          const  op  =  byName . get ( name ) ; 
22242240          if  ( ! op )  continue ; 
2241+ 
2242+           // For each dependency of the removed node, decrement its incoming count 
22252243          for  ( const  dep  of  op . dependencies  ||  [ ] )  { 
22262244            if  ( ! active . has ( dep ) )  continue ; 
2227-             const  cnt  =  dependentsCount . get ( dep ) ; 
2228-             if  ( cnt  ===  undefined )  continue ; 
2229-             dependentsCount . set ( dep ,  cnt  -  1 ) ; 
2230-             if  ( cnt  -  1  ===  0  &&  noopSet . has ( dep ) )  { 
2231-               queue . push ( dep ) ; 
2232-             } 
2245+             const  prev  =  incomingCount . get ( dep )  ||  0 ; 
2246+             incomingCount . set ( dep ,  Math . max ( 0 ,  prev  -  1 ) ) ; 
2247+             const  inc  =  incomingCount . get ( dep ) ; 
2248+             const  out  =  outgoingCount . get ( dep )  ||  0 ; 
2249+             if  ( noopSet . has ( dep )  &&  ( inc  ===  0  ||  inc  ===  1  ||  out  ===  1 ) )  queue . push ( dep ) ; 
2250+           } 
2251+ 
2252+           // For each dependent (nodes that depended on the removed node), decrement their outgoing count 
2253+           const  depsOf  =  dependents . get ( name )  ||  new  Set ( ) ; 
2254+           for  ( const  dependent  of  depsOf )  { 
2255+             if  ( ! active . has ( dependent ) )  continue ; 
2256+             const  prevOut  =  outgoingCount . get ( dependent )  ||  0 ; 
2257+             outgoingCount . set ( dependent ,  Math . max ( 0 ,  prevOut  -  1 ) ) ; 
2258+             const  inc  =  incomingCount . get ( dependent )  ||  0 ; 
2259+             const  out  =  outgoingCount . get ( dependent )  ||  0 ; 
2260+             if  ( noopSet . has ( dependent )  &&  ( inc  ===  0  ||  inc  ===  1  ||  out  ===  1 ) )  queue . push ( dependent ) ; 
22332261          } 
22342262        } 
2235-         return  base . filter ( ( o )  =>  active . has ( o . name ) ) ; 
2263+ 
2264+         // At this point `active` contains node names to keep. We must return op objects 
2265+         // whose dependency lists are rewritten so that any dependency chains that went 
2266+         // through removed noop nodes are bypassed. 
2267+         const  removed  =  new  Set ( base . map ( ( o )  =>  o . name ) . filter ( ( n )  =>  ! active . has ( n ) ) ) ; 
2268+ 
2269+         // Memoize resolved dependencies to avoid repeated recursion. 
2270+         const  resolvedMemo  =  new  Map ( ) ; 
2271+         function  resolveDeps ( name ,  seen )  { 
2272+           if  ( resolvedMemo . has ( name ) )  return  resolvedMemo . get ( name ) ; 
2273+           if  ( seen . has ( name ) )  return  new  Set ( ) ;  // break cycles defensively 
2274+           seen . add ( name ) ; 
2275+           const  op  =  byName . get ( name ) ; 
2276+           const  out  =  new  Set ( ) ; 
2277+           if  ( ! op )  return  out ; 
2278+           for  ( const  d  of  op . dependencies  ||  [ ] )  { 
2279+             if  ( ! byName . has ( d ) )  continue ; 
2280+             if  ( active . has ( d ) )  { 
2281+               out . add ( d ) ; 
2282+             }  else  { 
2283+               // dependency was removed; splice through it 
2284+               const  sub  =  resolveDeps ( d ,  seen ) ; 
2285+               for  ( const  s  of  sub )  out . add ( s ) ; 
2286+             } 
2287+           } 
2288+           seen . delete ( name ) ; 
2289+           resolvedMemo . set ( name ,  out ) ; 
2290+           return  out ; 
2291+         } 
2292+ 
2293+         const  result  =  base 
2294+           . filter ( ( o )  =>  active . has ( o . name ) ) 
2295+           . map ( ( o )  =>  { 
2296+             // Compute effective dependencies for o by splicing through removed noops 
2297+             const  deps  =  new  Set ( ) ; 
2298+             for  ( const  d  of  o . dependencies  ||  [ ] )  { 
2299+               if  ( ! byName . has ( d ) )  continue ; 
2300+               if  ( active . has ( d ) )  deps . add ( d ) ; 
2301+               else  { 
2302+                 const  sub  =  resolveDeps ( d ,  new  Set ( ) ) ; 
2303+                 for  ( const  s  of  sub )  deps . add ( s ) ; 
2304+               } 
2305+             } 
2306+             // Return a shallow copy so callers can freely inspect/modify dependencies 
2307+             return  Object . assign ( { } ,  o ,  {  dependencies : Array . from ( deps )  } ) ; 
2308+           } ) ; 
2309+ 
2310+         return  result ; 
22362311      } 
22372312
22382313      function  render ( )  { 
0 commit comments