@solidjs/signals 2.0.0-rc.16 → 2.0.0-rc.18

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 (56) hide show
  1. package/dist/dev-shared.js +1972 -1751
  2. package/dist/dev.attribution.js +16 -16
  3. package/dist/dev.js +672 -319
  4. package/dist/observe/boundaries.js +2 -2
  5. package/dist/observe/core/async.js +151 -116
  6. package/dist/observe/core/constants.js +3 -1
  7. package/dist/observe/core/context.js +2 -0
  8. package/dist/observe/core/core.js +51 -35
  9. package/dist/observe/core/effect.js +29 -30
  10. package/dist/observe/core/graph.js +13 -11
  11. package/dist/observe/core/lanes.js +25 -14
  12. package/dist/observe/core/owner.js +77 -51
  13. package/dist/observe/core/scheduler.js +202 -175
  14. package/dist/observe/core/verdict.js +5 -2
  15. package/dist/observe/deferred.js +16 -13
  16. package/dist/observe/from.js +248 -0
  17. package/dist/observe/index.js +2 -0
  18. package/dist/observe/map.js +2 -2
  19. package/dist/observe/signals.js +6 -1
  20. package/dist/observe/store/affects.js +13 -13
  21. package/dist/observe/store/optimistic.js +112 -71
  22. package/dist/observe/store/reconcile.js +137 -121
  23. package/dist/observe/store/store.js +612 -626
  24. package/dist/observe/store/target.js +1 -5
  25. package/dist/prod/boundaries.js +2 -2
  26. package/dist/prod/core/async.js +211 -176
  27. package/dist/prod/core/constants.js +3 -1
  28. package/dist/prod/core/context.js +2 -0
  29. package/dist/prod/core/core.js +63 -47
  30. package/dist/prod/core/effect.js +24 -25
  31. package/dist/prod/core/graph.js +12 -10
  32. package/dist/prod/core/heap.js +1 -1
  33. package/dist/prod/core/lanes.js +39 -28
  34. package/dist/prod/core/owner.js +70 -44
  35. package/dist/prod/core/scheduler.js +78 -51
  36. package/dist/prod/core/truth.js +4 -4
  37. package/dist/prod/core/verdict.js +11 -8
  38. package/dist/prod/deferred.js +17 -14
  39. package/dist/prod/from.js +248 -0
  40. package/dist/prod/index.js +2 -0
  41. package/dist/prod/map.js +2 -2
  42. package/dist/prod/signals.js +18 -13
  43. package/dist/prod/store/optimistic.js +112 -71
  44. package/dist/prod/store/reconcile.js +137 -121
  45. package/dist/prod/store/store.js +109 -121
  46. package/dist/prod/store/target.js +13 -17
  47. package/dist/types/core/async.d.ts +12 -2
  48. package/dist/types/core/constants.d.ts +5 -0
  49. package/dist/types/core/core.d.ts +29 -6
  50. package/dist/types/core/dev.d.ts +9 -1
  51. package/dist/types/core/error.d.ts +9 -1
  52. package/dist/types/core/owner.d.ts +4 -0
  53. package/dist/types/core/scheduler.d.ts +38 -7
  54. package/dist/types/from.d.ts +47 -0
  55. package/dist/types/index.d.ts +1 -0
  56. package/package.json +1 -1
@@ -228,4 +228,6 @@ const defaultContext = {};
228
228
  * @internal
229
229
  */ const $REFRESH = Symbol("refresh");
230
230
 
231
- export { $REFRESH, CAUGHT_REARMED, CONFIG_AUTHORITATIVE, CONFIG_AUTO_DISPOSE, CONFIG_CHILDREN_FORBIDDEN, CONFIG_FRESH_READ, CONFIG_GUESS, CONFIG_HAS_SNAPSHOT, CONFIG_HELD, CONFIG_HELD_FRAME, CONFIG_INPUTS_PUBLISHED, CONFIG_IN_SNAPSHOT_SCOPE, CONFIG_LAGS, CONFIG_MANUAL_WRITE, CONFIG_NO_SNAPSHOT, CONFIG_OVERRIDE, CONFIG_OWNED_WRITE, CONFIG_PROMOTED, CONFIG_REDERIVE, CONFIG_SLOT_NODE, CONFIG_STAGED, CONFIG_STAGED_ERROR, CONFIG_SYNC, CONFIG_TRANSPARENT, CONFIG_VERDICT, EFFECT_RENDER, EFFECT_TRACKED, EFFECT_USER, NOT_PENDING, NO_SNAPSHOT, REACTIVE_CHECK, REACTIVE_DIRTY, REACTIVE_DISPOSED, REACTIVE_FRAME_READ, REACTIVE_IN_HEAP, REACTIVE_IN_HEAP_HEIGHT, REACTIVE_JOINED, REACTIVE_LANE_DIRTY, REACTIVE_LANE_READ, REACTIVE_LAZY, REACTIVE_MANUAL_WRITE, REACTIVE_MISSED_WAKE, REACTIVE_NONE, REACTIVE_PROBED, REACTIVE_PROBE_UNANSWERED, REACTIVE_REASK, REACTIVE_RECOMPUTING_DEPS, REACTIVE_SCREEN_READ, REACTIVE_SNAPSHOT_STALE, REACTIVE_STAGED_READ, REACTIVE_ZOMBIE, STATUS_ERROR, STATUS_NONE, STATUS_PENDING, STATUS_UNINITIALIZED, SUPPORTS_PROXY, defaultContext };
231
+ const NewSet = Set;
232
+
233
+ export { $REFRESH, CAUGHT_REARMED, CONFIG_AUTHORITATIVE, CONFIG_AUTO_DISPOSE, CONFIG_CHILDREN_FORBIDDEN, CONFIG_FRESH_READ, CONFIG_GUESS, CONFIG_HAS_SNAPSHOT, CONFIG_HELD, CONFIG_HELD_FRAME, CONFIG_INPUTS_PUBLISHED, CONFIG_IN_SNAPSHOT_SCOPE, CONFIG_LAGS, CONFIG_MANUAL_WRITE, CONFIG_NO_SNAPSHOT, CONFIG_OVERRIDE, CONFIG_OWNED_WRITE, CONFIG_PROMOTED, CONFIG_REDERIVE, CONFIG_SLOT_NODE, CONFIG_STAGED, CONFIG_STAGED_ERROR, CONFIG_SYNC, CONFIG_TRANSPARENT, CONFIG_VERDICT, EFFECT_RENDER, EFFECT_TRACKED, EFFECT_USER, NOT_PENDING, NO_SNAPSHOT, NewSet, REACTIVE_CHECK, REACTIVE_DIRTY, REACTIVE_DISPOSED, REACTIVE_FRAME_READ, REACTIVE_IN_HEAP, REACTIVE_IN_HEAP_HEIGHT, REACTIVE_JOINED, REACTIVE_LANE_DIRTY, REACTIVE_LANE_READ, REACTIVE_LAZY, REACTIVE_MANUAL_WRITE, REACTIVE_MISSED_WAKE, REACTIVE_NONE, REACTIVE_PROBED, REACTIVE_PROBE_UNANSWERED, REACTIVE_REASK, REACTIVE_RECOMPUTING_DEPS, REACTIVE_SCREEN_READ, REACTIVE_SNAPSHOT_STALE, REACTIVE_STAGED_READ, REACTIVE_ZOMBIE, STATUS_ERROR, STATUS_NONE, STATUS_PENDING, STATUS_UNINITIALIZED, SUPPORTS_PROXY, defaultContext };
@@ -33,6 +33,8 @@ import { getOwner } from "./owner.js";
33
33
  let r = t.A[e.id];
