@solidjs/signals 2.0.0-rc.18 → 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 (65) hide show
  1. package/dist/dev-shared.js +168 -88
  2. package/dist/dev.attribution.js +16 -13
  3. package/dist/dev.js +272 -129
  4. package/dist/observe/boundaries.js +88 -67
  5. package/dist/observe/core/action.js +8 -0
  6. package/dist/observe/core/async.js +46 -34
  7. package/dist/observe/core/attribution.js +30 -28
  8. package/dist/observe/core/constants.js +21 -1
  9. package/dist/observe/core/core.js +164 -136
  10. package/dist/observe/core/effect.js +6 -6
  11. package/dist/observe/core/graph.js +3 -3
  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 +10 -9
  15. package/dist/observe/core/scheduler.js +38 -35
  16. package/dist/observe/core/truth.js +6 -6
  17. package/dist/observe/core/verdict.js +13 -13
  18. package/dist/observe/deferred.js +39 -40
  19. package/dist/observe/index.js +2 -2
  20. package/dist/observe/map.js +22 -22
  21. package/dist/observe/reveal.js +5 -5
  22. package/dist/observe/signals.js +57 -15
  23. package/dist/observe/store/affects.js +1 -1
  24. package/dist/observe/store/hooks.js +3 -0
  25. package/dist/observe/store/optimistic.js +11 -11
  26. package/dist/observe/store/projection.js +34 -21
  27. package/dist/observe/store/reconcile.js +2 -2
  28. package/dist/observe/store/store.js +63 -53
  29. package/dist/observe/store/utils.js +7 -2
  30. package/dist/prod/boundaries.js +153 -132
  31. package/dist/prod/core/action.js +8 -0
  32. package/dist/prod/core/async.js +55 -43
  33. package/dist/prod/core/constants.js +14 -1
  34. package/dist/prod/core/core.js +162 -134
  35. package/dist/prod/core/effect.js +9 -9
  36. package/dist/prod/core/graph.js +3 -3
  37. package/dist/prod/core/heap.js +12 -12
  38. package/dist/prod/core/lanes.js +98 -78
  39. package/dist/prod/core/owner.js +15 -14
  40. package/dist/prod/core/scheduler.js +43 -40
  41. package/dist/prod/core/truth.js +6 -6
  42. package/dist/prod/core/verdict.js +17 -17
  43. package/dist/prod/deferred.js +39 -40
  44. package/dist/prod/index.js +2 -2
  45. package/dist/prod/map.js +14 -14
  46. package/dist/prod/reveal.js +8 -8
  47. package/dist/prod/signals.js +46 -4
  48. package/dist/prod/store/affects.js +1 -1
  49. package/dist/prod/store/hooks.js +3 -0
  50. package/dist/prod/store/optimistic.js +16 -16
  51. package/dist/prod/store/projection.js +22 -9
  52. package/dist/prod/store/reconcile.js +2 -2
  53. package/dist/prod/store/store.js +262 -262
  54. package/dist/prod/store/utils.js +2 -0
  55. package/dist/types/core/action.d.ts +8 -0
  56. package/dist/types/core/constants.d.ts +20 -0
  57. package/dist/types/core/core.d.ts +14 -1
  58. package/dist/types/core/dev.d.ts +1 -1
  59. package/dist/types/core/index.d.ts +1 -1
  60. package/dist/types/core/lanes.d.ts +5 -1
  61. package/dist/types/core/scheduler.d.ts +2 -0
  62. package/dist/types/index.d.ts +3 -1
  63. package/dist/types/signals.d.ts +37 -0
  64. package/dist/types/store/target.d.ts +2 -0
  65. package/package.json +1 -1
