@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
@@ -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_PLUMBING, 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_PLUMBING, 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
 
@@ -14,7 +14,7 @@ import { attrHooks } from "./attribution-hooks.js";
14
14
 
15
15
  import { inheritId, linkChild, disposeChildren, markDisposal } from "./owner.js";
16
16
 
17
- 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";
17
+ 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";
18
18
 
19
19
  // The heap's per-node step. A tracked effect's heap visit is its compute
20
20
  // phase — empty, like a user effect whose compute reads nothing — and hands
@@ -82,7 +82,7 @@ function ownerInSnapshotScope(e) {
82
82
 
83
83
  function setSnapshotCapture(e) {
84
84
  snapshotCaptureActive = e;
85
- if (e && !snapshotSources) snapshotSources = new Set;
85
+ if (e && !snapshotSources) snapshotSources = new NewSet;
86
86
  }
87
87
 
88
88
  function markSnapshotScope(e) {
@@ -222,7 +222,9 @@ function recompute(e, t = false) {
222
222
  // and own nothing to park.
223
223
  if (n === EFFECT_TRACKED || e.l & CONFIG_STAGED) {
224
224
  // A lazy node only those children read is released when this pass
225
- // ends, if the pass doesn't read it again (#4142).
225
+ // ends, if the pass doesn't read it again (#4142). A cleanup or
226
+ // `unobserved` hook that throws here neither stops the pass nor skips
227
+ // that drain: its error is rethrown when the flush ends (owner.ts).
226
228
  const t = deferredReleases;
227
229
  deferReleases(o = []);
228
230
  disposeChildren(e);
@@ -240,15 +242,15 @@ function recompute(e, t = false) {
240
242
  // A conditional can drop its pending source and recover to an unchanged
241
243
  // value, leaving blocked dependents outside that source’s settle walk.
242
244
  const u = e.m & STATUS_ERROR ? e.T?.F : undefined;
243
- const _ = (e.m & STATUS_PENDING) !== 0;
244
- const a = _ ? e.T?.fe : undefined;
245
+ const a = (e.m & STATUS_PENDING) !== 0;
246
+ const _ = a ? e.T?.se : undefined;
245
247
  // Pending SOURCE-hood, captured before the compute clears status: a node
246
248
  // whose own flight parked dependents self-registers in _pendingSources
247
249
  // (notifyStatus, isSource). If this recompute supersedes that flight and
248
250
  // settles synchronously, those dependents settle HERE — asyncWrite's
249
251
  // settlePendingSource walk never runs for a landing that was preempted
250
252
  // (#3181).
251
- const I = e.T?.fe?.has(e);
253
+ const I = e.T?.se?.has(e);
252
254
  // D-41: this pass's error is staged, not published (`stageError`).
253
255
  let N = false;
254
256
  // A19 exc. 2: a `refresh()` re-asks the question already answered — quiet
@@ -273,7 +275,7 @@ function recompute(e, t = false) {
273
275
  // L2: did this pass read the future (REACTIVE_JOINED, set by read) — or,
274
276
  // a lane's mount, a staging of this flush (#3851, #4166)?
275
277
  let R = false;
276
- let d = tracking;
278
+ let C = tracking;
277
279
  tracking = true;
278
280
  try {
279
281
  if (!false && e.l & CONFIG_SYNC) {
@@ -317,7 +319,7 @@ function recompute(e, t = false) {
317
319
  // D-41: a pass of a held node, or one that read a staging of this
318
320
  // flush, stages its error (`stageError`).
319
321
  if (n || !(N = stageError(e, t, true))) {
320
- if (n) ext(e).se = true;
322
+ if (n) ext(e).fe = true;
321
323
  notifyStatus(e, n ? STATUS_PENDING : STATUS_ERROR, t);
322
324
  // The replacement source is fully propagated now. If no new flight
323
325
  // re-owned self, retire the superseded flight and its dependent copies.
@@ -332,10 +334,10 @@ function recompute(e, t = false) {
332
334
  // re-park twin of the unchanged-value recovery sweep below; dependents
333
335
  // with another path keep the source (retryReaches). A staged error
334
336
  // answered what the pass carried: the same.
335
- if ((n || N) && a) for (const t of a) if (t !== e && !e.T?.fe?.has(t)) settlePendingSource(e, t);
337
+ if ((n || N) && _) for (const t of _) if (t !== e && !e.T?.se?.has(t)) settlePendingSource(e, t);
336
338
  }
337
339
  } finally {
338
- tracking = d;
340
+ tracking = C;
339
341
  if (laneStageHook) laneRoot = E;
340
342
  // Consume the missed-wake latch (#3037, set by insertSubs): a dep write
341
343
  // landed beneath this pass on a link it had already validated. The wipe
@@ -343,7 +345,10 @@ function recompute(e, t = false) {
343
345
  // (markNode(c) in read()) marks the running node as part of ordinary
344
346
  // bookkeeping, and those marks are correctly discarded here.
345
347
  A = (e.R & REACTIVE_MISSED_WAKE) !== 0;
346
- R = !!(e.R & (l ? REACTIVE_JOINED | REACTIVE_STAGED_READ : REACTIVE_JOINED));
348
+ // A mount's staged read joins it — except a nested derivation left
349
+ // pending: no render effect reaches it yet, so it holds nothing and
350
+ // reaches no boundary (Reader, #4203). A reader that does is caught.
351
+ R = !!(e.R & (l && (s || n || !(e.m & STATUS_PENDING)) ? REACTIVE_JOINED | REACTIVE_STAGED_READ : REACTIVE_JOINED));
347
352
  // A verdict reader is one for as long as it probes, or reads a memo
348
353
  // that does (`read`): a pass that did neither (REACTIVE_PROBED, the same
349
354
  // bit) is an ordinary derivation again.
@@ -386,12 +391,12 @@ function recompute(e, t = false) {
386
391
  // seat that read none of the lane's world has left it: its result is the
387
392
  // frame's (a derivation whose branch no longer reaches the guess). A
388
393
  // guess is written, not derived — it never leaves this way.
389
- let C = !laneStageHook ? null : passLane;
394
+ let d = !laneStageHook ? null : passLane;
390
395
  // Listed before its staging, a pending pass included (the lane's own
391
396
  // flight is the lane's); false: the pass left the lane (lanes.ts).
392
397
  const O = !!e.T?.F || N;
393
- if (laneStageHook && C !== null) {
394
- if (laneStageHook(e, C, t, O)) setPassLane(C); else C = null;
398
+ if (laneStageHook && d !== null) {
399
+ if (laneStageHook(e, d, t, O)) setPassLane(d); else d = null;
395
400
  }
396
401
  if (!O) {
397
402
  // Observe-tier fan-in (HUGE_FAN_IN): the validated prefix [_deps.._depsTail]
@@ -440,7 +445,7 @@ function recompute(e, t = false) {
440
445
  // Lane work's run is the lane's (released at its reveal) — a first
441
446
  // pass under a lane too: `effect()` skips the synchronous first run of
442
447
  // a lane's node.
443
- if (!t || C !== null) globalQueue.enqueue(n, e.W ??= GlobalQueue.K.bind(null, e), C);
448
+ if (!t || d !== null) globalQueue.enqueue(n, e.W ??= GlobalQueue.K.bind(null, e), d);
444
449
  }
445
450
  // Lanes. A written guess's own pass from the frame: its source
446
451
  // recomputed it — the truth (A18: "the source is whatever recomputes the
@@ -448,7 +453,7 @@ function recompute(e, t = false) {
448
453
  // confirm is a landing too — the lane's end commits it) and notifies
449
454
  // only a correction: an equal truth re-runs nothing (lanes.ts). (As the
450
455
  // lane's own work the lane judged it — `laneStage`.)
451
- if (!t && C === null && e.l & CONFIG_GUESS && laneOutcomeHook(e, S, O)) ; else if (O) ; else if (s) {
456
+ if (!t && d === null && e.l & CONFIG_GUESS && laneOutcomeHook(e, S, O)) ; else if (O) ; else if (s) {
452
457
  // L2: a first pass is born held — staged, committed with the
453
458
  // transaction, its effect's first run the landing's (A29; `effect()`
454
459
  // skips the synchronous first run on a staged value) — when the flush
@@ -465,14 +470,14 @@ function recompute(e, t = false) {
465
470
  // pass that runs before anything joins: the pass's input, not a verdict
466
471
  // after it. An effect still carrying an uncommitted staged value
467
472
  // re-stages: the commit applies the latest pass, not the born-held one.
468
- if (C !== null) {
473
+ if (d !== null) {
469
474
  // Lane work: the lane's value — into the slot while the lane has not
470
475
  // revealed (its reveal shows it), as its staging once it has (the
471
476
  // next reveal promotes it; the screen keeps the revealed value). An
472
477
  // effect's value slot is private: the run the lane holds is what
473
478
  // shows it — unless the effect was born held, whose first run is the
474
479
  // commit's (A29): it re-stages, as below.
475
- if (n && e.ue === NOT_PENDING) e.Ie = S; else if (n || C.It) e.ue = S; else e.T.V = S;
480
+ if (n && e.ue === NOT_PENDING) e.Ie = S; else if (n || d.It) e.ue = S; else e.T.V = S;
476
481
  } else if (t ? !((flushTransaction !== null || passTx !== null) && (R || creatorPass(T)?.R & REACTIVE_JOINED)) : n && e.ue === NOT_PENDING) {
477
482
  // A first pass publishes directly. So does an effect: its value slot
478
483
  // is private (only its own run reads it), and the staging round-trip
@@ -512,7 +517,7 @@ function recompute(e, t = false) {
512
517
  if (e.o !== null) {
513
518
  insertSubs(e);
514
519
  // Lane work re-staged: the lane's members re-derive as its work.
515
- if (C !== null) laneDirty(e, C);
520
+ if (d !== null) laneDirty(e, d);
516
521
  }
517
522
  } else if (e.Et != c) {
518
523
  for (let t = e.o; t !== null; t = t.t) insertIntoHeapHeight(t.u, dirtyQueue);
@@ -526,7 +531,7 @@ function recompute(e, t = false) {
526
531
  if (u !== undefined) settleErroredDependents(e, u);
527
532
  // Self-registration (this node's own superseded flight) is the #3181
528
533
  // sweep's business below — retiring it here too would walk twice.
529
- if (a) for (const t of a) if (t !== e) settlePendingSource(e, t);
534
+ if (_) for (const t of _) if (t !== e) settlePendingSource(e, t);
530
535
  }
531
536
  // #3181: a synchronous settle supersedes the old landing callback, so
532
537
  // recompute owns its pending-source sweep. An uninitialized node without
@@ -541,7 +546,7 @@ function recompute(e, t = false) {
541
546
  // hold's commit (D-12) — but the hold does not wait for it: its only
542
547
  // readers are behind the fallback (`_hidden`; F-4, #3540). A boundary
543
548
  // re-armed by `on` owns what it collects outright (D-14): not held.
544
- if (t && C === null && R && e.m & STATUS_PENDING) {
549
+ if (t && d === null && R && e.m & STATUS_PENDING) {
545
550
  const t = flushTransaction ?? passTx;
546
551
  if (t && !t.V && freshHook?.(e) !== CAUGHT_REARMED) holdNode(e, t);
547
552
  }
@@ -567,7 +572,7 @@ function recompute(e, t = false) {
567
572
  }
568
573
  // Observe: the posture the run executed under — lane work (optimistic:
569
574
  // overlay, never waste), a transaction's pass (held), or plain.
570
- if (attrHooks !== null) attrHooks.recomputeEnd(e, t, i, C !== null, flushTransaction !== null || passTx !== null || (e.l & CONFIG_HELD) !== 0, e.ue !== NOT_PENDING);
575
+ if (attrHooks !== null) attrHooks.recomputeEnd(e, t, i, d !== null, flushTransaction !== null || passTx !== null || (e.l & CONFIG_HELD) !== 0, e.ue !== NOT_PENDING);
571
576
  // A staged value, a parked frame (L2: the commit retires it), or status the
572
577
  // commit sweep must settle (a pending or uninitialized pass), queues the
573
578
  // node for this flush's commit. A first pass queues only when pending or
@@ -576,7 +581,7 @@ function recompute(e, t = false) {
576
581
  if (e.ue !== NOT_PENDING || e.T !== null && (e.T.Nt !== null || e.T.ft !== null) || (e.m & (STATUS_PENDING | STATUS_UNINITIALIZED)) !== 0 && (!t || (e.m & STATUS_PENDING) !== 0)) {
577
582
  e.l |= CONFIG_STAGED;
578
583
  // Lane work is the lane's to reveal (its seam), not this flush's commit.
579
- if (C === null) queuePendingNode(e);
584
+ if (d === null) queuePendingNode(e);
580
585
  }
581
586
  setPassLane(s);
582
587
  // Missed-wake reschedule (see the finally above): values this pass read
@@ -656,10 +661,10 @@ function computed(e, t) {
656
661
  _: null,
657
662
  Ne: 0,
658
663
  o: null,
659
- dt: null,
664
+ Ct: null,
660
665
  _parent: context,
661
666
  Xe: null,
662
- Ct: null,
667
+ dt: null,
663
668
  ze: null,
664
669
  R: t?.lazy ? REACTIVE_LAZY : REACTIVE_NONE,
665
670
  m: o ? 0 : STATUS_UNINITIALIZED,
@@ -689,8 +694,8 @@ function computed(e, t) {
689
694
  Se: null,
690
695
  ae: null,
691
696
  F: undefined,
692
- se: undefined,
693
697
  fe: undefined,
698
+ se: undefined,
694
699
  Dt: undefined,
695
700
  lt: undefined,
696
701
  Nt: null,
@@ -726,6 +731,12 @@ function computed(e, t) {
726
731
  if (t.m & STATUS_UNINITIALIZED || e.i !== EFFECT_RENDER) joinPassTx(txOf(t), !e.i && e.m & STATUS_UNINITIALIZED || freshHook?.(e));
727
732
  }
728
733
 
734
+ /** `joinPass` into the hold `t` (a folded guess's: store/optimistic.ts
735
+ * `foldedRead`, D-34) — a reader is never a render effect there. */ function joinPassAt(e, t) {
736
+ e.R |= REACTIVE_JOINED;
737
+ joinPassTx(t, !e.i && e.m & STATUS_UNINITIALIZED || freshHook?.(e));
738
+ }
739
+
729
740
  /** A15's stale reader (shared-hole and reveal corollaries): a render effect
730
741
  * reading a node held by a transaction that is not the flush's. It is served
731
742
  * the committed value — a pending one's too — publishes mainline, and is
@@ -827,10 +838,10 @@ function computed(e, t) {
827
838
  _: null,
828
839
  Ne: 0,
829
840
  o: null,
830
- dt: null,
841
+ Ct: null,
831
842
  _parent: context,
832
843
  Xe: null,
833
- Ct: null,
844
+ dt: null,
834
845
  ze: null,
835
846
  R: REACTIVE_LAZY,
836
847
  m: STATUS_UNINITIALIZED,
@@ -875,7 +886,7 @@ function setEffectStatusNotify(e) {
875
886
  * 0, and only effect literals carry the field) get the shared notifier.
876
887
  * Presence doubles as the "display consumer" membership test in the status
877
888
  * walks, exactly as the per-node field did when every effect carried one. */ function statusNotifierOf(e) {
878
- return e.i ? effectStatusNotify ?? undefined : undefined;
889
+ return e.i ? effectStatusNotify : null;
879
890
  }
880
891
 
881
892
  const lazyOptions = {
@@ -908,7 +919,7 @@ function signal(e, t) {
908
919
  l: (t?.ownedWrite ? CONFIG_OWNED_WRITE : 0) | (t?.le ? CONFIG_NO_SNAPSHOT : 0),
909
920
  Ie: e,
910
921
  o: null,
911
- dt: null,
922
+ Ct: null,
912
923
  ie: clock,
913
924
  ue: NOT_PENDING,
914
925
  Ot: -1,
@@ -946,7 +957,7 @@ function isEqual(e, t) {
946
957
  l: CONFIG_OWNED_WRITE | CONFIG_SLOT_NODE,
947
958
  Ie: e,
948
959
  o: null,
949
- dt: null,
960
+ Ct: null,
950
961
  ie: clock,
951
962
  ue: NOT_PENDING,
952
963
  Ot: -1,
@@ -1197,7 +1208,9 @@ function read(e) {
1197
1208
  // Committed visibility for untracked and children-forbidden readers.
1198
1209
  if (!t || e.ue === NOT_PENDING || t.l & CONFIG_CHILDREN_FORBIDDEN) return e.Ie;
1199
1210
  // (A plain signal is never uninitialized — the carve-out is `serve`'s.)
1200
- if (observeFlightHook && t.l & CONFIG_VERDICT && stagedScreen(t)) return e.Ie;
1211
+ // Lane work over a folded lane (D-34), once the frame has a
1212
+ // transaction, reads the screen too.
1213
+ if ((observeFlightHook && t.l & CONFIG_VERDICT || laneStageHook && passLane?.folded) && stagedScreen(t)) return e.Ie;
1201
1214
  stagedRead(t);
1202
1215
  return e.ue;
1203
1216
  }
@@ -1338,7 +1351,10 @@ function read(e) {
1338
1351
 
1339
1352
  /** A pass read a staging of this flush: it derives from what the flush may
1340
1353
  * yet hold (REACTIVE_STAGED_READ — the seam decides). Lane work too (plan sec. 28, a
1341
- * lane sees the screen plus its own guesses): the staging is the screen if
1354
+ * lane sees the screen plus its own guesses) — a folded lane's only before
1355
+ * the frame has a transaction (after, it reads the screen like a verdict
1356
+ * reader, `stagedScreen` — a list pass never pairs a held length with the
1357
+ * guess's rows, D-34): the staging is the screen if
1342
1358
  * the frame commits — one pass, the common case — and a held write if it
1343
1359
  * parks, which the seam repairs (`stagedReaders`): the pass re-derives on
1344
1360
  * the committed world and its lane's runs wait that round, so the held
@@ -1580,4 +1596,4 @@ function staleValues(e, t = true) {
1580
1596
  }
1581
1597
  }
1582
1598
 
1583
- 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 };
1599
+ 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 };
@@ -18,43 +18,48 @@ import { globalQueue, schedule, heldRunHook, haltReactivity, GlobalQueue } from
18
18
  * Effects are the leaf nodes of our reactive graph. When their sources change, they are
19
19
  * automatically added to the queue of effects to re-execute, which will cause them to fetch their
20
20
  * sources and recompute
21
- */ function effect(t, e, o, u) {
22
- const E = !!u?.user;
23
- const r = createEffectNode(t, e, o, E ? EFFECT_USER : EFFECT_RENDER, u);
24
- recompute(r, true);
21
+ */ function effect(t, e, o, r) {
22
+ const u = !!r?.user;
23
+ const E = createEffectNode(t, e, o, u ? EFFECT_USER : EFFECT_RENDER, r);
24
+ recompute(E, true);
25
25
  // A first pass that read a staged value was staged itself (recompute: born
26
26
  // staged); the flush's commit replays this effect. Its first run is not this
27
27
  // creation's (A29). A lane's node likewise: its run is the lane's, queued
28
28
  // by the pass and released at the reveal (lanes.ts).
29
- !u?.defer && r.ue === NOT_PENDING && !r.T?.Je?.V && (r.i === EFFECT_USER || u?.schedule ? globalQueue.enqueue(r.i, runEffect.bind(null, r)) : runEffect(r, r.i));
29
+ !r?.defer && E.ue === NOT_PENDING && !E.T?.Je?.V && (E.i === EFFECT_USER || r?.schedule ? globalQueue.enqueue(E.i, runEffect.bind(null, E)) : runEffect(E, E.i));
30
30
  }
31
31
 
32
- function notifyEffectStatus(t, e) {
33
- // Use passed values if provided, otherwise read from node
34
- const o = t !== undefined ? t : this.m;
35
- const u = e !== undefined ? e : this.T?.F;
36
- if (o & STATUS_ERROR) {
32
+ function notifyEffectStatus() {
33
+ const t = this.m;
34
+ const e = this.T?.F;
35
+ if (t & STATUS_ERROR) {
37
36
  globalQueue.notify(this, STATUS_PENDING, 0);
38
37
  if (this.i === EFFECT_USER) {
39
38
  // The error handler is the error arm of the effect phase (#2840 ruling):
40
39
  // queue it like the effect function. It runs in the same imperative,
41
40
  // writable scope and throws escalate the same way. No payload is queued — the node already carries `_statusFlags`/`_error`,
42
41
  // and the runner dispatches on them, so a recovery before the effect
43
- // phase takes the success arm instead. Blocked forwards (explicit
44
- // `status` arg without node-state writes) don't queue: the status
45
- // re-propagates unblocked at commit.
42
+ // phase takes the success arm instead. (Re-read: the boundary
43
+ // notify above can run a redraw.)
46
44
  if (this.m & STATUS_ERROR) {
47
45
  this.Qe = true;
48
46
  globalQueue.enqueue(this.i, this.W ??= runEffect.bind(null, this));
49
47
  }
50
48
  return;
51
49
  }
52
- if (!globalQueue.notify(this, STATUS_ERROR, STATUS_ERROR)) {
53
- haltReactivity(unwrapStatusError(u));
54
- throw u;
55
- }
50
+ // Dev locates the halt at the node that threw: the StatusError's
51
+ // source, upstream of this effect when a computed failed.
52
+ escalate(this, unwrapStatusError(e), e, this);
56
53
  } else if (this.i === EFFECT_RENDER) {
57
- globalQueue.notify(this, STATUS_PENDING | STATUS_ERROR, o, u);
54
+ globalQueue.notify(this, STATUS_PENDING | STATUS_ERROR, t, e);
55
+ }
56
+ }
57
+
58
+ /** An effect's error no boundary takes halts the system (boundary-or-halt):
59
+ * halted with `cause`, `thrown` rethrown; dev locates it at `subject`. */ function escalate(t, e, o = e, r = t) {
60
+ if (!globalQueue.notify(t, STATUS_ERROR, STATUS_ERROR)) {
61
+ haltReactivity(e);
62
+ throw o;
58
63
  }
59
64
  }
60
65
 
@@ -90,10 +95,7 @@ function runEffect(t, e) {
90
95
  e?.();
91
96
  }) : console.error(e);
92
97
  } catch (e) {
93
- if (!globalQueue.notify(t, STATUS_ERROR, STATUS_ERROR)) {
94
- haltReactivity(e);
95
- throw e;
96
- }
98
+ escalate(t, e);
97
99
  }
98
100
  return;
99
101
  }
@@ -105,10 +107,10 @@ function runEffect(t, e) {
105
107
  // cascade an observer reports) and what times the callback (the `effect`
106
108
  // record) — facts a production observer needs, not only a dev console.
107
109
  if (attrHooks !== null) attrHooks.effectRunStart(t);
108
- const u = t.kt;
110
+ const r = t.kt;
109
111
  t.kt = undefined;
110
112
  try {
111
- u?.();
113
+ r?.();
112
114
  const e = t.Ge(t.Ie, t._t);
113
115
  if (false && e !== undefined && typeof e !== "function") ;
114
116
  // The final cleanup is invoked by disposeChildren at true disposal.
@@ -116,10 +118,7 @@ function runEffect(t, e) {
116
118
  } catch (e) {
117
119
  ext(t).F = new StatusError(t, e);
118
120
  t.m |= STATUS_ERROR;
119
- if (!globalQueue.notify(t, STATUS_ERROR, STATUS_ERROR)) {
120
- haltReactivity(e);
121
- throw e;
122
- }
121
+ escalate(t, e);
123
122
  } finally {
124
123
  t._t = t.Ie;
125
124
  t.Qe = false;
@@ -157,8 +156,8 @@ GlobalQueue.K = runEffect;
157
156
  const e = o.kt;
158
157
  o.kt = undefined;
159
158
  e?.();
160
- const u = staleValues(t);
161
- o.kt = u;
159
+ const r = staleValues(t);
160
+ o.kt = r;
162
161
  }, {
163
162
  ...e,
164
163
  lazy: true
@@ -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
 
@@ -21,13 +21,15 @@ function unlinkSubs(e) {
21
21
  const l = e.G;
22
22
  const o = e.t;
23
23
  const s = e.en;
24
- if (o !== null) o.en = s; else n.dt = s;
24
+ if (o !== null) o.en = s; else n.Ct = s;
25
25
  if (s !== null) s.t = o; else {
26
26
  n.o = o;
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?.Dt?.();
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?.Dt) runCleanup(n.T.Dt);
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.dt;
145
- if (t !== null && t.u === n && (!s || t.Te === n.Ne)) return;
146
- const r = n._ = e.dt = {
146
+ const r = e.Ct;
147
+ if (r !== null && r.u === n && (!s || r.Te === n.Ne)) return;
148
+ const t = n._ = e.Ct = {
147
149
  S: e,
148
150
  u: n,
149
151
  G: o,
150
- en: t,
152
+ en: 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
  }
@@ -461,12 +461,21 @@ function supersede(e, n, s) {
461
461
  * lane's latest. NOT_PENDING falls through: lane work's read of a pending
462
462
  * member throws like any. */ function laneRead(e, n) {
463
463
  const s = n.l & CONFIG_GUESS;
464
+ const t = txOf(n);
465
+ // A lane a correction folded under (D-34): store/optimistic.ts reads it.
466
+ // Before the authoritative arm: an `until` outside the lane reads the
467
+ // same truth there, and joins the hold it folded into — its posture over
468
+ // a foreign hold (D-31), which the truth read alone would not take.
469
+ const l = e !== null && t.folded;
470
+ if (l) {
471
+ const s = l(e, n);
472
+ if (s !== NOT_PENDING) return s;
473
+ }
464
474
  if (e !== null && e.l & CONFIG_AUTHORITATIVE) return GlobalQueue.rn(n);
465
- const t = n.m;
475
+ const u = n.m;
466
476
  // An errored member's lane value predates its error; the error is the answer (#4106).
467
- if (!s && t & STATUS_ERROR) return NOT_PENDING;
477
+ if (!s && u & STATUS_ERROR) return NOT_PENDING;
468
478
  if (e === null) return laneValueOf(n);
469
- const l = txOf(n);
470
479
  if (!tracking || e.l & CONFIG_CHILDREN_FORBIDDEN) {
471
480
  // An untracked read inside lane work derives from the lane like a
472
481
  // tracked one, minus the subscription — `untrack` is about
@@ -475,33 +484,33 @@ function supersede(e, n, s) {
475
484
  // lanes read by one pass link, as for tracked reads). Outside lane
476
485
  // work, the screen.
477
486
  if (passLane === null || e.l & CONFIG_CHILDREN_FORBIDDEN) return display(n);
478
- enterLane(l, e);
479
- if (!s && t & STATUS_PENDING) return NOT_PENDING;
487
+ enterLane(t, e);
488
+ if (!s && u & STATUS_PENDING) return NOT_PENDING;
480
489
  return laneValueOf(n);
481
490
  }
482
- if (e.i === EFFECT_RENDER && (passLane === null || !sameLane(passLane, l))) {
491
+ if (e.i === EFFECT_RENDER && (passLane === null || !sameLane(passLane, t))) {
483
492
  if (!s) {
484
493
  // A flight whose value has never shown: nothing to show — the leaf
485
494
  // waits on it, and its own frame holds (A15, #3334), not the lane's
486
495
  // parent. The landing lands the frame: its own pass (the flush
487
496
  // joined its transaction) reads the answer it waited on — the lane's
488
497
  // staging, revealed at this seam.
489
- if (t & STATUS_PENDING && n.T.V === NOT_PENDING) return NOT_PENDING;
498
+ if (u & STATUS_PENDING && n.T.V === NOT_PENDING) return NOT_PENDING;
490
499
  if (n.ue !== NOT_PENDING && e.l & CONFIG_HELD && flushTransaction !== null && resolveTx(flushTransaction) === txOf(e)) return n.ue;
491
500
  }
492
- if (l.ln || !s && t & STATUS_PENDING) {
493
- if (!s && !l.It && t & STATUS_UNINITIALIZED) throw new NotReadyError(null);
501
+ if (t.ln || !s && u & STATUS_PENDING) {
502
+ if (!s && !t.It && u & STATUS_UNINITIALIZED) throw new NotReadyError(null);
494
503
  // Re-derived at the reveal; once per pass (the pass may also be a stale
495
504
  // reader of a transaction, REACTIVE_FRAME_READ — both reruns apply).
496
505
  if (!(e.R & REACTIVE_SCREEN_READ)) {
497
506
  e.R |= REACTIVE_FRAME_READ | REACTIVE_SCREEN_READ;
498
- l.En.push(e);
507
+ t.En.push(e);
499
508
  }
500
509
  return display(n);
501
510
  }
502
511
  }
503
- enterLane(l, e);
504
- if (!s && t & STATUS_PENDING) return NOT_PENDING;
512
+ enterLane(t, e);
513
+ if (!s && u & STATUS_PENDING) return NOT_PENDING;
505
514
  return laneValueOf(n);
506
515
  }
507
516
 
@@ -626,10 +635,12 @@ function laneSeam(e, n) {
626
635
  // question is the one being asked (`question`: a slice's, a derive's
627
636
  // continuation's — the flight's); unstamped writes and mainline are
628
637
  // current.
629
- if (stale(e, question)) {
638
+ // A store re-bases a keyed arrangement guess over an older truth, and
639
+ // folds a current one beneath it (D-34): true.
640
+ const s = stale(e, question);
641
+ if (GlobalQueue.an?.(e, n, s) || s) {
630
642
  e.ue = n;
631
643
  e.l |= CONFIG_HELD;
632
- GlobalQueue.an?.(e, n);
633
644
  schedule();
634
645
  return n;
635
646
  }