octane 0.1.26 → 0.1.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/runtime.js CHANGED
@@ -1184,12 +1184,18 @@ function drainQueue() {
1184
1184
  }
1185
1185
  const crossRenderUpdate = block.crossRenderUpdate;
1186
1186
  block.crossRenderUpdate = false;
1187
+ const hiddenOwner = findHiddenRenderOwner(block, true);
1188
+ let hiddenActivity = null;
1189
+ let hiddenTry = null;
1190
+ if (hiddenOwner !== null) {
1191
+ if (hiddenOwner.__kind === "trySlotSlot") hiddenTry = hiddenOwner;
1192
+ else hiddenActivity = hiddenOwner;
1193
+ }
1187
1194
  try {
1188
1195
  if (block.nestedUpdateError) {
1189
1196
  block.nestedUpdateError = false;
1190
1197
  throw maximumUpdateDepthError();
1191
1198
  }
1192
- const hiddenTry = findSuspenseHiddenTry(block);
1193
1199
  if (hiddenTry !== null) {
1194
1200
  attemptHiddenReveal(hiddenTry, block.pendingMode ?? "urgent");
1195
1201
  continue;
@@ -1221,6 +1227,8 @@ function drainQueue() {
1221
1227
  while (root.parentBlock !== null) root = root.parentBlock;
1222
1228
  if (root.kind === "root" && !root.disposed) unmountBlock(root);
1223
1229
  }
1230
+ } finally {
1231
+ if (hiddenActivity !== null) rehideActivityAfterDescendantRender(hiddenActivity);
1224
1232
  }
1225
1233
  }
1226
1234
  QUEUE.length = 0;
