@solidjs/signals 2.0.0-rc.17 → 2.0.0-rc.19

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.
Files changed (71) hide show
  1. package/dist/dev-shared.js +426 -241
  2. package/dist/dev.attribution.js +19 -16
  3. package/dist/dev.js +768 -409
  4. package/dist/observe/boundaries.js +88 -67
  5. package/dist/observe/core/action.js +8 -0
  6. package/dist/observe/core/async.js +183 -138
  7. package/dist/observe/core/attribution.js +30 -28
  8. package/dist/observe/core/constants.js +23 -1
  9. package/dist/observe/core/core.js +185 -157
  10. package/dist/observe/core/effect.js +34 -37
  11. package/dist/observe/core/graph.js +8 -8
  12. package/dist/observe/core/heap.js +5 -5
  13. package/dist/observe/core/lanes.js +104 -84
  14. package/dist/observe/core/owner.js +17 -16
  15. package/dist/observe/core/scheduler.js +61 -47
  16. package/dist/observe/core/truth.js +6 -6
  17. package/dist/observe/core/verdict.js +15 -15
  18. package/dist/observe/deferred.js +40 -38
  19. package/dist/observe/from.js +248 -0
  20. package/dist/observe/index.js +4 -2
  21. package/dist/observe/map.js +22 -22
  22. package/dist/observe/reveal.js +5 -5
  23. package/dist/observe/signals.js +57 -15
  24. package/dist/observe/store/affects.js +1 -1
  25. package/dist/observe/store/hooks.js +3 -0
  26. package/dist/observe/store/optimistic.js +11 -11
  27. package/dist/observe/store/projection.js +34 -21
  28. package/dist/observe/store/reconcile.js +138 -122
  29. package/dist/observe/store/store.js +628 -641
  30. package/dist/observe/store/target.js +1 -5
  31. package/dist/observe/store/utils.js +7 -2
  32. package/dist/prod/boundaries.js +153 -132
  33. package/dist/prod/core/action.js +8 -0
  34. package/dist/prod/core/async.js +242 -197
  35. package/dist/prod/core/constants.js +16 -1
  36. package/dist/prod/core/core.js +166 -138
  37. package/dist/prod/core/effect.js +31 -34
  38. package/dist/prod/core/graph.js +5 -5
  39. package/dist/prod/core/heap.js +12 -12
  40. package/dist/prod/core/lanes.js +98 -78
  41. package/dist/prod/core/owner.js +15 -14
  42. package/dist/prod/core/scheduler.js +64 -50
  43. package/dist/prod/core/truth.js +6 -6
  44. package/dist/prod/core/verdict.js +17 -17
  45. package/dist/prod/deferred.js +40 -38
  46. package/dist/prod/from.js +248 -0
  47. package/dist/prod/index.js +4 -2
  48. package/dist/prod/map.js +14 -14
  49. package/dist/prod/reveal.js +8 -8
  50. package/dist/prod/signals.js +46 -4
  51. package/dist/prod/store/affects.js +1 -1
  52. package/dist/prod/store/hooks.js +3 -0
  53. package/dist/prod/store/optimistic.js +16 -16
  54. package/dist/prod/store/projection.js +22 -9
  55. package/dist/prod/store/reconcile.js +138 -122
  56. package/dist/prod/store/store.js +308 -331
  57. package/dist/prod/store/target.js +13 -17
  58. package/dist/prod/store/utils.js +2 -0
  59. package/dist/types/core/action.d.ts +8 -0
  60. package/dist/types/core/async.d.ts +12 -2
  61. package/dist/types/core/constants.d.ts +25 -0
  62. package/dist/types/core/core.d.ts +33 -4
  63. package/dist/types/core/dev.d.ts +1 -1
  64. package/dist/types/core/index.d.ts +1 -1
  65. package/dist/types/core/lanes.d.ts +5 -1
  66. package/dist/types/core/scheduler.d.ts +17 -3
  67. package/dist/types/from.d.ts +47 -0
  68. package/dist/types/index.d.ts +4 -1
  69. package/dist/types/signals.d.ts +37 -0
  70. package/dist/types/store/target.d.ts +2 -0
  71. package/package.json +1 -1
