@@ -416,6 +416,20 @@ const nodeTypes = { flow: FlowNodeCard };
416416export function FlowGraph ( { title, nodes, edges, sequence } : FlowGraphProps ) {
417417 const reduceMotion = ! ! useReducedMotion ( ) ;
418418
419+ // Stream re-renders hand us fresh array identities with identical content (the
420+ // message part is re-cloned per streamed token). Key the expensive layout and
421+ // the status timeline on the CONTENT, not the array reference, so dagre isn't
422+ // re-run and an in-flight animation doesn't reset to t=0 on every token.
423+ const nodesSig = nodes
424+ . map ( ( n ) => `${ n . id } |${ n . label } |${ n . sublabel ?? "" } |${ n . kind } |${ n . status ?? "" } ` )
425+ . join ( ";" ) ;
426+ const edgesSig = edges
427+ . map ( ( e ) => `${ e . from } >${ e . to } |${ e . kind ?? "" } |${ e . label ?? "" } ` )
428+ . join ( ";" ) ;
429+ const sequenceSig = ( sequence ?? [ ] )
430+ . map ( ( s ) => `${ s . nodeId } |${ s . status } |${ s . atMs } ` )
431+ . join ( ";" ) ;
432+
419433 // Robustness guard: a model can emit an edge whose `from`/`to` doesn't match
420434 // any node id (typo, stale reference, partial catalog data). React Flow
421435 // doesn't validate this itself — an edge pointing at a missing node id can
@@ -425,11 +439,13 @@ export function FlowGraph({ title, nodes, edges, sequence }: FlowGraphProps) {
425439 const safeEdges = useMemo ( ( ) => {
426440 const ids = new Set ( nodes . map ( ( n ) => n . id ) ) ;
427441 return edges . filter ( ( e ) => ids . has ( e . from ) && ids . has ( e . to ) ) ;
428- } , [ nodes , edges ] ) ;
442+ // eslint-disable-next-line react-hooks/exhaustive-deps
443+ } , [ nodesSig , edgesSig ] ) ;
429444
430445 const { positions, handles : edgeHandles } = useMemo (
431446 ( ) => computeLayout ( nodes , safeEdges ) ,
432- [ nodes , safeEdges ]
447+ // eslint-disable-next-line react-hooks/exhaustive-deps
448+ [ nodesSig , safeEdges ]
433449 ) ;
434450
435451 const revealDelays = useMemo ( ( ) => {
@@ -439,7 +455,8 @@ export function FlowGraph({ title, nodes, edges, sequence }: FlowGraphProps) {
439455 const out : Record < string , number > = { } ;
440456 for ( const [ id , o ] of order ) out [ id ] = 0.1 + o * step ;
441457 return out ;
442- } , [ nodes , safeEdges ] ) ;
458+ // eslint-disable-next-line react-hooks/exhaustive-deps
459+ } , [ nodesSig , safeEdges ] ) ;
443460
444461 const [ statuses , setStatuses ] = useState < Record < string , FlowNodeStatus > > ( ( ) =>
445462 initialStatuses ( nodes , sequence , reduceMotion )
@@ -456,7 +473,8 @@ export function FlowGraph({ title, nodes, edges, sequence }: FlowGraphProps) {
456473 } , Math . max ( 0 , s . atMs ) )
457474 ) ;
458475 return ( ) => timers . forEach ( ( t ) => window . clearTimeout ( t ) ) ;
459- } , [ nodes , sequence , reduceMotion ] ) ;
476+ // eslint-disable-next-line react-hooks/exhaustive-deps
477+ } , [ nodesSig , sequenceSig , reduceMotion ] ) ;
460478
461479 // Edges fade in mid-cascade so they don't dangle off still-hidden nodes.
462480 const [ edgesVisible , setEdgesVisible ] = useState ( reduceMotion ) ;
@@ -467,7 +485,9 @@ export function FlowGraph({ title, nodes, edges, sequence }: FlowGraphProps) {
467485 return ;
468486 }
469487 setEdgesVisible ( false ) ;
470- const t = window . setTimeout ( ( ) => setEdgesVisible ( true ) , revealSpan * 500 + 150 ) ;
488+ // revealDelays are in SECONDS; the last node starts at ~revealSpan*1000ms,
489+ // so wait until it's begun animating in before the edges appear.
490+ const t = window . setTimeout ( ( ) => setEdgesVisible ( true ) , revealSpan * 1000 + 150 ) ;
471491 return ( ) => window . clearTimeout ( t ) ;
472492 } , [ reduceMotion , revealSpan ] ) ;
473493
@@ -491,7 +511,8 @@ export function FlowGraph({ title, nodes, edges, sequence }: FlowGraphProps) {
491511 selectable : false ,
492512 connectable : false ,
493513 } ) ) ,
494- [ nodes , positions , statuses , revealDelays , reduceMotion ]
514+ // eslint-disable-next-line react-hooks/exhaustive-deps
515+ [ nodesSig , positions , statuses , revealDelays , reduceMotion ]
495516 ) ;
496517
497518 const rfEdges : Edge [ ] = useMemo (
@@ -527,7 +548,8 @@ export function FlowGraph({ title, nodes, edges, sequence }: FlowGraphProps) {
527548 const bottoms = nodes . map ( ( n ) => ( positions [ n . id ] ?. y ?? 0 ) + ( positions [ n . id ] ?. height ?? 48 ) ) ;
528549 const maxY = Math . max ( 0 , ...bottoms ) ;
529550 return Math . min ( 480 , Math . max ( 180 , maxY + 24 ) ) ;
530- } , [ nodes , positions ] ) ;
551+ // eslint-disable-next-line react-hooks/exhaustive-deps
552+ } , [ nodesSig , positions ] ) ;
531553
532554 return (
533555 < motion . div
0 commit comments