34
34
  if (r === undefined) r = e.defaultValue;
35
35
  if (r === undefined) {
36
+ // Dev names the context and why it has no value: no provider, or a
37
+ // provider that passed `undefined` (the key is set, to `undefined`).
36
38
  throw new ContextNotFoundError;
37
39
  }
38
40
  return r;
@@ -1,6 +1,6 @@
1
1
  import { notifyStatus, releaseFlightTeardown, handleAsync, clearStatus, parkLoadingWindow, stageError, settlePendingSource, releaseIfSettledUnobserved, settleErroredDependents } from "./async.js";
2
2
 
3
- import { STATUS_UNINITIALIZED, REACTIVE_LAZY, REACTIVE_NONE, defaultContext, CONFIG_TRANSPARENT, CONFIG_OWNED_WRITE, CONFIG_AUTO_DISPOSE, CONFIG_SYNC, CONFIG_NO_SNAPSHOT, CONFIG_IN_SNAPSHOT_SCOPE, NOT_PENDING, NO_SNAPSHOT, CONFIG_HAS_SNAPSHOT, CONFIG_HELD, REACTIVE_MANUAL_WRITE, STATUS_ERROR, REACTIVE_DIRTY, CONFIG_OVERRIDE, REACTIVE_DISPOSED, REACTIVE_CHECK, REACTIVE_IN_HEAP, REACTIVE_REASK, REACTIVE_SNAPSHOT_STALE, CONFIG_CHILDREN_FORBIDDEN, CONFIG_VERDICT, STATUS_PENDING, REACTIVE_LANE_DIRTY, CONFIG_GUESS, REACTIVE_RECOMPUTING_DEPS, CONFIG_LAGS, EFFECT_RENDER, CONFIG_HELD_FRAME, EFFECT_TRACKED, CONFIG_STAGED, CONFIG_STAGED_ERROR, REACTIVE_ZOMBIE, REACTIVE_MISSED_WAKE, REACTIVE_JOINED, REACTIVE_STAGED_READ, REACTIVE_PROBED, REACTIVE_FRAME_READ, REACTIVE_LANE_READ, REACTIVE_SCREEN_READ, REACTIVE_PROBE_UNANSWERED, CAUGHT_REARMED, CONFIG_PROMOTED, CONFIG_INPUTS_PUBLISHED, CONFIG_FRESH_READ, REACTIVE_IN_HEAP_HEIGHT, CONFIG_SLOT_NODE, EFFECT_USER, STATUS_NONE } from "./constants.js";
3
+ import { STATUS_UNINITIALIZED, REACTIVE_LAZY, REACTIVE_NONE, defaultContext, CONFIG_TRANSPARENT, CONFIG_OWNED_WRITE, CONFIG_AUTO_DISPOSE, CONFIG_SYNC, CONFIG_NO_SNAPSHOT, CONFIG_IN_SNAPSHOT_SCOPE, NOT_PENDING, NO_SNAPSHOT, CONFIG_HAS_SNAPSHOT, CONFIG_HELD, REACTIVE_MANUAL_WRITE, STATUS_ERROR, REACTIVE_DIRTY, CONFIG_OVERRIDE, REACTIVE_DISPOSED, REACTIVE_CHECK, REACTIVE_IN_HEAP, REACTIVE_REASK, REACTIVE_SNAPSHOT_STALE, CONFIG_CHILDREN_FORBIDDEN, CONFIG_VERDICT, STATUS_PENDING, REACTIVE_LANE_DIRTY, CONFIG_GUESS, REACTIVE_RECOMPUTING_DEPS, CONFIG_LAGS, EFFECT_RENDER, CONFIG_HELD_FRAME, EFFECT_TRACKED, CONFIG_STAGED, CONFIG_STAGED_ERROR, REACTIVE_ZOMBIE, REACTIVE_MISSED_WAKE, REACTIVE_JOINED, REACTIVE_STAGED_READ, REACTIVE_PROBED, REACTIVE_FRAME_READ, REACTIVE_LANE_READ, REACTIVE_SCREEN_READ, REACTIVE_PROBE_UNANSWERED, CAUGHT_REARMED, CONFIG_PROMOTED, CONFIG_INPUTS_PUBLISHED, CONFIG_FRESH_READ, REACTIVE_IN_HEAP_HEIGHT, CONFIG_SLOT_NODE, EFFECT_USER, NewSet, STATUS_NONE } from "./constants.js";
4
4
 
5
5
  import { NotReadyError } from "./error.js";
