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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/dist/dev-shared.js +426 -241
  2. package/dist/dev.attribution.js +19 -16
  3. package/dist/dev.js +768 -409
  4. package/dist/observe/boundaries.js +88 -67
  5. package/dist/observe/core/action.js +8 -0
  6. package/dist/observe/core/async.js +183 -138
  7. package/dist/observe/core/attribution.js +30 -28
  8. package/dist/observe/core/constants.js +23 -1
  9. package/dist/observe/core/core.js +185 -157
  10. package/dist/observe/core/effect.js +34 -37
  11. package/dist/observe/core/graph.js +8 -8
  12. package/dist/observe/core/heap.js +5 -5
  13. package/dist/observe/core/lanes.js +104 -84
  14. package/dist/observe/core/owner.js +17 -16
  15. package/dist/observe/core/scheduler.js +61 -47
  16. package/dist/observe/core/truth.js +6 -6
  17. package/dist/observe/core/verdict.js +15 -15
  18. package/dist/observe/deferred.js +40 -38
  19. package/dist/observe/from.js +248 -0
  20. package/dist/observe/index.js +4 -2
  21. package/dist/observe/map.js +22 -22
  22. package/dist/observe/reveal.js +5 -5
  23. package/dist/observe/signals.js +57 -15
  24. package/dist/observe/store/affects.js +1 -1
  25. package/dist/observe/store/hooks.js +3 -0
  26. package/dist/observe/store/optimistic.js +11 -11
  27. package/dist/observe/store/projection.js +34 -21
  28. package/dist/observe/store/reconcile.js +138 -122
  29. package/dist/observe/store/store.js +628 -641
  30. package/dist/observe/store/target.js +1 -5
  31. package/dist/observe/store/utils.js +7 -2
  32. package/dist/prod/boundaries.js +153 -132
  33. package/dist/prod/core/action.js +8 -0
  34. package/dist/prod/core/async.js +242 -197
  35. package/dist/prod/core/constants.js +16 -1
  36. package/dist/prod/core/core.js +166 -138
  37. package/dist/prod/core/effect.js +31 -34
  38. package/dist/prod/core/graph.js +5 -5
  39. package/dist/prod/core/heap.js +12 -12
  40. package/dist/prod/core/lanes.js +98 -78
  41. package/dist/prod/core/owner.js +15 -14
  42. package/dist/prod/core/scheduler.js +64 -50
  43. package/dist/prod/core/truth.js +6 -6
  44. package/dist/prod/core/verdict.js +17 -17
  45. package/dist/prod/deferred.js +40 -38
  46. package/dist/prod/from.js +248 -0
  47. package/dist/prod/index.js +4 -2
  48. package/dist/prod/map.js +14 -14
  49. package/dist/prod/reveal.js +8 -8
  50. package/dist/prod/signals.js +46 -4
  51. package/dist/prod/store/affects.js +1 -1
  52. package/dist/prod/store/hooks.js +3 -0
  53. package/dist/prod/store/optimistic.js +16 -16
  54. package/dist/prod/store/projection.js +22 -9
  55. package/dist/prod/store/reconcile.js +138 -122
  56. package/dist/prod/store/store.js +308 -331
  57. package/dist/prod/store/target.js +13 -17
  58. package/dist/prod/store/utils.js +2 -0
  59. package/dist/types/core/action.d.ts +8 -0
  60. package/dist/types/core/async.d.ts +12 -2
  61. package/dist/types/core/constants.d.ts +25 -0
  62. package/dist/types/core/core.d.ts +33 -4
  63. package/dist/types/core/dev.d.ts +1 -1
  64. package/dist/types/core/index.d.ts +1 -1
  65. package/dist/types/core/lanes.d.ts +5 -1
  66. package/dist/types/core/scheduler.d.ts +17 -3
  67. package/dist/types/from.d.ts +47 -0
  68. package/dist/types/index.d.ts +4 -1
  69. package/dist/types/signals.d.ts +37 -0
  70. package/dist/types/store/target.d.ts +2 -0
  71. package/package.json +1 -1
@@ -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 { ext, tracking, stagedRead } from "./core.js";
4
4
 
@@ -8,7 +8,7 @@ import { laneValueOf } from "./truth.js";
8
8
 
9
9
  import { enqueueSub } from "./heap.js";
10
10
 