@@ -26,7 +26,7 @@ import { globalQueue, schedule, heldRunHook, haltReactivity, GlobalQueue } from
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
- !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));
29
+ !r?.defer && E.ie === 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
32
  function notifyEffectStatus() {
@@ -86,10 +86,10 @@ function runEffect(t, e) {
86
86
  // same flush must not be hijacked by a later-arriving error status.
87
87
  if (t.m & STATUS_ERROR && t.i === EFFECT_USER) {
88
88
  const e = unwrapStatusError(t.T?.F);
89
- t._t = t.Ie;
89
+ t._t = t.le;
90
90
  t.Qe = false;
91
91
  try {
92
- t.ht ? t.ht(e, () => {
92
+ t.Ht ? t.Ht(e, () => {
93
93
  const e = t.kt;
94
94
  t.kt = undefined;
95
95
  e?.();
@@ -111,7 +111,7 @@ function runEffect(t, e) {
111
111
  t.kt = undefined;
112
112
  try {
113
113
  r?.();
114
- const e = t.Ge(t.Ie, t._t);
114
+ const e = t.Ge(t.le, t._t);
115
115
  if (false && e !== undefined && typeof e !== "function") ;
116
116
  // The final cleanup is invoked by disposeChildren at true disposal.
117
117
  t.kt = e;
@@ -120,7 +120,7 @@ function runEffect(t, e) {
120
120
  t.m |= STATUS_ERROR;
121
121
  escalate(t, e);
122
122
  } finally {
123
- t._t = t.Ie;
123
+ t._t = t.le;
124
124
  t.Qe = false;
125
125
  // The run applied: this is the frame now, so the dependency tail the
126
126
  // compute pass left linked goes (A30, #3438 — `recompute` defers an
@@ -172,7 +172,7 @@ GlobalQueue.K = runEffect;
172
172
  // Status dispatch rides the SHARED notifier (statusNotifierOf keys off
173
173
  // _type): its error arm is behavior-identical to the closure that used to
174
174
  // live here, without the per-node NodeExtension allocation.
175
- o.M = run;
175
+ o.Z = run;
176
176
  // The first run rides the heap like every wake (GlobalQueue._update), so a
177
177
  // tracked effect created inside a render-effect callback runs after that
178
178
  // pass's staged writes commit, not before.
@@ -133,7 +133,7 @@ function link(e, n) {
133
133
  if (s) {
134
134
  o = l !== null ? l.G : n.C;
135
135
  if (o !== null && o.S === e) {
136
- o.Te = n.Ne;
136
+ o.Ie = n.Ne;
137
137
  n._ = o;
138
138
  return;
139
139
  }
@@ -144,14 +144,14 @@ function link(e, n) {
144
144
  // (the old alien-signals `isValidLink` walk, O(n²) when a computation
145
145
  // re-reads earlier deps non-consecutively, e.g. store leaf reads).
146
146
  const r = e.Ct;
147
- if (r !== null && r.u === n && (!s || r.Te === n.Ne)) return;
147
+ if (r !== null && r.u === n && (!s || r.Ie === n.Ne)) return;
148
148
  const t = n._ = e.Ct = {
149
149
  S: e,
150
150
  u: n,
151
151
  G: o,
152
152
  en: r,
153
153
  t: null,
154
- Te: n.Ne
154
+ Ie: n.Ne
155
155
  };
156
156
  if (l !== null) l.G = t; else n.C = t;
157
157
  if (r !== null) r.t = t; else e.o = t;
@@ -11,12 +11,12 @@ import { dirtyQueue } from "./scheduler.js";
11
11
  * callback to the user queue once the pass has committed (see
12
12
  * GlobalQueue._update, #3291).
13
13
  */ function enqueueSub(e) {
14
- if (dirtyQueue.st > e.Et) dirtyQueue.st = e.Et;
14
+ if (dirtyQueue.lt > e.Et) dirtyQueue.lt = e.Et;
15
15
  insertIntoHeap(e, dirtyQueue);
16
16
  }
17
17
 
18
18
  function actualInsertIntoHeap(e, t) {
19
- const E = (e._parent?.Pt ? e._parent.Ht?.Et : e._parent?.Et) ?? -1;
19
+ const E = (e._parent?.Pt ? e._parent.ht?.Et : e._parent?.Et) ?? -1;
20
20
  if (E >= e.Et) e.Et = E + 1;
21
21
  const n = e.Et;
22
22
  const I = t.gt[n];
@@ -98,11 +98,11 @@ function markNode(e, t = REACTIVE_DIRTY) {
98
98
 
99
99
  function runHeap(e, t) {
100
100
  e.p = false;
101
- for (e.st = 0; e.st <= e.jt; e.st++) {
102
- let E = e.gt[e.st];
101
+ for (e.lt = 0; e.lt <= e.jt; e.lt++) {
102
+ let E = e.gt[e.lt];
103
103
  while (E !== undefined) {
104
104
  if (E.R & REACTIVE_IN_HEAP) t(E); else adjustHeight(E, e);
105
- E = e.gt[e.st];
105
+ E = e.gt[e.lt];
106
106
  }
107
107
  }
108
108
  e.jt = 0;
@@ -1,4 +1,4 @@
1
- import { CONFIG_GUESS, NOT_PENDING, CONFIG_OVERRIDE, REACTIVE_DISPOSED, CONFIG_VERDICT, REACTIVE_PROBE_UNANSWERED, REACTIVE_LANE_DIRTY, CONFIG_HELD, REACTIVE_LANE_READ, STATUS_UNINITIALIZED, REACTIVE_RECOMPUTING_DEPS, CONFIG_AUTHORITATIVE, STATUS_ERROR, CONFIG_CHILDREN_FORBIDDEN, STATUS_PENDING, EFFECT_RENDER, REACTIVE_SCREEN_READ, REACTIVE_FRAME_READ, REACTIVE_IN_HEAP, REACTIVE_DIRTY, REACTIVE_CHECK, CONFIG_INPUTS_PUBLISHED, CONFIG_SLOT_NODE, CONFIG_STAGED, REACTIVE_JOINED, REACTIVE_STAGED_READ } from "./constants.js";
1
+ import { CONFIG_GUESS, NOT_PENDING, CONFIG_OVERRIDE, REACTIVE_DISPOSED, CONFIG_VERDICT, REACTIVE_PROBE_UNANSWERED, REACTIVE_LANE_DIRTY, CONFIG_HELD, REACTIVE_LANE_READ, STATUS_UNINITIALIZED, REACTIVE_RECOMPUTING_DEPS, CONFIG_AUTHORITATIVE, STATUS_ERROR, CONFIG_CHILDREN_FORBIDDEN, EFFECT_RENDER, REACTIVE_SCREEN_READ, REACTIVE_FRAME_READ, REACTIVE_IN_HEAP, REACTIVE_DIRTY, REACTIVE_CHECK, STATUS_PENDING, CONFIG_INPUTS_PUBLISHED, CONFIG_SLOT_NODE, CONFIG_STAGED, REACTIVE_JOINED, REACTIVE_STAGED_READ } from "./constants.js";
2
2
 
3
3
  import { attrHooks } from "./attribution-hooks.js";
4
4
 
@@ -10,7 +10,7 @@ import { laneValueOf } from "./truth.js";
10
10
 
11
11
  import { enqueueSub } from "./heap.js";
12
12
 
13
- import { schedule, question, nextQuestion, installLaneHooks, GlobalQueue, resolveTx, newTransaction, ownFlights, blocked, txOf, clock, insertSubs, laneDirty, queuePendingNode, list, passLane, sameLane, flushTransaction, commitPendingNode, releaseQueues, holdNode, joinFuture, reruns, merge, setPassLane } from "./scheduler.js";
13
+ import { schedule, question, nextQuestion, installLaneHooks, GlobalQueue, resolveTx, newTransaction, ownFlights, blocked, txOf, clock, insertSubs, laneDirty, queuePendingNode, list, passLane, setPassLane, sameLane, flushTransaction, commitPendingNode, releaseQueues, holdNode, joinFuture, reruns, merge } from "./scheduler.js";
14
14
 
15
15
  // Lanes (plan sec. 28 replay, 2026-10-02; maintainer rulings 2026-10-01/02). A lane
16
16
  // is "a new base of a transition": the sub-frame an optimistic write opens
@@ -53,7 +53,7 @@ function newLane(e) {
53
53
  /** What the screen shows for a lane's node: the lane's value once the lane
54
54
  * has revealed, the committed truth before. */ function display(e) {
55
55
  const n = e.T.V;
56
- return e.T.Je.It && n !== NOT_PENDING ? n : e.Ie;
56
+ return e.T.Je.It && n !== NOT_PENDING ? n : e.le;
57
57
  }
58
58
 
59
59
  /** Guesses written since the last seam (`[node, value, question, …]`).
@@ -68,7 +68,7 @@ function newLane(e) {
68
68
  * on (a store setter's draft too, store/optimistic.ts). */ function guessedValueOf(e) {
69
69
  const n = pendingGuessOf(e);
70
70
  if (n !== NOT_PENDING) return n;
71
- return e.l & CONFIG_OVERRIDE ? laneValueOf(e) : e.Ie;
71
+ return e.l & CONFIG_OVERRIDE ? laneValueOf(e) : e.le;
72
72
  }
73
73
 
74
74
  /** The unflushed guess to `n`, else NOT_PENDING. */ function pendingGuessOf(e) {
@@ -84,7 +84,7 @@ function optimisticWrite(e, n) {
84
84
  // the guess's question and entangles its frame (#3347) without notifying.
85
85
  const s = guessedValueOf(e);
86
86
  if (typeof n === "function") n = n(s);
87
- if (e.Ye && e.Ye(s, n) && !(e.l & CONFIG_GUESS)) return n;
87
+ if (e.je && e.je(s, n) && !(e.l & CONFIG_GUESS)) return n;
88
88
  pendingGuesses.push(e, n, question || nextQuestion());
89
89
  schedule();
90
90
  return n;
@@ -114,25 +114,25 @@ function applyGuesses(e) {
114
114
  // The frame already holding the slot: a guess over it is a second
115
115
  // suggestion for the slot, and two cannot finish apart (A34 (1)) — the
116
116
  // frames entangle.
117
- let r = null;
117
+ let i = null;
118
118
  if (t.l & CONFIG_OVERRIDE) {
119
119
  // A lane's node already: a guess re-asked, or a derivation guessed
120
120
  // over (its staging is void — the guess is the value now). The same
121
121
  // value re-asked renews the question; nothing to notify.
122
- u = !t.Ye || !t.Ye(laneValueOf(t), l);
122
+ u = !t.je || !t.je(laneValueOf(t), l);
123
123
  if (!(t.l & CONFIG_GUESS)) {
124
- t.ue = NOT_PENDING;
124
+ t.ie = NOT_PENDING;
125
125
  t.l |= CONFIG_GUESS;
126
126
  }
127
127
  t.T.V = l;
128
- r = txOf(t)._parent;
128
+ i = txOf(t)._parent;
129
129
  } else {
130
130
  let s;
131
131
  // A held node: a truth staged there, or what a corrected guess showed.
132
132
  // The guess composes on it, so it ends with that hold — and with its
133
133
  // own frame's (F-11): it lands only when both have (the board's rapid
134
134
  // adds — a later add's row reverted with an earlier add's landing).
135
- if (t.l & CONFIG_HELD) s = newLane(r = txOf(t)); else {
135
+ if (t.l & CONFIG_HELD) s = newLane(i = txOf(t)); else {
136
136
  if (e === null && inFlight(t)) e = newTransaction(false);
137
137
  if (e === null) continue;
138
138
  s = n ??= newLane(e);
@@ -147,8 +147,8 @@ function applyGuesses(e) {
147
147
  t.l |= CONFIG_OVERRIDE | CONFIG_GUESS;
148
148
  list(t, s);
149
149
  }
150
- if (e !== null && r !== null) merge(e, r);
151
- ext(t).Ee = pendingGuesses[s + 2];
150
+ if (e !== null && i !== null) merge(e, i);
151
+ ext(t).de = pendingGuesses[s + 2];
152
152
  if (u) {
153
153
  // The lane's members re-derive as its work (REACTIVE_LANE_DIRTY): a
154
154
  // blocked lane's re-guess does not re-run its render effects as stale
@@ -266,7 +266,7 @@ function unlink(e) {
266
266
  }
267
267
  if (!e.i) {
268
268
  if (!(e.l & CONFIG_OVERRIDE)) {
269
- e.ue = NOT_PENDING;
269
+ e.ie = NOT_PENDING;
270
270
  e.l = e.l & -1048833 | CONFIG_OVERRIDE;
271
271
  ext(e);
272
272
  }
@@ -327,7 +327,7 @@ function supersede(e, n, s) {
327
327
  dissolveLane(t, l, e);
328
328
  // What the screen showed stays the screen until the parent lands (A18
329
329
  // (c): display and untracked reads keep the override until the commit).
330
- if (t.It) e.Ie = e.T.V;
330
+ if (t.It) e.le = e.T.V;
331
331
  e.l &= -10241;
332
332
  e.T.V = NOT_PENDING;
333
333
  holdNode(e, l);
@@ -337,7 +337,7 @@ function supersede(e, n, s) {
337
337
  // A confirmation is silent (A17); only truth readers are told.
338
338
  GlobalQueue.tn?.(t);
339
339
  }
340
- e.ue = n;
340
+ e.ie = n;
341
341
  joinFuture(l);
342
342
  }
343
343
 
@@ -361,16 +361,16 @@ function supersede(e, n, s) {
361
361
  e.it = null;
362
362
  if (lanes.indexOf(l) !== -1) dissolveLane(l, null);
363
363
  }
364
- for (let t = 0; t < e.je.length; t++) {
365
- const l = e.je[t];
364
+ for (let t = 0; t < e.Ye.length; t++) {
365
+ const l = e.Ye[t];
366
366
  const u = l.T;
367
367
  if (l === s || u.Je !== e) continue;
368
- const r = l.i;
369
- const i = l.l & CONFIG_GUESS;
368
+ const i = l.i;
369
+ const r = l.l & CONFIG_GUESS;
370
370
  const o = u.V;
371
371
  // What the screen shows of it (NOT_PENDING: nothing — a lane pass that
372
372
  // errored or pends staged no value), and the lane's latest.
373
- const E = e.It && !r ? o : l.Ie;
373
+ const E = e.It && !i ? o : l.le;
374
374
  const f = laneValueOf(l);
375
375
  u.V = NOT_PENDING;
376
376
  l.l &= -10241;
@@ -381,9 +381,9 @@ function supersede(e, n, s) {
381
381
  // displayed guess that lifts to something else — the truth staged
382
382
  // beneath it (superseded by a landing held there) or the value it
383
383
  // covered (reverted: nothing came true).
384
- if (i && e.It && attrHooks !== null) {
385
- const e = l.ue !== NOT_PENDING ? l.ue : l.Ie;
386
- if (e !== o) attrHooks.optimisticReverted(l, o, e, l.ue !== NOT_PENDING ? "superseded" : "reverted");
384
+ if (r && e.It && attrHooks !== null) {
385
+ const e = l.ie !== NOT_PENDING ? l.ie : l.le;
386
+ if (e !== o) attrHooks.optimisticReverted(l, o, e, l.ie !== NOT_PENDING ? "superseded" : "reverted");
387
387
  }
388
388
  // A derivation the revert re-derives (dirtied by the guess's
389
389
  // notification — listed before it), or one still in flight, shows the
@@ -391,19 +391,19 @@ function supersede(e, n, s) {
391
391
  // of its flight observes it (#3648, #3651; #3305's commit beneath a
392
392
  // flight).
393
393
  u.Je = null;
394
- if (!r && !i) l.ue = f;
394
+ if (!i && !r) l.ie = f;
395
395
  commitPendingNode(l);
396
- if (!r) {
396
+ if (!i) {
397
397
  if (l.R & (REACTIVE_IN_HEAP | REACTIVE_DIRTY | REACTIVE_CHECK) || l.m & STATUS_PENDING) l.l |= CONFIG_INPUTS_PUBLISHED;
398
- if (E !== NOT_PENDING && (!l.Ye || !l.Ye(E, l.Ie))) insertSubs(l);
398
+ if (E !== NOT_PENDING && (!l.je || !l.je(E, l.le))) insertSubs(l);
399
399
  }
400
- } else if (i) {
400
+ } else if (r) {
401
401
  // A guess of a dissolving lane: its truth — a landing held beneath it,
402
402
  // else the value it covered — is the parent's to land, and its readers
403
403
  // re-derive from it there. What the screen showed stays the screen
404
404
  // until then (A18 (c)).
405
- if (l.ue === NOT_PENDING) l.ue = covered(l);
406
- if (e.It) l.Ie = o;
405
+ if (l.ie === NOT_PENDING) l.ie = covered(l);
406
+ if (e.It) l.le = o;
407
407
  holdNode(l, n);
408
408
  insertSubs(l);
409
409
  } else {
@@ -415,18 +415,18 @@ function supersede(e, n, s) {
415
415
  // answer — it re-asks from the truth (whose notification may not reach
416
416
  // it: an input's truth equal to its committed value is silent).
417
417
  u.Je = null;
418
- if (!r) {
419
- if (E !== NOT_PENDING && e.It) l.ue = E;
418
+ if (!i) {
419
+ if (E !== NOT_PENDING && e.It) l.ie = E;
420
420
  if (l.m & STATUS_PENDING) {
421
- u.Se = null;
421
+ u.Ee = null;
422
422
  enqueueSub(l);
423
423
  }
424
424
  }
425
425
  if (e.It) {
426
426
  commitPendingNode(l);
427
- if (!r) l.l |= CONFIG_INPUTS_PUBLISHED;
427
+ if (!i) l.l |= CONFIG_INPUTS_PUBLISHED;
428
428
  } else {
429
- if (u.Nt !== null || u.ft !== null) GlobalQueue.et(l, false, true);
429
+ if (u.ft !== null || u.Nt !== null) GlobalQueue.et(l, false, true);
430
430
  l.l &= -1048961;
431
431
  }
432
432
  }
@@ -459,7 +459,11 @@ function supersede(e, n, s) {
459
459
  * has shown, or one the seam has not judged yet (its run waits on the
460
460
  * verdict: released if the lane shows, held if it blocks) — and sees the
461
461
  * lane's latest. NOT_PENDING falls through: lane work's read of a pending
462
- * member throws like any. */ function laneRead(e, n) {
462
+ * member throws like any. A verdict reader entering a lane the seam has
463
+ * not judged reads ahead of it like a staging read: if the seam holds the
464
+ * lane, the pass re-derives on the screen (`entered`, #4284) — `latest()`
465
+ * beside the lane's nodes tears ahead of the hold however the reads are
466
+ * grouped into effects. */ function laneRead(e, n) {
463
467
  const s = n.l & CONFIG_GUESS;
464
468
  const t = txOf(n);
465
469
  // A lane a correction folded under (D-34): store/optimistic.ts reads it.
@@ -476,6 +480,7 @@ function supersede(e, n, s) {
476
480
  // An errored member's lane value predates its error; the error is the answer (#4106).
477
481
  if (!s && u & STATUS_ERROR) return NOT_PENDING;
478
482
  if (e === null) return laneValueOf(n);
483
+ const i = !s && u & STATUS_PENDING;
479
484
  if (!tracking || e.l & CONFIG_CHILDREN_FORBIDDEN) {
480
485
  // An untracked read inside lane work derives from the lane like a
481
486
  // tracked one, minus the subscription — `untrack` is about
@@ -484,45 +489,51 @@ function supersede(e, n, s) {
484
489
  // lanes read by one pass link, as for tracked reads). Outside lane
485
490
  // work, the screen.
486
491
  if (passLane === null || e.l & CONFIG_CHILDREN_FORBIDDEN) return display(n);
487
- enterLane(t, e);
488
- if (!s && u & STATUS_PENDING) return NOT_PENDING;
489
- return laneValueOf(n);
490
- }
491
- if (e.i === EFFECT_RENDER && (passLane === null || !sameLane(passLane, t))) {
492
- if (!s) {
493
- // A flight whose value has never shown: nothing to show — the leaf
494
- // waits on it, and its own frame holds (A15, #3334), not the lane's
495
- // parent. The landing lands the frame: its own pass (the flush
496
- // joined its transaction) reads the answer it waited on — the lane's
497
- // staging, revealed at this seam.
498
- if (u & STATUS_PENDING && n.T.V === NOT_PENDING) return NOT_PENDING;
499
- if (n.ue !== NOT_PENDING && e.l & CONFIG_HELD && flushTransaction !== null && resolveTx(flushTransaction) === txOf(e)) return n.ue;
500
- }
501
- if (t.ln || !s && u & STATUS_PENDING) {
502
- if (!s && !t.It && u & STATUS_UNINITIALIZED) throw new NotReadyError(null);
503
- // Re-derived at the reveal; once per pass (the pass may also be a stale
504
- // reader of a transaction, REACTIVE_FRAME_READ — both reruns apply).
505
- if (!(e.R & REACTIVE_SCREEN_READ)) {
506
- e.R |= REACTIVE_FRAME_READ | REACTIVE_SCREEN_READ;
507
- t.En.push(e);
492
+ } else if (e.i === EFFECT_RENDER) {
493
+ // A flight whose value has never shown: nothing to show — the leaf
494
+ // waits on it, and its own frame holds (A15, #3334), not the lane's
495
+ // parent. The landing lands the frame: its own pass (the flush joined
496
+ // its transaction) reads the answer it waited on — the lane's staging,
497
+ // revealed at this seam.
498
+ const l = i && n.T.V === NOT_PENDING;
499
+ // D-28: a leaf a verdict read moved into the lane still throws on such
500
+ // a flight — the pass leaves the lane, and its frame holds as if the
501
+ // read had come first (S-39, #4275). Nor does a verdict lane's seat carry
502
+ // the leaf into another lane (no link): it reads that lane as from the
503
+ // frame's seat (#4284).
504
+ if ((l || passLane !== t) && passLane?._parent.it === passLane) setPassLane(null);
505
+ if (passLane === null || !sameLane(passLane, t)) {
506
+ // (A lane that has never shown: the screen is the committed value.)
507
+ if (l && t.It) return NOT_PENDING;
508
+ if (!s && n.ie !== NOT_PENDING && e.l & CONFIG_HELD && flushTransaction !== null && resolveTx(flushTransaction) === txOf(e)) return n.ie;
509
+ if (t.ln || i) {
510
+ if (!s && !t.It && u & STATUS_UNINITIALIZED) throw new NotReadyError(null);
511
+ // Re-derived at the reveal; once per pass (the pass may also be a stale
512
+ // reader of a transaction, REACTIVE_FRAME_READ — both reruns apply).
513
+ if (!(e.R & REACTIVE_SCREEN_READ)) {
514
+ e.R |= REACTIVE_FRAME_READ | REACTIVE_SCREEN_READ;
515
+ t.En.push(e);
516
+ }
517
+ return display(n);
508
518
  }
509
- return display(n);
519
+ // A verdict reader reads ahead of the seam's verdict: re-derived on the
520
+ // screen if the lane holds (#4284).
521
+ if (e.l & CONFIG_VERDICT && t._parent.it !== t) entered.push(e);
510
522
  }
511
523
  }
512
524
  enterLane(t, e);
513
- if (!s && u & STATUS_PENDING) return NOT_PENDING;
514
- return laneValueOf(n);
525
+ return i ? NOT_PENDING : laneValueOf(n);
515
526
  }
516
527
 
517
528
  /** The value a guess covered: the node's committed value — a store slot's
518
529
  * asked of the store (a chained link follows the inner store's commits). */ function covered(e) {
519
- return e.l & CONFIG_SLOT_NODE && GlobalQueue.fn !== undefined ? GlobalQueue.fn(e) : e.Ie;
530
+ return e.l & CONFIG_SLOT_NODE && GlobalQueue.fn !== undefined ? GlobalQueue.fn(e) : e.le;
520
531
  }
521
532
 
522
533
  /** A guess in `l` whose own truth is in flight (its refetch — authoritative:
523
534
  * the landing supersedes, not the body's end). */ function guessFlights(e) {
524
- for (let n = 0; n < e.je.length; n++) {
525
- const s = e.je[n];
535
+ for (let n = 0; n < e.Ye.length; n++) {
536
+ const s = e.Ye[n];
526
537
  if (s.l & CONFIG_GUESS && s.T.Je === e && inFlight(s)) return true;
527
538
  }
528
539
  return false;
@@ -552,12 +563,12 @@ function supersede(e, n, s) {
552
563
  // Each guess's truth: the one beneath it (a landing held there), else
553
564
  // the one it covered. (A correction dissolves the lane — the others are
554
565
  // re-homed with theirs, `dissolveLane`.)
555
- for (let e = 0; e < t.je.length && lanes.indexOf(t) !== -1; e++) {
556
- const n = t.je[e];
566
+ for (let e = 0; e < t.Ye.length && lanes.indexOf(t) !== -1; e++) {
567
+ const n = t.Ye[e];
557
568
  if (n.l & CONFIG_GUESS && n.T.Je === t) {
558
- const e = n.ue !== NOT_PENDING ? n.ue : covered(n);
559
- reverting = n.ue === NOT_PENDING;
560
- supersede(n, e, !n.Ye || !n.Ye(n.T.V, e));
569
+ const e = n.ie !== NOT_PENDING ? n.ie : covered(n);
570
+ reverting = n.ie === NOT_PENDING;
571
+ supersede(n, e, !n.je || !n.je(n.T.V, e));
561
572
  reverting = false;
562
573
  }
563
574
  }
@@ -575,21 +586,21 @@ function laneSeam(e, n) {
575
586
  e.ln = false;
576
587
  e.It = true;
577
588
  reruns(e);
578
- for (let n = 0; n < e.je.length; n++) {
579
- const s = e.je[n];
589
+ for (let n = 0; n < e.Ye.length; n++) {
590
+ const s = e.Ye[n];
580
591
  if (s.R & LEFT_SHOWN) {
581
592
  s.R ^= LEFT_SHOWN;
582
593
  insertSubs(s);
583
594
  }
584
595
  const t = s.T;
585
596
  if (t.Je !== e) continue;
586
- if (!(s.l & CONFIG_GUESS) && !s.i && s.ue !== NOT_PENDING) {
587
- t.V = s.ue;
588
- s.ue = NOT_PENDING;
597
+ if (!(s.l & CONFIG_GUESS) && !s.i && s.ie !== NOT_PENDING) {
598
+ t.V = s.ie;
599
+ s.ie = NOT_PENDING;
589
600
  }
590
601
  // The frame this pass built is the screen's now; the one it replaced
591
602
  // goes (as `commitPendingNode` retires a committed pass's).
592
- if (t.Nt !== null || t.ft !== null) GlobalQueue.et(s, false, true);
603
+ if (t.ft !== null || t.Nt !== null) GlobalQueue.et(s, false, true);
593
604
  s.l &= ~CONFIG_STAGED;
594
605
  }
595
606
  releaseQueues(e);
@@ -639,17 +650,17 @@ function laneSeam(e, n) {
639
650
  // folds a current one beneath it (D-34): true.
640
651
  const s = stale(e, question);
641
652
  if (GlobalQueue.an?.(e, n, s) || s) {
642
- e.ue = n;
653
+ e.ie = n;
643
654
  e.l |= CONFIG_HELD;
644
655
  schedule();
645
656
  return n;
646
657
  }
647
- supersede(e, n, !e.Ye || !e.Ye(e.T.V, n));
658
+ supersede(e, n, !e.je || !e.je(e.T.V, n));
648
659
  } else {
649
660
  const s = txOf(e);
650
- if (e.Ye && e.Ye(laneValueOf(e), n)) return n;
651
- if (s.It) e.ue = n; else e.T.V = n;
652
- if (e.X !== undefined) e.ie = clock;
661
+ if (e.je && e.je(laneValueOf(e), n)) return n;
662
+ if (s.It) e.ie = n; else e.T.V = n;
663
+ if (e.X !== undefined) e.ae = clock;
653
664
  insertSubs(e);
654
665
  laneDirty(e, s);
655
666
  }
@@ -671,18 +682,18 @@ function laneSeam(e, n) {
671
682
  * A24). The guess's own question answering supersedes as usual. */ function laneOutcome(e, n, s) {
672
683
  if (s) return true;
673
684
  if (stale(e, answered(e))) {
674
- e.ue = n;
685
+ e.ie = n;
675
686
  e.l |= CONFIG_HELD;
676
687
  return true;
677
688
  }
678
- supersede(e, n, !e.Ye || !e.Ye(e.T.V, n));
689
+ supersede(e, n, !e.je || !e.je(e.T.V, n));
679
690
  return true;
680
691
  }
681
692
 
682
693
  /** THE provenance test, for a derivation's pass and a write alike: an
683
694
  * answer to question `q` landing on a guess is stale when `q` is older
684
695
  * than the guess's own; unstamped (0) and mainline are current. */ function stale(e, n) {
685
- return n !== 0 && n < e.T.Ee;
696
+ return n !== 0 && n < e.T.de;
686
697
  }
687
698
 
688
699
  /** The question a derivation's pass answered: the newest among the stamped
@@ -691,7 +702,7 @@ function laneSeam(e, n) {
691
702
  let n = 0;
692
703
  for (let s = e.C; s !== null; s = s.G) {
693
704
  const e = s.S;
694
- if (e.T !== null && e.T.Ee > n && e.ie === clock) n = e.T.Ee;
705
+ if (e.T !== null && e.T.de > n && e.ae === clock) n = e.T.de;
695
706
  }
696
707
  return n;
697
708
  }
@@ -733,16 +744,25 @@ const laneSeams = e => {
733
744
  if (l != null && l.V && n.indexOf(l) === -1) n.push(l);
734
745
  }
735
746
  for (let e = Math.min(judged, lanes.length) - 1; e >= 0; e--) laneSeam(lanes[e], n.indexOf(lanes[e]) !== -1);
747
+ // A verdict reader that entered a lane this seam held: its run waits with
748
+ // the lane, and the verdict beside it would freeze — it re-derives next
749
+ // round from the frame's seat, a stale reader of the lane (#4284).
750
+ for (let e; e = entered.pop(); ) if (e.T.Je?.ln) {
751
+ e.R &= ~REACTIVE_LANE_DIRTY;
752
+ enqueueSub(e);
753
+ }
736
754
  };
737
755
 
756
+ /** Render effects that read a verdict and entered a lane before its seam (#4284). */ const entered = [];
757
+
738
758
  installLaneHooks(laneRead, laneStage, laneOutcome, laneWrite, applyGuessesAt, laneSeams, () => lanes.length !== 0 && laneCorrections(), endLanes, lanesBlocked);
739
759
 
740
- GlobalQueue.Ke = e => {
760
+ GlobalQueue.xe = e => {
741
761
  const n = [];
742
762
  for (let s = 0; s < lanes.length; s++) {
743
763
  const t = lanes[s];
744
764
  if (resolveTx(t._parent) !== e) continue;
745
- for (let e = 0; e < t.je.length; e++) if (t.je[e].l & CONFIG_GUESS && t.je[e].T.Je === t) n.push(t.je[e]);
765
+ for (let e = 0; e < t.Ye.length; e++) if (t.Ye[e].l & CONFIG_GUESS && t.Ye[e].T.Je === t) n.push(t.Ye[e]);
746
766
  }
747
767
  return n;
748
768
  };
@@ -8,7 +8,7 @@ import { unobserved, clearDeps } from "./graph.js";
8
8
 
9
9
  import { deleteFromHeap } from "./heap.js";
10
10
 
11
- import { globalQueue, schedule, dirtyQueue } from "./scheduler.js";
11
+ import { globalQueue, pendingNodes, schedule, dirtyQueue } from "./scheduler.js";
12
12
 
13
13
  // L2 (step 2): the parked frame. A pass parks the previous frame's children
14
14
  // and `_disposal` list on `_x._pendingFirstChild` / `_x._pendingDisposal`
@@ -69,21 +69,22 @@ function disposeChildren(e, n = false, r) {
69
69
  // cleanups never run and it stays subscribed, rerunning in a torn-down
70
70
  // tree (#3561). Death only (`self`): a re-pass leaves the frame reacting
71
71
  // until the commit.
72
- if (n && !r && e.T !== null && (e.T.Nt !== null || e.T.ft !== null)) disposeChildren(e, false, true);
72
+ if (n && !r && e.T !== null && (e.T.ft !== null || e.T.Nt !== null)) disposeChildren(e, false, true);
73
73
  if (n) {
74
74
  e.R = t | REACTIVE_DISPOSED;
75
75
  if (e._parent === null && e.Pt) unregisterRoot(e);
76
76
  }
77
77
  if (n && e.X) {
78
- if (e.T !== null) e.T.Se = null;
78
+ if (e.T !== null) e.T.Ee = null;
79
79
  // A death that can unblock the future (L2, `futureBlocked`): a held
80
80
  // flight's — a transaction's or a lane's (its `_x._transaction`) — or a
81
81
  // stale reader's (REACTIVE_FRAME_READ — a frame that derived from one).
82
+ // An idle pending queue would also pin it until an unrelated flush.
82
83
  // Nothing else re-examines a dead node, so make sure a seam comes.
83
- if (e.m & STATUS_PENDING && e.T?.Je != null || t & REACTIVE_FRAME_READ) schedule();
84
+ if (e.m & STATUS_PENDING && e.T?.Je != null || t & REACTIVE_FRAME_READ || pendingNodes.length) schedule();
84
85
  }
85
- let l = r ? e.T.Nt : e.ze;
86
- if (r) e.T.Nt = null; else e.ze = null;
86
+ let l = r ? e.T.ft : e.ze;
87
+ if (r) e.T.ft = null; else e.ze = null;
87
88
  while (l) {
88
89
  const e = l;
89
90
  // Owner teardown is death regardless of the child's own lifecycle
@@ -153,9 +154,9 @@ function runDisposal(e, n) {
153
154
  // walk ran every entry a second time. Detached, the re-entrant drain finds
154
155
  // nothing. The same shape latched a throwing cleanup: the list survived the
155
156
  // throw and every later drain re-ran and re-threw it.
156
- const r = n ? e.T.ft : e.rt;
157
+ const r = n ? e.T.Nt : e.rt;
157
158
  if (!r) return;
158
- if (n) e.T.ft = null; else e.rt = null;
159
+ if (n) e.T.Nt = null; else e.rt = null;
159
160
  if (Array.isArray(r)) {
160
161
  // Unwind order (#3572, restores 1.x #1562): later registrations run
161
162
  // before earlier ones. Children have already been disposed by the caller,
@@ -308,7 +309,7 @@ function disposeRootSelf(e = true) {
308
309
  // was 12 KB and most of disposal's time (2026-10-17).
309
310
  R: REACTIVE_NONE,
310
311
  Pt: true,
311
- Ht: n?.Pt ? n.Ht : n,
312
+ ht: n?.Pt ? n.ht : n,
312
313
  ze: null,
313
314
  Xe: null,
314
315
  dt: null,