package/dist/dev.js CHANGED
@@ -33,8 +33,8 @@ import {
33
33
  A as tracking,
34
34
  B as CONFIG_CHILDREN_FORBIDDEN,
35
35
  D as passLane,
36
- E as STATUS_PENDING,
37
- F as EFFECT_RENDER,
36
+ E as EFFECT_RENDER,
37
+ F as setPassLane,
38
38
  H as sameLane,
39
39
  I as flushTransaction,
40
40
  J as REACTIVE_SCREEN_READ,
@@ -44,15 +44,15 @@ import {
44
44
  O as REACTIVE_IN_HEAP,
45
45
  P as REACTIVE_DIRTY,
46
46
  Q as REACTIVE_CHECK,
47
- T as CONFIG_INPUTS_PUBLISHED,
48
- U as releaseQueues,
49
- V as CONFIG_SLOT_NODE,
50
- W as holdNode,
51
- X as joinFuture,
52
- Y as reruns,
53
- Z as CONFIG_STAGED,
54
- _ as merge$1,
55
- $ as setPassLane,
47
+ T as STATUS_PENDING,
48
+ U as CONFIG_INPUTS_PUBLISHED,
49
+ V as releaseQueues,
50
+ W as CONFIG_SLOT_NODE,
51
+ X as holdNode,
52
+ Y as joinFuture,
53
+ Z as reruns,
54
+ _ as CONFIG_STAGED,
55
+ $ as merge$1,
56
56
  a0 as REACTIVE_JOINED,
57
57
  a1 as REACTIVE_STAGED_READ,
58
58
  a2 as stagedRead,
@@ -64,123 +64,130 @@ import {
64
64
  a8 as link,
65
65
  a9 as pullComputed,
66
66
  aa as strictRead,
67
- ab as warnStrictReadUntracked,
68
- ac as staleReader,
69
- ad as globalQueue,
70
- ae as joinPass,
71
- af as markLateLinker,
72
- ag as spectating,
73
- ah as unflushedValue,
74
- ai as CONFIG_STAGED_ERROR,
75
- aj as stagedReaders,
76
- ak as bornInto,
77
- al as enterAction,
78
- am as setEffectCallback,
79
- an as exitAction,
80
- ao as isThenable,
81
- ap as actionDepth,
82
- aq as flush,
83
- ar as setQuestion,
84
- as as emitDiagnostic,
85
- at as enterCallback,
86
- au as exitCallback,
87
- av as getOwner,
88
- aw as inEffectCallback,
89
- ax as setExternalHooks,
90
- ay as signal,
91
- az as setSignal,
92
- aA as cleanup,
93
- aB as read,
94
- aC as DEV$1,
95
- aD as CONFIG_TRANSPARENT,
96
- aE as NoOwnerError,
97
- aF as ContextNotFoundError,
98
- aG as createEffectNode,
99
- aH as recompute,
100
- aI as EFFECT_USER,
101
- aJ as reportDiagnostic,
102
- aK as computed,
103
- aL as staleValues,
104
- aM as CONFIG_AUTO_DISPOSE,
105
- aN as EFFECT_TRACKED,
106
- aO as heldRunHook,
107
- aP as unwrapStatusError,
108
- aQ as haltReactivityAt,
109
- aR as setStrictRead,
110
- aS as StatusError,
111
- aT as trimStaleDeps,
112
- aU as setTrackedQueueCallback,
113
- aV as setEffectStatusNotify,
114
- aW as _hitUnhandledAsync,
115
- aX as resetUnhandledAsync,
116
- aY as ROOT_ERROR_HOOK,
117
- aZ as registerGraph,
118
- a_ as runWithOwner,
119
- a$ as setMemo,
120
- b0 as $REFRESH,
121
- b1 as markRefresh,
122
- b2 as createRoot,
123
- b3 as getObserver,
124
- b4 as dispose,
125
- b5 as TimeoutError,
126
- b6 as untrack,
127
- b7 as dirtyQueue,
128
- b8 as CONFIG_REDERIVE,
129
- b9 as passTx,
130
- ba as CONFIG_FRESH_READ,
131
- bb as createOwner,
132
- bc as writeHook,
133
- bd as REACTIVE_WRITE_IN_OWNED_SCOPE_SIGNAL_MESSAGE,
134
- be as ownedScopeWriteMessage,
135
- bf as isEqual,
136
- bg as CONFIG_MANUAL_WRITE,
137
- bh as snapshotCaptureActive,
138
- bi as captureWriteSnapshot,
139
- bj as notePromotedWrite,
140
- bk as slotSignal,
141
- bl as stagedScreen,
142
- bm as CONFIG_PROMOTED,
143
- bn as setSlotUnobserved,
144
- bo as installStoreHooks,
145
- bp as joinPassTx,
146
- bq as asyncTailFlights,
147
- br as untrackDepth,
148
- bs as checkPostAwaitRead,
149
- bt as disposalDepth,
150
- bu as insertIntoHeap,
151
- bv as isDisposed,
152
- bw as handleAsync,
153
- bx as addPendingSource,
154
- by as propagateStatus,
155
- bz as forEachDependent,
156
- bA as settlePendingSource,
157
- bB as joinPassAt,
158
- bC as notifyEpoch,
159
- bD as SUPPORTS_PROXY,
160
- bE as releaseMarksHook,
161
- bF as installMarkHooks,
162
- bG as CONFIG_LAGS,
163
- bH as REACTIVE_REASK,
164
- bI as haltReactivity,
165
- bJ as heldUnder,
166
- bK as unhold,
167
- bL as laneStageHook,
168
- bM as REACTIVE_ZOMBIE,
169
- bN as installBoundaryHooks,
170
- bO as CAUGHT_REARMED,
171
- bP as OBSERVE$1
67
+ ab as strictReadActive,
68
+ ac as warnStrictReadUntracked,
69
+ ad as staleReader,
70
+ ae as globalQueue,
71
+ af as joinPass,
72
+ ag as markLateLinker,
73
+ ah as spectating,
74
+ ai as unflushedValue,
75
+ aj as CONFIG_STAGED_ERROR,
76
+ ak as stagedReaders,
77
+ al as bornInto,
78
+ am as enterAction,
79
+ an as setEffectCallback,
80
+ ao as exitAction,
81
+ ap as isThenable,
82
+ aq as actionDepth,
83
+ ar as flush,
84
+ as as setQuestion,
85
+ at as emitDiagnostic,
86
+ au as enterCallback,
87
+ av as exitCallback,
88
+ aw as getOwner,
89
+ ax as inEffectCallback,
90
+ ay as effectCallbackEvent,
91
+ az as setExternalHooks,
92
+ aA as signal,
93
+ aB as setSignal,
94
+ aC as cleanup,
95
+ aD as read,
96
+ aE as DEV$1,
97
+ aF as CONFIG_TRANSPARENT,
98
+ aG as NoOwnerError,
99
+ aH as ContextNotFoundError,
100
+ aI as createEffectNode,
101
+ aJ as recompute,
102
+ aK as EFFECT_USER,
103
+ aL as reportDiagnostic,
104
+ aM as computed,
105
+ aN as staleValues,
106
+ aO as CONFIG_AUTO_DISPOSE,
107
+ aP as EFFECT_TRACKED,
108
+ aQ as heldRunHook,
109
+ aR as unwrapStatusError,
110
+ aS as setStrictRead,
111
+ aT as queueEvent,
112
+ aU as StatusError,
113
+ aV as trimStaleDeps,
114
+ aW as haltReactivityAt,
115
+ aX as strictReadEvent,
116
+ aY as setTrackedQueueCallback,
117
+ aZ as setEffectStatusNotify,
118
+ a_ as _hitUnhandledAsync,
119
+ a$ as resetUnhandledAsync,
120
+ b0 as ROOT_ERROR_HOOK,
121
+ b1 as registerGraph,
122
+ b2 as runWithOwner,
123
+ b3 as setMemo,
124
+ b4 as $REFRESH,
125
+ b5 as markRefresh,
126
+ b6 as createRoot,
127
+ b7 as getObserver,
128
+ b8 as dispose,
129
+ b9 as TimeoutError,
130
+ ba as untrack,
131
+ bb as dirtyQueue,
132
+ bc as CONFIG_REDERIVE,
133
+ bd as passTx,
134
+ be as CONFIG_FRESH_READ,
135
+ bf as createOwner,
136
+ bg as writeHook,
137
+ bh as REACTIVE_WRITE_IN_OWNED_SCOPE_SIGNAL_MESSAGE,
138
+ bi as ownedScopeWriteMessage,
139
+ bj as isEqual,
140
+ bk as CONFIG_MANUAL_WRITE,
141
+ bl as snapshotCaptureActive,
142
+ bm as captureWriteSnapshot,
143
+ bn as notePromotedWrite,
144
+ bo as slotSignal,
145
+ bp as stagedScreen,
146
+ bq as CONFIG_PROMOTED,
147
+ br as setSlotUnobserved,
148
+ bs as installStoreHooks,
149
+ bt as joinPassTx,
150
+ bu as asyncTailFlights,
151
+ bv as untrackDepth,
152
+ bw as checkPostAwaitRead,
153
+ bx as disposalDepth,
154
+ by as insertIntoHeap,
155
+ bz as isDisposed,
156
+ bA as handleAsync,
157
+ bB as addPendingSource,
158
+ bC as propagateStatus,
159
+ bD as forEachDependent,
160
+ bE as settlePendingSource,
161
+ bF as joinPassAt,
162
+ bG as notifyEpoch,
163
+ bH as SUPPORTS_PROXY,
164
+ bI as CONFIG_KEY_BAG,
165
+ bJ as releaseMarksHook,
166
+ bK as installMarkHooks,
167
+ bL as CONFIG_LAGS,
168
+ bM as REACTIVE_REASK,
169
+ bN as onSupersede,
170
+ bO as haltReactivity,
171
+ bP as CONFIG_FRAME,
172
+ bQ as heldUnder,
173
+ bR as unhold,
174
+ bS as laneStageHook,
175
+ bT as REACTIVE_ZOMBIE,
176
+ bU as installBoundaryHooks,
177
+ bV as CAUGHT_REARMED,
178
+ bW as OBSERVE$1
172
179
  } from "./dev-shared.js";
173
180
  export {
174
- bQ as clearSnapshots,
175
- bR as enforceLoadingBoundary,
176
- bS as getNextChildId,
177
- bT as markSnapshotScope,
178
- bU as peekNextChildId,
179
- bV as releaseSnapshotScope,
180
- bW as resetErrorHalt,
181
- bX as setConsoleFooter,
182
- bY as setSnapshotCapture,
183
- bZ as setWriteHook
181
+ bX as clearSnapshots,
182
+ bY as enforceLoadingBoundary,
183
+ bZ as getNextChildId,
184
+ b_ as markSnapshotScope,
185
+ b$ as peekNextChildId,
186
+ c0 as releaseSnapshotScope,
187
+ c1 as resetErrorHalt,
188
+ c2 as setConsoleFooter,
189
+ c3 as setSnapshotCapture,
190
+ c4 as setWriteHook
184
191
  } from "./dev-shared.js";
185
192
 
186
193
  // Truth reads (`until`, `refresh`; CONFIG_AUTHORITATIVE) over lane nodes.
@@ -695,7 +702,11 @@ function dissolveLane(l, into, except) {
695
702
  * has shown, or one the seam has not judged yet (its run waits on the
696
703
  * verdict: released if the lane shows, held if it blocks) — and sees the
697
704
  * lane's latest. NOT_PENDING falls through: lane work's read of a pending
698
- * member throws like any. */
705
+ * member throws like any. A verdict reader entering a lane the seam has
706
+ * not judged reads ahead of it like a staging read: if the seam holds the
707
+ * lane, the pass re-derives on the screen (`entered`, #4284) — `latest()`
708
+ * beside the lane's nodes tears ahead of the hold however the reads are
709
+ * grouped into effects. */
699
710
  function laneRead(c, el) {
700
711
  const guess = el._config & CONFIG_GUESS;
701
712
  const l = txOf(el);
@@ -713,6 +724,7 @@ function laneRead(c, el) {
713
724
  // An errored member's lane value predates its error; the error is the answer (#4106).
714
725
  if (!guess && status & STATUS_ERROR) return NOT_PENDING;
715
726
  if (c === null) return laneValueOf(el);
727
+ const pending = !guess && status & STATUS_PENDING;
716
728
  if (!tracking || c._config & CONFIG_CHILDREN_FORBIDDEN) {
717
729
  // An untracked read inside lane work derives from the lane like a
718
730
  // tracked one, minus the subscription — `untrack` is about
@@ -721,40 +733,47 @@ function laneRead(c, el) {
721
733
  // lanes read by one pass link, as for tracked reads). Outside lane
722
734
  // work, the screen.
723
735
  if (passLane === null || c._config & CONFIG_CHILDREN_FORBIDDEN) return display(el);
724
- enterLane(l, c);
725
- if (!guess && status & STATUS_PENDING) return NOT_PENDING;
726
- return laneValueOf(el);
727
- }
728
- if (c._type === EFFECT_RENDER && (passLane === null || !sameLane(passLane, l))) {
729
- if (!guess) {
730
- // A flight whose value has never shown: nothing to show — the leaf
731
- // waits on it, and its own frame holds (A15, #3334), not the lane's
732
- // parent. The landing lands the frame: its own pass (the flush
733
- // joined its transaction) reads the answer it waited on — the lane's
734
- // staging, revealed at this seam.
735
- if (status & STATUS_PENDING && el._x._lane === NOT_PENDING) return NOT_PENDING;
736
+ } else if (c._type === EFFECT_RENDER) {
737
+ // A flight whose value has never shown: nothing to show — the leaf
738
+ // waits on it, and its own frame holds (A15, #3334), not the lane's
739
+ // parent. The landing lands the frame: its own pass (the flush joined
740
+ // its transaction) reads the answer it waited on — the lane's staging,
741
+ // revealed at this seam.
742
+ const never = pending && el._x._lane === NOT_PENDING;
743
+ // D-28: a leaf a verdict read moved into the lane still throws on such
744
+ // a flight — the pass leaves the lane, and its frame holds as if the
745
+ // read had come first (S-39, #4275). Nor does a verdict lane's seat carry
746
+ // the leaf into another lane (no link): it reads that lane as from the
747
+ // frame's seat (#4284).
748
+ if ((never || passLane !== l) && passLane?._parent._verdict === passLane) setPassLane(null);
749
+ if (passLane === null || !sameLane(passLane, l)) {
750
+ // (A lane that has never shown: the screen is the committed value.)
751
+ if (never && l._shown) return NOT_PENDING;
736
752
  if (
753
+ !guess &&
737
754
  el._pendingValue !== NOT_PENDING &&
738
755
  c._config & CONFIG_HELD &&
739
756
  flushTransaction !== null &&
740
757
  resolveTx(flushTransaction) === txOf(c)
741
758
  )
742
759
  return el._pendingValue;
743
- }
744
- if (l._held || (!guess && status & STATUS_PENDING)) {
745
- if (!guess && !l._shown && status & STATUS_UNINITIALIZED) throw new NotReadyError(null);
746
- // Re-derived at the reveal; once per pass (the pass may also be a stale
747
- // reader of a transaction, REACTIVE_FRAME_READ — both reruns apply).
748
- if (!(c._flags & REACTIVE_SCREEN_READ)) {
749
- c._flags |= REACTIVE_FRAME_READ | REACTIVE_SCREEN_READ;
750
- l._reruns.push(c);
760
+ if (l._held || pending) {
761
+ if (!guess && !l._shown && status & STATUS_UNINITIALIZED) throw new NotReadyError(null);
762
+ // Re-derived at the reveal; once per pass (the pass may also be a stale
763
+ // reader of a transaction, REACTIVE_FRAME_READ — both reruns apply).
764
+ if (!(c._flags & REACTIVE_SCREEN_READ)) {
765
+ c._flags |= REACTIVE_FRAME_READ | REACTIVE_SCREEN_READ;
766
+ l._reruns.push(c);
767
+ }
768
+ return display(el);
751
769
  }
752
- return display(el);
770
+ // A verdict reader reads ahead of the seam's verdict: re-derived on the
771
+ // screen if the lane holds (#4284).
772
+ if (c._config & CONFIG_VERDICT && l._parent._verdict !== l) entered.push(c);
753
773
  }
754
774
  }
755
775
  enterLane(l, c);
756
- if (!guess && status & STATUS_PENDING) return NOT_PENDING;
757
- return laneValueOf(el);
776
+ return pending ? NOT_PENDING : laneValueOf(el);
758
777
  }
759
778
  /** The value a guess covered: the node's committed value — a store slot's
760
779
  * asked of the store (a chained link follows the inner store's commits). */
@@ -987,7 +1006,17 @@ const laneSeams = leaks => {
987
1006
  }
988
1007
  for (let k = Math.min(judged, lanes.length) - 1; k >= 0; k--)
989
1008
  laneSeam(lanes[k], leaked.indexOf(lanes[k]) !== -1);
1009
+ // A verdict reader that entered a lane this seam held: its run waits with
1010
+ // the lane, and the verdict beside it would freeze — it re-derives next
1011
+ // round from the frame's seat, a stale reader of the lane (#4284).
1012
+ for (let r; (r = entered.pop()); )
1013
+ if (r._x._transaction?._held) {
1014
+ r._flags &= ~REACTIVE_LANE_DIRTY;
1015
+ enqueueSub(r);
1016
+ }
990
1017
  };
1018
+ /** Render effects that read a verdict and entered a lane before its seam (#4284). */
1019
+ const entered = [];
991
1020
  installLaneHooks(
992
1021
  laneRead,
993
1022
  laneStage,
@@ -1350,7 +1379,7 @@ function verdictValue(el, c) {
1350
1379
  // verdict read as on any untracked read; the pending throw they add for a
1351
1380
  // plain read does not apply — a verdict read is a sanctioned read of a
1352
1381
  // pending node (an uninitialized one still throws NotReady below).
1353
- if (strictRead)
1382
+ if (strictRead && strictReadActive())
1354
1383
  warnStrictReadUntracked(strictRead, {
1355
1384
  ownerId: c?.id,
1356
1385
  ownerName: c?._name,
@@ -1505,6 +1534,14 @@ function inTransaction(t, q, fn) {
1505
1534
  * yield until(() => todos.some(t => t.id === saved.id));
1506
1535
  * ```
1507
1536
  *
1537
+ * The same holds inside an async helper the action yields
1538
+ * (`yield (async () => { ... })()`): everything after its first `await` runs
1539
+ * outside the transaction. An `await refresh(x)` or `await until(...)` there
1540
+ * waits for this action's commit, which waits on the helper, so it hangs
1541
+ * whenever `x` re-ran during the action (#4263) and works otherwise. Call
1542
+ * `refresh(x)` in the generator once the helper returns; `yield` it only when
1543
+ * the action needs the fresh value.
1544
+ *
1508
1545
  * (For the same reason, don't call `flush()` inside an action body — it
1509
1546
  * cannot reveal the held writes and is refused.)
1510
1547
  *
@@ -1583,21 +1620,23 @@ function action(genFn) {
1583
1620
  // it may have been called from — a root created in it (`until`,
1584
1621
  // `resolve`) is deliberate (PRIMITIVE_IN_EFFECT_CALLBACK is off).
1585
1622
  let effectCallback = false;
1623
+ let effectEvent;
1586
1624
  {
1587
1625
  enterCallback();
1588
1626
  effectCallback = inEffectCallback;
1627
+ effectEvent = effectCallbackEvent;
1589
1628
  setEffectCallback(false);
1590
1629
  }
1591
1630
  try {
1592
1631
  r = err ? it.throw(v) : it.next(v);
1593
1632
  } catch (e) {
1594
1633
  exitAction();
1595
- (exitCallback(), setEffectCallback(effectCallback));
1634
+ (exitCallback(), setEffectCallback(effectCallback, effectEvent));
1596
1635
  if (attrHooks !== null) attrHooks.actionStepEnd(it);
1597
1636
  return done(undefined, e, true);
1598
1637
  }
1599
1638
  exitAction();
1600
- (exitCallback(), setEffectCallback(effectCallback));
1639
+ (exitCallback(), setEffectCallback(effectCallback, effectEvent));
1601
1640
  if (attrHooks !== null) attrHooks.actionStepEnd(it);
1602
1641
  // A rejected iterator result (async generators) means the error already
1603
1642
  // escaped the generator body — it is completed, and throwing back in
@@ -1887,10 +1926,9 @@ function effect(compute, effect, error, options) {
1887
1926
  );
1888
1927
  }
1889
1928
  }
1890
- function notifyEffectStatus(status, error) {
1891
- // Use passed values if provided, otherwise read from node
1892
- const actualStatus = status !== undefined ? status : this._statusFlags;
1893
- const actualError = error !== undefined ? error : this._x?._error;
1929
+ function notifyEffectStatus() {
1930
+ const actualStatus = this._statusFlags;
1931
+ const actualError = this._x?._error;
1894
1932
  if (actualStatus & STATUS_ERROR) {
1895
1933
  globalQueue.notify(this, STATUS_PENDING, 0);
1896
1934
  if (this._type === EFFECT_USER) {
@@ -1898,24 +1936,22 @@ function notifyEffectStatus(status, error) {
1898
1936
  // queue it like the effect function. It runs in the same imperative,
1899
1937
  // writable scope and throws escalate the same way. No payload is queued — the node already carries `_statusFlags`/`_error`,
1900
1938
  // and the runner dispatches on them, so a recovery before the effect
1901
- // phase takes the success arm instead. Blocked forwards (explicit
1902
- // `status` arg without node-state writes) don't queue: the status
1903
- // re-propagates unblocked at commit.
1939
+ // phase takes the success arm instead. (Re-read: the boundary
1940
+ // notify above can run a redraw.)
1904
1941
  if (this._statusFlags & STATUS_ERROR) {
1905
1942
  this._modified = true;
1906
1943
  globalQueue.enqueue(this._type, (this._boundRunEffect ??= runEffect.bind(null, this)));
1907
1944
  }
1908
1945
  return;
1909
1946
  }
1910
- if (!globalQueue.notify(this, STATUS_ERROR, STATUS_ERROR)) {
1911
- // Dev locates the halt at the node that threw: the StatusError's
1912
- // source, upstream of this effect when a computed failed.
1913
- haltReactivityAt(
1914
- unwrapStatusError(actualError),
1915
- actualError instanceof StatusError ? actualError.source : this
1916
- );
1917
- throw actualError;
1918
- }
1947
+ // Dev locates the halt at the node that threw: the StatusError's
1948
+ // source, upstream of this effect when a computed failed.
1949
+ escalate(
1950
+ this,
1951
+ unwrapStatusError(actualError),
1952
+ actualError,
1953
+ actualError instanceof StatusError ? actualError.source : this
1954
+ );
1919
1955
  } else if (this._type === EFFECT_RENDER) {
1920
1956
  globalQueue.notify(this, STATUS_PENDING | STATUS_ERROR, actualStatus, actualError);
1921
1957
  if (_hitUnhandledAsync && resetUnhandledAsync()) {
@@ -1944,6 +1980,14 @@ function notifyEffectStatus(status, error) {
1944
1980
  }
1945
1981
  }
1946
1982
  }
1983
+ /** An effect's error no boundary takes halts the system (boundary-or-halt):
1984
+ * halted with `cause`, `thrown` rethrown; dev locates it at `subject`. */
1985
+ function escalate(node, cause, thrown = cause, subject = node) {
1986
+ if (!globalQueue.notify(node, STATUS_ERROR, STATUS_ERROR)) {
1987
+ haltReactivityAt(cause, subject);
1988
+ throw thrown;
1989
+ }
1990
+ }
1947
1991
  function runEffect(node, type) {
1948
1992
  if (!node._modified || node._flags & REACTIVE_DISPOSED) return;
1949
1993
  // A queued run behind a fallback (boundaries.ts) waits for the reveal: a
@@ -1978,10 +2022,7 @@ function runEffect(node, type) {
1978
2022
  })
1979
2023
  : console.error(err);
1980
2024
  } catch (error) {
1981
- if (!globalQueue.notify(node, STATUS_ERROR, STATUS_ERROR)) {
1982
- haltReactivityAt(error, node);
1983
- throw error;
1984
- }
2025
+ escalate(node, error);
1985
2026
  }
1986
2027
  return;
1987
2028
  }
@@ -1989,9 +2030,13 @@ function runEffect(node, type) {
1989
2030
  // the compute pass that produced `_value` was clean, so its tail still goes.
1990
2031
  const cleanPass = node._x?._error == null;
1991
2032
  let prevStrictRead = false;
2033
+ let prevStrictReadEvent;
1992
2034
  {
1993
- prevStrictRead = setStrictRead("an effect callback");
1994
- setEffectCallback(true);
2035
+ prevStrictReadEvent = strictReadEvent;
2036
+ // A run outside a queue (a render effect's first run at creation) records
2037
+ // no event: its callback is the compiler's DOM writes.
2038
+ prevStrictRead = setStrictRead("an effect callback", queueEvent);
2039
+ setEffectCallback(true, queueEvent);
1995
2040
  enterCallback();
1996
2041
  }
1997
2042
  // Observe tier, like its `effectRunEnd` twin below: the frame the engine
@@ -2014,13 +2059,10 @@ function runEffect(node, type) {
2014
2059
  } catch (error) {
2015
2060
  ext(node)._error = new StatusError(node, error);
2016
2061
  node._statusFlags |= STATUS_ERROR;
2017
- if (!globalQueue.notify(node, STATUS_ERROR, STATUS_ERROR)) {
2018
- haltReactivityAt(error, node);
2019
- throw error;
2020
- }
2062
+ escalate(node, error);
2021
2063
  } finally {
2022
2064
  {
2023
- setStrictRead(prevStrictRead);
2065
+ setStrictRead(prevStrictRead, prevStrictReadEvent);
2024
2066
  setEffectCallback(false);
2025
2067
  exitCallback();
2026
2068
  }
@@ -2210,9 +2252,6 @@ function lookupTarget(raw, fam) {
2210
2252
  const owner = raw[$OWNER];
2211
2253
  return owner !== undefined && owner.fam === fam ? owner : (fam?.map ?? storeLookup).get(raw);
2212
2254
  }
2213
- function devAssertNeverUserMutation(target) {
2214
- return;
2215
- }
2216
2255
  /** Sticky descendants flag up the parent chain (§6d): adoption descends
2217
2256
  * into a changed child pair only where a target with nodes exists below. */
2218
2257
  function markDescendants(target) {
@@ -3086,6 +3125,65 @@ function onSettled(callback) {
3086
3125
  }
3087
3126
  });
3088
3127
  }
3128
+ /**
3129
+ * Schedules `callback` to run **once**, right after the pending flush
3130
+ * finishes, in the same microtask checkpoint (before the browser paints).
3131
+ * Unlike `onSettled`, it never waits on async work: if that flush is held by
3132
+ * a pending transition, the callback still runs.
3133
+ *
3134
+ * Its main use is keeping a write out of another update's hold. Writes made
3135
+ * in the same synchronous frame are applied together, so when one of them
3136
+ * starts async work (a route change, a fetch), the others are held with it
3137
+ * until it resolves. A write made in `onFlush` happens after that flush, so
3138
+ * it is its own update: it commits on its own unless something it changes
3139
+ * also derives from the held work (a memo or `createEffect` reading both),
3140
+ * in which case it joins that hold as any later write would.
3141
+ *
3142
+ * Call it from event handlers and effect callbacks (including
3143
+ * `createTrackedEffect` and `onSettled` bodies). In an owned scope such as a
3144
+ * component body or a computation it warns in dev: use `onSettled` there.
3145
+ *
3146
+ * - A write in the callback lands after any write made inline in the same
3147
+ * frame. Don't write the same signal both inside and outside it; use a
3148
+ * functional setter if the order matters.
3149
+ * - Several `onFlush` callbacks run in call order and batch into one flush.
3150
+ * - `queueMicrotask` is not a substitute: called before the frame's first
3151
+ * write, it runs before that flush and its write joins it.
3152
+ *
3153
+ * @example
3154
+ * ```tsx
3155
+ * // A menu closes now even when the consumer's onSelect starts async work.
3156
+ * const select = (id: string) => {
3157
+ * onFlush(() => setOpen(false));
3158
+ * props.onSelect?.(id);
3159
+ * };
3160
+ * ```
3161
+ *
3162
+ * @param callback Function to run once after the pending flush
3163
+ */
3164
+ function onFlush(callback) {
3165
+ {
3166
+ const owner = getOwner();
3167
+ if (owner && !(owner._config & CONFIG_CHILDREN_FORBIDDEN)) {
3168
+ const message =
3169
+ "[ON_FLUSH_IN_OWNED_SCOPE] onFlush called in an owned scope (a component body or a computation). It is meant for event handlers and effect callbacks; to run work after this scope's first stable render, use onSettled.";
3170
+ reportDiagnostic(
3171
+ emitDiagnostic({
3172
+ code: "ON_FLUSH_IN_OWNED_SCOPE",
3173
+ kind: "lifecycle",
3174
+ severity: "warn",
3175
+ message,
3176
+ ownerId: owner.id,
3177
+ ownerName: owner._name
3178
+ })
3179
+ );
3180
+ }
3181
+ }
3182
+ // The frame's flush is queued (or already draining) ahead of this
3183
+ // microtask, so the callback's writes start a flush of their own.
3184
+ schedule();
3185
+ queueMicrotask(callback);
3186
+ }
3089
3187
 
3090
3188
  function mapArray(list, map, options) {
3091
3189
  const keyFn = typeof options?.keyed === "function" ? options.keyed : undefined;
@@ -3714,6 +3812,9 @@ function compare(key, a, b) {
3714
3812
  return key ? key(a) === key(b) : true;
3715
3813
  }
3716
3814
 
3815
+ // Set before any family is optimistic (`fam.opt`): a call site checks the
3816
+ // hooks first, so a build that does not import optimistic.ts folds the arm
3817
+ // away.
3717
3818
  let optHooks = null;
3718
3819
  function installOptHooks(hooks) {
3719
3820
  optHooks = hooks;
@@ -3835,7 +3936,7 @@ function wrap(value, parent = null, parentKey = null, fam = parent?.fam ?? null)
3835
3936
  const existing = lookupTarget(value, fam);
3836
3937
  if (existing !== undefined) return existing.px;
3837
3938
  const t = value[$TARGET];
3838
- if (t !== undefined && t.px === value) {
3939
+ if (t?.px === value) {
3839
3940
  // Foreign-family proxies re-wrap into THIS family (writes stay isolated);
3840
3941
  // same-family and plain-store proxies pass through.
3841
3942
  if (fam === null || t.fam === fam) return value;
@@ -3907,7 +4008,7 @@ function asBacking(v) {
3907
4008
  * pending backing is never "equal" to the committed one); a lane value is
3908
4009
  * an arrangement (`LaneView`), judged by the optimistic machinery. */
3909
4010
  const containerEquals = function (a, b) {
3910
- return a === b || (optHooks !== null && optHooks.arrangement(this._host, a, b));
4011
+ return a === b || !!optHooks?.arrangement(this._host, a, b);
3911
4012
  };
3912
4013
  function sameLogicalSlot(target, a, b) {
3913
4014
  if (a === null || typeof a !== "object" || b === null || typeof b !== "object") return false;
@@ -3924,10 +4025,10 @@ function releaseSlot(node) {
3924
4025
  if (t.k === node) t.k = null;
3925
4026
  } else if (key === $DEEP) {
3926
4027
  if (t.dk === node) t.dk = null;
3927
- } else if (t.n !== null && t.n[key] === node) {
4028
+ } else if (t.n?.[key] === node) {
3928
4029
  delete t.n[key];
3929
4030
  t.nc--;
3930
- } else if (t.h !== null && t.h[key] === node) delete t.h[key];
4031
+ } else if (t.h?.[key] === node) delete t.h[key];
3931
4032
  // The family's index keeps only targets that still carry a node.
3932
4033
  if (
3933
4034
  t.fam !== null &&
@@ -4008,12 +4109,7 @@ function getNode(
4008
4109
  accKnown === -1 ? isOwnAccessor(pb ?? target.v, key) : accKnown === 1
4009
4110
  ));
4010
4111
  if (pb !== null && !created.acc)
4011
- bornStaged(
4012
- target,
4013
- created,
4014
- target.del !== null && target.del.has(key) ? undefined : pb[key],
4015
- key
4016
- );
4112
+ bornStaged(target, created, target.del?.has(key) ? undefined : pb[key], key);
4017
4113
  // Attribution-only: name store property nodes by path segment so
4018
4114
  // attribution chains and wide-scope warnings read "store.todos" (or
4019
4115
  // "todos.title" when the store was declared with a name), not
@@ -4038,8 +4134,7 @@ function getHasNode(target, key) {
4038
4134
  if (node === undefined) {
4039
4135
  const created = (node = slotSignal(key in committed(target), isEqual, target, key, false));
4040
4136
  const pb = target.pb;
4041
- if (pb !== null)
4042
- bornStaged(target, created, key in pb && !(target.del !== null && target.del.has(key)), key);
4137
+ if (pb !== null) bornStaged(target, created, key in pb && !target.del?.has(key), key);
4043
4138
  if (attrHooks !== null) stampNodeOwner(created, target);
4044
4139
  nodes[key] = node;
4045
4140
  noteNode(target, created, key);
@@ -4213,7 +4308,7 @@ function scanAccessorsOnce(target) {
4213
4308
  const keys = Reflect.ownKeys(src);
4214
4309
  let plain = Object.getPrototypeOf(src) === Object.prototype;
4215
4310
  for (const key of keys) {
4216
- if (lookupGetter.call(src, key) !== undefined || lookupSetter.call(src, key) !== undefined) {
4311
+ if (isOwnAccessor(src, key)) {
4217
4312
  target.a = true;
4218
4313
  plain = false;
4219
4314
  break;
@@ -4238,7 +4333,7 @@ const plainProto = o => {
4238
4333
  * container at flatten so its shape stays fast (#3689). */
4239
4334
  function overlayRebuilds(t, pb) {
4240
4335
  if (t.kc > OVERLAY_REBUILD_MAX_KEYS) return false;
4241
- if (t.del !== null && t.del.size !== 0) return true;
4336
+ if (t.del?.size) return true;
4242
4337
  const wk = t.wk;
4243
4338
  if (wk !== null && wk !== WK_ALL && wk.u === undefined && wk.size <= OVERLAY_REBUILD_MIN_ADDS)
4244
4339
  return false;
@@ -4252,18 +4347,14 @@ function overlayRebuilds(t, pb) {
4252
4347
  function materializePB(target) {
4253
4348
  const pb = target.pb;
4254
4349
  if (!target.ovl) return pb;
4255
- const clone = target.sc === 2 ? wideClone(target.v, target) : cloneRaw(target.v, target);
4256
- if (target.del !== null) {
4257
- for (const key of target.del) delete clone[key];
4258
- target.kc -= target.del.size;
4259
- target.del = null;
4260
- }
4261
- for (const key of Reflect.ownKeys(pb))
4262
- target.sc === 2 ? (clone[key] = pb[key]) : copyOwn(clone, pb, key);
4350
+ const clone = overlayOnto(
4351
+ target,
4352
+ target.sc === 2 ? wideClone(target.v, target) : cloneRaw(target.v, target),
4353
+ pb
4354
+ );
4263
4355
  target.pb = clone;
4264
- target.ovl = false;
4265
4356
  // The staging IS the pending backing: re-point the container node.
4266
- if (target.k !== null && target.k._pendingValue === pb) target.k._pendingValue = clone;
4357
+ if (target.k?._pendingValue === pb) target.k._pendingValue = clone;
4267
4358
  return clone;
4268
4359
  }
4269
4360
  /** The batch's pending backing, created on the first draft write: an overlay
@@ -4275,7 +4366,7 @@ function ensurePB(target) {
4275
4366
  // An optimistic edit composes on the view the user saw and becomes
4276
4367
  // guesses at the setter's exit: nothing is staged, nothing folds
4277
4368
  // (optimistic.ts).
4278
- if (userWrite && target.fam?.opt === true && !pendingNotify.has(target))
4369
+ if (optHooks && userWrite && target.fam?.opt === true && !pendingNotify.has(target))
4279
4370
  return (target.pb = optHooks.draft(target));
4280
4371
  if (pb !== null && !isOwned(pb)) {
4281
4372
  // An adopted object awaiting its fold is the user's: the draft writes a
@@ -4315,7 +4406,6 @@ function privatizeCommitted(target) {
4315
4406
  target.ch = false;
4316
4407
  if (target.u) {
4317
4408
  privatizeCommitted(target.u);
4318
- devAssertNeverUserMutation(target.u.v);
4319
4409
  const pv = target.u.v;
4320
4410
  const slot = parentSlotKey(target, before);
4321
4411
  if (pv[slot] === before) pv[slot] = target.v;
@@ -4339,16 +4429,19 @@ function parentSlotKey(target, expected) {
4339
4429
  * place — the backing keeps its identity. */
4340
4430
  function flattenOverlay(t, pb) {
4341
4431
  privatizeCommitted(t);
4342
- const v = t.v;
4343
- for (const key of Reflect.ownKeys(pb)) t.sc === 2 ? (v[key] = pb[key]) : copyOwn(v, pb, key);
4432
+ overlayOnto(t, t.v, pb); // the caller (`foldTarget`) closes the staging
4433
+ }
4434
+ /** An overlay's own keys and deletions onto `to` (the two are disjoint: a
4435
+ * write un-deletes its key); the overlay is closed. */
4436
+ function overlayOnto(t, to, pb) {
4437
+ for (const key of Reflect.ownKeys(pb)) t.sc === 2 ? (to[key] = pb[key]) : copyOwn(to, pb, key);
4344
4438
  if (t.del !== null) {
4345
- for (const key of t.del) delete v[key];
4439
+ for (const key of t.del) delete to[key];
4346
4440
  t.kc -= t.del.size;
4347
4441
  t.del = null;
4348
4442
  }
4349
- t.pb = null;
4350
4443
  t.ovl = false;
4351
- t.wk = null; // written-keys window closes with the commit
4444
+ return to;
4352
4445
  }
4353
4446
  /** Adoption (a setter's returned replacement; reconcile and projection
4354
4447
  * landings in S3): the incoming object becomes the backing by reference at
@@ -4387,7 +4480,7 @@ function adoptPB(target, incoming, notify = true) {
4387
4480
  target.a = false;
4388
4481
  target.wk = null; // adoption supersedes staged trap writes
4389
4482
  const owner = incoming[$OWNER];
4390
- if (owner !== undefined && owner.fam === target.fam) incoming[$OWNER] = target;
4483
+ if (owner?.fam === target.fam) incoming[$OWNER] = target;
4391
4484
  else setLookup(incoming, target);
4392
4485
  if (notify && base !== incoming) notifyFold(target, base, incoming);
4393
4486
  }
@@ -4484,7 +4577,7 @@ function drainFolds() {
4484
4577
  * landing) read it. */
4485
4578
  GlobalQueue._slotFlight = n => {
4486
4579
  const fam = n._host?.fam;
4487
- return fam != null && fam.node !== null && (fam.node._statusFlags & STATUS_PENDING) !== 0;
4580
+ return (fam?.node?._statusFlags & STATUS_PENDING) !== 0;
4488
4581
  };
4489
4582
  /** Core asks (lanes.ts `covered`): the value a slot node's guess covered.
4490
4583
  * A chained link's committed value is the inner store's at the last
@@ -4495,8 +4588,8 @@ GlobalQueue._slotCovered = n => {
4495
4588
  if (t.ch) {
4496
4589
  const key = n._key;
4497
4590
  const inner = t.v;
4498
- if (t.h !== null && t.h[key] === n) n._value = untrack(() => key in inner);
4499
- else if (t.n !== null && t.n[key] === n) n._value = untrack(() => unwrapValue(inner[key]));
4591
+ if (t.h?.[key] === n) n._value = untrack(() => key in inner);
4592
+ else if (t.n?.[key] === n) n._value = untrack(() => unwrapValue(inner[key]));
4500
4593
  }
4501
4594
  return n._value;
4502
4595
  };
@@ -4529,12 +4622,8 @@ function foldTarget(t, old) {
4529
4622
  const k = t.k;
4530
4623
  const pb = t.pb;
4531
4624
  if (pb !== null) {
4532
- if (pb === t.v) {
4533
- // An eager adoption nothing wrote after: the backing is already
4534
- // the adopted object (shared ownership — never cloned).
4535
- t.pb = null;
4536
- t.wk = null;
4537
- } else if (
4625
+ if (pb === t.v);
4626
+ else if (
4538
4627
  t.ovl &&
4539
4628
  (t.v !== old || (!overlayRebuilds(t, pb) && k?._x?._snapshotValue !== old))
4540
4629
  ) {
@@ -4557,26 +4646,19 @@ function foldTarget(t, old) {
4557
4646
  else delete v[key];
4558
4647
  }
4559
4648
  } else {
4560
- for (const key of Reflect.ownKeys(pb)) {
4561
- const d = Object.getOwnPropertyDescriptor(pb, key);
4562
- if (d.get || d.set || !d.enumerable || !d.writable || !d.configurable)
4563
- Object.defineProperty(v, key, d);
4564
- else if (d.value !== old[key] || !hasOwn$1.call(old, key)) v[key] = d.value;
4565
- }
4649
+ for (const key of Reflect.ownKeys(pb)) if (key !== $OWNER) copyOwn(v, pb, key);
4566
4650
  for (const key of Reflect.ownKeys(old)) {
4567
- if (!hasOwn$1.call(pb, key)) delete v[key];
4651
+ if (key !== $OWNER && !hasOwn$1.call(pb, key)) delete v[key];
4568
4652
  }
4569
4653
  }
4570
- t.pb = null;
4571
- t.wk = null;
4572
4654
  } else {
4573
4655
  // The clone (or adopted object) becomes the committed backing
4574
4656
  // (path copy below).
4575
4657
  t.v = t.ovl ? materializePB(t) : pb;
4576
4658
  t.ch = t.v[$TARGET] !== undefined;
4577
- t.pb = null;
4578
- t.wk = null;
4579
4659
  }
4660
+ t.pb = null;
4661
+ t.wk = null; // the written-keys window closes with the commit
4580
4662
  }
4581
4663
  if (k !== null) {
4582
4664
  // The node's committed value is the backing. A container nobody
@@ -4591,7 +4673,6 @@ function foldTarget(t, old) {
4591
4673
  const slot = parentSlotKey(t, old);
4592
4674
  if (t.u.v[slot] === old) {
4593
4675
  privatizeCommitted(t.u);
4594
- devAssertNeverUserMutation(t.u.v);
4595
4676
  t.u.v[slot] = t.v;
4596
4677
  }
4597
4678
  }
@@ -4617,9 +4698,12 @@ function reportReplacedContainers(t, old, pb, writtenKeys) {
4617
4698
  const keys = writtenKeys ?? Reflect.ownKeys(pb);
4618
4699
  const isArray = Array.isArray(pb);
4619
4700
  const owner = storeRootOwner(t);
4701
+ // Items the array held before the write: an index that now holds one of
4702
+ // them was moved into place (an in-place splice / shift), not replaced.
4703
+ let oldArrayItems = null;
4620
4704
  for (const key of keys) {
4621
4705
  if ((isArray && key === "length") || key === $OWNER) continue;
4622
- if (t.del !== null && t.del.has(key)) continue;
4706
+ if (t.del?.has(key)) continue;
4623
4707
  const ov = unwrapValue(old[key]);
4624
4708
  const nv = unwrapValue(pb[key]);
4625
4709
  if (
@@ -4631,6 +4715,13 @@ function reportReplacedContainers(t, old, pb, writtenKeys) {
4631
4715
  targetsEqual(ov, nv)
4632
4716
  )
4633
4717
  continue;
4718
+ if (isArray) {
4719
+ if (oldArrayItems === null) {
4720
+ oldArrayItems = new Set();
4721
+ for (const item of old) oldArrayItems.add(unwrapValue(item));
4722
+ }
4723
+ if (oldArrayItems.has(nv)) continue;
4724
+ }
4634
4725
  const isArr = Array.isArray(nv);
4635
4726
  if (isArr !== Array.isArray(ov)) continue;
4636
4727
  let total;
@@ -4692,7 +4783,7 @@ function notifyWrites(t) {
4692
4783
  // Optimistic channel: a user's writes on an optimistic family become
4693
4784
  // guesses on the nodes (lanes); the draft is discarded, the committed
4694
4785
  // backing never touched.
4695
- if (userWrite && t.fam?.opt === true) {
4786
+ if (optHooks && userWrite && t.fam?.opt === true) {
4696
4787
  // The draft is discarded first: the diff reads the target (its nodes
4697
4788
  // are born from the committed frame, not the draft).
4698
4789
  t.pb = null;
@@ -4767,7 +4858,7 @@ function notifyWrites(t) {
4767
4858
  if (od?.get || od?.set || !isEqual(od?.value, nd.value)) setSignal(node, () => nd.value);
4768
4859
  continue;
4769
4860
  }
4770
- const nv = t.del !== null && t.del.has(key) ? undefined : pb[key];
4861
+ const nv = t.del?.has(key) ? undefined : pb[key];
4771
4862
  setSignal(node, () => nv);
4772
4863
  if (t.fam !== null) {
4773
4864
  // The derive's write is a derivation; a user setter's is a proposal
@@ -4785,13 +4876,13 @@ function notifyWrites(t) {
4785
4876
  for (const key of keys) {
4786
4877
  const node = has[key];
4787
4878
  if (node !== undefined) {
4788
- setSignal(node, key in pb && !(t.del !== null && t.del.has(key)));
4879
+ setSignal(node, key in pb && !t.del?.has(key));
4789
4880
  if (t.fam !== null) holdWithDerive(t, node);
4790
4881
  }
4791
4882
  }
4792
4883
  }
4793
4884
  if (t.dk !== null) {
4794
- if (t.del !== null && t.del.size !== 0) bumpDeep(t);
4885
+ if (t.del?.size) bumpDeep(t);
4795
4886
  else
4796
4887
  for (const key of writtenKeys ?? Reflect.ownKeys(pb)) {
4797
4888
  if (key === $OWNER) continue;
@@ -4811,7 +4902,7 @@ function notifyWrites(t) {
4811
4902
  if (k !== null && (k._subs !== null || k._config & CONFIG_OVERRIDE)) {
4812
4903
  let changed;
4813
4904
  if (t.ovl) {
4814
- changed = t.del !== null && t.del.size !== 0;
4905
+ changed = !!t.del?.size;
4815
4906
  if (!changed) {
4816
4907
  for (const key of Reflect.ownKeys(pb)) {
4817
4908
  if (!hasOwn$1.call(old, key)) {
@@ -4966,7 +5057,7 @@ function serveShallow(target, key, v) {
4966
5057
  function draftServe(target, proxy) {
4967
5058
  if (writeScopes !== null && inDraft(target)) {
4968
5059
  const ct = proxy?.[$TARGET];
4969
- if (ct !== undefined && ct.v !== undefined) writeScopes.add(scopeKey(ct));
5060
+ if (ct?.v !== undefined) writeScopes.add(scopeKey(ct));
4970
5061
  }
4971
5062
  return proxy;
4972
5063
  }
@@ -5009,13 +5100,7 @@ function readSource(target, key, shape = false) {
5009
5100
  // sees — the lanes' values and the tick's own guesses over the truth
5010
5101
  // (#3665), never a staging awaiting its fold: the draft is born on the
5011
5102
  // read (and notified at the setter's exit like any).
5012
- if (
5013
- userWrite &&
5014
- target.fam !== null &&
5015
- target.fam.opt === true &&
5016
- !pendingNotify.has(target) &&
5017
- inDraft(target)
5018
- ) {
5103
+ if (userWrite && target.fam?.opt === true && !pendingNotify.has(target) && inDraft(target)) {
5019
5104
  const draft = ensurePB(target);
5020
5105
  pendingNotify.add(target);
5021
5106
  return draft;
@@ -5054,7 +5139,7 @@ function readSource(target, key, shape = false) {
5054
5139
  // it).
5055
5140
  if (key !== undefined) {
5056
5141
  const leaf = target.n?.[key];
5057
- if (leaf !== undefined && leaf._config & CONFIG_OVERRIDE) return committed(target);
5142
+ if (leaf?._config & CONFIG_OVERRIDE) return committed(target);
5058
5143
  }
5059
5144
  const held = k !== null && (k._config & CONFIG_HELD) !== 0;
5060
5145
  // A held container: the hold's unit is the key (#3706). A key the HELD
@@ -5168,18 +5253,14 @@ function adoptChild(target, key) {
5168
5253
  // A raw-marked value wraps to itself, a proxy to its own target: neither
5169
5254
  // is a fresh target around `ov`.
5170
5255
  const ct = wrap(ov, target, key)[$TARGET];
5171
- if (ct === undefined || ct.v !== ov) return false;
5256
+ if (ct?.v !== ov) return false;
5172
5257
  adoptPB(ct, nv, false);
5173
5258
  holdNode(getContainerNode(ct), txOf(k));
5174
5259
  return true;
5175
5260
  }
5176
5261
  /** A family target's unheld staging, read with no pass (a handler). */
5177
5262
  function familyAhead(target) {
5178
- return (
5179
- target.fam !== null &&
5180
- context === null &&
5181
- !(target.k !== null && target.k._config & CONFIG_HELD)
5182
- );
5263
+ return target.fam !== null && context === null && !(target.k?._config & CONFIG_HELD);
5183
5264
  }
5184
5265
  /** Did this batch change `key` — value (slot equality), presence, and (for
5185
5266
  * a `shape` reader — a descriptor read, a node's hold) enumerability or
@@ -5204,7 +5285,7 @@ function changedBetween(target, v, pb, key, shape) {
5204
5285
  !plainProto(v)
5205
5286
  )
5206
5287
  return true;
5207
- if (target.del !== null && target.del.has(key)) return key in v;
5288
+ if (target.del?.has(key)) return key in v;
5208
5289
  const inNew = hasOwn$1.call(pb, key) || (target.ovl && hasOwn$1.call(target.v, key));
5209
5290
  if (inNew !== hasOwn$1.call(v, key)) return true;
5210
5291
  if (!inNew) return false;
@@ -5376,16 +5457,16 @@ function enumerationSource(target) {
5376
5457
  // A container's arrangement guess is not a backing: the view composes
5377
5458
  // over the base it is shown over.
5378
5459
  const base = asBacking(read(getContainerNode(target)));
5379
- return target.fam?.opt === true ? optHooks.view(target, base) : base;
5460
+ return optHooks && target.fam?.opt === true ? optHooks.view(target, base) : base;
5380
5461
  }
5381
5462
  const src = readSource(target);
5382
- return optRead(target) ? optHooks.view(target, src) : src;
5463
+ return optHooks && optRead(target) ? optHooks.view(target, src) : src;
5383
5464
  }
5384
5465
  /** A read on an optimistic family that composes the lanes' values: not the
5385
5466
  * draft's (it composed already), not a derive's write-override read (the
5386
5467
  * derive composes on the truth). */
5387
5468
  function optRead(target) {
5388
- return target.fam !== null && target.fam.opt === true && !inDraft(target) && !writeOverride;
5469
+ return target.fam?.opt === true && !inDraft(target) && !writeOverride;
5389
5470
  }
5390
5471
  /** Pollution keys are never served from the prototype (core R30). */
5391
5472
  const UNSAFE_KEYS = new Set(["__proto__", "prototype", "constructor"]);
@@ -5511,7 +5592,7 @@ const traps = {
5511
5592
  const src = readSource(target, key);
5512
5593
  // Overlay delete (#3044): a prototype overlay cannot shadow a delete, so
5513
5594
  // deleted keys are tracked aside and read as absent in the pending view.
5514
- if (target.del !== null && src === target.pb && target.del.has(key)) {
5595
+ if (src === target.pb && target.del?.has(key)) {
5515
5596
  if (!inDraft(target) && getObserver() !== null) read(getNode(target, key));
5516
5597
  return undefined;
5517
5598
  }
@@ -5539,7 +5620,8 @@ const traps = {
5539
5620
  !inDraft(target) &&
5540
5621
  typeof key === "string" &&
5541
5622
  key !== "then" &&
5542
- getObserver() === null
5623
+ getObserver() === null &&
5624
+ strictReadActive()
5543
5625
  ) {
5544
5626
  warnStrictReadUntracked(strictRead, {
5545
5627
  nodeName: key,
@@ -5638,12 +5720,12 @@ const traps = {
5638
5720
  node._value = present; // the link follows the inner store (S4)
5639
5721
  if (node._config & CONFIG_OVERRIDE) return !!nv;
5640
5722
  return present;
5641
- } else if (optRead(target)) {
5723
+ } else if (optHooks && optRead(target)) {
5642
5724
  const guessed = optHooks.has(target, key);
5643
5725
  if (guessed !== undefined) return guessed;
5644
5726
  }
5645
5727
  }
5646
- return key in src && !(target.del !== null && src === target.pb && target.del.has(key));
5728
+ return key in src && !(src === target.pb && target.del?.has(key));
5647
5729
  },
5648
5730
  ownKeys(target) {
5649
5731
  if (verdict !== null && affectsHooks !== null && getObserver() === null)
@@ -5678,7 +5760,7 @@ const traps = {
5678
5760
  return undefined;
5679
5761
  } else src = readSource(target, key, true);
5680
5762
  let desc;
5681
- if (!enumerating && optRead(target)) {
5763
+ if (optHooks && !enumerating && optRead(target)) {
5682
5764
  const guessed = optHooks.descriptor(target, key);
5683
5765
  if (guessed === null) return undefined;
5684
5766
  desc = guessed;
@@ -5690,8 +5772,7 @@ const traps = {
5690
5772
  },
5691
5773
  set(target, key, value) {
5692
5774
  const draft = inDraft(target);
5693
- const override = !draft && writeOverride;
5694
- if (!draft && !override) {
5775
+ if (!draft && !writeOverride) {
5695
5776
  warnWriteOutsideSetter(target, key, "set");
5696
5777
  return true;
5697
5778
  }
@@ -5717,17 +5798,8 @@ const traps = {
5717
5798
  touch(target, key);
5718
5799
  if (!(key in pb)) target.kc++;
5719
5800
  }
5720
- if (UNSAFE_KEYS.has(key)) {
5721
- Object.defineProperty(pb, key, {
5722
- value: uv,
5723
- writable: true,
5724
- enumerable: true,
5725
- configurable: true
5726
- });
5727
- if (target.del !== null) target.del.delete(key);
5728
- return true;
5729
- }
5730
- if (target.ovl && target.sc !== 2 && !hasOwn$1.call(pb, key)) {
5801
+ const unsafe = UNSAFE_KEYS.has(key);
5802
+ if (unsafe || (target.ovl && target.sc !== 2 && !hasOwn$1.call(pb, key))) {
5731
5803
  Object.defineProperty(pb, key, {
5732
5804
  value: uv,
5733
5805
  writable: true,
@@ -5735,15 +5807,15 @@ const traps = {
5735
5807
  configurable: true
5736
5808
  });
5737
5809
  } else pb[key] = uv;
5738
- if (target.del !== null) target.del.delete(key);
5810
+ target.del?.delete(key);
5811
+ if (unsafe) return true;
5739
5812
  if (target.s && uv !== null && typeof uv === "object") markRawOne(uv);
5740
- if (override) notifyWrites(target);
5813
+ if (!draft) notifyWrites(target); // a write-override write
5741
5814
  return true;
5742
5815
  },
5743
5816
  defineProperty(target, key, desc) {
5744
5817
  const draft = inDraft(target);
5745
- const override = !draft && writeOverride;
5746
- if (!draft && !override) {
5818
+ if (!draft && !writeOverride) {
5747
5819
  warnWriteOutsideSetter(target, key, "defineProperty");
5748
5820
  return true;
5749
5821
  }
@@ -5754,14 +5826,13 @@ const traps = {
5754
5826
  if (target.a || !(desc.enumerable && desc.writable && desc.configurable)) target.sc = 1;
5755
5827
  touch(target, key);
5756
5828
  Object.defineProperty(pb, key, desc);
5757
- if (target.del !== null) target.del.delete(key);
5758
- if (override) notifyWrites(target);
5829
+ target.del?.delete(key);
5830
+ if (!draft) notifyWrites(target); // a write-override write
5759
5831
  return true;
5760
5832
  },
5761
5833
  deleteProperty(target, key) {
5762
5834
  const draft = inDraft(target);
5763
- const override = !draft && writeOverride;
5764
- if (!draft && !override) {
5835
+ if (!draft && !writeOverride) {
5765
5836
  warnWriteOutsideSetter(target, key, "deleteProperty");
5766
5837
  return true;
5767
5838
  }
@@ -5782,7 +5853,7 @@ const traps = {
5782
5853
  committed || target.kc--;
5783
5854
  }
5784
5855
  if (committed) (target.del ??= new Set()).add(key);
5785
- if (override) notifyWrites(target);
5856
+ if (!draft) notifyWrites(target); // a write-override write
5786
5857
  return true;
5787
5858
  }
5788
5859
  };
@@ -5883,7 +5954,7 @@ function storeSetter(proxy, fn, guard = true) {
5883
5954
  // A returned replacement: on an optimistic family a user's is itself an
5884
5955
  // optimistic edit — diffed against the view as guesses (`next` parity);
5885
5956
  // otherwise it is adopted as the incoming truth.
5886
- if (guard && target.fam?.opt === true && !writeOverride)
5957
+ if (optHooks && guard && target.fam?.opt === true && !writeOverride)
5887
5958
  optHooks.writes(target, unwrapValue(result));
5888
5959
  else adoptPB(target, unwrapValue(result));
5889
5960
  }
@@ -5943,17 +6014,17 @@ function createStore$1(initialValue, options) {
5943
6014
  * slot-patch channel (collected row bodies) over per-record registration. */
5944
6015
  function storeIsShallow(proxy) {
5945
6016
  const t = proxy?.[$TARGET];
5946
- return t !== undefined && t.s === true;
6017
+ return t?.s === true;
5947
6018
  }
5948
6019
  /** True when `proxy` belongs to a projection family (S3). */
5949
6020
  function storeHasFamily(proxy) {
5950
6021
  const t = proxy?.[$TARGET];
5951
- return t !== undefined && t.fam !== null;
6022
+ return t?.fam != null;
5952
6023
  }
5953
6024
  /** True when `proxy` belongs to an OPTIMISTIC family (S4). */
5954
6025
  function storeHasOptimisticFamily(proxy) {
5955
6026
  const t = proxy?.[$TARGET];
5956
- return t !== undefined && t.fam?.opt === true;
6027
+ return t?.fam?.opt === true;
5957
6028
  }
5958
6029
  // ---------------------------------------------------------------------------
5959
6030
  // enumeration views (one visibility rule for the traps AND the deep walk)
@@ -5962,7 +6033,7 @@ function visibleKeys(target, src) {
5962
6033
  if (target.ovl && src === target.pb) {
5963
6034
  keys = Reflect.ownKeys(target.v);
5964
6035
  const del = target.del;
5965
- if (del !== null && del.size !== 0) keys = keys.filter(key => !del.has(key));
6036
+ if (del?.size) keys = keys.filter(key => !del.has(key));
5966
6037
  for (const key of Reflect.ownKeys(src)) {
5967
6038
  if (!hasOwn$1.call(target.v, key)) keys.push(key);
5968
6039
  }
@@ -5979,7 +6050,7 @@ function visibleKeys(target, src) {
5979
6050
  function visibleDescriptor(target, src, key) {
5980
6051
  let desc = Object.getOwnPropertyDescriptor(src, key);
5981
6052
  if (target.ovl && src === target.pb) {
5982
- if (target.del !== null && target.del.has(key)) return undefined;
6053
+ if (target.del?.has(key)) return undefined;
5983
6054
  if (desc === undefined) desc = Object.getOwnPropertyDescriptor(target.v, key);
5984
6055
  }
5985
6056
  return desc;
@@ -5990,7 +6061,7 @@ function visibleDescriptor(target, src, key) {
5990
6061
  * witness (one node per record, not one per path), then snapshots. */
5991
6062
  function deep$1(value) {
5992
6063
  const t0 = value?.[$TARGET];
5993
- if (t0 === undefined || t0.px !== value) return value;
6064
+ if (t0?.px !== value) return value;
5994
6065
  const visited = new Set();
5995
6066
  const childTarget = (t, child, key) => {
5996
6067
  let ct = lookupTarget(child, t.fam);
@@ -6063,7 +6134,7 @@ function snapshotWalk(value, seen, fam) {
6063
6134
  if (t.ovl) materializePB(t);
6064
6135
  backing = t.pb;
6065
6136
  }
6066
- if (optRead(t)) backing = optHooks.view(t, backing);
6137
+ if (optHooks && optRead(t)) backing = optHooks.view(t, backing);
6067
6138
  if (backing === src) break;
6068
6139
  src = backing;
6069
6140
  }
@@ -6232,14 +6303,14 @@ const storePath = /* @__PURE__ */ Object.assign(
6232
6303
  */
6233
6304
  function reconcileState(value, state, key, replace = false) {
6234
6305
  if (state == null) throw new Error("Cannot reconcile null or undefined state");
6235
- const t = state?.[$TARGET];
6236
- if (t === undefined || t.px !== state) throw new Error("reconcile target is not a store proxy");
6306
+ const t = state[$TARGET];
6307
+ if (t?.px !== state) throw new Error("reconcile target is not a store proxy");
6237
6308
  const keyFn = key === null ? null : typeof key === "string" ? item => item?.[key] : key;
6238
6309
  // A user's reconcile on an optimistic family (S4): not an adoption of
6239
6310
  // truth but an optimistic edit — the keyed diff is written into the draft
6240
6311
  // (a matched row keeps its proxy, its changed leaves become its guesses),
6241
6312
  // and the setter's exit turns the draft into guesses like any.
6242
- if (userWriting() && t.fam?.opt === true) {
6313
+ if (optHooks && userWriting() && t.fam?.opt === true) {
6243
6314
  optHooks.reconcile(state, unwrapValue(value), keyFn);
6244
6315
  return;
6245
6316
  }
@@ -6258,9 +6329,8 @@ function reconcileState(value, state, key, replace = false) {
6258
6329
  if (!replace) throw new Error("Cannot reconcile states with different identity");
6259
6330
  // A different entity at the root: the old backing is disowned (wraps
6260
6331
  // fresh if re-handed) and the new one adopted whole.
6261
- const out = t.pb ?? t.v;
6262
- if (isOwned(out)) delete out[$OWNER];
6263
- else (t.fam?.map ?? storeLookup).delete(out);
6332
+ if (isOwned(prev)) delete prev[$OWNER];
6333
+ else (t.fam?.map ?? storeLookup).delete(prev);
6264
6334
  adoptPB(t, incoming);
6265
6335
  return;
6266
6336
  }
@@ -6276,50 +6346,42 @@ function applyAdopt(t, incoming, keyFn, proj = false) {
6276
6346
  // as present through its prototype; materialized first — `adoptPB` does
6277
6347
  // so anyway, after the base was taken — so a deletion the draft made is
6278
6348
  // a deletion here (#3743: the presence and value diffs both read `old`).
6279
- const prev = t.ovl ? materializePB(t) : (t.pb ?? t.v);
6280
- if (incoming === prev && !isOwned(prev)) return;
6281
- const fam = t.fam;
6349
+ const old = t.ovl ? materializePB(t) : (t.pb ?? t.v);
6350
+ if (incoming === old && !isOwned(old)) return;
6282
6351
  // Q-D: a landing on an optimistic family is reconciled against the VIEW
6283
6352
  // — key matching from the arrangement the guesses made (R28), so a row
6284
6353
  // the guess added keeps its proxy when the server returns it.
6285
- const prevView = fam?.opt === true ? optHooks.view(t, prev) : prev;
6354
+ const prevView = optHooks && t.fam?.opt === true ? optHooks.view(t, old) : old;
6286
6355
  const nextArr = Array.isArray(incoming);
6287
6356
  const shallow = t.s === true;
6288
- const old = prev;
6289
6357
  adoptPB(t, incoming, false);
6290
6358
  if (shallow) markRawIngest(incoming);
6359
+ // The deep witness is bumped once per adoption (none to bump: done).
6360
+ let bumped = t.dk === null;
6291
6361
  if (Array.isArray(prevView) !== nextArr) {
6292
6362
  notifyFold(t, old, incoming);
6293
6363
  return;
6294
6364
  }
6295
6365
  if (nextArr) {
6296
- const prevRows = prevView;
6297
- const nextRows = incoming;
6298
6366
  const nodes = t.n;
6299
6367
  let nodesHit = 0;
6300
6368
  if (keyFn && !shallow) {
6301
- const plen = prevRows.length;
6302
- const nlen = nextRows.length;
6303
- let dkBumpedA = false;
6369
+ const plen = prevView.length;
6370
+ const nlen = incoming.length;
6304
6371
  let i = 0;
6305
6372
  // Aligned prefix: same row (by reference or key) at the same index.
6306
6373
  for (const end = Math.min(plen, nlen); i < end; i++) {
6307
- const nv = nextRows[i];
6308
- const pvRaw = prevRows[i];
6374
+ const nv = incoming[i];
6375
+ const pvRaw = prevView[i];
6309
6376
  if (pvRaw !== nv && !sameRow$1(pvRaw, nv, keyFn)) break; // misaligned: fall to the keyed remainder below
6377
+ if (nv !== null && typeof nv === "object" && (pvRaw !== nv || isOwned(nv)))
6378
+ descend(unwrapValue(pvRaw), nv, keyFn, proj, t, i);
6310
6379
  if (
6311
- (pvRaw !== nv || (nv !== null && typeof nv === "object" && isOwned(nv))) &&
6312
- nv !== null &&
6313
- typeof nv === "object"
6314
- )
6315
- descend(unwrapValue(pvRaw), nv, keyFn, fam, proj);
6316
- if (
6317
- t.dk !== null &&
6318
- !dkBumpedA &&
6380
+ !bumped &&
6319
6381
  !(nv !== null && typeof nv === "object" ? targetsEqual(pvRaw, nv) : isEqual(pvRaw, nv))
6320
6382
  ) {
6321
6383
  bumpDeep(t);
6322
- dkBumpedA = true;
6384
+ bumped = true;
6323
6385
  }
6324
6386
  if (nodes !== null) {
6325
6387
  const node = nodes[i];
@@ -6329,19 +6391,19 @@ function applyAdopt(t, incoming, keyFn, proj = false) {
6329
6391
  }
6330
6392
  }
6331
6393
  }
6332
- if (t.dk !== null && !dkBumpedA && i < nextRows.length) bumpDeep(t);
6394
+ if (!bumped && i < incoming.length) bumpDeep(t);
6333
6395
  const structStart = i; // misalignment point (== nlen on aligned ticks)
6334
6396
  let prevByKey = null;
6335
- for (; i < nextRows.length; i++) {
6336
- const nv = nextRows[i];
6397
+ for (; i < incoming.length; i++) {
6398
+ const nv = incoming[i];
6337
6399
  if (nv !== null && typeof nv === "object") {
6338
6400
  const nk = keyFn(nv);
6339
6401
  let pv;
6340
6402
  if (nk !== undefined) {
6341
6403
  if (prevByKey === null) {
6342
6404
  prevByKey = new Map();
6343
- for (let j = structStart; j < prevRows.length; j++) {
6344
- const p = unwrapValue(prevRows[j]);
6405
+ for (let j = structStart; j < prevView.length; j++) {
6406
+ const p = unwrapValue(prevView[j]);
6345
6407
  if (p !== null && typeof p === "object") {
6346
6408
  const pk = keyFn(p);
6347
6409
  if (pk === undefined) continue;
@@ -6355,16 +6417,16 @@ function applyAdopt(t, incoming, keyFn, proj = false) {
6355
6417
  const m = prevByKey.get(nk);
6356
6418
  if (m === undefined) pv = undefined;
6357
6419
  else if (Array.isArray(m)) {
6358
- pv = unwrapValue(prevRows[m.shift()]);
6420
+ pv = unwrapValue(prevView[m.shift()]);
6359
6421
  if (m.length === 1) prevByKey.set(nk, m[0]);
6360
6422
  } else {
6361
- pv = unwrapValue(prevRows[m]);
6423
+ pv = unwrapValue(prevView[m]);
6362
6424
  prevByKey.delete(nk);
6363
6425
  }
6364
6426
  } else {
6365
- pv = unwrapValue(prevRows[i]); // keyless item: positional fallback
6427
+ pv = unwrapValue(prevView[i]); // keyless item: positional fallback
6366
6428
  }
6367
- descend(pv, nv, keyFn, fam, proj);
6429
+ descend(pv, nv, keyFn, proj, t, i);
6368
6430
  }
6369
6431
  if (nodes !== null) {
6370
6432
  const node = nodes[i];
@@ -6375,22 +6437,20 @@ function applyAdopt(t, incoming, keyFn, proj = false) {
6375
6437
  }
6376
6438
  }
6377
6439
  } else {
6378
- const dlen = Math.min(prevRows.length, nextRows.length);
6379
- const nlen = nextRows.length;
6380
- let dkBumpedP = false;
6440
+ const dlen = Math.min(prevView.length, incoming.length);
6441
+ const nlen = incoming.length;
6381
6442
  for (let i = 0; i < nlen; i++) {
6382
- const nvP = nextRows[i];
6443
+ const nvP = incoming[i];
6383
6444
  if (!shallow && i < dlen && nvP !== null && typeof nvP === "object")
6384
- descend(unwrapValue(prevRows[i]), nvP, keyFn, fam, proj);
6445
+ descend(unwrapValue(prevView[i]), nvP, keyFn, proj, t, i);
6385
6446
  if (
6386
- t.dk !== null &&
6387
- !dkBumpedP &&
6447
+ !bumped &&
6388
6448
  !(nvP !== null && typeof nvP === "object"
6389
- ? targetsEqual(prevRows[i], nvP)
6390
- : isEqual(prevRows[i], nvP))
6449
+ ? targetsEqual(prevView[i], nvP)
6450
+ : isEqual(prevView[i], nvP))
6391
6451
  ) {
6392
6452
  bumpDeep(t);
6393
- dkBumpedP = true;
6453
+ bumped = true;
6394
6454
  }
6395
6455
  if (nodes !== null) {
6396
6456
  const node = nodes[i];
@@ -6405,7 +6465,7 @@ function applyAdopt(t, incoming, keyFn, proj = false) {
6405
6465
  if (nodes !== null && nodesHit < t.nc) {
6406
6466
  for (const key of Reflect.ownKeys(nodes)) {
6407
6467
  const idx = typeof key === "string" ? +key : NaN;
6408
- if (!(idx >= 0 && idx < nextRows.length))
6468
+ if (!(idx >= 0 && idx < incoming.length))
6409
6469
  notifyKeyDiff(nodes[key], key, old, incoming, false);
6410
6470
  }
6411
6471
  }
@@ -6414,7 +6474,6 @@ function applyAdopt(t, incoming, keyFn, proj = false) {
6414
6474
  }
6415
6475
  const nodes = t.n;
6416
6476
  let nodesHit = 0;
6417
- let dkBumped = false;
6418
6477
  for (const k in incoming) {
6419
6478
  const nv = incoming[k];
6420
6479
  const ov = old[k];
@@ -6424,13 +6483,13 @@ function applyAdopt(t, incoming, keyFn, proj = false) {
6424
6483
  (!isObj || !isOwned(nv)) &&
6425
6484
  (nodes === null || nodes[k] === undefined || !hasAccessorFlag(nodes[k]))
6426
6485
  ) {
6427
- if (nodes !== null && nodes[k] !== undefined) nodesHit++;
6486
+ if (nodes?.[k] !== undefined) nodesHit++;
6428
6487
  continue;
6429
6488
  }
6430
- if (isObj && !shallow) descend(unwrapValue(prevView[k]), nv, keyFn, fam, proj);
6431
- if (t.dk !== null && !dkBumped && !(isObj ? targetsEqual(ov, nv) : isEqual(ov, nv))) {
6489
+ if (isObj && !shallow) descend(unwrapValue(prevView[k]), nv, keyFn, proj, t, k);
6490
+ if (!bumped && !(isObj ? targetsEqual(ov, nv) : isEqual(ov, nv))) {
6432
6491
  bumpDeep(t);
6433
- dkBumped = true;
6492
+ bumped = true;
6434
6493
  }
6435
6494
  if (nodes !== null) {
6436
6495
  const node = nodes[k];
@@ -6446,7 +6505,7 @@ function applyAdopt(t, incoming, keyFn, proj = false) {
6446
6505
  if (k === $OWNER) continue;
6447
6506
  const nv = incoming[k];
6448
6507
  if (!shallow && nv !== null && typeof nv === "object")
6449
- descend(unwrapValue(old[k]), nv, keyFn, fam, proj);
6508
+ descend(unwrapValue(old[k]), nv, keyFn, proj, t, k);
6450
6509
  if (nodes !== null) {
6451
6510
  const node = nodes[k];
6452
6511
  if (node !== undefined) {
@@ -6466,16 +6525,39 @@ function applyAdopt(t, incoming, keyFn, proj = false) {
6466
6525
  const hasOwnP = Object.prototype.hasOwnProperty;
6467
6526
  /** Descend into a changed child pair — only where something is proxied
6468
6527
  * below (§6d), never into a raw-marked leaf, never across array/object or a
6469
- * key mismatch (that is a replacement, handled by the parent's slot). */
6470
- function descend(pv, nv, keyFn, fam, proj = false) {
6471
- if (pv === null || typeof pv !== "object" || nv === null || typeof nv !== "object") return;
6472
- const ct = lookupTarget(pv, fam);
6528
+ * key mismatch (that is a replacement, handled by the parent's slot).
6529
+ *
6530
+ * A raw in both frames (the same objects swapped, reordered or shifted,
6531
+ * #4001) must not leave two slots on one row. A raw already a live row keeps
6532
+ * it at the slot it moved to; a row's `pk` follows the slot it sits at (a
6533
+ * move or a pairing writes it), so a row whose raw is at that slot of the
6534
+ * incoming frame is spoken for and not paired again here; a raw still
6535
+ * resolving to a row that moved on drops the lookup (it wraps fresh, or is
6536
+ * paired here). One lookup of `nv` on the common path, no allocation. */
6537
+ function descend(pv, nv, keyFn, proj, t, k) {
6538
+ // `nv` is an object at every call site.
6539
+ if (pv === null || typeof pv !== "object") return;
6540
+ const fam = t.fam;
6541
+ nv = unwrapValue(nv);
6542
+ let ct = lookupTarget(pv, fam);
6543
+ const nt = lookupTarget(nv, fam);
6544
+ if (ct !== undefined && ct.pk !== k) {
6545
+ if (ct.pk != k && t.v[ct.pk] === ct.v) ct = undefined;
6546
+ else if (ct.u === t) ct.pk = k;
6547
+ }
6548
+ if (nt !== undefined && nt !== ct) {
6549
+ if (nt.v === nv) {
6550
+ if (nt.u === t) nt.pk = k;
6551
+ return;
6552
+ }
6553
+ (fam?.map ?? storeLookup).delete(nv);
6554
+ }
6473
6555
  if (ct === undefined) return; // nothing proxied below this pair
6474
6556
  if (!isWrappable(nv)) return;
6475
6557
  if (rawValuesUsed && isRawValue(nv)) return;
6476
- nv = unwrapValue(nv);
6477
6558
  if (Array.isArray(pv) !== Array.isArray(nv)) return;
6478
- if (keyFn) {
6559
+ // An array slot was paired by key already (or both rows are keyless).
6560
+ if (typeof k !== "number" && keyFn) {
6479
6561
  const pk = keyFn(pv);
6480
6562
  const nk = keyFn(nv);
6481
6563
  if (pk !== undefined && nk !== undefined && !sameKey(pk, nk)) return;
@@ -6585,8 +6667,14 @@ function cloneState(v, shallow) {
6585
6667
  * pending derivatively on the derive (its readers hold; it re-runs at the
6586
6668
  * landing). The leaves do not subscribe to the derive, so the family does
6587
6669
  * the walk. */
6588
- function wakeFamily(fam, error) {
6670
+ function wakeFamily(fam) {
6589
6671
  const fw = fam.node;
6672
+ // Pending on the derive itself, whatever source took it there (an upstream
6673
+ // memo's flight reaches it as a NotReady of that memo, #4271): the leaves
6674
+ // are not that source's dependents, so only the family's settle — which
6675
+ // retires the derive (`settleFamily`) — reaches the readers parked here. A
6676
+ // reader the landing leaves unchanged does not re-run to clear it (#4305).
6677
+ const error = new NotReadyError(fw);
6590
6678
  fam.woke = true;
6591
6679
  forEachFamilyNode(fam, n => {
6592
6680
  if (n._subs === null) return;
@@ -6624,6 +6712,13 @@ function settleFamily(fam) {
6624
6712
  if (n._subs !== null) settlePendingSource(n, fw);
6625
6713
  });
6626
6714
  }
6715
+ /** A source's status reached the derive by propagation (core
6716
+ * `propagateStatus`), its pass not re-run: the family learns it as from the
6717
+ * pass's own throw. */
6718
+ function familyStatus(status) {
6719
+ if (status & STATUS_PENDING) wakeFamily(this);
6720
+ else if (status & STATUS_ERROR) errorFamily(this);
6721
+ }
6627
6722
  function forEachFamilyNode(fam, fn) {
6628
6723
  for (const t of fam.live) {
6629
6724
  const nodes = t.n;
@@ -6710,7 +6805,7 @@ function runProjectionComputed(wrappedStore, fn, key, wrapCommit, aroundDraftWri
6710
6805
  // #2897) — and the flush that runs them advances the clock, so a
6711
6806
  // later tracked re-read may retry (core `read`).
6712
6807
  if (first && owner._statusFlags & STATUS_UNINITIALIZED) return;
6713
- if (pending) wakeFamily(fam, error);
6808
+ if (pending) wakeFamily(fam);
6714
6809
  else errorFamily(fam);
6715
6810
  });
6716
6811
  if (!owner._loading) commit(sync);
@@ -6725,7 +6820,7 @@ function runProjectionComputed(wrappedStore, fn, key, wrapCommit, aroundDraftWri
6725
6820
  // they do not subscribe to the derive.
6726
6821
  // A synchronous error the same way (#3956: a rerun that finds the
6727
6822
  // entry failed throws): every live reader re-derives and meets it.
6728
- if (!first) e instanceof NotReadyError ? wakeFamily(fam, e) : errorFamily(fam);
6823
+ if (!first) e instanceof NotReadyError ? wakeFamily(fam) : errorFamily(fam);
6729
6824
  throw e;
6730
6825
  }
6731
6826
  // A pass that returned with a flight up (an iterator's sync first yield):
@@ -6738,7 +6833,7 @@ function runProjectionComputed(wrappedStore, fn, key, wrapCommit, aroundDraftWri
6738
6833
  !owner._loading &&
6739
6834
  !first
6740
6835
  )
6741
- wakeFamily(fam, owner._x?._error);
6836
+ wakeFamily(fam);
6742
6837
  return owner;
6743
6838
  }
6744
6839
  function createProjectionInternal(fn, seed, options) {
@@ -6750,6 +6845,7 @@ function createProjectionInternal(fn, seed, options) {
6750
6845
  map: new WeakMap(),
6751
6846
  node: null,
6752
6847
  live: new Set(),
6848
+ st: familyStatus,
6753
6849
  shallow: !!options?.shallow,
6754
6850
  // The row key, for a child paired after the adoption (store.ts
6755
6851
  // `adoptChild`, #3712) as the adoption pairs them.
@@ -8289,7 +8385,9 @@ function merge(...sources) {
8289
8385
  count++;
8290
8386
  only = s;
8291
8387
  if (typeof s === "function") {
8292
- flattened[n] = createMemo(s);
8388
+ // Observe tiers name it and exempt it from UNSTABLE_MEMO_OUTPUT (see
8389
+ // CONFIG_KEY_BAG); prod passes no options.
8390
+ flattened[n] = createMemo(s, { name: "merge", _extraConfig: CONFIG_KEY_BAG });
8293
8391
  kinds[n++] = SOURCE_MEMO;
8294
8392
  continue;
8295
8393
  }
@@ -8596,7 +8694,7 @@ function walk(value, entry, found, fam, visited) {
8596
8694
  if (t !== undefined) {
8597
8695
  if (t.pb !== null) entry._scope.add(t.v);
8598
8696
  // The writer's view: the tick's own unflushed guesses are in motion too.
8599
- if (t.fam?.opt === true) raw = optHooks.view(t, raw, true);
8697
+ if (optHooks && t.fam?.opt === true) raw = optHooks.view(t, raw, true);
8600
8698
  const nodes = t.n;
8601
8699
  if (nodes !== null)
8602
8700
  for (const k of Reflect.ownKeys(nodes))
@@ -8904,46 +9002,282 @@ function createDeferred(fn, options) {
8904
9002
  // Sticky, and read before this wrapper ever opens the window itself.
8905
9003
  const open = (answered ||= !(node._statusFlags & STATUS_UNINITIALIZED) && !node._loading);
8906
9004
  if (open) ((node._loading = true), (node._config |= CONFIG_LAGS));
8907
- let r;
8908
9005
  try {
8909
- r = fn(prev);
9006
+ const r = fn(prev);
9007
+ if (open && typeof r === "object" && r !== null) {
9008
+ if (isThenable(r)) {
9009
+ ask(r);
9010
+ const done = () => land(r, true);
9011
+ r.then(done, done);
9012
+ return serve(r);
9013
+ }
9014
+ const iterate = r[Symbol.asyncIterator];
9015
+ if (iterate) {
9016
+ // A stream's flight ends at its first yield (or its end).
9017
+ const it = iterate.call(r);
9018
+ let first = true;
9019
+ const done = () => land(s, true);
9020
+ const s = {
9021
+ // Registered ahead of the engine's own reaction to the same
9022
+ // promise, as for a plain flight: verdict and value in one flush.
9023
+ next: v => {
9024
+ const p = it.next(v);
9025
+ if (first) {
9026
+ first = false;
9027
+ if (isThenable(p)) p.then(done, done);
9028
+ else done();
9029
+ }
9030
+ return p;
9031
+ },
9032
+ return: v => it.return?.(v) ?? Promise.resolve({ done: true, value: v })
9033
+ };
9034
+ ask(s);
9035
+ return serve({ [Symbol.asyncIterator]: () => s });
9036
+ }
9037
+ }
9038
+ land(flight);
9039
+ return r;
8910
9040
  } catch (e) {
8911
9041
  // The wrapping form: an unready source parks the node in the window,
8912
9042
  // and the wait is its flight until the source's settle re-runs it.
8913
9043
  if (open && e instanceof NotReadyError) ask(e);
9044
+ else land(flight);
8914
9045
  throw e;
8915
9046
  }
8916
- if (open && typeof r === "object" && r !== null) {
8917
- if (isThenable(r)) {
8918
- ask(r);
8919
- const done = () => land(r, true);
8920
- r.then(done, done);
8921
- return serve(r);
9047
+ }, options)
9048
+ );
9049
+ }
9050
+
9051
+ // `from` — a push source (callback subscription, Observable, AsyncIterable)
9052
+ // as an AsyncIterable a computation can return or `for await` over:
9053
+ //
9054
+ // const value = createMemo(() => from(source));
9055
+ //
9056
+ // A subscription keeps the latest value: values that arrive between pulls
9057
+ // coalesce. An AsyncIterable streams through unchanged. Either way the source
9058
+ // opens when the result is iterated and closes on return(), on supersede (the
9059
+ // owning computation re-runs) and on owner disposal, whichever comes first.
9060
+ function toSubscribe(source) {
9061
+ if (typeof source === "function") return source;
9062
+ // TC39 / RxJS interop: `Symbol.observable` (or "@@observable") returns the observable.
9063
+ // Read per call, not at module scope, so the module stays side-effect free.
9064
+ const $$observable = Symbol.observable;
9065
+ const interop = ($$observable && source?.[$$observable]) ?? source?.["@@observable"];
9066
+ const obs = typeof interop === "function" ? interop.call(source) : source;
9067
+ if (obs && typeof obs.subscribe === "function")
9068
+ return (next, error, complete) => obs.subscribe({ next, error, complete });
9069
+ return null;
9070
+ }
9071
+ const DONE = { value: undefined, done: true };
9072
+ /**
9073
+ * Ties an iterator's `finish` to the computation iterating it. Solid reaches
9074
+ * an iterator's return() only when the compute returns the iterable directly.
9075
+ * Inside an async generator the generator's own return() is queued behind its
9076
+ * pending `await`, and owner cleanups wait for the replacing pass to commit,
9077
+ * so close on supersede, with owner disposal as the backstop. No owner (an
9078
+ * async compute iterating it after its first `await`, or plain iteration):
9079
+ * no registration and no diagnostic, so only return() closes it. Scopes that
9080
+ * forbid cleanups rely on supersede and return().
9081
+ */
9082
+ function bindToOwner(finish) {
9083
+ const owner = getOwner();
9084
+ if (!owner) return;
9085
+ onSupersede(finish);
9086
+ if (!(owner._config & CONFIG_CHILDREN_FORBIDDEN)) cleanup(finish);
9087
+ }
9088
+ /**
9089
+ * An AsyncIterable as a stream bound to the iterating computation: every
9090
+ * value passes through (no coalescing). `finish` calls the inner return()
9091
+ * once and settles pending pulls as done, so a `for await` over it exits as
9092
+ * soon as the computation re-runs — even when the inner iterator is an async
9093
+ * generator whose own return() the language queues behind its pending
9094
+ * `await`.
9095
+ */
9096
+ function wrapIterable(source) {
9097
+ return {
9098
+ [Symbol.asyncIterator]() {
9099
+ const it = source[Symbol.asyncIterator]();
9100
+ let closed = false;
9101
+ const pending = new Set();
9102
+ const finish = () => {
9103
+ if (closed) return DONE;
9104
+ closed = true;
9105
+ for (const resolve of pending) resolve(DONE);
9106
+ pending.clear();
9107
+ try {
9108
+ const returned = it.return?.();
9109
+ if (isThenable(returned)) returned.then(undefined, () => {});
9110
+ } catch {}
9111
+ return DONE;
9112
+ };
9113
+ bindToOwner(finish);
9114
+ return {
9115
+ next(...args) {
9116
+ if (closed) return Promise.resolve(DONE);
9117
+ const step = it.next(...args);
9118
+ // A bare result (the producer's buffered fast path) passes through.
9119
+ if (!isThenable(step)) return step;
9120
+ return new Promise((resolve, reject) => {
9121
+ pending.add(resolve);
9122
+ step.then(
9123
+ r => pending.delete(resolve) && resolve(r),
9124
+ e => pending.delete(resolve) && reject(e)
9125
+ );
9126
+ });
9127
+ },
9128
+ return() {
9129
+ return Promise.resolve(finish());
8922
9130
  }
8923
- const iterate = r[Symbol.asyncIterator];
8924
- if (iterate) {
8925
- // A stream's flight ends at its first yield (or its end).
8926
- const it = iterate.call(r);
8927
- let first = true;
8928
- const done = () => land(s, true);
8929
- const s = {
8930
- // Registered ahead of the engine's own reaction to the same
8931
- // promise, as for a plain flight: verdict and value in one flush.
8932
- next: v => {
8933
- const p = it.next(v);
8934
- if (first) ((first = false), p.then(done, done));
8935
- return p;
8936
- },
8937
- return: v => it.return?.(v) ?? Promise.resolve({ done: true, value: v })
8938
- };
8939
- ask(s);
8940
- return serve({ [Symbol.asyncIterator]: () => s });
9131
+ };
9132
+ }
9133
+ };
9134
+ }
9135
+ const toCleanup = t =>
9136
+ typeof t === "function"
9137
+ ? t
9138
+ : t && typeof t.unsubscribe === "function"
9139
+ ? () => t.unsubscribe()
9140
+ : () => {};
9141
+ /**
9142
+ * Converts a callback subscription, an Observable or an AsyncIterable into an
9143
+ * AsyncIterable for a Solid computation. A subscription keeps only the latest
9144
+ * value, and a value the source emits during `subscribe` is served without a
9145
+ * loading frame; an AsyncIterable streams every value through.
9146
+ *
9147
+ * The source closes on supersede — the owning computation re-running — and
9148
+ * on owner disposal. Inside an async generator memo that holds for a
9149
+ * `for await` that starts before the first `await`; one that starts after it
9150
+ * has no owner to bind to, so its source closes only when the loop ends.
9151
+ * Prefer returning `from(...)` from its own memo, with awaited inputs read
9152
+ * from upstream memos and transforms done in a downstream memo.
9153
+ *
9154
+ * Iterate the result with `for await` or return it from a computation: its
9155
+ * `next()` returns an already-buffered value as a bare result, not a promise.
9156
+ *
9157
+ * @example
9158
+ * ```ts
9159
+ * const position = createMemo(() =>
9160
+ * from<Position>((next, error) => {
9161
+ * const id = navigator.geolocation.watchPosition(next, error);
9162
+ * return () => navigator.geolocation.clearWatch(id);
9163
+ * })
9164
+ * );
9165
+ * const ticks = createMemo(() => from(interval$(rate())));
9166
+ * ```
9167
+ */
9168
+ function from(source) {
9169
+ const subscribe = toSubscribe(source);
9170
+ if (!subscribe) {
9171
+ if (source && typeof source[Symbol.asyncIterator] === "function") return wrapIterable(source);
9172
+ throw new TypeError("from(): expected a subscribe function, an Observable or an AsyncIterable");
9173
+ }
9174
+ return {
9175
+ [Symbol.asyncIterator]() {
9176
+ let latest;
9177
+ let has = false;
9178
+ let done = false;
9179
+ let failed = false;
9180
+ let failure;
9181
+ let waiting = null;
9182
+ let unsubscribe = null;
9183
+ let closed = false;
9184
+ const close = () => {
9185
+ if (closed) return;
9186
+ closed = true;
9187
+ done = true;
9188
+ const c = unsubscribe;
9189
+ unsubscribe = null;
9190
+ // A throwing unsubscribe must not escape: at supersede this runs
9191
+ // inside flush(), ahead of the replacing pass (the returned-iterator
9192
+ // path swallows it the same way).
9193
+ try {
9194
+ c?.();
9195
+ } catch {}
9196
+ };
9197
+ const settle = () => {
9198
+ if (!waiting) return;
9199
+ const w = waiting;
9200
+ if (has) {
9201
+ waiting = null;
9202
+ has = false;
9203
+ w.resolve({ value: latest, done: false });
9204
+ } else if (failed) {
9205
+ waiting = null;
9206
+ failed = false;
9207
+ w.reject(failure);
9208
+ } else if (done) {
9209
+ waiting = null;
9210
+ w.resolve({ value: undefined, done: true });
8941
9211
  }
9212
+ };
9213
+ const teardown = subscribe(
9214
+ value => {
9215
+ if (done) return;
9216
+ latest = value;
9217
+ has = true;
9218
+ settle();
9219
+ },
9220
+ err => {
9221
+ if (done) return;
9222
+ failed = true;
9223
+ failure = err;
9224
+ done = true;
9225
+ settle();
9226
+ close();
9227
+ },
9228
+ () => {
9229
+ if (done) return;
9230
+ done = true;
9231
+ settle();
9232
+ close();
9233
+ }
9234
+ );
9235
+ unsubscribe = toCleanup(teardown);
9236
+ // Completed or errored during subscribe: release what it just returned.
9237
+ if (closed) {
9238
+ const c = unsubscribe;
9239
+ unsubscribe = null;
9240
+ try {
9241
+ c();
9242
+ } catch {}
8942
9243
  }
8943
- land(flight);
8944
- return r;
8945
- }, options)
8946
- );
9244
+ // Idempotent: return(), supersede and disposal may all reach it.
9245
+ const finish = () => {
9246
+ close();
9247
+ has = false;
9248
+ if (waiting) {
9249
+ const w = waiting;
9250
+ waiting = null;
9251
+ w.resolve(DONE);
9252
+ }
9253
+ return DONE;
9254
+ };
9255
+ bindToOwner(finish);
9256
+ return {
9257
+ next() {
9258
+ // A buffered value is returned as a bare result, not a promise: the
9259
+ // iterator consumer lands it in the same pass, so a source that
9260
+ // emits during subscribe (BehaviorSubject-style) shows its value
9261
+ // with no loading frame. `for await` accepts either form.
9262
+ if (has) {
9263
+ has = false;
9264
+ return { value: latest, done: false };
9265
+ }
9266
+ if (failed) {
9267
+ failed = false;
9268
+ return Promise.reject(failure);
9269
+ }
9270
+ if (done) return Promise.resolve({ value: undefined, done: true });
9271
+ return new Promise((resolve, reject) => {
9272
+ waiting = { resolve, reject };
9273
+ });
9274
+ },
9275
+ return() {
9276
+ return Promise.resolve(finish());
9277
+ }
9278
+ };
9279
+ }
9280
+ };
8947
9281
  }
8948
9282
 
8949
9283
  // CARVE 4: `flatten` lived in boundaries.ts but is the general children
@@ -9062,6 +9396,9 @@ const BOUNDARY = Symbol("boundary");
9062
9396
  * slot of (reveal.ts); a boundary clears it for its content — only direct
9063
9397
  * children are slots. */
9064
9398
  const REVEAL = Symbol("reveal");
9399
+ // Every call site checks the hooks themselves first (not only the slot or
9400
+ // controller, which are never set without them): a build that does not
9401
+ // import `createRevealOrder` folds the reveal-order arms away.
9065
9402
  let revealHooks = null;
9066
9403
  function setRevealHooks(h) {
9067
9404
  revealHooks = h;
@@ -9099,7 +9436,7 @@ function caught(b, node, error) {
9099
9436
  // nothing on screen. The fallback is instantiated once per show — a later
9100
9437
  // error reaches it through the `error` accessor, which is why it is one.
9101
9438
  if (!b._fallback) redraw(b);
9102
- if (b._reveal !== null) revealHooks._changed(b._reveal);
9439
+ if (revealHooks && b._reveal !== null) revealHooks._changed(b._reveal);
9103
9440
  }
9104
9441
  /** A re-armed boundary found something pending: fresh again, and the swap
9105
9442
  * the arming pass asked for is that pass's lane's (if any): the output is
@@ -9111,7 +9448,8 @@ function flip(b) {
9111
9448
  b._lane = b._armLane;
9112
9449
  const o = b._output;
9113
9450
  if (!b._lane && o._config & CONFIG_HELD && txOf(o) !== flushTransaction) unhold(o);
9114
- if (b._lane !== null && b._output !== null) laneStageHook(b._output, b._lane, true, false);
9451
+ if (laneStageHook && b._lane !== null && b._output !== null)
9452
+ laneStageHook(b._output, b._lane, true, false);
9115
9453
  }
9116
9454
  function redraw(b) {
9117
9455
  if (b._output !== null && !(b._output._flags & (REACTIVE_DISPOSED | REACTIVE_ZOMBIE))) {
@@ -9139,7 +9477,7 @@ function catchStatus(node, flags, error) {
9139
9477
  b._readers.size === 0
9140
9478
  ) {
9141
9479
  if (b._fallback) redraw(b);
9142
- if (b._reveal !== null) revealHooks._changed(b._reveal);
9480
+ if (revealHooks && b._reveal !== null) revealHooks._changed(b._reveal);
9143
9481
  }
9144
9482
  return false;
9145
9483
  }
@@ -9273,7 +9611,7 @@ function boundarySeam() {
9273
9611
  if (b._readers.size === 0) {
9274
9612
  collecting.delete(b);
9275
9613
  if (b._fallback) redraw(b);
9276
- if (b._reveal !== null) revealHooks._changed(b._reveal);
9614
+ if (revealHooks && b._reveal !== null) revealHooks._changed(b._reveal);
9277
9615
  }
9278
9616
  }
9279
9617
  }
@@ -9352,6 +9690,8 @@ function arm(b) {
9352
9690
  return;
9353
9691
  }
9354
9692
  b._armed = true;
9693
+ // The swap is the change's work: the output joins it (D-13, #3540).
9694
+ b._output._config &= ~CONFIG_FRAME;
9355
9695
  // A verdict read in `on` (`isPending`, or a memo of one — #3528's
9356
9696
  // `createMemo(() => isPending(m2))`: the mark carries, core.ts `read`)
9357
9697
  // whose frame has no transaction yet: the swap is still display-ahead — the frame gets one now (it lands
@@ -9431,7 +9771,7 @@ function createBoundary(type, fn, show, onFn) {
9431
9771
  if (getOwner())
9432
9772
  cleanup(() => {
9433
9773
  collecting.delete(b);
9434
- if (controller) revealHooks._unregister(controller, b);
9774
+ if (revealHooks && controller) revealHooks._unregister(controller, b);
9435
9775
  });
9436
9776
  // The `on` dependencies live OUTSIDE the boundary, as the condition of a
9437
9777
  // `<Show>` wrapping it would: created before the owner's context names it.
@@ -9470,9 +9810,16 @@ function createBoundary(type, fn, show, onFn) {
9470
9810
  b._tree = tree;
9471
9811
  // A slot of the reveal order in its context (reveal.ts): gated until the
9472
9812
  // controller releases it — before the output's first pass.
9473
- if (controller) revealHooks._register(controller, b);
9813
+ if (revealHooks && controller) revealHooks._register(controller, b);
9474
9814
  const output = computed(
9475
9815
  () => {
9816
+ // Structure, not a derivation of its content (CONFIG_FRAME, #4283): it
9817
+ // reads a hold it is not part of as the screen, as the render effect
9818
+ // flattening the same children without a boundary would. Not while a
9819
+ // re-arm's swap is the change's work (`arm`), until that swap commits
9820
+ // (D-13, #3540).
9821
+ const self = getOwner();
9822
+ if (!b._armed && !(self._config & CONFIG_HELD)) self._config |= CONFIG_FRAME;
9476
9823
  // Reveal order (reveal.ts): a gated slot shows its fallback — or, in a
9477
9824
  // collapsed tail, nothing — whatever its content's state; the
9478
9825
  // controller is told the pass ran (its readiness may have changed) and
@@ -9483,8 +9830,8 @@ function createBoundary(type, fn, show, onFn) {
9483
9830
  // It may release this very slot (the frontier whose content just
9484
9831
  // settled): the pass goes on to show it — the redraw it asks for is
9485
9832
  // refused mid-pass and not needed.
9486
- link(tree, getOwner());
9487
- if (b._reveal !== null) revealHooks._changed(b._reveal);
9833
+ link(tree, self);
9834
+ if (revealHooks && b._reveal !== null) revealHooks._changed(b._reveal);
9488
9835
  if (b._gated) {
9489
9836
  if (b._collapsed) {
9490
9837
  b._fallback = true;
@@ -9502,7 +9849,7 @@ function createBoundary(type, fn, show, onFn) {
9502
9849
  // once clean.
9503
9850
  const status = tree._statusFlags & (STATUS_PENDING | STATUS_ERROR);
9504
9851
  if (status !== 0) {
9505
- link(tree, getOwner());
9852
+ link(tree, self);
9506
9853
  if (status & type) {
9507
9854
  if (isCollecting(b)) {
9508
9855
  caught(b, tree, undefined);
@@ -9528,7 +9875,6 @@ function createBoundary(type, fn, show, onFn) {
9528
9875
  // enters: the outside sees its committed value until the landing,
9529
9876
  // which reveals the content — a fallback staged earlier is replaced
9530
9877
  // ahead of the commit and never shown.
9531
- const self = getOwner();
9532
9878
  if (
9533
9879
  b._readers.size !== 0 &&
9534
9880
  self._statusFlags & STATUS_UNINITIALIZED &&
@@ -9569,19 +9915,27 @@ function createBoundary(type, fn, show, onFn) {
9569
9915
  // landing when the frame parks (the run is held with it, as the old
9570
9916
  // children of a held pass are) — not now: a held swap keeps the
9571
9917
  // fallback on screen, live, until it commits. A pass back to the
9572
- // fallback before then finds it still built and keeps it.
9918
+ // fallback before then finds it still built and keeps it. A fallback
9919
+ // the screen does not show (a swap staged with a held frame, replaced
9920
+ // before it commits) dies now: what it reads holds nothing (S-22 row
9921
+ // 3′, #4268).
9573
9922
  const fb = b._fb;
9923
+ const drop = () => {
9924
+ if (!b._fallback && b._fb === fb) {
9925
+ // A fallback that threw (a rethrow the boundary does not
9926
+ // handle) is what an enclosing boundary waits on: it died, and
9927
+ // the seam of another round drops it there (#4270).
9928
+ if (fb._statusFlags & STATUS_ERROR) schedule();
9929
+ dispose(fb);
9930
+ b._fb = null;
9931
+ }
9932
+ };
9574
9933
  if (fb !== null)
9575
- globalQueue.enqueue(EFFECT_RENDER, () => {
9576
- if (!b._fallback && b._fb === fb) {
9577
- dispose(fb);
9578
- b._fb = null;
9579
- }
9580
- });
9934
+ b._output._value !== fb._value ? drop() : globalQueue.enqueue(EFFECT_RENDER, drop);
9581
9935
  release(b);
9582
9936
  // Shown: the slot leaves its reveal order (a later re-arm gates nobody
9583
9937
  // else; the frontier moves on).
9584
- if (b._reveal !== null) {
9938
+ if (revealHooks && b._reveal !== null) {
9585
9939
  const controller = b._reveal;
9586
9940
  b._reveal = null;
9587
9941
  revealHooks._changed(controller);
@@ -9594,6 +9948,8 @@ function createBoundary(type, fn, show, onFn) {
9594
9948
  { name: "value", _noSnapshot: true }
9595
9949
  );
9596
9950
  output._config |= CONFIG_REDERIVE;
9951
+ // The tree is structure too (the output's pass marks the output).
9952
+ tree._config |= CONFIG_FRAME;
9597
9953
  b._output = output;
9598
9954
  return accessor(output);
9599
9955
  }
@@ -9962,6 +10318,7 @@ export {
9962
10318
  TimeoutError,
9963
10319
  action,
9964
10320
  affects,
10321
+ clock,
9965
10322
  configureClientErrors,
9966
10323
  createContext,
9967
10324
  createDeferred,
@@ -9984,6 +10341,7 @@ export {
9984
10341
  enableExternalSource,
9985
10342
  flatten,
9986
10343
  flush,
10344
+ from,
9987
10345
  getContext,
9988
10346
  getObserver,
9989
10347
  getOwner,
@@ -10002,6 +10360,7 @@ export {
10002
10360
  omit,
10003
10361
  omitView,
10004
10362
  onCleanup,
10363
+ onFlush,
10005
10364
  onSettled,
10006
10365
  reconcile,
10007
10366
  refresh,