6
6
 
@@ -10,7 +10,7 @@ import { insertIntoHeap, deleteFromHeap, insertIntoHeapHeight, enqueueSub, markN
10
10
 
11
11
  import { inheritId, linkChild, disposeChildren, markDisposal } from "./owner.js";
12
12
 
13
- import { schedule, externalUntrackHook, clock, txOf, flushTransaction, laneWriteHook, joinFuture, queuePendingNode, globalQueue, notifyEpoch, insertSubs, dirtyQueue, observeFlightHook, laneReadHook, laneStageHook, passLane, wireExternalSourceHook, bumpNotifyEpoch, setPassLane, joinPassTx, freshHook, GlobalQueue, laneOutcomeHook, passTx, holdNode, laneDirty, heldTrim, staleReader, laneSeamsHook, stagedReaders, deferZombie } from "./scheduler.js";
13
+ import { schedule, externalUntrackHook, clock, txOf, flushTransaction, laneWriteHook, joinFuture, queuePendingNode, globalQueue, notifyEpoch, insertSubs, dirtyQueue, observeFlightHook, laneStageHook, passLane, laneReadHook, wireExternalSourceHook, bumpNotifyEpoch, setPassLane, joinPassTx, freshHook, GlobalQueue, laneOutcomeHook, passTx, holdNode, laneDirty, heldTrim, staleReader, laneSeamsHook, stagedReaders, deferZombie } from "./scheduler.js";
14
14
 
15
15
  // The heap's per-node step. A tracked effect's heap visit is its compute
16
16
  // phase — empty, like a user effect whose compute reads nothing — and hands
@@ -78,7 +78,7 @@ function ownerInSnapshotScope(e) {
78
78
 
79
79
  function setSnapshotCapture(e) {
80
80
  snapshotCaptureActive = e;
81
- if (e && !snapshotSources) snapshotSources = new Set;
81
+ if (e && !snapshotSources) snapshotSources = new NewSet;
82
82
  }
83
83
 
84
84
  function markSnapshotScope(e) {
@@ -199,7 +199,7 @@ function recompute(e, t = false) {
199
199
  // after the new frame's first runs, and the held pass's own
200
200
  // children die below (`disposeChildren`). The landing re-derives a
201
201
  // fresh held frame for the reveal (`_reruns`).
202
- e.se = NOT_PENDING;
202
+ e.ue = NOT_PENDING;
203
203
  if (n === EFFECT_RENDER) e.l &= ~CONFIG_HELD;
204
204
  }
205
205
  }
@@ -210,7 +210,9 @@ function recompute(e, t = false) {
210
210
  // and own nothing to park.
211
211
  if (n === EFFECT_TRACKED || e.l & CONFIG_STAGED) {
212
212
  // A lazy node only those children read is released when this pass
213
- // ends, if the pass doesn't read it again (#4142).
213
+ // ends, if the pass doesn't read it again (#4142). A cleanup or
214
+ // `unobserved` hook that throws here neither stops the pass nor skips
215
+ // that drain: its error is rethrown when the flush ends (owner.ts).
214
216
  const t = deferredReleases;
215
217
  deferReleases(o = []);
216
218
  disposeChildren(e);
@@ -219,7 +221,7 @@ function recompute(e, t = false) {
219
221
  // An error a pass staged (D-41) is replaced by this pass's answer: a
220
222
  // value, a flight, or an error of its own (#4109, #4145).
221
223
  if (e.l & CONFIG_STAGED_ERROR) {
222
- e.se = NOT_PENDING;
224
+ e.ue = NOT_PENDING;
223
225
  e.l ^= CONFIG_STAGED_ERROR;
224
226
  }
225
227
  }
@@ -255,7 +257,7 @@ function recompute(e, t = false) {
255
257
  e.ie = clock;
256
258
  // The pass's previous value: its staging, else a lane node's lane value
257
259
  // when it has one (lanes.ts), else the committed one.
258
- let T = e.se !== NOT_PENDING ? e.se : e.l & CONFIG_OVERRIDE && e.T.v !== NOT_PENDING ? e.T.v : e.Ie;
260
+ let T = e.ue !== NOT_PENDING ? e.ue : e.l & CONFIG_OVERRIDE && e.T.v !== NOT_PENDING ? e.T.v : e.Ie;
259
261
  let S = e.Fe;
260
262
  let c = false;
261
263
  // L2: did this pass read the future (REACTIVE_JOINED, set by read) — or,
@@ -305,7 +307,7 @@ function recompute(e, t = false) {
305
307
  // D-41: a pass of a held node, or one that read a staging of this
306
308
  // flush, stages its error (`stageError`).
307
309
  if (n || !(I = stageError(e, t, true))) {
308
- if (n) ext(e).ue = true;
310
+ if (n) ext(e).se = true;
309
311
  notifyStatus(e, n ? STATUS_PENDING : STATUS_ERROR, t);
310
312
  // The replacement source is fully propagated now. If no new flight
311
313
  // re-owned self, retire the superseded flight and its dependent copies.
@@ -331,7 +333,10 @@ function recompute(e, t = false) {
331
333
  // (markNode(c) in read()) marks the running node as part of ordinary
332
334
  // bookkeeping, and those marks are correctly discarded here.
333
335
  c = (e.R & REACTIVE_MISSED_WAKE) !== 0;
334
- A = !!(e.R & (i ? REACTIVE_JOINED | REACTIVE_STAGED_READ : REACTIVE_JOINED));
336
+ // A mount's staged read joins it — except a nested derivation left
337
+ // pending: no render effect reaches it yet, so it holds nothing and
338
+ // reaches no boundary (Reader, #4203). A reader that does is caught.
339
+ A = !!(e.R & (i && (E || n || !(e.m & STATUS_PENDING)) ? REACTIVE_JOINED | REACTIVE_STAGED_READ : REACTIVE_JOINED));
335
340
  // A verdict reader is one for as long as it probes, or reads a memo
336
341
  // that does (`read`): a pass that did neither (REACTIVE_PROBED, the same
337
342
  // bit) is an ordinary derivation again.
@@ -385,7 +390,7 @@ function recompute(e, t = false) {
385
390
  // publishes to — the staged `_pendingValue` when staged; a lane node's
386
391
  // lane value (a node entering the lane this pass has none yet — its
387
392
  // committed value, `laneStage` having voided a frame staging).
388
- const o = e.se !== NOT_PENDING ? e.se : e.l & CONFIG_OVERRIDE && e.T.v !== NOT_PENDING ? e.T.v : e.Ie;
393
+ const o = e.ue !== NOT_PENDING ? e.ue : e.l & CONFIG_OVERRIDE && e.T.v !== NOT_PENDING ? e.T.v : e.Ie;
389
394
  // A throwing comparator is contained inside `isSame` (A3, #2837; #4111).
390
395
  const E = !n && l || !!e.T?.de || !isSame(e, o, T);
391
396
  // Effects use `_equals: false` (no per-effect closure). The side effects that
@@ -398,7 +403,7 @@ function recompute(e, t = false) {
398
403
  // last applied owes no run (lanes.ts).
399
404
  !(e.R & REACTIVE_SCREEN_READ && !e.Ae && Object.is(e.ge, T)) &&
400
405
  // A re-staging pass owes no run: the commit replays the effect (#3802).
401
- e.se === NOT_PENDING) {
406
+ e.ue === NOT_PENDING) {
402
407
  e.Ae = !e.T?.F;
403
408
  // Reuse one bound runner per effect — runEffect no-ops on a stale
404
409
  // `_modified`, so re-enqueueing the same function is harmless.
@@ -437,8 +442,8 @@ function recompute(e, t = false) {
437
442
  // effect's value slot is private: the run the lane holds is what
438
443
  // shows it — unless the effect was born held, whose first run is the
439
444
  // commit's (A29): it re-stages, as below.
440
- if (n && e.se === NOT_PENDING) e.Ie = T; else if (n || C.ke) e.se = T; else e.T.v = T;
441
- } else if (t ? !((flushTransaction !== null || passTx !== null) && (A || creatorPass(f)?.R & REACTIVE_JOINED)) : n && e.se === NOT_PENDING) {
445
+ if (n && e.ue === NOT_PENDING) e.Ie = T; else if (n || C.ke) e.ue = T; else e.T.v = T;
446
+ } else if (t ? !((flushTransaction !== null || passTx !== null) && (A || creatorPass(f)?.R & REACTIVE_JOINED)) : n && e.ue === NOT_PENDING) {
442
447
  // A first pass publishes directly. So does an effect: its value slot
443
448
  // is private (only its own run reads it), and the staging round-trip
444
449
  // (queuePendingNode + commitPendingNodes) paid per effect on the
@@ -451,7 +456,7 @@ function recompute(e, t = false) {
451
456
  // that separation; it cannot be skipped on any path a pull can reach.
452
457
  e.Ie = T;
453
458
  } else {
454
- e.se = T;
459
+ e.ue = T;
455
460
  // Born held: in the transaction from birth, so a read of it this
456
461
  // tick is a read of its world (the mount-during-a-hold case), not
457
462
  // only after the seam parks it.
@@ -527,7 +532,7 @@ function recompute(e, t = false) {
527
532
  // kept at a park — one spurious recompute at most). A creation pass and a
528
533
  // tracked effect trim now: their frames are replaceable like a direct
529
534
  // commit, and a tracked effect's spurious run would be user-visible.
530
- if (!e.T?.F && e.se === NOT_PENDING && !(n && e.Ae)) {
535
+ if (!e.T?.F && e.ue === NOT_PENDING && !(n && e.Ae)) {
531
536
  if (t || n === EFFECT_TRACKED) trimStaleDeps(e); else heldTrim(e);
532
537
  }
533
538
  // A staged value, a parked frame (L2: the commit retires it), or status the
@@ -535,7 +540,7 @@ function recompute(e, t = false) {
535
540
  // node for this flush's commit. A first pass queues only when pending or
536
541
  // born held: otherwise its value published directly. A queued pass's
537
542
  // children are uncommitted (CONFIG_STAGED) until that commit.
538
- if (e.se !== NOT_PENDING || e.T !== null && (e.T.Ue !== null || e.T.me !== null) || (e.m & (STATUS_PENDING | STATUS_UNINITIALIZED)) !== 0 && (!t || (e.m & STATUS_PENDING) !== 0)) {
543
+ if (e.ue !== NOT_PENDING || e.T !== null && (e.T.Ue !== null || e.T.me !== null) || (e.m & (STATUS_PENDING | STATUS_UNINITIALIZED)) !== 0 && (!t || (e.m & STATUS_PENDING) !== 0)) {
539
544
  e.l |= CONFIG_STAGED;
540
545
  // Lane work is the lane's to reveal (its seam), not this flush's commit.
541
546
  if (C === null) queuePendingNode(e);
@@ -624,7 +629,7 @@ function computed(e, t) {
624
629
  // A loadingValue node is born committed: commit #0 is already in _value.
625
630
  m: o ? 0 : STATUS_UNINITIALIZED,
626
631
  ie: clock,
627
- se: NOT_PENDING,
632
+ ue: NOT_PENDING,
628
633
  Me: -1,
629
634
  ce: o,
630
635
  // Cold machinery (async slots) lives one hop away in the lazily-allocated extension — the core literal MUST
@@ -645,7 +650,7 @@ function computed(e, t) {
645
650
  Se: null,
646
651
  ae: null,
647
652
  F: undefined,
648
- ue: undefined,
653
+ se: undefined,
649
654
  fe: undefined,
650
655
  we: undefined,
651
656
  Pe: undefined,
@@ -653,8 +658,8 @@ function computed(e, t) {
653
658
  me: null,
654
659
  Re: null,
655
660
  Ve: false,
656
- Be: NOT_PENDING,
657
- Qe: -1,
661
+ je: NOT_PENDING,
662
+ Be: -1,
658
663
  Ee: 0,
659
664
  v: NOT_PENDING,
660
665
  I: 0,
@@ -682,6 +687,12 @@ function computed(e, t) {
682
687
  if (t.m & STATUS_UNINITIALIZED || e.i !== EFFECT_RENDER) joinPassTx(txOf(t), !e.i && e.m & STATUS_UNINITIALIZED || freshHook?.(e));
683
688
  }
684
689
 
690
+ /** `joinPass` into the hold `t` (a folded guess's: store/optimistic.ts
691
+ * `foldedRead`, D-34) — a reader is never a render effect there. */ function joinPassAt(e, t) {
692
+ e.R |= REACTIVE_JOINED;
693
+ joinPassTx(t, !e.i && e.m & STATUS_UNINITIALIZED || freshHook?.(e));
694
+ }
695
+
685
696
  /** A15's stale reader (shared-hole and reveal corollaries): a render effect
686
697
  * reading a node held by a transaction that is not the flush's. It is served
687
698
  * the committed value — a pending one's too — publishes mainline, and is
@@ -739,7 +750,7 @@ function computed(e, t) {
739
750
 
740
751
  /** The computed whose pass a creation under `owner` belongs to (a root's is
741
752
  * its nearest computed ancestor's), or null outside every pass. */ function creatorPass(e) {
742
- return e !== null && e.je ? e.Ye : e;
753
+ return e !== null && e.Qe ? e.Ye : e;
743
754
  }
744
755
 
745
756
  /** L2 — park the previous pass's frame for this node's commit: the children
@@ -791,7 +802,7 @@ function computed(e, t) {
791
802
  R: REACTIVE_LAZY,
792
803
  m: STATUS_UNINITIALIZED,
793
804
  ie: clock,
794
- se: NOT_PENDING,
805
+ ue: NOT_PENDING,
795
806
  Me: -1,
796
807
  ce: false,
797
808
  Ae: false,
@@ -828,7 +839,7 @@ function setEffectStatusNotify(e) {
828
839
  * 0, and only effect literals carry the field) get the shared notifier.
829
840
  * Presence doubles as the "display consumer" membership test in the status
830
841
  * walks, exactly as the per-node field did when every effect carried one. */ function statusNotifierOf(e) {
831
- return e.i ? effectStatusNotify ?? undefined : undefined;
842
+ return e.i ? effectStatusNotify : null;
832
843
  }
833
844
 
834
845
  const lazyOptions = {
@@ -837,7 +848,7 @@ const lazyOptions = {
837
848
 
838
849
  function setupComputedNode(e, t) {
839
850
  e.We = e;
840
- const n = context?.je ? context.Ye : context;
851
+ const n = context?.Qe ? context.Ye : context;
841
852
  if (context) linkChild(context, e);
842
853
  if (n) e.Fe = n.Fe + 1;
843
854
  wireExternalSourceHook?.(e);
@@ -863,7 +874,7 @@ function signal(e, t) {
863
874
  o: null,
864
875
  ve: null,
865
876
  ie: clock,
866
- se: NOT_PENDING,
877
+ ue: NOT_PENDING,
867
878
  // Signal-literal diet (§12e): NO _time/_fn/_statusFlags slots. Stores
868
879
  // materialize one signal per touched leaf, so signal bytes are store
869
880
  // bytes. _time is write-only on signals (every read site is computed-
@@ -903,7 +914,7 @@ function isEqual(e, t) {
903
914
  o: null,
904
915
  ve: null,
905
916
  ie: clock,
906
- se: NOT_PENDING,
917
+ ue: NOT_PENDING,
907
918
  Me: -1,
908
919
  T: null,
909
920
  O: n,
@@ -1033,7 +1044,7 @@ function setVerdict(e) {
1033
1044
  * never the graph — A32).
1034
1045
  * False means the reader derives from the staged value.
1035
1046
  */ function readerSeesCommitted(e, t) {
1036
- return !t || e.se === NOT_PENDING || t.l & CONFIG_CHILDREN_FORBIDDEN;
1047
+ return !t || e.ue === NOT_PENDING || t.l & CONFIG_CHILDREN_FORBIDDEN;
1037
1048
  }
1038
1049
 
1039
1050
  /** A28 — set when a node is staged (queuePendingNode) OUTSIDE a flush;
@@ -1054,11 +1065,11 @@ function markUnflushedStaged() {
1054
1065
  * is unflushed. Exempt: promoted writes (A28 (4): a write issued inside a
1055
1066
  * recompute is promoted at that recompute's end — boundary and loading
1056
1067
  * machinery, signals declared for in-computation writes). */ function unflushedValue(e) {
1057
- if (globalQueue.q || e.se === NOT_PENDING || e.l & CONFIG_PROMOTED) return NOT_PENDING;
1068
+ if (globalQueue.q || e.ue === NOT_PENDING || e.l & CONFIG_PROMOTED) return NOT_PENDING;
1058
1069
  // A held node (L2) carries a staged value between flushes too, but it was
1059
1070
  // flushed — and parked. Not an unflushed write — unless rewritten since:
1060
1071
  // then the flushed staging is what the world sees until the next flush.
1061
- if (e.l & CONFIG_HELD) return e.T.Qe === clock ? e.T.Be : NOT_PENDING;
1072
+ if (e.l & CONFIG_HELD) return e.T.Be === clock ? e.T.je : NOT_PENDING;
1062
1073
  return e.Ie;
1063
1074
  }
1064
1075
 
@@ -1066,9 +1077,9 @@ function markUnflushedStaged() {
1066
1077
  * flush left (`_flushed`) until the flush that carries the rewrite — the
1067
1078
  * stash is stamped with the clock, which that flush advances. */ function stashFlushed(e) {
1068
1079
  const t = e.T;
1069
- if (t.Qe !== clock) {
1070
- t.Be = e.se;
1071
- t.Qe = clock;
1080
+ if (t.Be !== clock) {
1081
+ t.je = e.ue;
1082
+ t.Be = clock;
1072
1083
  unflushedStaged = true;
1073
1084
  }
1074
1085
  }
@@ -1115,7 +1126,7 @@ function resyncUnflushedCompanions() {
1115
1126
 
1116
1127
  function read(e) {
1117
1128
  let t = context;
1118
- if (t?.je) t = t.Ye;
1129
+ if (t?.Qe) t = t.Ye;
1119
1130
  const n = e;
1120
1131
  // CARVE 1: `owner` was `el._firewall || el` — a store leaf's status,
1121
1132
  // height and error were read off its projection computed. (Typed loosely
@@ -1126,7 +1137,7 @@ function read(e) {
1126
1137
  // The verdict windows (`latest`, `isPending`; verdict.ts): one cold path,
1127
1138
  // off this one, present only while a window is open.
1128
1139
  if (verdict !== null) return verdict(e, t);
1129
- if (!n.X && e.T?.Pe === undefined && !snapshotCaptureActive && (!unflushedStaged || e.se === NOT_PENDING) && // A28, see readNodeFast
1140
+ if (!n.X && e.T?.Pe === undefined && !snapshotCaptureActive && (!unflushedStaged || e.ue === NOT_PENDING) && // A28, see readNodeFast
1130
1141
  !(e.l & CONFIG_OVERRIDE) && // lanes: the slow path's one arm
1131
1142
  true) {
1132
1143
  if (t) {
@@ -1147,11 +1158,13 @@ function read(e) {
1147
1158
  }
1148
1159
  }
1149
1160
  // Committed visibility for untracked and children-forbidden readers.
1150
- if (!t || e.se === NOT_PENDING || t.l & CONFIG_CHILDREN_FORBIDDEN) return e.Ie;
1161
+ if (!t || e.ue === NOT_PENDING || t.l & CONFIG_CHILDREN_FORBIDDEN) return e.Ie;
1151
1162
  // (A plain signal is never uninitialized — the carve-out is `serve`'s.)
1152
- if (observeFlightHook && t.l & CONFIG_VERDICT && stagedScreen(t)) return e.Ie;
1163
+ // Lane work over a folded lane (D-34), once the frame has a
1164
+ // transaction, reads the screen too.
1165
+ if ((observeFlightHook && t.l & CONFIG_VERDICT || laneStageHook && passLane?.folded) && stagedScreen(t)) return e.Ie;
1153
1166
  stagedRead(t);
1154
- return e.se;
1167
+ return e.ue;
1155
1168
  }
1156
1169
  // Rule 3: this read serves the committed value to a render effect outside a
1157
1170
  // parking flush (decided after the pull below — a pull can join).
@@ -1237,7 +1250,7 @@ function read(e) {
1237
1250
  const n = e.T?.Pe;
1238
1251
  if (n !== undefined) {
1239
1252
  const o = n === NO_SNAPSHOT ? undefined : n;
1240
- const E = e.se !== NOT_PENDING ? e.se : e.Ie;
1253
+ const E = e.ue !== NOT_PENDING ? e.ue : e.Ie;
1241
1254
  if (E !== o) t.R |= REACTIVE_SNAPSHOT_STALE;
1242
1255
  return o;
1243
1256
  }
@@ -1274,7 +1287,7 @@ function read(e) {
1274
1287
  // an untracked reader has nothing to serve and holds (A19 exception 1) — a
1275
1288
  // bookkeeping read (`spectate`) likewise, and a children-forbidden reader
1276
1289
  // (A32: the frame, which has nothing here).
1277
- if (e.se !== NOT_PENDING && e.m & STATUS_UNINITIALIZED && (!t || spectating || t.l & CONFIG_CHILDREN_FORBIDDEN)) throw new NotReadyError(null);
1290
+ if (e.ue !== NOT_PENDING && e.m & STATUS_UNINITIALIZED && (!t || spectating || t.l & CONFIG_CHILDREN_FORBIDDEN)) throw new NotReadyError(null);
1278
1291
  const n = t && unflushedStaged ? unflushedValue(e) : NOT_PENDING;
1279
1292
  if (n !== NOT_PENDING) {
1280
1293
  markLateLinker(t);
@@ -1284,13 +1297,16 @@ function read(e) {
1284
1297
  if (observeFlightHook && t.l & CONFIG_VERDICT && !(e.m & STATUS_UNINITIALIZED) && stagedScreen(t)) return e.Ie;
1285
1298
  stagedRead(t, e);
1286
1299
  // D-41: the staged answer is an error (`stageError`).
1287
- if (e.l & CONFIG_STAGED_ERROR) throw e.se;
1288
- return e.se;
1300
+ if (e.l & CONFIG_STAGED_ERROR) throw e.ue;
1301
+ return e.ue;
1289
1302
  }
1290
1303
 
1291
1304
  /** A pass read a staging of this flush: it derives from what the flush may
1292
1305
  * yet hold (REACTIVE_STAGED_READ — the seam decides). Lane work too (plan sec. 28, a
1293
- * lane sees the screen plus its own guesses): the staging is the screen if
1306
+ * lane sees the screen plus its own guesses) — a folded lane's only before
1307
+ * the frame has a transaction (after, it reads the screen like a verdict
1308
+ * reader, `stagedScreen` — a list pass never pairs a held length with the
1309
+ * guess's rows, D-34): the staging is the screen if
1294
1310
  * the frame commits — one pass, the common case — and a held write if it
1295
1311
  * parks, which the seam repairs (`stagedReaders`): the pass re-derives on
1296
1312
  * the committed world and its lane's runs wait that round, so the held
@@ -1377,7 +1393,7 @@ function read(e) {
1377
1393
 
1378
1394
  function setSignal(e, t) {
1379
1395
  if (writeHook !== null) writeHook(e);
1380
- const n = e.se === NOT_PENDING ? e.Ie : e.se;
1396
+ const n = e.ue === NOT_PENDING ? e.Ie : e.ue;
1381
1397
  if (typeof t === "function") t = t(n);
1382
1398
  // Lanes: a write landing on a guess is its truth (A18 — an async guess's
1383
1399
  // own landing), judged against the guess; one landing on a lane's
@@ -1404,12 +1420,12 @@ function setSignal(e, t) {
1404
1420
  // pass reads a value the server never had. Store leaves written here are
1405
1421
  // plain signals and qualify the same way.
1406
1422
  if (snapshotCaptureActive) captureWriteSnapshot(e, n);
1407
- const E = e.se !== NOT_PENDING;
1423
+ const E = e.ue !== NOT_PENDING;
1408
1424
  if (!E) queuePendingNode(e);
1409
1425
  // A28: a held node rewritten outside a flush keeps its flushed staging
1410
1426
  // for every reader until the flush that carries the rewrite.
1411
1427
  else if (e.l & CONFIG_HELD && !globalQueue.q) stashFlushed(e);
1412
- e.se = t;
1428
+ e.ue = t;
1413
1429
  if (context !== null) notePromotedWrite(e);
1414
1430
  // _time is a computed-only slot (§12e): writing it on a signal would fork
1415
1431
  // the lean shape. Every read site is computed-typed.
@@ -1466,7 +1482,7 @@ function setSignal(e, t) {
1466
1482
  e.R = e.R & ~REACTIVE_CHECK | REACTIVE_DIRTY;
1467
1483
  insertIntoHeap(e, dirtyQueue);
1468
1484
  schedule();
1469
- } else if (e.se !== NOT_PENDING) e.R |= REACTIVE_MANUAL_WRITE;
1485
+ } else if (e.ue !== NOT_PENDING) e.R |= REACTIVE_MANUAL_WRITE;
1470
1486
  return t;
1471
1487
  }
1472
1488
 
@@ -1527,4 +1543,4 @@ function staleValues(e, t = true) {
1527
1543
  }
1528
1544
  }
1529
1545
 
1530
- export { bornInto, captureWriteSnapshot, clearSnapshots, computed, context, createEffectNode, effectStatusNotify, ext, isEqual, isSame, joinPass, markLateLinker, markRefresh, markSnapshotScope, markUnflushedStaged, notePromotedWrite, prepareComputed, pullComputed, read, readerSeesCommitted, recompute, releaseSnapshotScope, resyncUnflushedCompanions, runWithOwner, serve, setEffectStatusNotify, setMemo, setSignal, setSnapshotCapture, setVerdict, setWriteHook, signal, slotSignal, snapshotCaptureActive, snapshotSources, spectating, stagedRead, stagedScreen, staleValues, stashFlushed, statusNotifierOf, tracking, unflushedStaged, unflushedValue, untrack, verdict, writeHook };
1546
+ export { bornInto, captureWriteSnapshot, clearSnapshots, computed, context, createEffectNode, effectStatusNotify, ext, isEqual, isSame, joinPass, joinPassAt, markLateLinker, markRefresh, markSnapshotScope, markUnflushedStaged, notePromotedWrite, prepareComputed, pullComputed, read, readerSeesCommitted, recompute, releaseSnapshotScope, resyncUnflushedCompanions, runWithOwner, serve, setEffectStatusNotify, setMemo, setSignal, setSnapshotCapture, setVerdict, setWriteHook, signal, slotSignal, snapshotCaptureActive, snapshotSources, spectating, stagedRead, stagedScreen, staleValues, stashFlushed, statusNotifierOf, tracking, unflushedStaged, unflushedValue, untrack, verdict, writeHook };
@@ -15,42 +15,47 @@ import { globalQueue, schedule, heldRunHook, haltReactivity, GlobalQueue } from
15
15
  * automatically added to the queue of effects to re-execute, which will cause them to fetch their
16
16
  * sources and recompute
17
17
  */ function effect(t, e, E, u) {
18
- const f = !!u?.user;
19
- const r = createEffectNode(t, e, E, f ? EFFECT_USER : EFFECT_RENDER, u);
20
- recompute(r, true);
18
+ const r = !!u?.user;
19
+ const f = createEffectNode(t, e, E, r ? EFFECT_USER : EFFECT_RENDER, u);
20
+ recompute(f, true);
21
21
  // A first pass that read a staged value was staged itself (recompute: born
22
22
  // staged); the flush's commit replays this effect. Its first run is not this
23
23
  // creation's (A29). A lane's node likewise: its run is the lane's, queued
24
24
  // by the pass and released at the reveal (lanes.ts).
25
- !u?.defer && r.se === NOT_PENDING && !r.T?.Re?.v && (r.i === EFFECT_USER || u?.schedule ? globalQueue.enqueue(r.i, runEffect.bind(null, r)) : runEffect(r, r.i));
25
+ !u?.defer && f.ue === NOT_PENDING && !f.T?.Re?.v && (f.i === EFFECT_USER || u?.schedule ? globalQueue.enqueue(f.i, runEffect.bind(null, f)) : runEffect(f, f.i));
26
26
  }
27
27
 
28
- function notifyEffectStatus(t, e) {
29
- // Use passed values if provided, otherwise read from node
30
- const E = t !== undefined ? t : this.m;
31
- const u = e !== undefined ? e : this.T?.F;
32
- if (E & STATUS_ERROR) {
28
+ function notifyEffectStatus() {
29
+ const t = this.m;
30
+ const e = this.T?.F;
31
+ if (t & STATUS_ERROR) {
33
32
  globalQueue.notify(this, STATUS_PENDING, 0);
34
33
  if (this.i === EFFECT_USER) {
35
34
  // The error handler is the error arm of the effect phase (#2840 ruling):
36
35
  // queue it like the effect function. It runs in the same imperative,
37
36
  // writable scope and throws escalate the same way. No payload is queued — the node already carries `_statusFlags`/`_error`,
38
37
  // and the runner dispatches on them, so a recovery before the effect
39
- // phase takes the success arm instead. Blocked forwards (explicit
40
- // `status` arg without node-state writes) don't queue: the status
41
- // re-propagates unblocked at commit.
38
+ // phase takes the success arm instead. (Re-read: the boundary
39
+ // notify above can run a redraw.)
42
40
  if (this.m & STATUS_ERROR) {
43
41
  this.Ae = true;
44
42
  globalQueue.enqueue(this.i, this.W ??= runEffect.bind(null, this));
45
43
  }
46
44
  return;
47
45
  }
48
- if (!globalQueue.notify(this, STATUS_ERROR, STATUS_ERROR)) {
49
- haltReactivity(unwrapStatusError(u));
50
- throw u;
51
- }
46
+ // Dev locates the halt at the node that threw: the StatusError's
47
+ // source, upstream of this effect when a computed failed.
48
+ escalate(this, unwrapStatusError(e), e, this);
52
49
  } else if (this.i === EFFECT_RENDER) {
53
- globalQueue.notify(this, STATUS_PENDING | STATUS_ERROR, E, u);
50
+ globalQueue.notify(this, STATUS_PENDING | STATUS_ERROR, t, e);
51
+ }
52
+ }
53
+
54
+ /** An effect's error no boundary takes halts the system (boundary-or-halt):
55
+ * halted with `cause`, `thrown` rethrown; dev locates it at `subject`. */ function escalate(t, e, E = e, u = t) {
56
+ if (!globalQueue.notify(t, STATUS_ERROR, STATUS_ERROR)) {
57
+ haltReactivity(e);
58
+ throw E;
54
59
  }
55
60
  }
56
61
 
@@ -86,10 +91,7 @@ function runEffect(t, e) {
86
91
  e?.();
87
92
  }) : console.error(e);
88
93
  } catch (e) {
89
- if (!globalQueue.notify(t, STATUS_ERROR, STATUS_ERROR)) {
90
- haltReactivity(e);
91
- throw e;
92
- }
94
+ escalate(t, e);
93
95
  }
94
96
  return;
95
97
  }
@@ -107,10 +109,7 @@ function runEffect(t, e) {
107
109
  } catch (e) {
108
110
  ext(t).F = new StatusError(t, e);
109
111
  t.m |= STATUS_ERROR;
110
- if (!globalQueue.notify(t, STATUS_ERROR, STATUS_ERROR)) {
111
- haltReactivity(e);
112
- throw e;
113
- }
112
+ escalate(t, e);
114
113
  } finally {
115
114
  t.ge = t.Ie;
116
115
  t.Ae = false;
@@ -1,8 +1,8 @@
1
- import { REACTIVE_RECOMPUTING_DEPS, CONFIG_AUTO_DISPOSE, STATUS_PENDING, REACTIVE_DISPOSED, REACTIVE_ZOMBIE, CONFIG_SLOT_NODE } from "./constants.js";
1
+ import { NewSet, REACTIVE_RECOMPUTING_DEPS, CONFIG_AUTO_DISPOSE, STATUS_PENDING, REACTIVE_DISPOSED, REACTIVE_ZOMBIE, CONFIG_SLOT_NODE } from "./constants.js";
2
2
 
3
3
  import { deleteFromHeap } from "./heap.js";
4
4
 
5
- import { disposeChildren } from "./owner.js";
5
+ import { disposeChildren, runCleanup } from "./owner.js";
6
6
 
7
7
  import { bumpNotifyEpoch, dirtyQueue } from "./scheduler.js";
8
8
 
@@ -27,7 +27,9 @@ function unlinkSubs(e) {
27
27
  if (o === null) {
28
28
  // Store slot nodes dispatch to the ONE shared hook — no per-node
29
29
  // unobserved closure, no NodeExtension to hold it.
30
- if (n.l & CONFIG_SLOT_NODE) slotUnobservedHook(n); else n.T?.we?.();
30
+ // A user hook that throws is collected like a cleanup (owner.ts): the
31
+ // unlink and the teardown around it carry on.
32
+ if (n.l & CONFIG_SLOT_NODE) slotUnobservedHook(n); else if (n.T?.we) runCleanup(n.T.we);
31
33
  // No more subscribers; only tear down if CONFIG_AUTO_DISPOSE is set.
32
34
  // A pending node is exempt: its in-flight async work is an observer —
33
35
  // tearing down would orphan the work and re-execute it on the next
@@ -105,7 +107,7 @@ function unobserved(e) {
105
107
  * stays alive and serves its cache, uniform with observed memos) while
106
108
  * reclamation still happens within one microtask — the enqueue site arms
107
109
  * schedule(), so a flush is guaranteed even when no other work is queued.
108
- */ const dormantNodes = new Set;
110
+ */ const dormantNodes = new NewSet;
109
111
 
110
112
  function sweepDormant() {
111
113
  if (dormantNodes.size === 0) return;
@@ -141,18 +143,18 @@ function link(e, n) {
141
143
  // [deps.._depsTail] prefix — the O(1) equivalent of scanning the dep list
142
144
  // (the old alien-signals `isValidLink` walk, O(n²) when a computation
143
145
  // re-reads earlier deps non-consecutively, e.g. store leaf reads).
144
- const t = e.ve;
145
- if (t !== null && t.u === n && (!s || t.Te === n.Ne)) return;
146
- const r = n._ = e.ve = {
146
+ const r = e.ve;
147
+ if (r !== null && r.u === n && (!s || r.Te === n.Ne)) return;
148
+ const t = n._ = e.ve = {
147
149
  S: e,
148
150
  u: n,
149
151
  G: o,
150
- Xe: t,
152
+ Xe: r,
151
153
  t: null,
152
154
  Te: n.Ne
153
155
  };
154
- if (l !== null) l.G = r; else n.C = r;
155
- if (t !== null) t.t = r; else e.o = r;
156
+ if (l !== null) l.G = t; else n.C = t;
157
+ if (r !== null) r.t = t; else e.o = t;
156
158
  // New subscriber edge: staged-rewrite skips (§12d) must not miss it.
157
159
  bumpNotifyEpoch();
158
160
  }
@@ -16,7 +16,7 @@ import { dirtyQueue } from "./scheduler.js";
16
16
  }
17
17
 
18
18
  function actualInsertIntoHeap(e, t) {
19
- const E = (e._parent?.je ? e._parent.Ye?.Fe : e._parent?.Fe) ?? -1;
19
+ const E = (e._parent?.Qe ? e._parent.Ye?.Fe : e._parent?.Fe) ?? -1;
20
20
  if (E >= e.Fe) e.Fe = E + 1;
21
21
  const n = e.Fe;
22
22
  const I = t.$e[n];