@@ -83,8 +83,33 @@ export function diff(
8383 ( isHydrating = oldVNode . _flags & MODE_HYDRATE ) &&
8484 oldVNode . _component . _excess
8585 ) {
86- excessDomChildren = oldVNode . _component . _excess ;
87- oldDom = excessDomChildren [ 0 ] ;
86+ let startMarker = oldVNode . _component . _excess [ 0 ] ;
87+ if (
88+ startMarker &&
89+ startMarker . nodeType == 8 &&
90+ startMarker . data . startsWith ( '$s' )
91+ ) {
92+ // Deferred restoration: re-scan current DOM from the stored start marker.
93+ // This ensures we always hydrate against the most up-to-date DOM state,
94+ // even if a streaming SSR patcher replaced the content between markers.
95+ excessDomChildren = [ ] ;
96+ let depth = 1 ;
97+ let node = startMarker . nextSibling ;
98+ while ( node && depth > 0 ) {
99+ if ( node . nodeType == 8 ) {
100+ if ( node . data . startsWith ( '$s' ) ) depth ++ ;
101+ else if ( node . data . startsWith ( '/$s' ) ) {
102+ if ( -- depth == 0 ) break ;
103+ }
104+ }
105+ excessDomChildren . push ( node ) ;
106+ node = node . nextSibling ;
107+ }
108+ oldDom = excessDomChildren [ 0 ] ;
109+ } else {
110+ excessDomChildren = oldVNode . _component . _excess ;
111+ oldDom = excessDomChildren [ 0 ] ;
112+ }
88113 oldVNode . _component . _excess = NULL ;
89114 }
90115
@@ -311,46 +336,47 @@ export function diff(
311336 if ( isHydrating || excessDomChildren != NULL ) {
312337 if ( e . then ) {
313338 let commentMarkersToFind = 0 ,
314- done ;
339+ done ,
340+ startMarker ;
315341
316342 newVNode . _flags |= isHydrating
317343 ? MODE_HYDRATE | MODE_SUSPENDED
318344 : MODE_SUSPENDED ;
319345
320- newVNode . _component . _excess = [ ] ;
321346 for ( let i = 0 ; i < excessDomChildren . length ; i ++ ) {
322347 let child = excessDomChildren [ i ] ;
323348 if ( child == NULL || done ) continue ;
324349
325- // When we encounter a boundary with $s we are opening
326- // a boundary, this implies that we need to bump
327- // the amount of markers we need to find before closing
328- // the outer boundary.
329- // We exclude the open and closing marker from
330- // the future excessDomChildren but any nested one
331- // needs to be included for future suspensions.
350+ // When we encounter a $s boundary marker we are opening a
351+ // suspended region. Track nesting depth to find the matching
352+ // close marker. We null out ALL nodes in the region so the
353+ // parent diff doesn't try to remove them; the children will be
354+ // re-scanned from the stored start marker on resume.
332355 if ( child . nodeType == 8 ) {
333- if ( child . data == '$s' ) {
334- if ( commentMarkersToFind ) {
335- newVNode . _component . _excess . push ( child ) ;
356+ if ( child . data . startsWith ( '$s' ) ) {
357+ if ( ! commentMarkersToFind ) {
358+ // Store outermost start marker for deferred restoration
359+ startMarker = child ;
336360 }
337361 commentMarkersToFind ++ ;
338- } else if ( child . data == '/$s' ) {
339- commentMarkersToFind -- ;
340- if ( commentMarkersToFind ) {
341- newVNode . _component . _excess . push ( child ) ;
362+ } else if ( child . data . startsWith ( '/$s' ) ) {
363+ if ( -- commentMarkersToFind == 0 ) {
364+ done = true ;
365+ oldDom = excessDomChildren [ i ] ;
342366 }
343- done = commentMarkersToFind == 0 ;
344- oldDom = excessDomChildren [ i ] ;
345367 }
346368 excessDomChildren [ i ] = NULL ;
347369 } else if ( commentMarkersToFind ) {
348- newVNode . _component . _excess . push ( child ) ;
349370 excessDomChildren [ i ] = NULL ;
350371 }
351372 }
352373
353- if ( ! done ) {
374+ if ( done ) {
375+ // Store only the start marker; children are re-scanned on resume
376+ // so we always hydrate against the current DOM state.
377+ // TODO: consider just storing in _dom and getting rid of _excess altogether?
378+ newVNode . _component . _excess = [ startMarker ] ;
379+ } else {
354380 while ( oldDom && oldDom . nodeType == 8 && oldDom . nextSibling ) {
355381 oldDom = oldDom . nextSibling ;
356382 }
0 commit comments