11
- import { schedule, question, nextQuestion, installLaneHooks, GlobalQueue, newTransaction, resolveTx, ownFlights, blocked, txOf, clock, insertSubs, laneDirty, queuePendingNode, list, passLane, sameLane, flushTransaction, commitPendingNode, releaseQueues, holdNode, joinFuture, reruns, merge, setPassLane } from "./scheduler.js";
11
+ import { schedule, question, nextQuestion, installLaneHooks, GlobalQueue, newTransaction, resolveTx, ownFlights, blocked, txOf, clock, insertSubs, laneDirty, queuePendingNode, list, passLane, setPassLane, sameLane, flushTransaction, commitPendingNode, releaseQueues, holdNode, joinFuture, reruns, merge } from "./scheduler.js";
12
12
 
13
13
  // Lanes (plan sec. 28 replay, 2026-10-02; maintainer rulings 2026-10-01/02). A lane
14
14
  // is "a new base of a transition": the sub-frame an optimistic write opens
@@ -50,8 +50,8 @@ function newLane(e) {
50
50
 
51
51
  /** What the screen shows for a lane's node: the lane's value once the lane
52
52
  * has revealed, the committed truth before. */ function display(e) {
53
- const n = e.T.v;
54
- return e.T.Re.ke && n !== NOT_PENDING ? n : e.Ie;
53
+ const n = e.T.P;
54
+ return e.T.Re.ge && n !== NOT_PENDING ? n : e.le;
55
55
  }
56
56
 
57
57
  /** Guesses written since the last seam (`[node, value, question, …]`).
@@ -66,7 +66,7 @@ function newLane(e) {
66
66
  * on (a store setter's draft too, store/optimistic.ts). */ function guessedValueOf(e) {
67
67
  const n = pendingGuessOf(e);
68
68
  if (n !== NOT_PENDING) return n;
69
- return e.l & CONFIG_OVERRIDE ? laneValueOf(e) : e.Ie;
69
+ return e.l & CONFIG_OVERRIDE ? laneValueOf(e) : e.le;
70
70
  }
71
71
 
72
72
  /** The unflushed guess to `n`, else NOT_PENDING. */ function pendingGuessOf(e) {
@@ -119,10 +119,10 @@ function applyGuesses(e) {
119
119
  // value re-asked renews the question; nothing to notify.
120
120
  u = !t.xe || !t.xe(laneValueOf(t), l);
121
121
  if (!(t.l & CONFIG_GUESS)) {
122
- t.se = NOT_PENDING;
122
+ t.ie = NOT_PENDING;
123
123
  t.l |= CONFIG_GUESS;
124
124
  }
125
- t.T.v = l;
125
+ t.T.P = l;
126
126
  i = txOf(t)._parent;
127
127
  } else {
128
128
  let s;
@@ -141,12 +141,12 @@ function applyGuesses(e) {
141
141
  // screen's plus the guess (plan sec. 19) — a derivation reading a held write
142
142
  // beside the guess must not show it. Two flights for one input, when
143
143
  // the worlds coincide, is the price.)
144
- ext(t).v = l;
144
+ ext(t).P = l;
145
145
  t.l |= CONFIG_OVERRIDE | CONFIG_GUESS;
146
146
  list(t, s);
147
147
  }
148
148
  if (e !== null && i !== null) merge(e, i);
149
- ext(t).Ee = pendingGuesses[s + 2];
149
+ ext(t).Te = pendingGuesses[s + 2];
150
150
  if (u) {
151
151
  // The lane's members re-derive as its work (REACTIVE_LANE_DIRTY): a
152
152
  // blocked lane's re-guess does not re-run its render effects as stale
@@ -187,7 +187,7 @@ function applyGuesses(e) {
187
187
  }
188
188
 
189
189
  /** Is `l` nested under `a` (an ancestor lane)? */ function under(e, n) {
190
- for (let s = e._parent; s !== null && s.v; s = s._parent) if (s === n) return true;
190
+ for (let s = e._parent; s !== null && s.P; s = s._parent) if (s === n) return true;
191
191
  return false;
192
192
  }
193
193
 
@@ -231,7 +231,7 @@ function unlink(e) {
231
231
  * lane under the holder, shown now, re-derived at the landing, ending with
232
232
  * it. Judged at the seam like any lane: blocked while a verdict reader's
233
233
  * own derivation is in flight (lane rule 3), shown otherwise. */ function verdictLane(e) {
234
- return e.he ??= newLane(e);
234
+ return e.Pe ??= newLane(e);
235
235
  }
236
236
 
237
237
  /** A lane pass's node (core.ts `recompute`, before its staging; a pending
@@ -255,16 +255,16 @@ function unlink(e) {
255
255
  const s = n.Re;
256
256
  // Cleared before equality, so a return to the committed value notifies
257
257
  // nobody. The seam wakes it unless the frame is still held (#3892).
258
- if (s?.ke && !blocked(resolveTx(s._parent))) e.R |= LEFT_SHOWN;
259
- if (s?.v) n.Re = null;
260
- n.v = NOT_PENDING;
258
+ if (s?.ge && !blocked(resolveTx(s._parent))) e.R |= LEFT_SHOWN;
259
+ if (s?.P) n.Re = null;
260
+ n.P = NOT_PENDING;
261
261
  }
262
262
  e.l &= -1050881;
263
263
  return false;
264
264
  }
265
265
  if (!e.i) {
266
266
  if (!(e.l & CONFIG_OVERRIDE)) {
267
- e.se = NOT_PENDING;
267
+ e.ie = NOT_PENDING;
268
268
  e.l = e.l & -1048833 | CONFIG_OVERRIDE;
269
269
  ext(e);
270
270
  }
@@ -287,7 +287,7 @@ function unlink(e) {
287
287
  // for the commit sweep as mainline's (async.ts). A node born in the lane
288
288
  // has nothing on screen: its leaf waits as the lane's.
289
289
  else if (t) {
290
- if (t !== true && !(t.m & STATUS_UNINITIALIZED) && (e.l & CONFIG_HELD || e.R & REACTIVE_RECOMPUTING_DEPS) && !e.T?.Re?.v) {
290
+ if (t !== true && !(t.m & STATUS_UNINITIALIZED) && (e.l & CONFIG_HELD || e.R & REACTIVE_RECOMPUTING_DEPS) && !e.T?.Re?.P) {
291
291
  queuePendingNode(e);
292
292
  return false;
293
293
  }
@@ -306,14 +306,14 @@ function supersede(e, n, s) {
306
306
  // A store row answered by a new object the store would reconcile into
307
307
  // the guessed one unchanged is an echo, not a correction (#3898): the
308
308
  // server's object never matches the guess by reference.
309
- if (s && e.l & CONFIG_SLOT_NODE && GlobalQueue.nn?.(e.T.v, n)) s = false;
309
+ if (s && e.l & CONFIG_SLOT_NODE && GlobalQueue.nn?.(e.T.P, n)) s = false;
310
310
  if (s) {
311
311
  dissolveLane(t, l, e);
312
312
  // What the screen showed stays the screen until the parent lands (A18
313
313
  // (c): display and untracked reads keep the override until the commit).
314
- if (t.ke) e.Ie = e.T.v;
314
+ if (t.ge) e.le = e.T.P;
315
315
  e.l &= -10241;
316
- e.T.v = NOT_PENDING;
316
+ e.T.P = NOT_PENDING;
317
317
  holdNode(e, l);
318
318
  if (e.o !== null) insertSubs(e);
319
319
  } else {
@@ -321,7 +321,7 @@ function supersede(e, n, s) {
321
321
  // A confirmation is silent (A17); only truth readers are told.
322
322
  GlobalQueue.sn?.(t);
323
323
  }
324
- e.se = n;
324
+ e.ie = n;
325
325
  joinFuture(l);
326
326
  }
327
327
 
@@ -340,9 +340,9 @@ function supersede(e, n, s) {
340
340
  unlink(e);
341
341
  // Its verdicts end with it (a correction's dissolution reaches here before
342
342
  // `endLanes` would): the readers re-derive from the truth's notification.
343
- const l = e.he;
343
+ const l = e.Pe;
344
344
  if (l !== null) {
345
- e.he = null;
345
+ e.Pe = null;
346
346
  if (lanes.indexOf(l) !== -1) dissolveLane(l, null);
347
347
  }
348
348
  for (let t = 0; t < e.tn.length; t++) {
@@ -351,12 +351,12 @@ function supersede(e, n, s) {
351
351
  if (l === s || u.Re !== e) continue;
352
352
  const i = l.i;
353
353
  const r = l.l & CONFIG_GUESS;
354
- const o = u.v;
354
+ const o = u.P;
355
355
  // What the screen shows of it (NOT_PENDING: nothing — a lane pass that
356
356
  // errored or pends staged no value), and the lane's latest.
357
- const E = e.ke && !i ? o : l.Ie;
357
+ const E = e.ge && !i ? o : l.le;
358
358
  const f = laneValueOf(l);
359
- u.v = NOT_PENDING;
359
+ u.P = NOT_PENDING;
360
360
  l.l &= -10241;
361
361
  if (n === null) {
362
362
  // A derivation the revert re-derives (dirtied by the guess's
@@ -365,19 +365,19 @@ function supersede(e, n, s) {
365
365
  // of its flight observes it (#3648, #3651; #3305's commit beneath a
366
366
  // flight).
367
367
  u.Re = null;
368
- if (!i && !r) l.se = f;
368
+ if (!i && !r) l.ie = f;
369
369
  commitPendingNode(l);
370
370
  if (!i) {
371
371
  if (l.R & (REACTIVE_IN_HEAP | REACTIVE_DIRTY | REACTIVE_CHECK) || l.m & STATUS_PENDING) l.l |= CONFIG_INPUTS_PUBLISHED;
372
- if (E !== NOT_PENDING && (!l.xe || !l.xe(E, l.Ie))) insertSubs(l);
372
+ if (E !== NOT_PENDING && (!l.xe || !l.xe(E, l.le))) insertSubs(l);
373
373
  }
374
374
  } else if (r) {
375
375
  // A guess of a dissolving lane: its truth — a landing held beneath it,
376
376
  // else the value it covered — is the parent's to land, and its readers
377
377
  // re-derive from it there. What the screen showed stays the screen
378
378
  // until then (A18 (c)).
379
- if (l.se === NOT_PENDING) l.se = covered(l);
380
- if (e.ke) l.Ie = o;
379
+ if (l.ie === NOT_PENDING) l.ie = covered(l);
380
+ if (e.ge) l.le = o;
381
381
  holdNode(l, n);
382
382
  insertSubs(l);
383
383
  } else {
@@ -390,13 +390,13 @@ function supersede(e, n, s) {
390
390
  // it: an input's truth equal to its committed value is silent).
391
391
  u.Re = null;
392
392
  if (!i) {
393
- if (E !== NOT_PENDING && e.ke) l.se = E;
393
+ if (E !== NOT_PENDING && e.ge) l.ie = E;
394
394
  if (l.m & STATUS_PENDING) {
395
- u.Se = null;
395
+ u.Ee = null;
396
396
  enqueueSub(l);
397
397
  }
398
398
  }
399
- if (e.ke) {
399
+ if (e.ge) {
400
400
  commitPendingNode(l);
401
401
  if (!i) l.l |= CONFIG_INPUTS_PUBLISHED;
402
402
  } else {
@@ -416,7 +416,7 @@ function supersede(e, n, s) {
416
416
  // void: nothing of it reached the screen. So are the runs a blocked lane
417
417
  // parked: they show a re-guess the seam never revealed, beside derivations
418
418
  // that keep what the screen showed.
419
- if (n === null || e.ke && !e.ln) releaseQueues(e); else e.un[0].length = e.un[1].length = 0;
419
+ if (n === null || e.ge && !e.ln) releaseQueues(e); else e.un[0].length = e.un[1].length = 0;
420
420
  }
421
421
 
422
422
  /** Lanes. A lane's node is read by its reader's kind: an authoritative
@@ -433,7 +433,11 @@ function supersede(e, n, s) {
433
433
  * has shown, or one the seam has not judged yet (its run waits on the
434
434
  * verdict: released if the lane shows, held if it blocks) — and sees the
435
435
  * lane's latest. NOT_PENDING falls through: lane work's read of a pending
436
- * member throws like any. */ function laneRead(e, n) {
436
+ * member throws like any. A verdict reader entering a lane the seam has
437
+ * not judged reads ahead of it like a staging read: if the seam holds the
438
+ * lane, the pass re-derives on the screen (`entered`, #4284) — `latest()`
439
+ * beside the lane's nodes tears ahead of the hold however the reads are
440
+ * grouped into effects. */ function laneRead(e, n) {
437
441
  const s = n.l & CONFIG_GUESS;
438
442
  const t = txOf(n);
439
443
  // A lane a correction folded under (D-34): store/optimistic.ts reads it.
@@ -450,6 +454,7 @@ function supersede(e, n, s) {
450
454
  // An errored member's lane value predates its error; the error is the answer (#4106).
451
455
  if (!s && u & STATUS_ERROR) return NOT_PENDING;
452
456
  if (e === null) return laneValueOf(n);
457
+ const i = !s && u & STATUS_PENDING;
453
458
  if (!tracking || e.l & CONFIG_CHILDREN_FORBIDDEN) {
454
459
  // An untracked read inside lane work derives from the lane like a
455
460
  // tracked one, minus the subscription — `untrack` is about
@@ -458,39 +463,45 @@ function supersede(e, n, s) {
458
463
  // lanes read by one pass link, as for tracked reads). Outside lane
459
464
  // work, the screen.
460
465
  if (passLane === null || e.l & CONFIG_CHILDREN_FORBIDDEN) return display(n);
461
- enterLane(t, e);
462
- if (!s && u & STATUS_PENDING) return NOT_PENDING;
463
- return laneValueOf(n);
464
- }
465
- if (e.i === EFFECT_RENDER && (passLane === null || !sameLane(passLane, t))) {
466
- if (!s) {
467
- // A flight whose value has never shown: nothing to show — the leaf
468
- // waits on it, and its own frame holds (A15, #3334), not the lane's
469
- // parent. The landing lands the frame: its own pass (the flush
470
- // joined its transaction) reads the answer it waited on — the lane's
471
- // staging, revealed at this seam.
472
- if (u & STATUS_PENDING && n.T.v === NOT_PENDING) return NOT_PENDING;
473
- if (n.se !== NOT_PENDING && e.l & CONFIG_HELD && flushTransaction !== null && resolveTx(flushTransaction) === txOf(e)) return n.se;
474
- }
475
- if (t.ln || !s && u & STATUS_PENDING) {
476
- if (!s && !t.ke && u & STATUS_UNINITIALIZED) throw new NotReadyError(null);
477
- // Re-derived at the reveal; once per pass (the pass may also be a stale
478
- // reader of a transaction, REACTIVE_FRAME_READ — both reruns apply).
479
- if (!(e.R & REACTIVE_SCREEN_READ)) {
480
- e.R |= REACTIVE_FRAME_READ | REACTIVE_SCREEN_READ;
481
- t.En.push(e);
466
+ } else if (e.i === EFFECT_RENDER) {
467
+ // A flight whose value has never shown: nothing to show — the leaf
468
+ // waits on it, and its own frame holds (A15, #3334), not the lane's
469
+ // parent. The landing lands the frame: its own pass (the flush joined
470
+ // its transaction) reads the answer it waited on — the lane's staging,
471
+ // revealed at this seam.
472
+ const l = i && n.T.P === NOT_PENDING;
473
+ // D-28: a leaf a verdict read moved into the lane still throws on such
474
+ // a flight — the pass leaves the lane, and its frame holds as if the
475
+ // read had come first (S-39, #4275). Nor does a verdict lane's seat carry
476
+ // the leaf into another lane (no link): it reads that lane as from the
477
+ // frame's seat (#4284).
478
+ if ((l || passLane !== t) && passLane?._parent.Pe === passLane) setPassLane(null);
479
+ if (passLane === null || !sameLane(passLane, t)) {
480
+ // (A lane that has never shown: the screen is the committed value.)
481
+ if (l && t.ge) return NOT_PENDING;
482
+ if (!s && n.ie !== NOT_PENDING && e.l & CONFIG_HELD && flushTransaction !== null && resolveTx(flushTransaction) === txOf(e)) return n.ie;
483
+ if (t.ln || i) {
484
+ if (!s && !t.ge && u & STATUS_UNINITIALIZED) throw new NotReadyError(null);
485
+ // Re-derived at the reveal; once per pass (the pass may also be a stale
486
+ // reader of a transaction, REACTIVE_FRAME_READ — both reruns apply).
487
+ if (!(e.R & REACTIVE_SCREEN_READ)) {
488
+ e.R |= REACTIVE_FRAME_READ | REACTIVE_SCREEN_READ;
489
+ t.En.push(e);
490
+ }
491
+ return display(n);
482
492
  }
483
- return display(n);
493
+ // A verdict reader reads ahead of the seam's verdict: re-derived on the
494
+ // screen if the lane holds (#4284).
495
+ if (e.l & CONFIG_VERDICT && t._parent.Pe !== t) entered.push(e);
484
496
  }
485
497
  }
486
498
  enterLane(t, e);
487
- if (!s && u & STATUS_PENDING) return NOT_PENDING;
488
- return laneValueOf(n);
499
+ return i ? NOT_PENDING : laneValueOf(n);
489
500
  }
490
501
 
491
502
  /** The value a guess covered: the node's committed value — a store slot's
492
503
  * asked of the store (a chained link follows the inner store's commits). */ function covered(e) {
493
- return e.l & CONFIG_SLOT_NODE && GlobalQueue.fn !== undefined ? GlobalQueue.fn(e) : e.Ie;
504
+ return e.l & CONFIG_SLOT_NODE && GlobalQueue.fn !== undefined ? GlobalQueue.fn(e) : e.le;
494
505
  }
495
506
 
496
507
  /** A guess in `l` whose own truth is in flight (its refetch — authoritative:
@@ -521,7 +532,7 @@ function supersede(e, n, s) {
521
532
  const t = n[s];
522
533
  if (lanes.indexOf(t) === -1) continue;
523
534
  const l = resolveTx(t._parent);
524
- if (!l.nt || l.tt !== 0 || l.v || ownFlights(l) || guessFlights(t)) continue;
535
+ if (!l.nt || l.tt !== 0 || l.P || ownFlights(l) || guessFlights(t)) continue;
525
536
  e = true;
526
537
  // Each guess's truth: the one beneath it (a landing held there), else
527
538
  // the one it covered. (A correction dissolves the lane — the others are
@@ -529,8 +540,8 @@ function supersede(e, n, s) {
529
540
  for (let e = 0; e < t.tn.length && lanes.indexOf(t) !== -1; e++) {
530
541
  const n = t.tn[e];
531
542
  if (n.l & CONFIG_GUESS && n.T.Re === t) {
532
- const e = n.se !== NOT_PENDING ? n.se : covered(n);
533
- supersede(n, e, !n.xe || !n.xe(n.T.v, e));
543
+ const e = n.ie !== NOT_PENDING ? n.ie : covered(n);
544
+ supersede(n, e, !n.xe || !n.xe(n.T.P, e));
534
545
  }
535
546
  }
536
547
  }
@@ -545,7 +556,7 @@ function laneSeam(e, n) {
545
556
  return;
546
557
  }
547
558
  e.ln = false;
548
- e.ke = true;
559
+ e.ge = true;
549
560
  reruns(e);
550
561
  for (let n = 0; n < e.tn.length; n++) {
551
562
  const s = e.tn[n];
@@ -555,9 +566,9 @@ function laneSeam(e, n) {
555
566
  }
556
567
  const t = s.T;
557
568
  if (t.Re !== e) continue;
558
- if (!(s.l & CONFIG_GUESS) && !s.i && s.se !== NOT_PENDING) {
559
- t.v = s.se;
560
- s.se = NOT_PENDING;
569
+ if (!(s.l & CONFIG_GUESS) && !s.i && s.ie !== NOT_PENDING) {
570
+ t.P = s.ie;
571
+ s.ie = NOT_PENDING;
561
572
  }
562
573
  // The frame this pass built is the screen's now; the one it replaced
563
574
  // goes (as `commitPendingNode` retires a committed pass's).
@@ -582,7 +593,7 @@ function laneSeam(e, n) {
582
593
  * frame is not complete without it (A15) — landing it would commit the write
583
594
  * beside a derivation still showing the old one. A guess whose own flight is
584
595
  * up likewise: the question was asked in this frame. */ function lanesBlocked(e) {
585
- return e.v && linkBlocked(e) || nestedBlocked(e);
596
+ return e.P && linkBlocked(e) || nestedBlocked(e);
586
597
  }
587
598
 
588
599
  /** The lanes under `t` (its own; a link group's are each other's). */ function nestedBlocked(e) {
@@ -611,17 +622,17 @@ function laneSeam(e, n) {
611
622
  // folds a current one beneath it (D-34): true.
612
623
  const s = stale(e, question);
613
624
  if (GlobalQueue.an?.(e, n, s) || s) {
614
- e.se = n;
625
+ e.ie = n;
615
626
  e.l |= CONFIG_HELD;
616
627
  schedule();
617
628
  return n;
618
629
  }
619
- supersede(e, n, !e.xe || !e.xe(e.T.v, n));
630
+ supersede(e, n, !e.xe || !e.xe(e.T.P, n));
620
631
  } else {
621
632
  const s = txOf(e);
622
633
  if (e.xe && e.xe(laneValueOf(e), n)) return n;
623
- if (s.ke) e.se = n; else e.T.v = n;
624
- if (e.X !== undefined) e.ie = clock;
634
+ if (s.ge) e.ie = n; else e.T.P = n;
635
+ if (e.X !== undefined) e.Se = clock;
625
636
  insertSubs(e);
626
637
  laneDirty(e, s);
627
638
  }
@@ -643,18 +654,18 @@ function laneSeam(e, n) {
643
654
  * A24). The guess's own question answering supersedes as usual. */ function laneOutcome(e, n, s) {
644
655
  if (s) return true;
645
656
  if (stale(e, answered(e))) {
646
- e.se = n;
657
+ e.ie = n;
647
658
  e.l |= CONFIG_HELD;
648
659
  return true;
649
660
  }
650
- supersede(e, n, !e.xe || !e.xe(e.T.v, n));
661
+ supersede(e, n, !e.xe || !e.xe(e.T.P, n));
651
662
  return true;
652
663
  }
653
664
 
654
665
  /** THE provenance test, for a derivation's pass and a write alike: an
655
666
  * answer to question `q` landing on a guess is stale when `q` is older
656
667
  * than the guess's own; unstamped (0) and mainline are current. */ function stale(e, n) {
657
- return n !== 0 && n < e.T.Ee;
668
+ return n !== 0 && n < e.T.Te;
658
669
  }
659
670
 
660
671
  /** The question a derivation's pass answered: the newest among the stamped
@@ -663,7 +674,7 @@ function laneSeam(e, n) {
663
674
  let n = 0;
664
675
  for (let s = e.C; s !== null; s = s.G) {
665
676
  const e = s.S;
666
- if (e.T !== null && e.T.Ee > n && e.ie === clock) n = e.T.Ee;
677
+ if (e.T !== null && e.T.Te > n && e.Se === clock) n = e.T.Te;
667
678
  }
668
679
  return n;
669
680
  }
@@ -693,20 +704,29 @@ const laneSeams = e => {
693
704
  // now (one run — #3322, #3540). Other verdict-lane work re-derives only
694
705
  // if the frame stays parked: a transaction landing at this seam makes
695
706
  // what it read the screen (one run).
696
- if (t.R & REACTIVE_DISPOSED || l != null && l._parent?.he === l && (t.l & CONFIG_VERDICT ? !(t.R & REACTIVE_PROBE_UNANSWERED) : !blocked(l._parent))) continue;
707
+ if (t.R & REACTIVE_DISPOSED || l != null && l._parent?.Pe === l && (t.l & CONFIG_VERDICT ? !(t.R & REACTIVE_PROBE_UNANSWERED) : !blocked(l._parent))) continue;
697
708
  // A leaked effect re-derives seated in its lane from its first read: it
698
709
  // carries no lane value (`laneStage`), so without this its pass starts
699
710
  // as mainline, joins the parked frame on its first held read and reads
700
711
  // the staging, then re-enters the lane at its guess read and leaks
701
712
  // again — every round (#4014). A computed is already seated by its
702
713
  // override, and a written guess's own pass stays the frame's.
703
- if (l != null && l.v && t.i) t.R |= REACTIVE_LANE_DIRTY;
714
+ if (l != null && l.P && t.i) t.R |= REACTIVE_LANE_DIRTY;
704
715
  enqueueSub(t);
705
- if (l != null && l.v && n.indexOf(l) === -1) n.push(l);
716
+ if (l != null && l.P && n.indexOf(l) === -1) n.push(l);
706
717
  }
707
718
  for (let e = Math.min(judged, lanes.length) - 1; e >= 0; e--) laneSeam(lanes[e], n.indexOf(lanes[e]) !== -1);
719
+ // A verdict reader that entered a lane this seam held: its run waits with
720
+ // the lane, and the verdict beside it would freeze — it re-derives next
721
+ // round from the frame's seat, a stale reader of the lane (#4284).
722
+ for (let e; e = entered.pop(); ) if (e.T.Re?.ln) {
723
+ e.R &= ~REACTIVE_LANE_DIRTY;
724
+ enqueueSub(e);
725
+ }
708
726
  };
709
727
 
728
+ /** Render effects that read a verdict and entered a lane before its seam (#4284). */ const entered = [];
729
+
710
730
  installLaneHooks(laneRead, laneStage, laneOutcome, laneWrite, applyGuessesAt, laneSeams, () => lanes.length !== 0 && laneCorrections(), endLanes, lanesBlocked);
711
731
 
712
732
  GlobalQueue.J = verdictLane;
@@ -6,7 +6,7 @@ import { unobserved, clearDeps } from "./graph.js";
6
6
 
7
7
  import { deleteFromHeap } from "./heap.js";
8
8
 
9
- import { globalQueue, schedule, dirtyQueue } from "./scheduler.js";
9
+ import { globalQueue, pendingNodes, schedule, dirtyQueue } from "./scheduler.js";
10
10
 
11
11
  // L2 (step 2): the parked frame. A pass parks the previous frame's children
12
12
  // and `_disposal` list on `_x._pendingFirstChild` / `_x._pendingDisposal`
@@ -16,11 +16,11 @@ import { globalQueue, schedule, dirtyQueue } from "./scheduler.js";
16
16
  // key off it, and the heap defers a zombie's recompute past the seam (no
17
17
  // second heap — the deferral list in scheduler.ts).
18
18
  function markDisposal(e) {
19
- let n = e.De;
19
+ let n = e.pe;
20
20
  while (n) {
21
21
  n.R |= REACTIVE_ZOMBIE;
22
22
  markDisposal(n);
23
- n = n.pe;
23
+ n = n.Ge;
24
24
  }
25
25
  }
26
26
 
@@ -72,15 +72,16 @@ function disposeChildren(e, n = false, r) {
72
72
  e.R = t | REACTIVE_DISPOSED;
73
73
  }
74
74
  if (n && e.X) {
75
- if (e.T !== null) e.T.Se = null;
75
+ if (e.T !== null) e.T.Ee = null;
76
76
  // A death that can unblock the future (L2, `futureBlocked`): a held
77
77
  // flight's — a transaction's or a lane's (its `_x._transaction`) — or a
78
78
  // stale reader's (REACTIVE_FRAME_READ — a frame that derived from one).
79
+ // An idle pending queue would also pin it until an unrelated flush.
79
80
  // Nothing else re-examines a dead node, so make sure a seam comes.
80
- if (e.m & STATUS_PENDING && e.T?.Re != null || t & REACTIVE_FRAME_READ) schedule();
81
+ if (e.m & STATUS_PENDING && e.T?.Re != null || t & REACTIVE_FRAME_READ || pendingNodes.length) schedule();
81
82
  }
82
- let l = r ? e.T.Ue : e.De;
83
- if (r) e.T.Ue = null; else e.De = null;
83
+ let l = r ? e.T.Ue : e.pe;
84
+ if (r) e.T.Ue = null; else e.pe = null;
84
85
  while (l) {
85
86
  const e = l;
86
87
  // Owner teardown is death regardless of the child's own lifecycle
@@ -110,7 +111,7 @@ function disposeChildren(e, n = false, r) {
110
111
  // itself out of the detached chain, and a cleanup may then have linked
111
112
  // it at the fresh head, which rewrote the `_nextSibling` a pre-read
112
113
  // would still be holding.
113
- l = l.pe;
114
+ l = l.Ge;
114
115
  }
115
116
  if (!r) e.ye = 0;
116
117
  // O(1) splice out of parent's chain on individual dispose. Skipped during
@@ -120,8 +121,8 @@ function disposeChildren(e, n = false, r) {
120
121
  // still reach later siblings.
121
122
  if (n && !r && !(t & REACTIVE_ZOMBIE) && e._parent !== null && !(e._parent.R & REACTIVE_DISPOSED)) {
122
123
  const n = e.Ze;
123
- const r = e.pe;
124
- if (n !== null) n.pe = r; else e._parent.De = r;
124
+ const r = e.Ge;
125
+ if (n !== null) n.Ge = r; else e._parent.pe = r;
125
126
  if (r !== null) r.Ze = n;
126
127
  e.Ze = null;
127
128
  }
@@ -136,11 +137,11 @@ function disposeChildren(e, n = false, r) {
136
137
  }
137
138
 
138
139
  function linkChild(e, n) {
139
- const r = e.De;
140
+ const r = e.pe;
140
141
  n.Ze = null;
141
- n.pe = r;
142
+ n.Ge = r;
142
143
  if (r !== null) r.Ze = n;
143
- e.De = n;
144
+ e.pe = n;
144
145
  }
145
146
 
146
147
  function runDisposal(e, n) {
@@ -306,8 +307,8 @@ function disposeRootSelf(e = true) {
306
307
  R: REACTIVE_NONE,
307
308
  Qe: true,
308
309
  Ye: n?.Qe ? n.Ye : n,
309
- De: null,
310
310
  pe: null,
311
+ Ge: null,
311
312
  Ze: null,
312
313
  He: null,
313
314
  A: n?.A || defaultContext,