@@ -10700,8 +10708,63 @@ function setTransitionFallbackTimeout(ms) {
10700
10708
  function getTransitionFallbackTimeout() {
10701
10709
  return TRANSITION_FALLBACK_TIMEOUT_MS;
10702
10710
  }
10703
- const SUSPENSE_HIDDEN_DISPLAYS = /* @__PURE__ */ new WeakMap();
10704
- const SUSPENSE_HIDDEN_TEXTS = /* @__PURE__ */ new WeakMap();
10711
+ const HIDDEN_DISPLAYS = /* @__PURE__ */ new WeakMap();
10712
+ const HIDDEN_TEXTS = /* @__PURE__ */ new WeakMap();
10713
+ function enforceHiddenDisplay(el) {
10714
+ const hidden = HIDDEN_DISPLAYS.get(el);
10715
+ el.style.setProperty(
10716
+ "display",
10717
+ "none",
10718
+ hidden !== void 0 && hidden.importantOwners > 0 ? "important" : ""
10719
+ );
10720
+ }
10721
+ function retainHiddenDisplay(el, important) {
10722
+ const existing = HIDDEN_DISPLAYS.get(el);
10723
+ if (existing === void 0) {
10724
+ HIDDEN_DISPLAYS.set(el, {
10725
+ owners: 1,
10726
+ importantOwners: important ? 1 : 0,
10727
+ value: el.style.getPropertyValue("display"),
10728
+ priority: el.style.getPropertyPriority("display"),
10729
+ hadStyle: el.hasAttribute("style")
10730
+ });
10731
+ } else {
10732
+ existing.owners++;
10733
+ if (important) existing.importantOwners++;
10734
+ }
10735
+ enforceHiddenDisplay(el);
10736
+ }
10737
+ function releaseHiddenDisplay(el, important) {
10738
+ const display = HIDDEN_DISPLAYS.get(el);
10739
+ if (display === void 0) return;
10740
+ if (important) display.importantOwners--;
10741
+ if (--display.owners === 0) {
10742
+ HIDDEN_DISPLAYS.delete(el);
10743
+ if (display.value === "") el.style.removeProperty("display");
10744
+ else el.style.setProperty("display", display.value, display.priority);
10745
+ if (!display.hadStyle && el.getAttribute("style") === "") el.removeAttribute("style");
10746
+ } else {
10747
+ enforceHiddenDisplay(el);
10748
+ }
10749
+ }
10750
+ function enforceHiddenText(text) {
10751
+ if (text.data !== "") text.data = "";
10752
+ }
10753
+ function retainHiddenText(text) {
10754
+ const existing = HIDDEN_TEXTS.get(text);
10755
+ if (existing === void 0) HIDDEN_TEXTS.set(text, { owners: 1, data: text.data });
10756
+ else existing.owners++;
10757
+ }
10758
+ function releaseHiddenText(text) {
10759
+ const saved = HIDDEN_TEXTS.get(text);
10760
+ if (saved === void 0) return;
10761
+ if (--saved.owners === 0) {
10762
+ HIDDEN_TEXTS.delete(text);
10763
+ text.data = saved.data;
10764
+ } else {
10765
+ enforceHiddenText(text);
10766
+ }
10767
+ }
10705
10768
  function setTryBranch(slot, next) {
10706
10769
  slot.branch = next;
10707
10770
  if (typeof __OCTANE_PROFILE_ENABLED__ !== "undefined" && __OCTANE_PROFILE_ENABLED__) {
@@ -11063,31 +11126,17 @@ function hideBlockHostRange(block, hidden) {
11063
11126
  const el = node;
11064
11127
  if (!hidden.displays.has(el)) {
11065
11128
  hidden.displays.add(el);
11066
- const existing = SUSPENSE_HIDDEN_DISPLAYS.get(el);
11067
- if (existing === void 0) {
11068
- SUSPENSE_HIDDEN_DISPLAYS.set(el, {
11069
- owners: 1,
11070
- value: el.style.getPropertyValue("display"),
11071
- priority: el.style.getPropertyPriority("display"),
11072
- hadStyle: el.hasAttribute("style")
11073
- });
11074
- } else {
11075
- existing.owners++;
11076
- }
11129
+ retainHiddenDisplay(el, true);
11130
+ } else {
11131
+ enforceHiddenDisplay(el);
11077
11132
  }
11078
- el.style.setProperty("display", "none", "important");
11079
11133
  } else if (node.nodeType === 3) {
11080
11134
  const text = node;
11081
11135
  if (!hidden.texts.has(text)) {
11082
11136
  hidden.texts.add(text);
11083
- const existing = SUSPENSE_HIDDEN_TEXTS.get(text);
11084
- if (existing === void 0) {
11085
- SUSPENSE_HIDDEN_TEXTS.set(text, { owners: 1, data: text.data });
11086
- } else {
11087
- existing.owners++;
11088
- }
11137
+ retainHiddenText(text);
11089
11138
  }
11090
- if (text.data !== "") text.data = "";
11139
+ enforceHiddenText(text);
11091
11140
  }
11092
11141
  node = node.nextSibling;
11093
11142
  }
@@ -11115,26 +11164,10 @@ function showTryBlock(state) {
11115
11164
  if (hidden === null) return;
11116
11165
  state.hiddenDom = null;
11117
11166
  for (const el of hidden.displays) {
11118
- const display = SUSPENSE_HIDDEN_DISPLAYS.get(el);
11119
- if (display === void 0) continue;
11120
- if (--display.owners === 0) {
11121
- SUSPENSE_HIDDEN_DISPLAYS.delete(el);
11122
- if (display.value === "") el.style.removeProperty("display");
11123
- else el.style.setProperty("display", display.value, display.priority);
11124
- if (!display.hadStyle && el.getAttribute("style") === "") el.removeAttribute("style");
11125
- } else {
11126
- el.style.setProperty("display", "none", "important");
11127
- }
11167
+ releaseHiddenDisplay(el, true);
11128
11168
  }
11129
11169
  for (const text of hidden.texts) {
11130
- const saved = SUSPENSE_HIDDEN_TEXTS.get(text);
11131
- if (saved === void 0) continue;
11132
- if (--saved.owners === 0) {
11133
- SUSPENSE_HIDDEN_TEXTS.delete(text);
11134
- text.data = saved.data;
11135
- } else if (text.data !== "") {
11136
- text.data = "";
11137
- }
11170
+ releaseHiddenText(text);
11138
11171
  }
11139
11172
  }
11140
11173
  function releaseHeldTransition(state) {
@@ -11286,6 +11319,7 @@ function commitResume(state) {
11286
11319
  }
11287
11320
  function commitResumeInner(state) {
11288
11321
  if (state.parentBlock.disposed) return;
11322
+ const hiddenActivity = findHiddenActivity(state.parentBlock);
11289
11323
  const wasHeld = state.transitionHeld;
11290
11324
  if (wasHeld) state.transitionHeld = false;
11291
11325
  HELD_TRANSITIONS.delete(state);
@@ -11377,14 +11411,33 @@ function commitResumeInner(state) {
11377
11411
  }
11378
11412
  if (!deferringStagedRevealEffects) commitEffects();
11379
11413
  } finally {
11414
+ if (hiddenActivity !== null) rehideActivityAfterDescendantRender(hiddenActivity);
11380
11415
  if (wasHeld) tickTransitionCount(-1);
11381
11416
  }
11382
11417
  }
11383
- function findSuspenseHiddenTry(block) {
11418
+ function findHiddenRenderOwner(block, includeActivity) {
11419
+ let activity = null;
11384
11420
  for (let p = block; p !== null; p = p.parentBlock) {
11421
+ if (includeActivity && activity === null && p.inactive) {
11422
+ const candidate = p.__activitySlot;
11423
+ if (candidate !== void 0 && candidate.block === p && candidate.hidden) {
11424
+ activity = candidate;
11425
+ }
11426
+ }
11385
11427
  const slot = p.__trySlot;
11386
11428
  if (slot !== void 0 && slot.tryBlock === p && slot.hiddenDom !== null) return slot;
11387
11429
  }
11430
+ return activity;
11431
+ }
11432
+ function findSuspenseHiddenTry(block) {
11433
+ return findHiddenRenderOwner(block, false);
11434
+ }
11435
+ function findHiddenActivity(block) {
11436
+ for (let p = block; p !== null; p = p.parentBlock) {
11437
+ if (!p.inactive) continue;
11438
+ const candidate = p.__activitySlot;
11439
+ if (candidate !== void 0 && candidate.block === p && candidate.hidden) return candidate;
11440
+ }
11388
11441
  return null;
11389
11442
  }
11390
11443
  function snapshotSubtreeEffectDeps(scope) {
@@ -11500,11 +11553,19 @@ function queueCurrentHiddenRefs(state) {
11500
11553
  }
11501
11554
  }
11502
11555
  function attemptHiddenReveal(state, scheduledMode) {
11556
+ const hiddenActivity = findHiddenActivity(state.tryBlock);
11557
+ try {
11558
+ attemptHiddenRevealInner(state, scheduledMode);
11559
+ } finally {
11560
+ if (hiddenActivity !== null) rehideActivityAfterDescendantRender(hiddenActivity);
11561
+ }
11562
+ }
11563
+ function attemptHiddenRevealInner(state, scheduledMode) {
11503
11564
  const tryBlock2 = state.tryBlock;
11504
11565
  if (tryBlock2 === null || tryBlock2.disposed || state.hiddenDom === null) return;
11505
11566
  const hiddenAncestor = findSuspenseHiddenTry(tryBlock2.parentBlock);
11506
11567
  if (hiddenAncestor !== null) {
11507
- attemptHiddenReveal(hiddenAncestor, scheduledMode);
11568
+ attemptHiddenRevealInner(hiddenAncestor, scheduledMode);
11508
11569
  return;
11509
11570
  }
11510
11571
  STAGED_REVEALS.delete(state);
@@ -12492,25 +12553,49 @@ function ifBlock(parentScope, slotKey, domParent, cond, thenBody, elseBody, anch
12492
12553
  function hideActivityRange(state) {
12493
12554
  const b = state.block;
12494
12555
  if (!b) return;
12556
+ for (const el of state.hiddenDisplays) {
12557
+ if (el.parentNode !== b.parentNode) {
12558
+ state.hiddenDisplays.delete(el);
12559
+ releaseHiddenDisplay(el, false);
12560
+ }
12561
+ }
12562
+ for (const text of state.hiddenTexts) {
12563
+ if (text.parentNode !== b.parentNode) {
12564
+ state.hiddenTexts.delete(text);
12565
+ releaseHiddenText(text);
12566
+ }
12567
+ }
12495
12568
  let node = b.startMarker.nextSibling;
12496
12569
  while (node && node !== b.endMarker) {
12497
12570
  if (node.nodeType === 1) {
12498
12571
  const el = node;
12499
- if (!state.savedDisplay.has(el)) state.savedDisplay.set(el, el.style.display);
12500
- el.style.display = "none";
12572
+ if (!state.hiddenDisplays.has(el)) {
12573
+ state.hiddenDisplays.add(el);
12574
+ retainHiddenDisplay(el, false);
12575
+ } else {
12576
+ enforceHiddenDisplay(el);
12577
+ }
12501
12578
  } else if (node.nodeType === 3) {
12502
12579
  const t = node;
12503
- if (!state.savedText.has(t)) state.savedText.set(t, t.nodeValue ?? "");
12504
- if (t.nodeValue !== "") t.nodeValue = "";
12580
+ if (!state.hiddenTexts.has(t)) {
12581
+ state.hiddenTexts.add(t);
12582
+ retainHiddenText(t);
12583
+ }
12584
+ enforceHiddenText(t);
12505
12585
  }
12506
12586
  node = node.nextSibling;
12507
12587
  }
12508
12588
  }
12589
+ function rehideActivityAfterDescendantRender(state) {
12590
+ const b = state.block;
12591
+ if (b === null || !state.hidden || state.deactivationPending || blockSubtreeDisposed(b)) return;
12592
+ hideActivityRange(state);
12593
+ }
12509
12594
  function showActivityRange(state) {
12510
- for (const [el, display] of state.savedDisplay) el.style.display = display;
12511
- state.savedDisplay.clear();
12512
- for (const [t, data] of state.savedText) t.nodeValue = data;
12513
- state.savedText.clear();
12595
+ for (const el of state.hiddenDisplays) releaseHiddenDisplay(el, false);
12596
+ state.hiddenDisplays.clear();
12597
+ for (const text of state.hiddenTexts) releaseHiddenText(text);
12598
+ state.hiddenTexts.clear();
12514
12599
  }
12515
12600
  function queueActivityDeactivation(state, block, commitVersion) {
12516
12601
  enqueueEffectEventCommitAction(() => {
@@ -12560,9 +12645,10 @@ function activityBlock(parentScope, slotKey, domParent, mode, body, anchor, env)
12560
12645
  hidden: false,
12561
12646
  commitVersion: 0,
12562
12647
  deactivationPending: false,
12563
- savedDisplay: /* @__PURE__ */ new Map(),
12564
- savedText: /* @__PURE__ */ new Map()
12648
+ hiddenDisplays: /* @__PURE__ */ new Set(),
12649
+ hiddenTexts: /* @__PURE__ */ new Set()
12565
12650
  };
12651
+ b2.__activitySlot = state;
12566
12652
  parentScope.slots[slotKey] = state;
12567
12653
  registerSlot(parentScope, state);
12568
12654
  const adopted = open !== null;
@@ -617,6 +617,7 @@ function createOwnerRecord(root, component, parent, identityPath, key) {
617
617
  renderer: root.renderer,
618
618
  component,
619
619
  componentProps: null,
620
+ componentRevision: 0,
620
621
  parent,
621
622
  identityPath,
622
623
  key,
@@ -625,6 +626,7 @@ function createOwnerRecord(root, component, parent, identityPath, key) {
625
626
  hooks: /* @__PURE__ */ new Map(),
626
627
  effectOrder: [],
627
628
  children: [],
629
+ dirtyEpoch: 0,
628
630
  range: null,
629
631
  contextValues: null,
630
632
  updates: /* @__PURE__ */ new Map(),
@@ -642,13 +644,16 @@ function draftOwner(record, parent, replayPath) {
642
644
  return {
643
645
  record,
644
646
  componentProps: record.componentProps,
647
+ componentRevision: record.componentRevision,
645
648
  parent,
646
649
  replayPath,
647
650
  hooks: new Map(record.hooks),
648
651
  clonedHooks: /* @__PURE__ */ new Set(),
649
652
  seenEffects: [],
650
653
  children: [],
654
+ retainedChildren: null,
651
655
  claimedChildren: /* @__PURE__ */ new Set(),
656
+ sequentialClaimCursor: 0,
652
657
  childOwnerBuckets: null,
653
658
  childClaimCursors: null,
654
659
  childReplayOrdinals: null,
@@ -661,7 +666,8 @@ function draftOwner(record, parent, replayPath) {
661
666
  boundaryThenable: record.boundaryThenable,
662
667
  isBoundary: record.isBoundary,
663
668
  canHandleSuspense: record.canHandleSuspense,
664
- visibility: parent?.visibility ?? record.visibility
669
+ visibility: parent?.visibility ?? record.visibility,
670
+ contextStable: null
665
671
  };
666
672
  }
667
673
  function childReplayPath(parent, component, identityPath, key) {
@@ -686,30 +692,79 @@ function childOwnerBucket(parent, component, identityPath, key) {
686
692
  }
687
693
  return readOwnerIdentity(buckets, component, identityPath, key);
688
694
  }
689
- function claimChildOwner(parent, component, identityPath, key) {
690
- const attempt = currentAttempt();
691
- let record;
692
- const bucket = childOwnerBucket(parent, component, identityPath, key);
693
- if (bucket !== void 0) {
694
- const cursors = parent.childClaimCursors ??= createOwnerIdentityIndex();
695
- let cursor = readOwnerIdentity(cursors, component, identityPath, key) ?? 0;
696
- while (cursor < bucket.length && parent.claimedChildren.has(bucket[cursor])) cursor++;
697
- record = bucket[cursor];
698
- writeOwnerIdentity(
699
- cursors,
700
- component,
701
- identityPath,
702
- key,
703
- cursor + (record === void 0 ? 0 : 1)
704
- );
695
+ function identityPathsEqual(left, right) {
696
+ if (left === right) return true;
697
+ if (left.length !== right.length) return false;
698
+ for (let index = 0; index < left.length; index++) {
699
+ if (!Object.is(left[index], right[index])) return false;
700
+ }
701
+ return true;
702
+ }
703
+ function findClaimableChildRecord(parent, component, identityPath, key) {
704
+ if (parent.childOwnerBuckets === null) {
705
+ const candidate = parent.record.children[parent.sequentialClaimCursor];
706
+ if (candidate !== void 0 && candidate.component === component && Object.is(candidate.key, key) && identityPathsEqual(candidate.identityPath, identityPath) && !parent.claimedChildren.has(candidate)) {
707
+ parent.sequentialClaimCursor++;
708
+ return candidate;
709
+ }
705
710
  }
706
- record ??= createOwnerRecord(attempt.root, component, parent.record, identityPath, key);
711
+ const bucket = childOwnerBucket(parent, component, identityPath, key);
712
+ if (bucket === void 0) return void 0;
713
+ const cursors = parent.childClaimCursors ??= createOwnerIdentityIndex();
714
+ let cursor = readOwnerIdentity(cursors, component, identityPath, key) ?? 0;
715
+ while (cursor < bucket.length && parent.claimedChildren.has(bucket[cursor])) cursor++;
716
+ const record = bucket[cursor];
717
+ writeOwnerIdentity(
718
+ cursors,
719
+ component,
720
+ identityPath,
721
+ key,
722
+ cursor + (record === void 0 ? 0 : 1)
723
+ );
724
+ return record;
725
+ }
726
+ function adoptChildOwner(parent, record, component, identityPath, key) {
727
+ const attempt = currentAttempt();
707
728
  parent.claimedChildren.add(record);
708
729
  const draft = draftOwner(record, parent, childReplayPath(parent, component, identityPath, key));
709
730
  parent.children.push(draft);
710
731
  attempt.owners.push(draft);
711
732
  return draft;
712
733
  }
734
+ function claimChildOwner(parent, component, identityPath, key) {
735
+ const record = findClaimableChildRecord(parent, component, identityPath, key) ?? createOwnerRecord(currentAttempt().root, component, parent.record, identityPath, key);
736
+ return adoptChildOwner(parent, record, component, identityPath, key);
737
+ }
738
+ function ancestorContextsStable(owner) {
739
+ if (owner.contextStable !== null) return owner.contextStable;
740
+ let stable = owner.parent === null ? true : ancestorContextsStable(owner.parent);
741
+ if (stable && owner.contextValues !== null) {
742
+ const committed = owner.record.mounted ? owner.record.contextValues : null;
743
+ if (committed === null) {
744
+ stable = owner.contextValues.size === 0;
745
+ } else if (committed.size !== owner.contextValues.size) {
746
+ stable = false;
747
+ } else {
748
+ for (const [context, value] of owner.contextValues) {
749
+ if (!committed.has(context) || !Object.is(committed.get(context), value)) {
750
+ stable = false;
751
+ break;
752
+ }
753
+ }
754
+ }
755
+ }
756
+ owner.contextStable = stable;
757
+ return stable;
758
+ }
759
+ function ownerSubtreeRetainable(owner) {
760
+ if (owner.updates.size !== 0 || owner.visibility !== "visible" || owner.isBoundary && (owner.hasBoundaryError || owner.boundaryThenable !== null) || owner.component?.__warm !== void 0 || owner.component !== null && universalComponentRevision(owner.component) !== owner.componentRevision) {
761
+ return false;
762
+ }
763
+ for (const child of owner.children) {
764
+ if (!ownerSubtreeRetainable(child)) return false;
765
+ }
766
+ return true;
767
+ }
713
768
  function activateLazyLeafOwner() {
714
769
  const scope = CURRENT_LAZY_LEAF_OWNER;
715
770
  const attempt = CURRENT_ATTEMPT;
@@ -759,9 +814,12 @@ function executeOwner(owner, build, initialRenderCount = 0) {
759
814
  if (renderCount > 0) resetDraftChildren(owner);
760
815
  owner.seenEffects = [];
761
816
  owner.children = [];
817
+ owner.retainedChildren = null;
762
818
  owner.claimedChildren = /* @__PURE__ */ new Set();
819
+ owner.sequentialClaimCursor = 0;
763
820
  owner.childClaimCursors = null;
764
821
  owner.childReplayOrdinals = null;
822
+ owner.contextStable = null;
765
823
  owner.needsRender = false;
766
824
  owner.implicitSlot = 0;
767
825
  const previousOwner = CURRENT_OWNER;
@@ -846,7 +904,35 @@ function materializeComponentValue(value, expectedRenderer, path) {
846
904
  const parent = CURRENT_OWNER;
847
905
  if (parent === null) throw new Error("A nested universal component requires an owner.");
848
906
  const normalized = normalizePropsValue(value.props);
849
- const owner = claimChildOwner(parent, value.component, path, value.hasKey ? value.key : null);
907
+ const key = value.hasKey ? value.key : null;
908
+ const attempt = currentAttempt();
909
+ const record = findClaimableChildRecord(parent, value.component, path, key);
910
+ if (attempt.retainEligible && record !== void 0 && record.mounted && !record.disposed && record.dirtyEpoch !== attempt.dirtyEpoch && record.visibility === "visible" && parent.visibility === "visible" && record.componentProps !== null && universalShallowEqual(record.componentProps, normalized.props) && record.range !== null && record.range.owner === record && ancestorContextsStable(parent) && ownerSubtreeRetainable(record)) {
911
+ const features = logicalTreeFeatures(record.range);
912
+ if ((features & (UNIVERSAL_TREE_PORTAL | UNIVERSAL_TREE_REGION)) === 0) {
913
+ parent.claimedChildren.add(record);
914
+ (parent.retainedChildren ??= []).push({ record, position: parent.children.length });
915
+ attempt.treeFeatures |= features;
916
+ attempt.retainedCount++;
917
+ return [
918
+ {
919
+ kind: "range",
920
+ key: record.range.key,
921
+ owner: record,
922
+ retained: record.range,
923
+ children: []
924
+ }
925
+ ];
926
+ }
927
+ }
928
+ const owner = adoptChildOwner(
929
+ parent,
930
+ record ?? createOwnerRecord(attempt.root, value.component, parent.record, path, key),
931
+ value.component,
932
+ path,
933
+ key
934
+ );
935
+ owner.componentRevision = universalComponentRevision(value.component);
850
936
  const props = { ...normalized.props };
851
937
  owner.componentProps = props;
852
938
  const nodes = executeOwner(owner, () => {
@@ -886,7 +972,9 @@ function disposeUncommittedDraft(owner) {
886
972
  function resetDraftChildren(owner) {
887
973
  for (const child of owner.children) disposeUncommittedDraft(child);
888
974
  owner.children = [];
975
+ owner.retainedChildren = null;
889
976
  owner.claimedChildren = /* @__PURE__ */ new Set();
977
+ owner.sequentialClaimCursor = 0;
890
978
  owner.childClaimCursors = null;
891
979
  owner.childReplayOrdinals = null;
892
980
  }
@@ -895,7 +983,9 @@ function retainCommittedOwnerTree(owner) {
895
983
  owner.seenEffects = [...owner.record.effectOrder];
896
984
  owner.contextValues = owner.record.contextValues === null ? null : new Map(owner.record.contextValues);
897
985
  owner.children = [];
986
+ owner.retainedChildren = null;
898
987
  owner.claimedChildren = new Set(owner.record.children);
988
+ owner.sequentialClaimCursor = 0;
899
989
  owner.childClaimCursors = null;
900
990
  owner.childReplayOrdinals = null;
901
991
  for (const childRecord of owner.record.children) {
@@ -1773,11 +1863,12 @@ function physicalRecords(records) {
1773
1863
  }
1774
1864
  return output;
1775
1865
  }
1776
- function physicalDrafts(records) {
1866
+ function physicalDrafts(drafts) {
1777
1867
  const output = [];
1778
- for (const record of records) {
1779
- if (record.record.kind === "host") output.push(record);
1780
- else if (record.record.kind === "range") output.push(...physicalDrafts(record.children));
1868
+ for (const draft of drafts) {
1869
+ if (draft.record.kind === "host") output.push(draft.record);
1870
+ else if (draft.retained === true) output.push(...physicalRecords(draft.record.children));
1871
+ else if (draft.record.kind === "range") output.push(...physicalDrafts(draft.children));
1781
1872
  }
1782
1873
  return output;
1783
1874
  }
@@ -1842,6 +1933,10 @@ function stableLogicalChildren(records, blueprints) {
1842
1933
  for (let index = 0; index < records.length; index++) {
1843
1934
  const record = records[index];
1844
1935
  const blueprint = blueprints[index];
1936
+ if (blueprint.kind === "range" && blueprint.retained !== void 0) {
1937
+ if (blueprint.retained === record) continue;
1938
+ return false;
1939
+ }
1845
1940
  if (blueprint.kind === "range" && blueprint.compactLeafList !== void 0 || !sameRecordShape(record, blueprint) || !stableLogicalChildren(record.children, blueprint.children)) {
1846
1941
  return false;
1847
1942
  }
@@ -3469,6 +3564,12 @@ class UniversalRootImpl {
3469
3564
  scheduledFullRoot = false;
3470
3565
  scheduledOwners = /* @__PURE__ */ new Set();
3471
3566
  scheduledPreparationDepth = 0;
3567
+ // The current dirty epoch: scheduleOwned() stamps it on the scheduled owner
3568
+ // and its ancestors, and prepare() bumps it as it consumes the batch, so an
3569
+ // attempt recognizes exactly the owners scheduled into it.
3570
+ dirtyEpoch = 1;
3571
+ attemptDirtyEpoch = 0;
3572
+ attemptFullRootScheduled = false;
3472
3573
  scheduledTransitionBatches = /* @__PURE__ */ new Set();
3473
3574
  unattemptedTransitionBatches = /* @__PURE__ */ new Set();
3474
3575
  eventScopeDepth = 0;
@@ -4125,6 +4226,9 @@ class UniversalRootImpl {
4125
4226
  scheduleOwned(owner) {
4126
4227
  if (owner.root !== this || owner.disposed) return;
4127
4228
  this.scheduledOwners.add(owner);
4229
+ for (let current = owner; current !== null && current.dirtyEpoch !== this.dirtyEpoch; current = current.parent) {
4230
+ current.dirtyEpoch = this.dirtyEpoch;
4231
+ }
4128
4232
  this.markUrgentScheduled();
4129
4233
  }
4130
4234
  schedule() {
@@ -4292,7 +4396,8 @@ class UniversalRootImpl {
4292
4396
  replay.props,
4293
4397
  replay.entries,
4294
4398
  replay.transitionBatches,
4295
- replay.transitionRender
4399
+ replay.transitionRender,
4400
+ false
4296
4401
  );
4297
4402
  } catch (error) {
4298
4403
  const batches = new Set(replay.transitionBatches);
@@ -4326,7 +4431,8 @@ class UniversalRootImpl {
4326
4431
  replay.props,
4327
4432
  replay.entries,
4328
4433
  replay.transitionBatches,
4329
- replay.transitionRender
4434
+ replay.transitionRender,
4435
+ false
4330
4436
  );
4331
4437
  } catch (error) {
4332
4438
  const batches = new Set(replay.transitionBatches);
@@ -4538,7 +4644,7 @@ class UniversalRootImpl {
4538
4644
  }
4539
4645
  }
4540
4646
  const unsupported = UNIVERSAL_TREE_PORTAL | UNIVERSAL_TREE_REGION;
4541
- const scopeFeatures = logicalTreeFeatures(range);
4647
+ const scopeFeatures = range === this.rootRecord ? this.treeFeatures : logicalTreeFeatures(range);
4542
4648
  if ((scopeFeatures & unsupported) !== 0) return null;
4543
4649
  this.flushPassivesBeforeRender();
4544
4650
  this.pending?.abort();
@@ -4550,6 +4656,7 @@ class UniversalRootImpl {
4550
4656
  ordinal: 0
4551
4657
  }
4552
4658
  ]);
4659
+ owner.componentRevision = universalComponentRevision(target.component);
4553
4660
  const previousAttempt = CURRENT_ATTEMPT;
4554
4661
  const previousOwner = CURRENT_OWNER;
4555
4662
  const previousWarm = CURRENT_UNIVERSAL_WARM;
@@ -4567,7 +4674,12 @@ class UniversalRootImpl {
4567
4674
  implicitSlot: 0,
4568
4675
  transitionBatches: EMPTY_UNIVERSAL_TRANSITION_BATCHES,
4569
4676
  transitionRender: false,
4570
- bridgeContextReads: null
4677
+ bridgeContextReads: null,
4678
+ // The owned-update guards already require a plain urgent local-driver
4679
+ // attempt, which is exactly the retain-eligible shape.
4680
+ retainEligible: true,
4681
+ retainedCount: 0,
4682
+ dirtyEpoch: this.attemptDirtyEpoch
4571
4683
  };
4572
4684
  ACTIVE_UNIVERSAL_WARM_PLANS.length = 0;
4573
4685
  CURRENT_UNIVERSAL_WARM = null;
@@ -4608,13 +4720,16 @@ class UniversalRootImpl {
4608
4720
  }
4609
4721
  const blueprint = {
4610
4722
  kind: "range",
4611
- key: target.rangeKey,
4723
+ // The committed range key: rangeKey for an ownerRange() scope, null for
4724
+ // the root range, so the reapplied key never drifts from what the full
4725
+ // render path would commit.
4726
+ key: range.key,
4612
4727
  owner: target,
4613
4728
  children: nodes
4614
4729
  };
4615
4730
  this.expandCompactLeafLists(blueprint);
4616
4731
  let scopePlacement = null;
4617
- if (!stableLogicalChildren(range.children, blueprint.children)) {
4732
+ if (range !== this.rootRecord && !stableLogicalChildren(range.children, blueprint.children)) {
4618
4733
  scopePlacement = this.scopePhysicalPlacement(range);
4619
4734
  if (scopePlacement === null) {
4620
4735
  this.discardDraftOwners(attempt.owners);
@@ -4698,6 +4813,9 @@ class UniversalRootImpl {
4698
4813
  prepare(component, props) {
4699
4814
  const ownedTarget = this.scheduledPreparationDepth > 0 && this.scheduledUrgent && !this.scheduledFullRoot && this.scheduledOwners.size !== 0 ? this.resolveScopedTarget() : void 0;
4700
4815
  const scheduledUrgent = this.scheduledUrgent || this.scheduledPreparationDepth === 0;
4816
+ this.attemptFullRootScheduled = this.scheduledFullRoot;
4817
+ this.attemptDirtyEpoch = this.dirtyEpoch;
4818
+ this.dirtyEpoch++;
4701
4819
  this.scheduledUrgent = false;
4702
4820
  this.scheduledFullRoot = false;
4703
4821
  this.scheduledOwners.clear();
@@ -4750,7 +4868,7 @@ class UniversalRootImpl {
4750
4868
  throw error;
4751
4869
  }
4752
4870
  }
4753
- prepareWithReplay(component, props, replayEntries, transitionBatches, transitionRender) {
4871
+ prepareWithReplay(component, props, replayEntries, transitionBatches, transitionRender, allowRetain = true) {
4754
4872
  if (this.unmounted || this.unmounting) {
4755
4873
  throw new Error("Cannot render an unmounted universal root.");
4756
4874
  }
@@ -4769,6 +4887,7 @@ class UniversalRootImpl {
4769
4887
  ];
4770
4888
  const owner = draftOwner(ownerRecord, null, rootPath);
4771
4889
  owner.componentProps = props;
4890
+ owner.componentRevision = universalComponentRevision(component);
4772
4891
  const previousAttempt = CURRENT_ATTEMPT;
4773
4892
  const previousOwner = CURRENT_OWNER;
4774
4893
  const previousWarm = CURRENT_UNIVERSAL_WARM;
@@ -4786,7 +4905,14 @@ class UniversalRootImpl {
4786
4905
  implicitSlot: 0,
4787
4906
  transitionBatches,
4788
4907
  transitionRender,
4789
- bridgeContextReads: null
4908
+ bridgeContextReads: null,
4909
+ // Replay, transition, bridge, and transport attempts re-execute every
4910
+ // owner for their own bookkeeping (warm replays, lane rebasing, bridge
4911
+ // context read tracking, remote commit protocols); only a plain urgent
4912
+ // local attempt may adopt committed subtrees without re-rendering them.
4913
+ retainEligible: allowRetain && this.transport === null && this.bridge === null && !this.attemptFullRootScheduled && replayEntries.length === 0 && transitionBatches.size === 0 && !transitionRender,
4914
+ retainedCount: 0,
4915
+ dirtyEpoch: this.attemptDirtyEpoch
4790
4916
  };
4791
4917
  ACTIVE_UNIVERSAL_WARM_PLANS.length = 0;
4792
4918
  CURRENT_UNIVERSAL_WARM = null;
@@ -4797,10 +4923,13 @@ class UniversalRootImpl {
4797
4923
  try {
4798
4924
  nodes = executeOwner(owner, () => {
4799
4925
  const value = component(props, componentContext(this.renderer));
4800
- return materializeValue(value, this.renderer, null, ["root-output"]);
4926
+ return materializeValue(value, this.renderer, null, [
4927
+ ...ownerRecord.identityPath,
4928
+ "output"
4929
+ ]);
4801
4930
  });
4802
4931
  } catch (error) {
4803
- const suspendedMemos = collectSuspendedMemos(attempt);
4932
+ const suspendedMemos = attempt.retainedCount !== 0 ? [] : collectSuspendedMemos(attempt);
4804
4933
  this.discardDraftOwners(attempt.owners);
4805
4934
  if (error instanceof UniversalSuspense) {
4806
4935
  return this.suspend(
@@ -4823,7 +4952,12 @@ class UniversalRootImpl {
4823
4952
  CURRENT_OWNER = previousOwner;
4824
4953
  }
4825
4954
  try {
4826
- const rootBlueprint = { kind: "range", key: null, children: nodes };
4955
+ const rootBlueprint = {
4956
+ kind: "range",
4957
+ key: null,
4958
+ owner: owner.record,
4959
+ children: nodes
4960
+ };
4827
4961
  const transaction = this.createTransaction(rootBlueprint, attempt, component, props);
4828
4962
  this.pending = transaction;
4829
4963
  return transaction;
@@ -5024,6 +5158,7 @@ class UniversalRootImpl {
5024
5158
  for (const draft of attempt.owners) {
5025
5159
  const record = draft.record;
5026
5160
  record.componentProps = draft.componentProps;
5161
+ record.componentRevision = draft.componentRevision;
5027
5162
  record.parent = draft.parent?.record ?? null;
5028
5163
  record.hooks = draft.hooks;
5029
5164
  record.effectOrder = [...draft.seenEffects];
@@ -5133,6 +5268,7 @@ class UniversalRootImpl {
5133
5268
  for (const draft of attempt.owners) {
5134
5269
  const record = draft.record;
5135
5270
  record.componentProps = draft.componentProps;
5271
+ record.componentRevision = draft.componentRevision;
5136
5272
  record.parent = draft.parent?.record ?? null;
5137
5273
  record.hooks = draft.hooks;
5138
5274
  record.effectOrder = [...draft.seenEffects];
@@ -5229,11 +5365,29 @@ class UniversalRootImpl {
5229
5365
  const used = /* @__PURE__ */ new Set([scopeRecord]);
5230
5366
  const changedRangeOwners = [];
5231
5367
  let topologyChanged = false;
5368
+ const retainedScopes = /* @__PURE__ */ new Set();
5369
+ const retainedDraft = (record, blueprint2) => {
5370
+ retainedScopes.add(record);
5371
+ return { record, blueprint: blueprint2, children: [], isNew: false, hostUpdate: null, retained: true };
5372
+ };
5373
+ const expandRetained = (marker) => {
5374
+ const previousAttempt = CURRENT_ATTEMPT;
5375
+ CURRENT_ATTEMPT = attempt;
5376
+ try {
5377
+ return blueprintFromLogical(marker.retained);
5378
+ } finally {
5379
+ CURRENT_ATTEMPT = previousAttempt;
5380
+ }
5381
+ };
5232
5382
  const reconcileChildren = (oldChildren, blueprints) => {
5233
5383
  if ((treeFeatures & UNIVERSAL_TREE_PORTAL) === 0 && oldChildren.length === blueprints.length && oldChildren.every((record, index) => sameRecordShape(record, blueprints[index]))) {
5234
5384
  return oldChildren.map((record, index) => {
5235
5385
  used.add(record);
5236
- const blueprint2 = blueprints[index];
5386
+ let blueprint2 = blueprints[index];
5387
+ if (blueprint2.kind === "range" && blueprint2.retained !== void 0) {
5388
+ if (blueprint2.retained === record) return retainedDraft(record, blueprint2);
5389
+ blueprint2 = expandRetained(blueprint2);
5390
+ }
5237
5391
  const draft = {
5238
5392
  record,
5239
5393
  blueprint: blueprint2,
@@ -5253,7 +5407,7 @@ class UniversalRootImpl {
5253
5407
  const nextKeys = /* @__PURE__ */ new Set();
5254
5408
  const output = [];
5255
5409
  for (let childIndex = 0; childIndex < blueprints.length; childIndex++) {
5256
- const child = blueprints[childIndex];
5410
+ let child = blueprints[childIndex];
5257
5411
  let record;
5258
5412
  if (child.key !== null) {
5259
5413
  if (nextKeys.has(child.key)) {
@@ -5270,6 +5424,15 @@ class UniversalRootImpl {
5270
5424
  record = candidate;
5271
5425
  }
5272
5426
  }
5427
+ if (child.kind === "range" && child.retained !== void 0) {
5428
+ if (record === child.retained && record !== void 0) {
5429
+ claimed.add(record);
5430
+ used.add(record);
5431
+ output.push(retainedDraft(record, child));
5432
+ continue;
5433
+ }
5434
+ child = expandRetained(child);
5435
+ }
5273
5436
  if (record?.kind === "portal" && child.kind === "portal") {
5274
5437
  const previousRegistration = record.portalRegistration;
5275
5438
  const nextRegistration = child.registration;
@@ -5309,11 +5472,14 @@ class UniversalRootImpl {
5309
5472
  isNew: false,
5310
5473
  hostUpdate: null
5311
5474
  };
5475
+ if (scopeRecord.kind === "range" && scopeRecord.owner !== (blueprint.owner ?? null)) {
5476
+ changedRangeOwners.push(draftRoot);
5477
+ }
5312
5478
  const removedRoots = [];
5313
5479
  const findRemoved = (parent) => {
5314
5480
  for (const child of parent.children) {
5315
5481
  if (!used.has(child)) removedRoots.push(child);
5316
- else findRemoved(child);
5482
+ else if (!retainedScopes.has(child)) findRemoved(child);
5317
5483
  }
5318
5484
  };
5319
5485
  if (topologyChanged) findRemoved(scopeRecord);
@@ -5456,7 +5622,7 @@ class UniversalRootImpl {
5456
5622
  const planPlacements = (parentId, oldRecords, newDrafts, sourceParentId = parentId, forceMove = false, endAnchor = null) => {
5457
5623
  const oldPhysical = physicalRecords(oldRecords);
5458
5624
  const newPhysical = physicalDrafts(newDrafts);
5459
- const desiredIds = new Set(newPhysical.map((entry) => entry.record.id));
5625
+ const desiredIds = new Set(newPhysical.map((entry) => entry.id));
5460
5626
  for (const old of oldPhysical) {
5461
5627
  if (!desiredIds.has(old.id)) {
5462
5628
  removes.push({ op: "remove", parent: sourceParentId, id: old.id });
@@ -5465,8 +5631,7 @@ class UniversalRootImpl {
5465
5631
  const previousIds = new Set(oldPhysical.map((entry) => entry.id));
5466
5632
  const current = forceMove ? [] : oldPhysical.filter((entry) => desiredIds.has(entry.id)).map((entry) => entry.id);
5467
5633
  for (let index = 0; index < newPhysical.length; index++) {
5468
- const draft = newPhysical[index];
5469
- const id = draft.record.id;
5634
+ const id = newPhysical[index].id;
5470
5635
  if (current[index] === id) continue;
5471
5636
  const currentIndex = current.indexOf(id);
5472
5637
  const before = current[index] ?? endAnchor;
@@ -5662,18 +5827,24 @@ class UniversalRootImpl {
5662
5827
  const retryMemos = retryThenables.length === 0 ? [] : collectSuspendedMemos(attempt);
5663
5828
  const refDetaches = [];
5664
5829
  const refAttaches = [];
5665
- const hostDraftsById = /* @__PURE__ */ new Map();
5666
- const lifecycleDrafts = /* @__PURE__ */ new Set();
5830
+ const lifecycleHosts = /* @__PURE__ */ new Set();
5667
5831
  if ((treeFeatures & UNIVERSAL_TREE_LIFECYCLE) !== 0) {
5832
+ const hostsById = /* @__PURE__ */ new Map();
5668
5833
  walkDraft(draftRoot, (draft) => {
5834
+ if (draft.retained === true) {
5835
+ for (const host of physicalRecords(draft.record.children)) {
5836
+ hostsById.set(host.id, host);
5837
+ }
5838
+ return;
5839
+ }
5669
5840
  if (draft.record.kind !== "host") return;
5670
- hostDraftsById.set(draft.record.id, draft);
5671
- if (draft.isNew || draft.hostUpdate !== null) lifecycleDrafts.add(draft);
5841
+ hostsById.set(draft.record.id, draft.record);
5842
+ if (draft.isNew || draft.hostUpdate !== null) lifecycleHosts.add(draft.record);
5672
5843
  });
5673
5844
  for (const placement of placements) {
5674
5845
  if (placement.op !== "move") continue;
5675
- const draft = hostDraftsById.get(placement.id);
5676
- if (draft !== void 0) lifecycleDrafts.add(draft);
5846
+ const host = hostsById.get(placement.id);
5847
+ if (host !== void 0) lifecycleHosts.add(host);
5677
5848
  }
5678
5849
  }
5679
5850
  if ((treeFeatures & UNIVERSAL_TREE_REF) !== 0) {
@@ -5721,11 +5892,18 @@ class UniversalRootImpl {
5721
5892
  }
5722
5893
  }
5723
5894
  const draftedRecords = new Set(draftOwnersParentFirst.map((owner) => owner.record));
5895
+ const retainedOwnerRoots = /* @__PURE__ */ new Set();
5896
+ for (const draft of draftOwnersParentFirst) {
5897
+ if (draft.retainedChildren === null) continue;
5898
+ for (const entry of draft.retainedChildren) retainedOwnerRoots.add(entry.record);
5899
+ }
5724
5900
  const committedOwnersParentFirst = [];
5725
5901
  const walkCommittedOwners = (owner) => {
5726
5902
  if (owner === null) return;
5727
5903
  committedOwnersParentFirst.push(owner);
5728
- for (const child of owner.children) walkCommittedOwners(child);
5904
+ for (const child of owner.children) {
5905
+ if (!retainedOwnerRoots.has(child)) walkCommittedOwners(child);
5906
+ }
5729
5907
  };
5730
5908
  walkCommittedOwners(scoped ? attempt.owner.record : this.owner);
5731
5909
  const removedOwners = committedOwnersParentFirst.filter((owner) => !draftedRecords.has(owner));
@@ -5803,6 +5981,10 @@ class UniversalRootImpl {
5803
5981
  };
5804
5982
  const apply = (draft, parent) => {
5805
5983
  const record = draft.record;
5984
+ if (draft.retained === true) {
5985
+ record.parent = parent;
5986
+ return;
5987
+ }
5806
5988
  record.parent = parent;
5807
5989
  record.key = draft.blueprint.key;
5808
5990
  if (record.kind === "host") {
@@ -5831,24 +6013,32 @@ class UniversalRootImpl {
5831
6013
  }
5832
6014
  };
5833
6015
  const lifecycleOrder = [];
5834
- if (lifecycleDrafts.size !== 0) {
6016
+ if (lifecycleHosts.size !== 0) {
5835
6017
  walkDraftPostOrder(draftRoot, (draft) => {
5836
- if (lifecycleDrafts.has(draft)) lifecycleOrder.push(draft);
6018
+ if (draft.retained === true) {
6019
+ for (const host of physicalRecords(draft.record.children)) {
6020
+ if (lifecycleHosts.has(host)) lifecycleOrder.push(host);
6021
+ }
6022
+ return;
6023
+ }
6024
+ if (lifecycleHosts.has(draft.record)) lifecycleOrder.push(draft.record);
5837
6025
  });
5838
6026
  }
5839
6027
  const hasPassiveWork = removedEffectEventCells.length !== 0 || orderedEffectCleanups.some((cleanup) => cleanup.phase === "passive") || effectChanges.some(({ next, changed }) => changed && next.phase === "passive");
5840
- const previousScopedEventListeners = /* @__PURE__ */ new Set();
5841
- const previousScopedLocalCallbacks = /* @__PURE__ */ new Set();
5842
- if (scoped) {
5843
- walkLogical(scopeRecord, (record) => {
6028
+ const previousReplacedEventListeners = /* @__PURE__ */ new Set();
6029
+ const previousReplacedLocalCallbacks = /* @__PURE__ */ new Set();
6030
+ if ((treeFeatures & (UNIVERSAL_TREE_EVENT | UNIVERSAL_TREE_LOCAL_CALLBACK)) !== 0) {
6031
+ const collectReplaced = (record) => {
5844
6032
  if (record.kind !== "host") return;
5845
6033
  for (const event of record.events.values()) {
5846
- previousScopedEventListeners.add(event.listener);
6034
+ previousReplacedEventListeners.add(event.listener);
5847
6035
  }
5848
6036
  for (const callback of record.localCallbacks.values()) {
5849
- previousScopedLocalCallbacks.add(callback.listener);
6037
+ previousReplacedLocalCallbacks.add(callback.listener);
5850
6038
  }
5851
- });
6039
+ };
6040
+ walkDraft(draftRoot, (draft) => collectReplaced(draft.record));
6041
+ for (const removed of removedHosts) collectReplaced(removed);
5852
6042
  }
5853
6043
  const prepareHost = (value) => this.driver.prepareBatch(this.container, value, {
5854
6044
  invokeLocalCallback: (listener, args) => this.invokeLocalCallback(listener, args)
@@ -5884,9 +6074,8 @@ class UniversalRootImpl {
5884
6074
  }
5885
6075
  }
5886
6076
  if ((treeFeatures & UNIVERSAL_TREE_EVENT) !== 0) {
5887
- const handlers = scoped ? this.handlers : /* @__PURE__ */ new Map();
5888
- const replacedListeners = scoped ? previousScopedEventListeners : this.publishedListeners;
5889
- for (const listener of replacedListeners) {
6077
+ const handlers = this.handlers;
6078
+ for (const listener of previousReplacedEventListeners) {
5890
6079
  EVENT_DISPATCHERS.delete(listener);
5891
6080
  this.publishedListeners.delete(listener);
5892
6081
  handlers.delete(listener);
@@ -5902,11 +6091,10 @@ class UniversalRootImpl {
5902
6091
  );
5903
6092
  }
5904
6093
  }
5905
- this.handlers = handlers;
5906
6094
  }
5907
6095
  if ((treeFeatures & UNIVERSAL_TREE_LOCAL_CALLBACK) !== 0) {
5908
- const localCallbacks = scoped ? this.localCallbacks : /* @__PURE__ */ new Map();
5909
- for (const listener of previousScopedLocalCallbacks) {
6096
+ const localCallbacks = this.localCallbacks;
6097
+ for (const listener of previousReplacedLocalCallbacks) {
5910
6098
  localCallbacks.delete(listener);
5911
6099
  }
5912
6100
  for (const callbacks of localCallbackStage.staged.values()) {
@@ -5914,7 +6102,6 @@ class UniversalRootImpl {
5914
6102
  localCallbacks.set(callback.listener, callback);
5915
6103
  }
5916
6104
  }
5917
- this.localCallbacks = localCallbacks;
5918
6105
  }
5919
6106
  for (const owner of removedOwners) {
5920
6107
  owner.disposed = true;
@@ -5926,12 +6113,25 @@ class UniversalRootImpl {
5926
6113
  for (const draft of draftOwnersParentFirst) {
5927
6114
  const record = draft.record;
5928
6115
  record.componentProps = draft.componentProps;
6116
+ record.componentRevision = draft.componentRevision;
5929
6117
  if (!scoped || draft !== attempt.owner) {
5930
6118
  record.parent = draft.parent?.record ?? null;
5931
6119
  }
5932
6120
  record.hooks = draft.hooks;
5933
6121
  record.effectOrder = [...draft.seenEffects];
5934
- record.children = draft.children.map((child) => child.record);
6122
+ if (draft.retainedChildren === null) {
6123
+ record.children = draft.children.map((child) => child.record);
6124
+ } else {
6125
+ const merged = [];
6126
+ let retainedIndex = 0;
6127
+ for (let index = 0; index <= draft.children.length; index++) {
6128
+ while (retainedIndex < draft.retainedChildren.length && draft.retainedChildren[retainedIndex].position === index) {
6129
+ merged.push(draft.retainedChildren[retainedIndex++].record);
6130
+ }
6131
+ if (index < draft.children.length) merged.push(draft.children[index].record);
6132
+ }
6133
+ record.children = merged;
6134
+ }
5935
6135
  record.contextValues = draft.contextValues;
5936
6136
  record.isBoundary = draft.isBoundary;
5937
6137
  record.canHandleSuspense = draft.canHandleSuspense;
@@ -6036,8 +6236,7 @@ class UniversalRootImpl {
6036
6236
  },
6037
6237
  () => {
6038
6238
  const tasks = [];
6039
- for (const draft of lifecycleOrder) {
6040
- const record = draft.record;
6239
+ for (const record of lifecycleOrder) {
6041
6240
  for (const callback of record.lifecycles.values()) {
6042
6241
  tasks.push(
6043
6242
  () => runOwnedCommit(
@@ -6381,6 +6580,7 @@ class UniversalRootImpl {
6381
6580
  }
6382
6581
  }
6383
6582
  this.rootRecord.children = [];
6583
+ this.rootRecord.owner = null;
6384
6584
  let portalReleaseError = NO_PENDING_PASSIVE_ERROR;
6385
6585
  for (const registration of portalRegistrations) {
6386
6586
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "octane",
3
- "version": "0.1.26",
3
+ "version": "0.1.28",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "sideEffects": false,