@solidjs/signals 2.0.0-beta.29 → 2.0.0-beta.31

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.
package/dist/dev.js CHANGED
@@ -429,6 +429,16 @@ const zombieQueue = {
429
429
  _min: 0,
430
430
  _max: 0
431
431
  };
432
+ /** runHeap callback that discards a queued zombie recompute instead of running
433
+ * it: unlink pure recompute entries; strip just the recompute bit from dirtied
434
+ * height-adjust entries so their height work still happens. */
435
+ function cancelZombieRecompute(el) {
436
+ if (el._flags & REACTIVE_IN_HEAP_HEIGHT) el._flags &= -12;
437
+ else {
438
+ deleteFromHeap(el, zombieQueue);
439
+ el._flags &= -4;
440
+ }
441
+ }
432
442
  let clock = 0;
433
443
  let activeTransition = null;
434
444
  let scheduled = false;
@@ -748,7 +758,18 @@ class GlobalQueue extends Queue {
748
758
  const isComplete = transitionComplete(activeTransition);
749
759
  if (!isComplete) {
750
760
  const stashedTransition = activeTransition;
751
- runHeap(zombieQueue, GlobalQueue._update);
761
+ // When the parking batch IS the transition, all of its writes commit
762
+ // only with it — every zombie recompute they queued would run against
763
+ // a world the zombie never displays (zombies render mainline until
764
+ // commit), so cancel them instead of running them. Only an ambient
765
+ // batch's mainline writes (the #2916 shape below) legitimately reach
766
+ // zombies here. Height-adjust entries still process normally: a
767
+ // dirtied one keeps its height flag and falls through to runHeap's
768
+ // adjustHeight path on the next pass of the bucket.
769
+ runHeap(
770
+ zombieQueue,
771
+ this._batch === stashedTransition ? cancelZombieRecompute : GlobalQueue._update
772
+ );
752
773
  // Detach: the stashed transition keeps its batch; ambient work that
753
774
  // follows lands in a fresh one. If the batch is already a separate
754
775
  // ambient one — action done() restored activeTransition without
@@ -895,6 +916,12 @@ class GlobalQueue extends Queue {
895
916
  }
896
917
  if (batch._affectsNodes.length) activeTransition._affectsNodes.push(...batch._affectsNodes);
897
918
  for (const store of batch._optimisticStores) activeTransition._optimisticStores.add(store);
919
+ // Gated readers recorded against the ambient batch move with it: their
920
+ // replay-at-commit now happens at the transaction's completion.
921
+ if (batch._gatedSubs.size) {
922
+ for (const sub of batch._gatedSubs) activeTransition._gatedSubs.add(sub);
923
+ batch._gatedSubs.clear();
924
+ }
898
925
  currentBatch = this._batch = activeTransition;
899
926
  }
900
927
  for (const lane of activeLanes) {
@@ -982,13 +1009,19 @@ function finalizePureQueue(completingTransition = null, incomplete = false) {
982
1009
  // which installed the engine's hooks.
983
1010
  if (batch._optimisticNodes.length) GlobalQueue._resolveOptimistic(batch._optimisticNodes);
984
1011
  // Replay entanglement: subs recorded by the read-time gate get rescheduled
985
- // so they re-run with the now-committed values visible.
986
- if (completingTransition && completingTransition._gatedSubs.size) {
987
- for (const sub of completingTransition._gatedSubs) {
1012
+ // so they re-run with the now-committed values visible. The ambient batch
1013
+ // replays too laneReadsCommitted records readers whose committed-view
1014
+ // read hid a same-tick plain write that just committed above (#2963).
1015
+ if (batch._gatedSubs.size) {
1016
+ for (const sub of batch._gatedSubs) {
988
1017
  if (sub._flags & REACTIVE_DISPOSED) continue;
989
1018
  enqueueSub(sub);
990
1019
  }
991
- completingTransition._gatedSubs.clear();
1020
+ batch._gatedSubs.clear();
1021
+ // A completing transition keeps the outer flush loop alive by itself;
1022
+ // the ambient batch needs the re-arm or the replay sits in the heap
1023
+ // until the next unrelated write.
1024
+ schedule();
992
1025
  }
993
1026
  // Declared motion ends with the transaction: settle (or plain flush end
994
1027
  // for ambient marks) releases each registration's refcount. A non-empty
@@ -3482,12 +3515,25 @@ function gatedRead(el, owner, c) {
3482
3515
  * optimistic/lane-assigned signals, stale-mode reads, and pending owners.
3483
3516
  */
3484
3517
  function laneReadsCommitted(el, owner, c) {
3485
- return (
3518
+ if (
3486
3519
  el._overrideValue !== undefined ||
3487
3520
  !!el._optimisticLane ||
3488
- (owner === el && stale && c._parentSource !== el) ||
3489
3521
  !!(owner._statusFlags & STATUS_PENDING)
3490
- );
3522
+ )
3523
+ return true;
3524
+ if (owner === el && stale && c._parentSource !== el) {
3525
+ // The committed view can hide a same-tick ambient write (a lane member —
3526
+ // even just an isPending companion flip — puts the reader "under a lane"
3527
+ // against unrelated plain writes). With no transaction the write commits
3528
+ // at THIS flush's end with no re-delivery, so record the reader for
3529
+ // replay at commit — the same contract gatedRead provides when a
3530
+ // transaction is active (#2963). If a transaction forms mid-flush,
3531
+ // initTransition carries the recording over to it.
3532
+ if (el._pendingValue !== NOT_PENDING && activeTransition === null)
3533
+ globalQueue._batch._gatedSubs.add(c);
3534
+ return true;
3535
+ }
3536
+ return false;
3491
3537
  }
3492
3538
  /**
3493
3539
  * recompute()'s lane posture: resolve the node's own lane (own=true), or adopt