@solidjs/signals 2.0.0-rc.7 → 2.0.0-rc.9

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 (134) hide show
  1. package/dist/dev-shared.js +6357 -0
  2. package/dist/dev.attribution.js +2485 -0
  3. package/dist/dev.js +2851 -7027
  4. package/dist/observe/affects.js +129 -0
  5. package/dist/observe/attribution.js +7 -0
  6. package/dist/observe/boundaries.js +641 -0
  7. package/dist/observe/core/action.js +177 -0
  8. package/dist/observe/core/async.js +846 -0
  9. package/dist/observe/core/attribution-costs.js +66 -0
  10. package/dist/observe/core/attribution-feedback.js +282 -0
  11. package/dist/observe/core/attribution-hooks.js +82 -0
  12. package/dist/observe/core/attribution-queries.js +28 -0
  13. package/dist/observe/core/attribution.js +2095 -0
  14. package/dist/observe/core/constants.js +248 -0
  15. package/dist/{types-cjs/core/context.d.cts → observe/core/context.js} +37 -12
  16. package/dist/observe/core/core.js +1789 -0
  17. package/dist/observe/core/dev.js +293 -0
  18. package/dist/observe/core/effect.js +195 -0
  19. package/dist/observe/core/error-hooks.js +71 -0
  20. package/dist/observe/core/error.js +80 -0
  21. package/dist/observe/core/external.js +98 -0
  22. package/dist/observe/core/graph.js +155 -0
  23. package/dist/observe/core/heap.js +147 -0
  24. package/dist/observe/core/invariants.js +45 -0
  25. package/dist/observe/core/lanes.js +205 -0
  26. package/dist/observe/core/optimistic.js +585 -0
  27. package/dist/observe/core/owner.js +322 -0
  28. package/dist/observe/core/scheduler.js +1428 -0
  29. package/dist/observe/core/verdict.js +669 -0
  30. package/dist/observe/index.js +59 -0
  31. package/dist/observe/map.js +484 -0
  32. package/dist/{types-cjs/signals.d.cts → observe/signals.js} +311 -317
  33. package/dist/observe/store/index.js +40 -0
  34. package/dist/observe/store/next/optimistic.js +660 -0
  35. package/dist/observe/store/next/projection.js +255 -0
  36. package/dist/observe/store/next/reconcile.js +375 -0
  37. package/dist/observe/store/next/store.js +2327 -0
  38. package/dist/observe/store/next/target.js +51 -0
  39. package/dist/observe/store/store.js +325 -0
  40. package/dist/observe/store/storePath.js +103 -0
  41. package/dist/observe/store/utils.js +1023 -0
  42. package/dist/prod/attribution.js +41 -0
  43. package/dist/prod/boundaries.js +128 -76
  44. package/dist/prod/core/action.js +65 -37
  45. package/dist/prod/core/async.js +324 -172
  46. package/dist/prod/core/constants.js +57 -1
  47. package/dist/prod/core/context.js +3 -3
  48. package/dist/prod/core/core.js +1003 -364
  49. package/dist/prod/core/dev.js +17 -1
  50. package/dist/prod/core/effect.js +76 -42
  51. package/dist/prod/core/error-hooks.js +71 -0
  52. package/dist/prod/core/external.js +4 -4
  53. package/dist/prod/core/graph.js +37 -37
  54. package/dist/prod/core/heap.js +61 -56
  55. package/dist/prod/core/lanes.js +107 -63
  56. package/dist/prod/core/optimistic.js +374 -93
  57. package/dist/prod/core/owner.js +62 -46
  58. package/dist/prod/core/scheduler.js +653 -286
  59. package/dist/prod/core/verdict.js +245 -127
  60. package/dist/prod/index.js +15 -5
  61. package/dist/prod/map.js +292 -141
  62. package/dist/prod/signals.js +29 -16
  63. package/dist/prod/store/next/optimistic.js +77 -55
  64. package/dist/prod/store/next/projection.js +31 -20
  65. package/dist/prod/store/next/reconcile.js +87 -84
  66. package/dist/prod/store/next/store.js +718 -401
  67. package/dist/prod/store/next/target.js +32 -10
  68. package/dist/prod/store/store.js +12 -14
  69. package/dist/prod/store/utils.js +950 -130
  70. package/dist/types/attribution.d.ts +20 -0
  71. package/dist/types/attribution.prod.d.ts +22 -0
  72. package/dist/types/boundaries.d.ts +10 -1
  73. package/dist/types/core/action.d.ts +12 -5
  74. package/dist/types/core/async.d.ts +1 -1
  75. package/dist/types/core/attribution-costs.d.ts +35 -0
  76. package/dist/types/core/attribution-feedback.d.ts +133 -0
  77. package/dist/types/core/attribution-hooks.d.ts +147 -9
  78. package/dist/types/core/attribution-queries.d.ts +10 -0
  79. package/dist/types/core/attribution.d.ts +216 -182
  80. package/dist/types/core/constants.d.ts +56 -0
  81. package/dist/types/core/core.d.ts +190 -2
  82. package/dist/types/core/dev.d.ts +246 -28
  83. package/dist/types/core/effect.d.ts +8 -1
  84. package/dist/types/core/error-hooks.d.ts +71 -0
  85. package/dist/types/core/index.d.ts +4 -1
  86. package/dist/types/core/invariants.d.ts +4 -0
  87. package/dist/types/core/lanes.d.ts +39 -5
  88. package/dist/types/core/scheduler.d.ts +153 -0
  89. package/dist/types/core/types.d.ts +32 -9
  90. package/dist/types/index.d.ts +10 -3
  91. package/dist/types/map.d.ts +2 -0
  92. package/dist/types/signals.d.ts +8 -0
  93. package/dist/types/store/index.d.ts +2 -0
  94. package/dist/types/store/next/optimistic.d.ts +1 -1
  95. package/dist/types/store/next/store.d.ts +11 -5
  96. package/dist/types/store/next/target.d.ts +37 -10
  97. package/dist/types/store/utils.d.ts +178 -0
  98. package/package.json +21 -17
  99. package/dist/node.cjs +0 -10541
  100. package/dist/node.dev.cjs +0 -13724
  101. package/dist/types-cjs/affects.d.cts +0 -47
  102. package/dist/types-cjs/boundaries.d.cts +0 -175
  103. package/dist/types-cjs/core/action.d.cts +0 -64
  104. package/dist/types-cjs/core/async.d.cts +0 -23
  105. package/dist/types-cjs/core/attribution-hooks.d.cts +0 -127
  106. package/dist/types-cjs/core/attribution.d.cts +0 -495
  107. package/dist/types-cjs/core/constants.d.cts +0 -159
  108. package/dist/types-cjs/core/core.d.cts +0 -185
  109. package/dist/types-cjs/core/dev.d.cts +0 -136
  110. package/dist/types-cjs/core/effect.d.cts +0 -30
  111. package/dist/types-cjs/core/error.d.cts +0 -56
  112. package/dist/types-cjs/core/external.d.cts +0 -15
  113. package/dist/types-cjs/core/graph.d.cts +0 -28
  114. package/dist/types-cjs/core/heap.d.cts +0 -24
  115. package/dist/types-cjs/core/index.d.cts +0 -13
  116. package/dist/types-cjs/core/invariants.d.cts +0 -59
  117. package/dist/types-cjs/core/lanes.d.cts +0 -54
  118. package/dist/types-cjs/core/optimistic.d.cts +0 -6
  119. package/dist/types-cjs/core/owner.d.cts +0 -124
  120. package/dist/types-cjs/core/scheduler.d.cts +0 -236
  121. package/dist/types-cjs/core/types.d.cts +0 -204
  122. package/dist/types-cjs/core/verdict.d.cts +0 -2
  123. package/dist/types-cjs/index.d.cts +0 -10
  124. package/dist/types-cjs/map.d.cts +0 -69
  125. package/dist/types-cjs/package.json +0 -3
  126. package/dist/types-cjs/store/index.d.cts +0 -18
  127. package/dist/types-cjs/store/next/optimistic.d.cts +0 -25
  128. package/dist/types-cjs/store/next/projection.d.cts +0 -8
  129. package/dist/types-cjs/store/next/reconcile.d.cts +0 -10
  130. package/dist/types-cjs/store/next/store.d.cts +0 -121
  131. package/dist/types-cjs/store/next/target.d.cts +0 -166
  132. package/dist/types-cjs/store/store.d.cts +0 -143
  133. package/dist/types-cjs/store/storePath.d.cts +0 -58
  134. package/dist/types-cjs/store/utils.d.cts +0 -74
@@ -1,6 +1,6 @@
1
1
  import { releaseFlightTeardown, handleAsync, clearStatus, parkLoadingWindow, notifyStatus, settlePendingSource, settleErroredDependents } from "./async.js";
2
2
 
3
- import { EFFECT_TRACKED, EFFECT_USER, REACTIVE_OPTIMISTIC_DIRTY, CONFIG_OPTIMISTIC, NOT_PENDING, STATUS_UNINITIALIZED, STATUS_ERROR, REACTIVE_REASK, REACTIVE_RECOMPUTING_DEPS, CONFIG_SYNC, CONFIG_HAS_LANE, STATUS_PENDING, REACTIVE_MISSED_WAKE, REACTIVE_NONE, REACTIVE_SNAPSHOT_STALE, unwrapOverride, CONFIG_DIRECT_COMMIT, OVERRIDE_UNDEFINED, CONFIG_AUTHORITATIVE_OBSERVED, CONFIG_HAS_COMPANIONS, REACTIVE_LAZY, REACTIVE_DISPOSED, CONFIG_AUTO_DISPOSE, CONFIG_CHILDREN_FORBIDDEN, CONFIG_FRESH_READ, CONFIG_AUTHORITATIVE_READ, CONFIG_HELD_TRUTH, REACTIVE_CHECK, REACTIVE_DIRTY, REACTIVE_IN_HEAP, REACTIVE_IN_HEAP_HEIGHT, defaultContext, CONFIG_IN_SNAPSHOT_SCOPE, CONFIG_TRANSPARENT, CONFIG_OWNED_WRITE, CONFIG_NO_SNAPSHOT, CONFIG_FW_CHILDREN, NO_SNAPSHOT, CONFIG_HAS_SNAPSHOT, REACTIVE_MANUAL_WRITE, CONFIG_SLOT_NODE, STORE_SNAPSHOT_PROPS } from "./constants.js";
3
+ import { NOT_PENDING, EFFECT_TRACKED, EFFECT_USER, CONFIG_HELD_CHILDREN, REACTIVE_OPTIMISTIC_DIRTY, CONFIG_OPTIMISTIC, CONFIG_DERIVED_OVERRIDE, STATUS_UNINITIALIZED, STATUS_ERROR, STATUS_PENDING, REACTIVE_REASK, REACTIVE_RECOMPUTING_DEPS, CONFIG_SYNC, CONFIG_HAS_LANE, REACTIVE_MISSED_WAKE, REACTIVE_NONE, REACTIVE_SNAPSHOT_STALE, unwrapOverride, CONFIG_DIRECT_COMMIT, CONFIG_HAS_COMPANIONS, CONFIG_PROMOTED, CONFIG_ADOPTED_UNFLUSHED, CONFIG_CHILDREN_FORBIDDEN, CONFIG_FRESH_READ, CONFIG_INPUTS_PUBLISHED, CONFIG_AUTO_DISPOSE, REACTIVE_LAZY, REACTIVE_DISPOSED, REACTIVE_CHECK, REACTIVE_DIRTY, REACTIVE_IN_HEAP, REACTIVE_IN_HEAP_HEIGHT, CONFIG_AUTHORITATIVE_READ, CONFIG_OVERRIDE_SUPERSEDED, CONFIG_AUTHORITATIVE_OBSERVED, defaultContext, CONFIG_HELD_TRUTH, CONFIG_TRANSPARENT, CONFIG_OWNED_WRITE, CONFIG_NO_SNAPSHOT, CONFIG_IN_SNAPSHOT_SCOPE, NO_SNAPSHOT, CONFIG_HAS_SNAPSHOT, REACTIVE_MANUAL_WRITE, CONFIG_FW_CHILDREN, CONFIG_SLOT_NODE, STORE_SNAPSHOT_PROPS } from "./constants.js";
4
4
 
5
5
  import { NotReadyError } from "./error.js";
6
6
 
@@ -8,7 +8,7 @@ import { trimStaleDeps, link, dormantNodes } from "./graph.js";
8
8
 
9
9
  import { deleteFromHeap, queueFor, insertIntoHeapHeight, enqueueSub, markNode, markHeap, insertIntoHeap } from "./heap.js";
10
10
 
11
- import { GlobalQueue, bumpNotifyEpoch, activeTransition, globalQueue, clock, insertSubs, queuePendingNode, runInTransition, schedule, notifyEpoch, dirtyQueue, projectionWriteActive, reaskArmed, armReaskClear } from "./scheduler.js";
11
+ import { GlobalQueue, bumpNotifyEpoch, activeTransition, globalQueue, clock, currentTransition, insertSubs, queuePendingNode, wakeParked, heldTrims, runInTransition, schedule, batchJoins, notifyEpoch, dirtyQueue, projectionWriteActive, reaskArmed, armReaskClear } from "./scheduler.js";
12
12
 
13
13
  import "./invariants.js";
14
14
 
@@ -22,15 +22,15 @@ import { disposeChildren, markDisposal, inheritId } from "./owner.js";
22
22
  // render-effect callback stages its value for the next pass, but a wake pushed
23
23
  // directly into the user queue ran in the SAME pass, read the old value, and
24
24
  // nothing re-notified it when the value landed (#3291).
25
- GlobalQueue.Fe = e => {
26
- if (e.ge === EFFECT_TRACKED) {
25
+ GlobalQueue.We = e => {
26
+ if (e.Le === EFFECT_TRACKED) {
27
27
  deleteFromHeap(e, queueFor(e));
28
- e.He = true;
29
- e.C.enqueue(EFFECT_USER, e.Ve);
28
+ e.Ye = true;
29
+ e.C.enqueue(EFFECT_USER, e.Ke);
30
30
  } else recompute(e);
31
31
  };
32
32
 
33
- GlobalQueue.ke = disposeChildren;
33
+ GlobalQueue.Be = disposeChildren;
34
34
 
35
35
  let tracking = false;
36
36
 
@@ -62,8 +62,8 @@ let snapshotSources = null;
62
62
 
63
63
  function ownerInSnapshotScope(e) {
64
64
  while (e) {
65
- if (e.xe) return true;
66
- e = e.Le;
65
+ if (e.Je) return true;
66
+ e = e._parent;
67
67
  }
68
68
  return false;
69
69
  }
@@ -74,44 +74,49 @@ function setSnapshotCapture(e) {
74
74
  }
75
75
 
76
76
  function markSnapshotScope(e) {
77
- e.xe = true;
77
+ e.Je = true;
78
78
  }
79
79
 
80
80
  function releaseSnapshotScope(e) {
81
- e.xe = false;
81
+ e.Je = false;
82
82
  releaseSubtree(e);
83
83
  schedule();
84
84
  }
85
85
 
86
86
  function releaseSubtree(e) {
87
- let t = e.Qe;
87
+ let t = e.Xe;
88
88
  while (t) {
89
- if (t.xe) {
90
- t = t.We;
89
+ if (t.Je) {
90
+ t = t.$e;
91
91
  continue;
92
92
  }
93
- if (t.oe) {
93
+ if (t.ce) {
94
94
  const e = t;
95
95
  e.T &= ~CONFIG_IN_SNAPSHOT_SCOPE;
96
- if (e.ie & REACTIVE_SNAPSHOT_STALE) {
97
- e.ie &= ~REACTIVE_SNAPSHOT_STALE;
98
- e.ie |= REACTIVE_DIRTY;
99
- if (dirtyQueue.Me > e.qe) dirtyQueue.Me = e.qe;
96
+ if (e.ue & REACTIVE_SNAPSHOT_STALE) {
97
+ e.ue &= ~REACTIVE_SNAPSHOT_STALE;
98
+ e.ue |= REACTIVE_DIRTY;
99
+ if (dirtyQueue.et > e.tt) dirtyQueue.et = e.tt;
100
100
  insertIntoHeap(e, dirtyQueue);
101
101
  }
102
102
  }
103
103
  releaseSubtree(t);
104
- t = t.We;
104
+ t = t.$e;
105
105
  }
106
106
  }
107
107
 
108
108
  function clearSnapshots() {
109
109
  if (snapshotSources) {
110
110
  for (const e of snapshotSources) {
111
- delete e.o?.Ye;
111
+ // The extension is a fixed-shape object with `_snapshotValue`
112
+ // pre-initialized to undefined (see ext()), and every reader tests
113
+ // `!== undefined` — assign, don't `delete`: deleting a field pushes the
114
+ // object to dictionary mode for every later read of every field.
115
+ const t = e.o;
116
+ if (t != null) t.nt = undefined;
112
117
  // StoreNode targets share one pre-initialized hidden class (see
113
- // createStoreProxy) — assign undefined instead of deleting, and only
114
- // when present so signal-node sources don't grow the field.
118
+ // createStoreProxy) — same rule, and only when present so signal-node
119
+ // sources don't grow the field.
115
120
  if (e[STORE_SNAPSHOT_PROPS] !== undefined) e[STORE_SNAPSHOT_PROPS] = undefined;
116
121
  }
117
122
  snapshotSources = null;
@@ -122,12 +127,16 @@ function clearSnapshots() {
122
127
  function recompute(e, t = false) {
123
128
  // §12d: any recompute can clean a marked subscriber — invalidate skips.
124
129
  bumpNotifyEpoch();
125
- const n = e.ge;
130
+ const n = e.Le;
126
131
  if (!t) {
127
- if (e.Ae && (!n || activeTransition) && activeTransition !== e.Ae) globalQueue.initTransition(e.Ae);
132
+ // A stamped memo re-enters its hold: its value is that transaction's work.
133
+ // An effect's pass belongs to whatever dirtied it (A15 corollary: effects
134
+ // don't entangle parallel transactions); it joins its stamp only when the
135
+ // pass observes the held flight — queue notification (#3407).
136
+ if (e.Ge && !n && activeTransition !== e.Ge) globalQueue.initTransition(e.Ge);
128
137
  deleteFromHeap(e, queueFor(e));
129
138
  if (e.o !== null) {
130
- e.o.Ie = null;
139
+ e.o.Re = null;
131
140
  // Supersede is where an iterator flight dies (#3122): close it now.
132
141
  // Its cleanup(close) registration may sit in a zombie-deferred
133
142
  // disposal list that a held transition only drains when the
@@ -135,50 +144,66 @@ function recompute(e, t = false) {
135
144
  // work that replaced it. Idempotent with the cleanup-channel close.
136
145
  releaseFlightTeardown(e);
137
146
  }
138
- // Tracked effects run after finalizePureQueue, so dispose immediately instead of deferring
139
- if (e.Ae || n === EFFECT_TRACKED) disposeChildren(e); else if (e.Qe !== null || e.Ge !== null) {
147
+ // Tracked effects run after finalizePureQueue, so dispose immediately
148
+ // instead of deferring. Children built by an uncommitted recompute
149
+ // (CONFIG_HELD_CHILDREN) die immediately too: no frame ever showed them.
150
+ // Everything else is the committed frame's and is deferred as zombies
151
+ // until this node's commit — a transaction-owned node included (#3404):
152
+ // a parked node's children predate the hold, and tearing them down when
153
+ // the source lands ran cleanups before the transaction's atomic reveal.
154
+ if (n === EFFECT_TRACKED || e.T & CONFIG_HELD_CHILDREN) disposeChildren(e); else if (e.Xe !== null || e.ke !== null) {
140
155
  markDisposal(e);
141
156
  const t = ext(e);
142
- t.Ze = e.Ge;
143
- t.Ke = e.Qe;
144
- e.Ge = null;
145
- e.Qe = null;
146
- e.je = 0;
157
+ t.it = e.ke;
158
+ t.lt = e.Xe;
159
+ e.ke = null;
160
+ e.Xe = null;
161
+ e.ut = 0;
147
162
  } else ;
148
163
  }
149
- let i = !!(e.ie & REACTIVE_OPTIMISTIC_DIRTY);
150
- const u = (e.T & CONFIG_OPTIMISTIC) !== 0 && e.o?.Pe !== NOT_PENDING && e.o?.Pe !== undefined;
151
- const l = !!(e.S & STATUS_UNINITIALIZED);
152
- // Outgoing error, captured before the compute clears status: if this run
153
- // recovers to an unchanged value, dependents still holding this object must
154
- // be swept (settleErroredDependents, #2949).
164
+ let i = !!(e.ue & REACTIVE_OPTIMISTIC_DIRTY);
165
+ // A derived override (lanes stage, #3479) is override-covered like a written
166
+ // one: a plain pass over it — its source superseded (A18) stages the truth
167
+ // and supersedes the override through the sync twin below.
168
+ const l = (e.T & (CONFIG_OPTIMISTIC | CONFIG_DERIVED_OVERRIDE)) !== 0 && e.o?.Ce !== NOT_PENDING && e.o?.Ce !== undefined;
169
+ const u = !!(e.S & STATUS_UNINITIALIZED);
170
+ // Capture both error and pending status before the compute clears them.
171
+ // A conditional can drop its pending source and recover to an unchanged
172
+ // value, leaving blocked dependents outside that source’s settle walk.
155
173
  const o = e.S & STATUS_ERROR ? e.o?._ : undefined;
174
+ const s = (e.S & STATUS_PENDING) !== 0;
175
+ const r = s ? e.o?.ae : undefined;
156
176
  // Pending SOURCE-hood, captured before the compute clears status: a node
157
177
  // whose own flight parked dependents self-registers in _pendingSources
158
178
  // (notifyStatus, isSource). If this recompute supersedes that flight and
159
179
  // settles synchronously, those dependents settle HERE — asyncWrite's
160
180
  // settlePendingSource walk never runs for a landing that was preempted
161
181
  // (#3181).
162
- const s = e.o?.le?.has(e);
182
+ const a = e.o?.ae?.has(e);
163
183
  // Re-ask classification lives in the verdict module; capture the flag before
164
184
  // the recompute wipes _flags below.
165
- const a = (e.ie & REACTIVE_REASK) !== 0;
185
+ const c = (e.ue & REACTIVE_REASK) !== 0;
166
186
  // Captured before the compute clears it on a sync landing: if that landing
167
187
  // is transition-held below, the window must stay open until the hold
168
188
  // commits (commitPendingNode) — a closed window plus a held value reads as
169
189
  // a pending frame to live observers of the verdict (#2990).
170
- const r = e.Ne;
171
- const _ = context;
190
+ const f = e.ge;
191
+ // Creation-time A29 (see enterStagedRead): a pass outside a flush that is
192
+ // served a live transaction's staged value records the transaction here
193
+ // and is staged INTO it below — "born held" — instead of committing.
194
+ const _ = stagedEntry;
195
+ stagedEntry = null;
196
+ const N = context;
172
197
  context = e;
173
- e.Be = null;
174
- e.ze++;
175
- e.ie = REACTIVE_RECOMPUTING_DEPS;
176
- e.Te = clock;
177
- let c = e.Re === NOT_PENDING ? e.be : e.Re;
178
- let f = e.qe;
198
+ e.ot = null;
199
+ e.Ze++;
200
+ e.ue = REACTIVE_RECOMPUTING_DEPS;
201
+ e.Pe = clock;
202
+ let d = e.ve === NOT_PENDING ? e.Qe : e.ve;
203
+ let E = e.tt;
179
204
  let I = false;
180
- let E = tracking;
181
- let N = currentOptimisticLane;
205
+ let T = tracking;
206
+ let S = currentOptimisticLane;
182
207
  tracking = true;
183
208
  // A computed's fn establishes its OWN dependencies, so it must never run
184
209
  // inside a latest() read window: read() short-circuits through the
@@ -186,67 +211,102 @@ function recompute(e, t = false) {
186
211
  // computed) inside latest(fn) came out permanently dependency-less (#2926).
187
212
  // latestRead() already suspends the flag for its pull-recomputes; this
188
213
  // covers creation-time computes and flushes that run inside the window.
189
- const T = latestReadActive;
214
+ const O = latestReadActive;
190
215
  latestReadActive = false;
216
+ // A memo computes under its OWN lane posture, never the puller's (A31,
217
+ // #3442): its value is one shared slot every reader sees, so a pull from a
218
+ // lane-carrying reader (a probe effect on its companion lane pulling a sync
219
+ // memo) must not run it with that lane's read carve-outs — under a lane, a
220
+ // pending node on no lane serves its committed value instead of throwing,
221
+ // and the memo then published a stale "settled" value, dropped its
222
+ // pending status, and stopped holding its transaction. The branches below
223
+ // re-establish the posture the memo itself owns (OPT-dirty, or adopted
224
+ // through its deps). Effects keep the ambient lane: their runs are the
225
+ // lane's own view.
226
+ if (!n) currentOptimisticLane = null;
191
227
  // Lane posture lives with the engine: OPTIMISTIC_DIRTY is only ever set by
192
228
  // engine-driven paths, and _optimisticNodes is only pushed by
193
229
  // _optimisticWrite, so the hook is installed whenever either gate holds.
194
230
  if (i) {
195
- const t = GlobalQueue.Je(e, true);
231
+ const t = GlobalQueue.st(e, true);
196
232
  if (t) currentOptimisticLane = t;
197
233
  // `false` = wake-only lane demotion: recompute plain so a mid-tick
198
234
  // latest()/isPending() pull stages instead of direct-committing (#3009).
199
235
  // The predicate lives with the engine (recomputeLane).
200
236
  else if (t === false) i = false;
201
- } else if (activeTransition && !t && activeTransition.Xe.length) {
237
+ } else if (e.T & CONFIG_DERIVED_OVERRIDE) {
238
+ // Lanes stage (#3479): a pass over a live lane member carrying a derived
239
+ // override is the lane's pass whatever channel dirtied it (a boundary
240
+ // reset, an unrelated sync write) — its inputs serve the lane's view, so
241
+ // its result is the lane's and belongs in the override slot. Run plain,
242
+ // A18's sync twin below read that re-derived lane view as a differing
243
+ // truth (a fresh array), superseded the override and demoted the lane;
244
+ // the lane's next pass then dropped the staged "truth" and left the node
245
+ // flagged superseded with nothing to serve (fuzzer latest-1 #2481). A
246
+ // demoted node resolves no lane and stays plain: its pass IS the truth.
247
+ const t = GlobalQueue.st(e, true);
248
+ if (t) {
249
+ i = true;
250
+ currentOptimisticLane = t;
251
+ }
252
+ } else if (activeTransition && !t && activeTransition.rt.length) {
202
253
  // Lane adoption: parent-deeper-than-owned-child can run before its OPT-dirty
203
254
  // child propagates. Walk deps once and inherit the OPT lane so this node
204
255
  // recomputes under the right posture and propagates correctly.
205
- const t = GlobalQueue.Je(e, false);
256
+ const t = GlobalQueue.st(e, false);
206
257
  if (t) {
207
258
  i = true;
208
259
  currentOptimisticLane = t;
209
260
  }
210
261
  }
211
- const S = n && n !== EFFECT_USER;
212
- const d = stale;
213
- if (S) stale = true;
262
+ const C = n && n !== EFFECT_USER;
263
+ const A = stale;
264
+ if (C) stale = true;
265
+ // An effect recorded for this transaction's commit replay (it once read a
266
+ // node the transaction held and showed the committed value) and now
267
+ // recomputing UNDER the transaction sees its staged view: the value this
268
+ // run produces is applied by the commit itself, and the stale recording
269
+ // would publish the frame a second time. Drop it; the reads below re-record
270
+ // if they are served the committed view again (a lane's committed read).
271
+ if (n && activeTransition !== null && activeTransition.ct.size) activeTransition.ct.delete(e);
214
272
  try {
215
273
  if (!false && e.T & CONFIG_SYNC) {
216
- c = e.oe(c);
217
- if (e.o !== null) e.o.Ie = null;
218
- e.Ne = false;
274
+ d = e.ce(d);
275
+ if (e.o !== null) e.o.Re = null;
276
+ e.ge = false;
219
277
  } else {
220
278
  // Snapshot `_inFlight` so we can detect whether `_fn` self-registered an async
221
279
  // subscription (e.g. `createProjection` calls `handleAsync` from inside its body
222
280
  // with a setter callback). In that case, the outer `handleAsync` call below would
223
281
  // clobber the fresh subscription, so we skip it and let the internally-registered
224
282
  // iteration drive updates.
225
- const t = e.o?.Ie;
226
- const n = e.oe(c);
283
+ const t = e.o?.Re;
284
+ const n = e.ce(d);
227
285
  const i = typeof n === "object" && n !== null;
228
- const u = e.o?.Ie !== t;
229
- c = u || !i ? n : handleAsync(e, n);
230
- if (!u && !i) {
231
- if (e.o !== null) e.o.Ie = null;
286
+ const l = e.o?.Re !== t;
287
+ d = l || !i ? n : handleAsync(e, n);
288
+ if (!l && !i) {
289
+ if (e.o !== null) e.o.Re = null;
232
290
  // A sync (non-object) return is the first real answer; async-shaped
233
291
  // results clear inside handleAsync at their own landing points, and a
234
292
  // self-registered flight (inFlightChanged — projections) clears when
235
293
  // its internal handleAsync lands.
236
- e.Ne = false;
294
+ e.ge = false;
237
295
  }
238
296
  }
239
297
  // On a status-free node clearStatus is a guaranteed no-op: every field
240
298
  // its body gates on is either _statusFlags or lives in the cold
241
299
  // extension — no extension, no status to clear. (_x from an unrelated
242
300
  // installer just makes clearStatus a cheap re-verified no-op.)
243
- if (e.S !== 0 || e.o !== null) clearStatus(e, t);
301
+ // A node born held keeps STATUS_UNINITIALIZED until its transaction
302
+ // commits: it has no committed value, and read() holds its readers.
303
+ if (e.S !== 0 || e.o !== null) clearStatus(e, t && stagedEntry === null);
244
304
  // _optimisticLane is only ever assigned by engine paths (CONFIG_HAS_LANE
245
305
  // is their sticky presence mark).
246
- if (e.T & CONFIG_HAS_LANE && e.o?.$e) GlobalQueue.et(e);
306
+ if (e.T & CONFIG_HAS_LANE && e.o?.Ue) GlobalQueue.ft(e);
247
307
  } catch (t) {
248
308
  const n = t instanceof NotReadyError;
249
- if (n && e.Ne) {
309
+ if (n && e.ge) {
250
310
  // Loading window with an unready sync dependency: register for the
251
311
  // source's settle (the settlePendingSource walk runs off
252
312
  // _pendingSources + _blocked alone) but take NO read-visible pending
@@ -258,37 +318,56 @@ function recompute(e, t = false) {
258
318
  } else {
259
319
  // Track pending async in the lane (not the lane's source — it creates the lane
260
320
  // but doesn't belong to it). Set lane BEFORE notifyStatus for downstream propagation.
261
- if (n && currentOptimisticLane) GlobalQueue.tt(e);
321
+ if (n && currentOptimisticLane) GlobalQueue._t(e);
262
322
  let i = false;
263
323
  if (n) {
264
- ext(e).fe = true;
265
- if (GlobalQueue.nt !== null) i = GlobalQueue.nt(e, a);
324
+ ext(e).Ie = true;
325
+ if (GlobalQueue.Nt !== null) i = GlobalQueue.Nt(e, c);
266
326
  }
267
- notifyStatus(e, n ? STATUS_PENDING : STATUS_ERROR, t, undefined, n ? e.o?.$e : undefined);
327
+ notifyStatus(e, n ? STATUS_PENDING : STATUS_ERROR, t, undefined, n ? e.o?.Ue : undefined);
268
328
  // The replacement source is fully propagated now. If no new flight
269
329
  // re-owned self, retire the superseded flight and its dependent copies.
270
- if (n && s && !e.o?.Ie) settlePendingSource(e);
330
+ if (n && a && !e.o?.Re) settlePendingSource(e);
331
+ // A re-park drops what the earlier pass carried (#3456): a source this
332
+ // pass no longer reaches — its branch switched, or a fresh flight
333
+ // replaced the inputs' pending with its own — stays copied onto
334
+ // dependents that reached it only through here, and its landing walk
335
+ // stops at this node (nothing left to retire) before it finds them. A
336
+ // dependent then waits forever on a flight it has no path to. The
337
+ // re-park twin of the unchanged-value recovery sweep below; dependents
338
+ // with another path keep the source (retryReaches).
339
+ if (n && r) for (const t of r) if (t !== e && !e.o?.ae?.has(t)) settlePendingSource(e, t);
271
340
  if (i) GlobalQueue.k(e);
272
341
  }
273
342
  } finally {
274
- tracking = E;
275
- latestReadActive = T;
276
- if (S) stale = d;
343
+ tracking = T;
344
+ latestReadActive = O;
345
+ if (C) stale = A;
277
346
  // Consume the missed-wake latch (#3037, set by insertSubs): a dep write
278
347
  // landed beneath this pass on a link it had already validated. The wipe
279
348
  // below must not key off DIRTY/CHECK — the read-time pull protocol
280
349
  // (markNode(c) in read()) marks the running node as part of ordinary
281
350
  // bookkeeping, and those marks are correctly discarded here.
282
- I = (e.ie & REACTIVE_MISSED_WAKE) !== 0;
283
- e.ie = REACTIVE_NONE | (t ? e.ie & REACTIVE_SNAPSHOT_STALE : 0);
284
- context = _;
351
+ I = (e.ue & REACTIVE_MISSED_WAKE) !== 0;
352
+ e.ue = REACTIVE_NONE | (t ? e.ue & REACTIVE_SNAPSHOT_STALE : 0);
353
+ context = N;
285
354
  }
355
+ // The cast re-widens: TS narrowed `stagedEntry` to `null` at the reset
356
+ // above and does not invalidate that across the compute call that
357
+ // `enterStagedRead` runs under. No emitted code.
358
+ const p = stagedEntry;
359
+ stagedEntry = _;
286
360
  if (!e.o?._) {
287
- trimStaleDeps(e);
288
- const a = u ? unwrapOverride(e.o?.Pe) : e.Re === NOT_PENDING ? e.be : e.Re;
289
- let _ = false;
361
+ // INV-11 (#3330): the equality gate compares against the slot this run
362
+ // publishes to. An override-covered node publishes the override; a lane
363
+ // recompute (OPT-dirty) direct-commits `_value` — the lane's own reveal
364
+ // schedule — so a transaction-held `_pendingValue` that already equals
365
+ // the new result is not "unchanged": the screen still shows `_value`.
366
+ // Only a transaction-staged run compares against `_pendingValue`.
367
+ const s = l ? unwrapOverride(e.o?.Ce) : i || e.ve === NOT_PENDING ? e.Qe : e.ve;
368
+ let c = false;
290
369
  try {
291
- _ = !n && l || !e.pe || !e.pe(a, c);
370
+ c = !n && u || !e.Fe || !e.Fe(s, d);
292
371
  } catch (t) {
293
372
  // A throwing user comparator is an error of this node's computation.
294
373
  // Route it through the same status path as a compute-phase throw so
@@ -301,15 +380,38 @@ function recompute(e, t = false) {
301
380
  // its runner — happen here instead. `!create` matches the previous `initialized`
302
381
  // gate: the explicit recompute(node, true) inside effect() does not enqueue, so
303
382
  // effect() can call its runner synchronously for the first run.
304
- if (n && _) {
305
- e.He = !e.o?._;
383
+ if (n && c) {
384
+ e.Ye = !e.o?._;
306
385
  // Reuse one bound runner per effect — runEffect no-ops on a stale
307
386
  // `_modified`, so re-enqueueing the same function is harmless.
308
- if (!t) e.C.enqueue(n, e.it ??= GlobalQueue.ut.bind(null, e));
387
+ if (!t) {
388
+ e.C.enqueue(n, e.dt ??= GlobalQueue.Et.bind(null, e));
389
+ // Contested effect (#3322). Effects don't entangle transactions (a
390
+ // shared effect is not shared state), yet they have one value slot:
391
+ // when this write commits under a different transaction
392
+ // (`activeTransition`, null = mainline) than the one that produced the
393
+ // previous value (`_valueTransition`), that view is gone. Rather than
394
+ // merge the two, each commit re-derives the effect against its own
395
+ // committed world (Transition._contested, re-dirtied by
396
+ // finalizePureQueue ahead of the heap run). The previous owner always
397
+ // needs it if still live: its commit is silent (staging already
398
+ // notified) and the value it computed is gone. The new owner needs it
399
+ // too, for the same reason, unless it is mainline — mainline publishes
400
+ // what it computes. A previous owner that was mainline, or already
401
+ // committed, left nothing to protect.
402
+ let t = e.It;
403
+ if (t !== activeTransition) {
404
+ e.It = activeTransition;
405
+ if (t !== null && (t = currentTransition(t)) !== activeTransition && !t.Tt) {
406
+ (t.St ??= []).push(e);
407
+ if (activeTransition !== null) (activeTransition.St ??= []).push(e);
408
+ }
409
+ }
410
+ }
309
411
  }
310
- if (e.o?._) ; else if (_) {
311
- const l = u ? e.o?.Pe : undefined;
312
- if (t ||
412
+ if (e.o?._) ; else if (c) {
413
+ const u = l ? e.o?.Ce : undefined;
414
+ if (t && p === null ||
313
415
  // Plain sync flush (no transition on either side) commits effect
314
416
  // values directly — the pending round-trip (queuePendingNode +
315
417
  // commitPendingNodes) exists to sequence transition reveals, and
@@ -318,48 +420,84 @@ function recompute(e, t = false) {
318
420
  // their own held transition: their applies deliver on a microtask,
319
421
  // not the stashed queues, so a staged value would hand the immediate
320
422
  // apply stale state — see CONFIG_DIRECT_COMMIT.
321
- n && (activeTransition !== e.Ae || activeTransition === null || e.T & CONFIG_DIRECT_COMMIT) || i) {
322
- e.be = c;
323
- // Lane-propagated correction: upstream data is fresh, correct the
324
- // override unconditionally. The direct _value commit is the lane's
325
- // own reveal schedule; drop any superseded older hold so its queued
326
- // commit can't clobber the fresh value.
327
- if (u && i) {
328
- ext(e).Pe = c === undefined ? OVERRIDE_UNDEFINED : c;
329
- e.Re = NOT_PENDING;
330
- }
423
+ n && p === null && (activeTransition !== e.Ge || activeTransition === null || e.T & CONFIG_DIRECT_COMMIT) || i) {
424
+ // Lanes stage (#3479): a lane pass on a memo publishes its speculative
425
+ // result as an OVERRIDE `_value` stays the committed truth, so a
426
+ // reader off the lane (A17's committed view, #3460) sees a whole
427
+ // committed frame: the source's shadow and its derivations together,
428
+ // never a committed shadow beside a speculative memo. The lane's own
429
+ // readers and untracked reads see the override (A17); the revert
430
+ // drops it and re-derives (resolveOptimisticNodes). Effects keep the
431
+ // direct commit — their `_value` is a run result the lane's queues
432
+ // already sequence. Either way the lane pass drops any superseded
433
+ // older hold so its queued commit can't clobber the fresh frame: a
434
+ // hold staged on an earlier, lane-free pass of the SAME transaction
435
+ // is superseded — left in place, the commit published the older
436
+ // frame over the fresh one (#3377). A reversion pass (OPT-dirty with
437
+ // no lane — the override dropped) commits directly: it IS the truth.
438
+ if (i && !n && currentOptimisticLane !== null) GlobalQueue.Ve(e, d, currentOptimisticLane); else e.Qe = d;
439
+ if (i) e.ve = NOT_PENDING;
331
440
  } else {
332
- e.Re = c;
441
+ e.ve = d;
442
+ if (p !== null) {
443
+ // Born held: the pass ran from mainline and derived from this
444
+ // transaction's staged world (enterStagedRead). Its value is the
445
+ // transaction's — staged into it directly, stamped, and committed
446
+ // with it; mainline's batch never sees it. A born-held effect
447
+ // skips its synchronous first run (effect()) and is replayed by
448
+ // the commit like a stale reader that showed the committed frame.
449
+ e.Ge = p;
450
+ p.Ot.push(e);
451
+ if (n) p.ct.add(e);
452
+ }
333
453
  // A window landing that gets held re-opens the window until the hold
334
454
  // commits — the verdict's held-value branch is window-gated (#2990).
335
- if (r) e.Ne = true;
336
- // Transition-held sync recompute is a write path like setSignal/asyncWrite,
455
+ if (f) e.ge = true;
456
+ // A staged sync recompute is a write path like setSignal/asyncWrite,
337
457
  // so sync derivations of held sources stay visible to isPending()/latest()
338
458
  // (#2831). Both companion writes are transition-scoped (optimistic) and
339
- // auto-revert/re-derive at commit. Skipped for plain flushes where the
340
- // pending value commits before effects run.
341
- if ((activeTransition || e.Ae) && GlobalQueue.Ue !== null) GlobalQueue.Ue(e, c);
459
+ // auto-revert/re-derive at commit. Not gated on an active transition:
460
+ // a plain flush can still become a hold after this recompute — an
461
+ // async memo downstream pends and the batch is adopted into a
462
+ // transaction (scheduler.enterTransition) — and nothing re-derives
463
+ // the companion at adoption, so a memo held that way read
464
+ // isPending() false while its held source read true (#3413).
465
+ if (e.T & CONFIG_HAS_COMPANIONS && GlobalQueue.me !== null) GlobalQueue.me(e, d);
342
466
  }
343
467
  // insertSubs only walks _subs (no scheduling of its own), so a
344
468
  // subscriber-less node has nothing to notify.
345
- if (e.u !== null && (!u || i || e.o?.Pe !== l)) insertSubs(e, i || u);
346
- } else if (u) {
469
+ if (e.u !== null && (!l || i || e.o?.Ce !== u)) insertSubs(e, i || l);
470
+ // A18 supersession, sync twin of asyncWrite's override branch (#3331):
471
+ // this pass published truth that differs from the override (the gate
472
+ // compared against it) into the transaction-held slot. Whether the
473
+ // source is this node's own async or an upstream node it derives from
474
+ // synchronously makes no difference — "the source recomputed". The
475
+ // override stays displayed until the commit; the graph moves to the
476
+ // staged truth now (plain channel, lane demoted). Ordering: "a new
477
+ // value from the source" postdates the override — an override written
478
+ // in this same tick (optimisticWrite stamps `_overrideTime`) is the
479
+ // newer intent over whatever this pass derives from the batch's staged
480
+ // inputs, and is not superseded by it.
481
+ else if (l && !i && e.o.Ct !== clock) GlobalQueue.we(e, d);
482
+ } else if (l) {
347
483
  // Unchanged value (equals the override) recomputed while the override
348
484
  // is active: _value may still be stale, so hold the authoritative value
349
485
  // for commit on its own transition's schedule — invisibly (A17/A18).
350
- if (e.Re === NOT_PENDING) queuePendingNode(e);
351
- e.Re = c;
352
- if (r) e.Ne = true;
486
+ if (e.ve === NOT_PENDING) queuePendingNode(e);
487
+ e.ve = d;
488
+ if (f) e.ge = true;
353
489
  // see the held branch above (#2990)
354
- // An authoritative-view reader (until()'s predicate, refresh()'s waiter)
355
- // observed this node past its override and "authoritative arrival
356
- // equal to the override" is exactly the acknowledgment it waits for.
357
- // Wake those readers only; A17 silence holds for every ordinary
358
- // subscriber. (Hook installed by both setters of the gating bit, #3303.)
359
- if (e.T & CONFIG_AUTHORITATIVE_OBSERVED) GlobalQueue.he(e);
360
- } else if (e.qe != f) {
361
- for (let t = e.u; t !== null; t = t.ae) {
362
- insertIntoHeapHeight(t.ce, queueFor(t.ce));
490
+ // A confirmation after a supersession restores the override as the
491
+ // graph's value and notifies; a plain confirmation wakes only an
492
+ // authoritative-view reader (until()'s predicate, refresh()'s waiter)
493
+ // that observed this node past its override "authoritative arrival
494
+ // equal to the override" is exactly the acknowledgment it waits for;
495
+ // A17 silence holds for every ordinary subscriber. Both live in the
496
+ // engine's supersedeOverride.
497
+ GlobalQueue.we(e, d);
498
+ } else if (e.tt != E) {
499
+ for (let t = e.u; t !== null; t = t.Ne) {
500
+ insertIntoHeapHeight(t._e, queueFor(t._e));
363
501
  }
364
502
  }
365
503
  // Silent recovery: errored → unchanged value fires no notification, but
@@ -367,21 +505,92 @@ function recompute(e, t = false) {
367
505
  // in an errored run and may sit on stale commits (#2949). Changed-value
368
506
  // recoveries ride insertSubs above; a comparator throw re-errored the node
369
507
  // (el._x?._error re-set), so this only runs on a genuinely clean recovery.
370
- if (o !== undefined && !_ && !e.o?._) settleErroredDependents(e, o);
508
+ if (!c && !e.o?._) {
509
+ if (o !== undefined) settleErroredDependents(e, o);
510
+ // Self-registration (this node's own superseded flight) is the #3181
511
+ // sweep's business below — retiring it here too would walk twice.
512
+ if (r) for (const t of r) if (t !== e) settlePendingSource(e, t);
513
+ }
371
514
  // #3181: a synchronous settle supersedes the old landing callback, so
372
515
  // recompute owns its pending-source sweep. An uninitialized node without
373
516
  // a replacement source still has no truth to reveal and must stay parked.
374
- if (s && !(e.S & (STATUS_PENDING | STATUS_UNINITIALIZED))) settlePendingSource(e);
517
+ if (a && !(e.S & (STATUS_PENDING | STATUS_UNINITIALIZED))) settlePendingSource(e);
518
+ }
519
+ // A REPORTER whose pass stopped reading a source it reported on (a gate
520
+ // closed) stops counting for the transaction waiting on it (A15 / #3426:
521
+ // the hold lasts while a live reporter observes the flight). Nothing else
522
+ // re-judges a parked transaction (see wokenTransitions; the disposal
523
+ // (#3372) and boundary (#3375) twins of this site), so the writes it held
524
+ // stayed staged for as long as the flight stayed up — forever, for one
525
+ // that never lands (fuzzer #3446 P1, spec O3). The event is "this pass
526
+ // dropped a dep" — deps past `_depsTail` (trimmed below, or kept by A30
527
+ // for a staged pass), or a pass that read nothing — not "recovered from
528
+ // pending": a reporter registered by the stale-reader carve-out
529
+ // (heldFromStale, an INITIALIZED source refetching) displays the committed
530
+ // value and is never pending (fuzzer case 79). Every parked transaction,
531
+ // not the reporter's stamp: the transaction waiting on it registered it
532
+ // without stamping it. One idle pass per parked transaction; done ones
533
+ // return at re-entry. Effects only — reporters register from render-effect
534
+ // notification (INV-3).
535
+ // (`_depsTail` was reset at the top of the pass; TS keeps that narrowing.)
536
+ const R = e.ot;
537
+ if (n && (s && !(e.S & STATUS_PENDING) || (R === null ? e.Se !== null : R.de !== null))) wakeParked();
538
+ // Dependencies are the committed frame's until it is replaced (A30, #3410; the
539
+ // deps twin of the held children above): a pass that staged its value
540
+ // leaves the previous pass's tail linked for `commitPendingNode` to trim,
541
+ // so a write to a dependency the committed value still derives from
542
+ // reaches this node — and joins its hold if the stage is transaction-held
543
+ // by then (a plain flush decides nothing here: the transaction that holds
544
+ // the pass may open later in the same flush). A pass that published
545
+ // directly (a first pass, a lane's own reveal) trims now. An errored pass
546
+ // (a throw, NotReady included, or a comparator throw above) keeps its full
547
+ // list as before — `_depsTail` marks where it stopped — and the commit
548
+ // skips it by the same `_error`. An effect's frame is the run its value is
549
+ // applied by, not the value slot (#3438): a direct-committed pass that
550
+ // still owes a run (`_modified`) has not replaced what the last run
551
+ // published — the same flush may stash that run into a transaction it
552
+ // opens later — so its tail waits for `runEffect` to trim once the run
553
+ // applies. A pass that changed nothing replaced nothing either (#3469): the
554
+ // same flush may park with its inputs held, and the committed frame still
555
+ // derives from the tail — its trim waits on the flush's verdict (heldTrims).
556
+ // A tracked effect's pass IS its run (it bypasses the heap and runs after
557
+ // the commit): the frame is replaced, trim now.
558
+ if (!e.o?._ && e.ve === NOT_PENDING && !(n && e.Ye)) {
559
+ if (t || i || n === EFFECT_TRACKED) trimStaleDeps(e); else if (e.ot?.de ?? e.Se) heldTrims.push(e);
375
560
  }
376
- currentOptimisticLane = N;
377
- const A = e.Re !== NOT_PENDING || e.o !== null && (e.o.Ke !== null || e.o.Ze !== null) || (e.S & (STATUS_PENDING | STATUS_UNINITIALIZED)) !== 0;
561
+ currentOptimisticLane = S;
562
+ const G = e.ve !== NOT_PENDING || e.o !== null && (e.o.lt !== null || e.o.it !== null) || (e.S & (STATUS_PENDING | STATUS_UNINITIALIZED)) !== 0;
378
563
  // Override-covered holds (hasOverride) always queue: their commit belongs
379
564
  // to their own transition's schedule (A18 re-rule) and is unobservable
380
565
  // under the override (A17). Revert no longer commits anything, so an
381
566
  // unqueued covered hold would leak (INV-7) once the revert clears
382
567
  // _transition.
383
- A && (!t || e.S & STATUS_PENDING) && (!e.Ae || u) && queuePendingNode(e);
384
- e.Ae && n && activeTransition !== e.Ae && runInTransition(e.Ae, () => recompute(e));
568
+
569
+ // While a pass's result waits on a commit, its children (and `_disposal`)
570
+ // wait with it, and a re-run may tear them down immediately
571
+ // (CONFIG_HELD_CHILDREN, #3404). One exception: a transaction-owned effect
572
+ // recomputed mainline (contested, #3322) published its value directly — a
573
+ // `_pendingValue` left from an earlier held pass is that transaction's,
574
+ // not this one's — so this pass's children are the frame's, and any
575
+ // zombies deferred at the top are superseded on that same frame. Its
576
+ // commit rides the transaction, not this flush: release them here rather
577
+ // than let two generations render at once.
578
+ let D = G && (!t || p !== null || (e.S & STATUS_PENDING) !== 0);
579
+ if (D && (!e.Ge || l)) queuePendingNode(e); else if (D && activeTransition === null && !(e.S & (STATUS_PENDING | STATUS_UNINITIALIZED))) {
580
+ D = false;
581
+ disposeChildren(e, false, true);
582
+ }
583
+ if (D) e.T |= CONFIG_HELD_CHILDREN; else e.T &= ~CONFIG_HELD_CHILDREN;
584
+ // (A born-held pass IS the transaction's staged view — nothing to refresh.)
585
+ if (e.Ge && n && activeTransition !== e.Ge && p === null) {
586
+ // The re-run refreshes the transaction's STAGED view (_pendingValue); the
587
+ // value this pass published in _value belongs to the run that just
588
+ // finished. Keep that ownership, or the effect phase parks a
589
+ // mainline-computed value with the transaction (#3412).
590
+ const t = e.It;
591
+ runInTransition(e.Ge, () => recompute(e));
592
+ e.It = t;
593
+ }
385
594
  // Missed-wake reschedule (see the finally above): values this pass read
386
595
  // before the nested commit are stale, so run again now that the heap will
387
596
  // accept the node. Equality gates stop same-value landings from cascading,
@@ -400,23 +609,23 @@ function updateIfNecessary(e) {
400
609
  // (#3037); readers meanwhile serve the values the pass has so far.
401
610
  // Never recompute a DISPOSED node either: recompute rewrites _flags and
402
611
  // would resurrect it (#2983) — readers serve its last value.
403
- if (e.ie & (REACTIVE_RECOMPUTING_DEPS | REACTIVE_DISPOSED)) return;
404
- if (e.ie & REACTIVE_CHECK) {
405
- for (let t = e.lt; t; t = t.ot) {
406
- const n = t.st;
407
- const i = n.rt || n;
408
- if (i.oe) {
612
+ if (e.ue & (REACTIVE_RECOMPUTING_DEPS | REACTIVE_DISPOSED)) return;
613
+ if (e.ue & REACTIVE_CHECK) {
614
+ for (let t = e.Se; t; t = t.de) {
615
+ const n = t.Ee;
616
+ const i = n.Te || n;
617
+ if (i.ce) {
409
618
  updateIfNecessary(i);
410
619
  }
411
- if (e.ie & REACTIVE_DIRTY) {
620
+ if (e.ue & REACTIVE_DIRTY) {
412
621
  break;
413
622
  }
414
623
  }
415
624
  }
416
- if (e.ie & (REACTIVE_DIRTY | REACTIVE_OPTIMISTIC_DIRTY) || e.o?._ && e.Te < clock && !e.o?.Ie) {
625
+ if (e.ue & (REACTIVE_DIRTY | REACTIVE_OPTIMISTIC_DIRTY) || e.o?._ && e.Pe < clock && !e.o?.Re) {
417
626
  recompute(e);
418
627
  }
419
- e.ie = e.ie & (REACTIVE_SNAPSHOT_STALE | REACTIVE_IN_HEAP | REACTIVE_IN_HEAP_HEIGHT);
628
+ e.ue = e.ue & (REACTIVE_SNAPSHOT_STALE | REACTIVE_IN_HEAP | REACTIVE_IN_HEAP_HEIGHT);
420
629
  }
421
630
 
422
631
  function computed(e, t) {
@@ -425,45 +634,53 @@ function computed(e, t) {
425
634
  // `T | undefined` node is a real commit #0. The typeof guard tolerates
426
635
  // non-object option values that older call shapes force through `as any`.
427
636
  const i = t !== null && typeof t === "object" && "loadingValue" in t;
428
- const u = {
637
+ // Two literals, one per tier, selected at build time (the observe flag is
638
+ // a literal after replacement; the untaken branch is dead code). The observe
639
+ // literal is the prod literal plus its `_name` slot — a slot in the
640
+ // boilerplate, because a post-construction `self._name = …` forces a
641
+ // hidden-class transition and an out-of-object property store on EVERY node
642
+ // (measured: the whole of the observe tier's creation overhead). Keep the
643
+ // two in sync — the dist artifact test pins observe's key set to prod's
644
+ // plus `_name`.
645
+ const l = {
429
646
  id: inheritId(t, n, context),
430
- T: (n ? CONFIG_TRANSPARENT : 0) | (t?.ownedWrite ? CONFIG_OWNED_WRITE : 0) | (!context || t?.lazy ? CONFIG_AUTO_DISPOSE : 0) | (t?.sync ? CONFIG_SYNC : 0) | (t?.H ? CONFIG_NO_SNAPSHOT : 0) | (snapshotCaptureActive && ownerInSnapshotScope(context) ? CONFIG_IN_SNAPSHOT_SCOPE : 0),
431
- pe: t?.equals ?? isEqual,
432
- Ge: null,
647
+ T: (n ? CONFIG_TRANSPARENT : 0) | (t?.ownedWrite ? CONFIG_OWNED_WRITE : 0) | (!context || t?.lazy ? CONFIG_AUTO_DISPOSE : 0) | (t?.sync ? CONFIG_SYNC : 0) | (t?.Z ? CONFIG_NO_SNAPSHOT : 0) | (snapshotCaptureActive && ownerInSnapshotScope(context) ? CONFIG_IN_SNAPSHOT_SCOPE : 0),
648
+ Fe: t?.equals ?? isEqual,
649
+ ke: null,
433
650
  C: context?.C ?? globalQueue,
434
- we: context?.we ?? defaultContext,
435
- je: 0,
436
- oe: e,
437
- be: i ? t.loadingValue : undefined,
438
- qe: 0,
439
- _t: undefined,
440
- ct: null,
441
- lt: null,
442
- Be: null,
443
- ze: 0,
651
+ ze: context?.ze ?? defaultContext,
652
+ ut: 0,
653
+ ce: e,
654
+ Qe: i ? t.loadingValue : undefined,
655
+ tt: 0,
656
+ At: undefined,
657
+ Rt: null,
658
+ Se: null,
659
+ ot: null,
660
+ Ze: 0,
444
661
  u: null,
445
- ft: null,
446
- Le: context,
447
- We: null,
448
- It: null,
449
- Qe: null,
450
- ie: t?.lazy ? REACTIVE_LAZY : REACTIVE_NONE,
662
+ Gt: null,
663
+ _parent: context,
664
+ $e: null,
665
+ Dt: null,
666
+ Xe: null,
667
+ ue: t?.lazy ? REACTIVE_LAZY : REACTIVE_NONE,
451
668
  // A loadingValue node is born committed: commit #0 is already in _value.
452
669
  S: i ? 0 : STATUS_UNINITIALIZED,
453
- Te: clock,
454
- Re: NOT_PENDING,
455
- Ae: null,
456
- Et: -1,
457
- Ne: i,
670
+ Pe: clock,
671
+ ve: NOT_PENDING,
672
+ Ge: null,
673
+ ht: -1,
674
+ ge: i,
458
675
  // Cold machinery (async/transition/optimistic/verdict slots) lives one
459
676
  // hop away in the lazily-allocated extension — the core literal MUST
460
677
  // stay under V8's in-object boundary (§12: past ~39 fields every
461
678
  // allocation spills to a backing store and creation cost ~4x's).
462
679
  o: null
463
680
  };
464
- if (t?.unobserved) ext(u).Nt = t.unobserved;
465
- setupComputedNode(u, t);
466
- return u;
681
+ if (t?.unobserved) ext(l).Pt = t.unobserved;
682
+ setupComputedNode(l, t);
683
+ return l;
467
684
  }
468
685
 
469
686
  /** Lazily allocate a node's cold extension (ONE shape for signals and
@@ -471,26 +688,29 @@ function computed(e, t) {
471
688
  * this; hot paths read `el._x?._field` gated by the _config presence bits.
472
689
  * Never call ext() just to store a field's default. */ function ext(e) {
473
690
  return e.o ??= {
474
- Pe: undefined,
475
- Tt: undefined,
476
- $e: undefined,
477
- ye: undefined,
478
691
  Ce: undefined,
479
- St: undefined,
692
+ Ft: undefined,
693
+ Ct: 0,
694
+ gt: NOT_PENDING,
695
+ vt: 0,
696
+ Ue: undefined,
697
+ je: undefined,
698
+ xe: undefined,
699
+ Ht: undefined,
480
700
  t: 0,
481
- Ie: null,
482
- Ee: null,
701
+ Re: null,
702
+ Ae: null,
483
703
  _: undefined,
484
- fe: undefined,
485
- le: undefined,
704
+ Ie: undefined,
705
+ ae: undefined,
486
706
  h: undefined,
487
- De: false,
707
+ be: false,
488
708
  i: null,
489
- Nt: undefined,
490
- Ye: undefined,
491
- Ze: null,
492
- Ke: null,
493
- dt: undefined
709
+ Pt: undefined,
710
+ nt: undefined,
711
+ it: null,
712
+ lt: null,
713
+ bt: undefined
494
714
  };
495
715
  }
496
716
 
@@ -499,43 +719,49 @@ function computed(e, t) {
499
719
  * so V8 sees the full hidden class shape at construction time. Effects always run in lazy
500
720
  * mode (recompute is called explicitly by `effect()`), so we hardcode the lazy bits and skip
501
721
  * the auto-dispose CONFIG bit (effect() previously cleared it post-construction).
502
- */ function createEffectNode(e, t, n, i, u) {
503
- const l = u?.transparent ?? false;
504
- const o = {
505
- id: inheritId(u, l, context),
506
- T: (l ? CONFIG_TRANSPARENT : 0) | (u?.ownedWrite ? CONFIG_OWNED_WRITE : 0) | (u?.sync ? CONFIG_SYNC : 0) | (u?.At ?? 0) | (snapshotCaptureActive && ownerInSnapshotScope(context) ? CONFIG_IN_SNAPSHOT_SCOPE : 0),
507
- pe: false,
508
- Ge: null,
722
+ */ function createEffectNode(e, t, n, i, l) {
723
+ const u = l?.transparent ?? false;
724
+ // Prod and observe boilerplates — see computed() for why the observe tier
725
+ // gets its `_name` as a literal slot rather than a write after the fact.
726
+ // The default label is the node kind (tracked effects relabel their computed
727
+ // in trackedEffect); the wrappers in signals.ts no longer spread a name into
728
+ // the options to get it.
729
+ const o = {
730
+ id: inheritId(l, u, context),
731
+ T: (u ? CONFIG_TRANSPARENT : 0) | (l?.ownedWrite ? CONFIG_OWNED_WRITE : 0) | (l?.sync ? CONFIG_SYNC : 0) | (l?.kt ?? 0) | (snapshotCaptureActive && ownerInSnapshotScope(context) ? CONFIG_IN_SNAPSHOT_SCOPE : 0),
732
+ Fe: false,
733
+ ke: null,
509
734
  C: context?.C ?? globalQueue,
510
- we: context?.we ?? defaultContext,
511
- je: 0,
512
- oe: e,
513
- be: undefined,
514
- qe: 0,
515
- _t: undefined,
516
- ct: null,
517
- lt: null,
518
- Be: null,
519
- ze: 0,
735
+ ze: context?.ze ?? defaultContext,
736
+ ut: 0,
737
+ ce: e,
738
+ Qe: undefined,
739
+ tt: 0,
740
+ At: undefined,
741
+ Rt: null,
742
+ Se: null,
743
+ ot: null,
744
+ Ze: 0,
520
745
  u: null,
521
- ft: null,
522
- Le: context,
523
- We: null,
524
- It: null,
525
- Qe: null,
526
- ie: REACTIVE_LAZY,
746
+ Gt: null,
747
+ _parent: context,
748
+ $e: null,
749
+ Dt: null,
750
+ Xe: null,
751
+ ue: REACTIVE_LAZY,
527
752
  S: STATUS_UNINITIALIZED,
528
- Te: clock,
529
- Re: NOT_PENDING,
530
- Ae: null,
531
- Et: -1,
532
- Ne: false,
533
- He: false,
534
- Ot: undefined,
535
- Ct: t,
536
- Rt: n,
537
- Gt: undefined,
538
- ge: i,
753
+ Pe: clock,
754
+ ve: NOT_PENDING,
755
+ Ge: null,
756
+ ht: -1,
757
+ ge: false,
758
+ Ye: false,
759
+ Ut: undefined,
760
+ Lt: t,
761
+ Vt: n,
762
+ yt: undefined,
763
+ Le: i,
764
+ It: null,
539
765
  o: null
540
766
  };
541
767
  // Effects dispatch status through the SHARED notifier (statusNotifierOf,
@@ -543,7 +769,7 @@ function computed(e, t) {
543
769
  // allocation on EVERY effect at creation (an alloc + 19 field stores,
544
770
  // +23% effect creation, caught by the creation benches). Only genuinely
545
771
  // per-node channels (boundaries) live on _x.
546
- if (u?.unobserved) ext(o).Nt = u.unobserved;
772
+ if (l?.unobserved) ext(o).Pt = l.unobserved;
547
773
  setupComputedNode(o, lazyOptions);
548
774
  return o;
549
775
  }
@@ -566,7 +792,7 @@ function setEffectStatusNotify(e) {
566
792
  * per-node field did when every effect carried one. */ function statusNotifierOf(e) {
567
793
  const t = e.o?.h;
568
794
  if (t !== undefined) return t;
569
- return e.ge ? effectStatusNotify ?? undefined : undefined;
795
+ return e.Le ? effectStatusNotify ?? undefined : undefined;
570
796
  }
571
797
 
572
798
  const lazyOptions = {
@@ -574,24 +800,24 @@ const lazyOptions = {
574
800
  };
575
801
 
576
802
  function setupComputedNode(e, t) {
577
- e.ct = e;
578
- const n = context?.Dt ? context.Pt : context;
803
+ e.Rt = e;
804
+ const n = context?.xt ? context.Qt : context;
579
805
  if (context) {
580
- const t = context.Qe;
806
+ const t = context.Xe;
581
807
  if (t === null) {
582
- context.Qe = e;
808
+ context.Xe = e;
583
809
  } else {
584
- e.We = t;
585
- t.It = e;
586
- context.Qe = e;
810
+ e.$e = t;
811
+ t.Dt = e;
812
+ context.Xe = e;
587
813
  }
588
814
  }
589
- if (n) e.qe = n.qe + 1;
590
- if (GlobalQueue.Ft !== null) GlobalQueue.Ft(e);
815
+ if (n) e.tt = n.tt + 1;
816
+ if (GlobalQueue.wt !== null) GlobalQueue.wt(e);
591
817
  !t?.lazy && recompute(e, true);
592
818
  if (snapshotCaptureActive && !t?.lazy) {
593
819
  if (!(e.S & STATUS_PENDING) && !(e.T & CONFIG_NO_SNAPSHOT)) {
594
- ext(e).Ye = e.be === undefined ? NO_SNAPSHOT : e.be;
820
+ ext(e).nt = e.Qe === undefined ? NO_SNAPSHOT : e.Qe;
595
821
  e.T |= CONFIG_HAS_SNAPSHOT;
596
822
  snapshotSources.add(e);
597
823
  }
@@ -599,32 +825,34 @@ function setupComputedNode(e, t) {
599
825
  }
600
826
 
601
827
  function signal(e, t, n = null) {
828
+ // Prod and observe boilerplates — see computed(). The observe literal adds
829
+ // `_name` and `_owner` (the creating owner, stamped by registerGraph for
830
+ // createSignal nodes so ownerPath can locate signal subjects; null here,
831
+ // and staying null on internal signals — one shape either way).
602
832
  const i = {
603
- pe: t?.equals ?? isEqual,
604
- T: (t?.ownedWrite ? CONFIG_OWNED_WRITE : 0) | (t?.H ? CONFIG_NO_SNAPSHOT : 0),
605
- be: e,
833
+ Fe: t?.equals ?? isEqual,
834
+ T: (t?.ownedWrite ? CONFIG_OWNED_WRITE : 0) | (t?.Z ? CONFIG_NO_SNAPSHOT : 0),
835
+ Qe: e,
606
836
  u: null,
607
- ft: null,
608
- Te: clock,
609
- rt: n,
610
- Se: n?.o?.i || null,
611
- Re: NOT_PENDING,
837
+ Gt: null,
838
+ Pe: clock,
839
+ Te: n,
840
+ De: n?.o?.i || null,
841
+ Mt: null,
842
+ ve: NOT_PENDING,
612
843
  // Signal-literal diet (§12e): NO _time/_fn/_statusFlags slots. Stores
613
844
  // materialize one signal per touched leaf, so signal bytes are store
614
845
  // bytes. _time is write-only on signals (every read site is computed-
615
846
  // typed error-retry gating); _fn/_statusFlags read falsy-identically as
616
847
  // missing properties on the shared paths (undefined masks to 0).
617
- Ae: null,
618
- Et: -1,
848
+ Ge: null,
849
+ ht: -1,
619
850
  o: null
620
851
  };
621
- if (t?.unobserved) ext(i).Nt = t.unobserved;
622
- if (n) {
623
- ext(n).i = i;
624
- n.T |= CONFIG_FW_CHILDREN;
625
- }
852
+ if (t?.unobserved) ext(i).Pt = t.unobserved;
853
+ if (n) linkFirewallChild(n, i);
626
854
  if (snapshotCaptureActive && !(i.T & CONFIG_NO_SNAPSHOT) && !((n?.S ?? 0) & STATUS_PENDING)) {
627
- ext(i).Ye = e === undefined ? NO_SNAPSHOT : e;
855
+ ext(i).nt = e === undefined ? NO_SNAPSHOT : e;
628
856
  i.T |= CONFIG_HAS_SNAPSHOT;
629
857
  snapshotSources.add(i);
630
858
  }
@@ -649,34 +877,66 @@ function signal(e, t, n = null) {
649
877
  slotUnobservedHook = e;
650
878
  }
651
879
 
652
- function slotSignal(e, t, n, i, u, l = null) {
880
+ /** Push a new node onto its firewall's child chain (the literal already
881
+ * points `_nextChild` at the old head). Doubly linked so a released leaf
882
+ * unlinks in O(1) — the chain is walked per mark of the projection and
883
+ * would otherwise grow by one node per leaf ever read (#3351). */ function linkFirewallChild(e, t) {
884
+ const n = t.De;
885
+ if (n !== null) n.Mt = t;
886
+ ext(e).i = t;
887
+ e.T |= CONFIG_FW_CHILDREN;
888
+ }
889
+
890
+ /** Release a firewall child the store no longer addresses (unobserved sweep
891
+ * dropped it from its target's cache): unlink it from the chain so the
892
+ * projection stops retaining it and its last value. The node keeps its own
893
+ * `_nextChild` so a walk that is mid-chain on it still terminates. It also
894
+ * leaves `_companionChildren` (#3503): the companions themselves are
895
+ * permanent on the node, but the node is unreachable through the store, so
896
+ * the set would only retain it and its last value. */ function unlinkFirewallChild(e) {
897
+ const t = e;
898
+ const n = t.Te;
899
+ if (!n) return;
900
+ const i = t.Mt;
901
+ const l = t.De;
902
+ if (i !== null) i.De = l; else if (n.o.i === t) n.o.i = l;
903
+ if (l !== null) l.Mt = i;
904
+ t.Mt = null;
905
+ // #3503: the companion set is the last projection-side reference to a
906
+ // released leaf; leaving it there kept the leaf and its last value alive
907
+ // for the projection's lifetime after every latest()/isPending() reader
908
+ // was disposed.
909
+ n.o.bt?.delete(t);
910
+ }
911
+
912
+ function slotSignal(e, t, n, i, l, u = null) {
913
+ // Prod and observe boilerplates — see computed(). The store relabels the
914
+ // observe slot (`store.<key>`) when the attribution engine is installed.
653
915
  const o = {
654
- pe: t,
916
+ Fe: t,
655
917
  T: CONFIG_OWNED_WRITE | CONFIG_SLOT_NODE,
656
- be: e,
918
+ Qe: e,
657
919
  u: null,
658
- ft: null,
659
- Te: clock,
660
- rt: l,
661
- Se: l?.o?.i || null,
662
- Re: NOT_PENDING,
663
- Ae: null,
664
- Et: -1,
920
+ Gt: null,
921
+ Pe: clock,
922
+ Te: u,
923
+ De: u?.o?.i || null,
924
+ Mt: null,
925
+ ve: NOT_PENDING,
926
+ Ge: null,
927
+ ht: -1,
665
928
  o: null,
666
929
  // Slot backrefs: what the equals/unobserved closures used to capture.
667
- ht: n,
668
- Ht: i,
930
+ Wt: n,
931
+ qt: i,
669
932
  // Store read-path caches, pre-shaped (were post-construction expandos).
670
- acc: u,
933
+ acc: l,
671
934
  px: undefined,
672
935
  pxv: undefined
673
936
  };
674
- if (l) {
675
- ext(l).i = o;
676
- l.T |= CONFIG_FW_CHILDREN;
677
- }
678
- if (snapshotCaptureActive && !((l?.S ?? 0) & STATUS_PENDING)) {
679
- ext(o).Ye = e === undefined ? NO_SNAPSHOT : e;
937
+ if (u) linkFirewallChild(u, o);
938
+ if (snapshotCaptureActive && !((u?.S ?? 0) & STATUS_PENDING)) {
939
+ ext(o).nt = e === undefined ? NO_SNAPSHOT : e;
680
940
  o.T |= CONFIG_HAS_SNAPSHOT;
681
941
  snapshotSources.add(o);
682
942
  }
@@ -685,14 +945,14 @@ function slotSignal(e, t, n, i, u, l = null) {
685
945
 
686
946
  function optimisticSignal(e, t) {
687
947
  const n = signal(e, t);
688
- ext(n).Pe = NOT_PENDING;
948
+ ext(n).Ce = NOT_PENDING;
689
949
  n.T |= CONFIG_OPTIMISTIC;
690
950
  return n;
691
951
  }
692
952
 
693
953
  function optimisticComputed(e, t) {
694
954
  const n = computed(e, t);
695
- ext(n).Pe = NOT_PENDING;
955
+ ext(n).Ce = NOT_PENDING;
696
956
  n.T |= CONFIG_OPTIMISTIC;
697
957
  return n;
698
958
  }
@@ -723,26 +983,51 @@ function isEqual(e, t) {
723
983
  * );
724
984
  * ```
725
985
  */ function untrack(e, t) {
726
- if (GlobalQueue.gt === null && !tracking && true) return e();
986
+ if (GlobalQueue.Yt === null && !tracking && true) return e();
727
987
  const n = tracking;
728
988
  tracking = false;
729
989
  try {
730
- if (GlobalQueue.gt !== null) return GlobalQueue.gt(e);
990
+ if (GlobalQueue.Yt !== null) return GlobalQueue.Yt(e);
731
991
  return e();
732
992
  } finally {
733
993
  tracking = n;
734
994
  }
735
995
  }
736
996
 
997
+ /**
998
+ * Set while runtime bookkeeping evaluates a user accessor inside another
999
+ * node's pass (a loading boundary's `on` key, read from `notify` while the
1000
+ * node that went pending is still recomputing). `context` is that node, but
1001
+ * the read is nobody's: the value is compared, never derived from, so the
1002
+ * untracked-pending re-run link (`read`, `!tracking`) must not be recorded on
1003
+ * it — it made the key's source a dependency of an unrelated async memo
1004
+ * (#3528: `isPending(m2)` through a memo in `on()` linked the memo into `m2`,
1005
+ * a cycle that re-derived `m2` on every pending mark and never converged).
1006
+ */ let spectating = false;
1007
+
1008
+ /**
1009
+ * Evaluates `fn` untracked and without recording a dependency for the
1010
+ * untracked-pending re-run rule on the current `context`. For bookkeeping
1011
+ * reads made mid-propagation on behalf of no node — see `spectating`.
1012
+ */ function spectate(e) {
1013
+ const t = spectating;
1014
+ spectating = true;
1015
+ try {
1016
+ return untrack(e);
1017
+ } finally {
1018
+ spectating = t;
1019
+ }
1020
+ }
1021
+
737
1022
  /**
738
1023
  * Bring a computed to a readable state: lazy/disposed nodes are (re)computed;
739
1024
  * an isPending() probe (`refresh`) additionally pulls the node fully up to
740
1025
  * date so its status flags reflect the current graph.
741
1026
  */ function prepareComputed(e, t) {
742
- if (e.ie & REACTIVE_LAZY) {
743
- e.ie &= ~REACTIVE_LAZY;
1027
+ if (e.ue & REACTIVE_LAZY) {
1028
+ e.ue &= ~REACTIVE_LAZY;
744
1029
  recompute(e, true);
745
- } else if (e.ie & REACTIVE_DISPOSED) {
1030
+ } else if (e.ue & REACTIVE_DISPOSED) {
746
1031
  // Two disposal lifecycles share the flag (#3024). Observation-lifecycle
747
1032
  // nodes (CONFIG_AUTO_DISPOSE) are dormant — torn down by unobserved()
748
1033
  // when the last subscriber left — and reads reawaken them; that is the
@@ -777,14 +1062,14 @@ function isEqual(e, t) {
777
1062
  * Pay-for-use: reached through GlobalQueue._notifyAuthoritativeObservers,
778
1063
  * installed at first until() call — apps that never use until() shake it.
779
1064
  */ function notifyAuthoritativeObservers(e) {
780
- for (let t = e.u; t !== null; t = t.ae) {
781
- const e = t.ce;
1065
+ for (let t = e.u; t !== null; t = t.Ne) {
1066
+ const e = t._e;
782
1067
  if (!(e.T & CONFIG_AUTHORITATIVE_READ)) continue;
783
1068
  // Missed-wake latch (#3037), same contract as insertSubs: the reader may
784
1069
  // itself have pulled this recompute (updateIfNecessary from its own
785
1070
  // read), and the heap refuses RECOMPUTING nodes — latch so recompute's
786
1071
  // tail reschedules it with the staged value visible.
787
- if (e.ie & REACTIVE_RECOMPUTING_DEPS && t.vt === e.ze && t !== e.Be) e.ie |= REACTIVE_MISSED_WAKE;
1072
+ if (e.ue & REACTIVE_RECOMPUTING_DEPS && t.qe === e.Ze && t !== e.ot) e.ue |= REACTIVE_MISSED_WAKE;
788
1073
  enqueueSub(e);
789
1074
  }
790
1075
  schedule();
@@ -795,19 +1080,272 @@ function isEqual(e, t) {
795
1080
  * before its first read (same late-binding contract as the optimistic engine;
796
1081
  * the gating bit is only ever set by such a read, so the `!` call sites are
797
1082
  * safe once every setter installs, #3303). */ function installAuthoritativeRead() {
798
- if (GlobalQueue.he === null) GlobalQueue.he = notifyAuthoritativeObservers;
1083
+ if (GlobalQueue.Zt === null) GlobalQueue.Zt = notifyAuthoritativeObservers;
1084
+ }
1085
+
1086
+ /**
1087
+ * Stale-reader term of the value selections below: a render effect reading a
1088
+ * node some OTHER live transaction has staged sees the committed value. The
1089
+ * commit is silent — the staging walk was the notification — so a reader
1090
+ * that linked AFTER that walk (an effect created during the hold, a store
1091
+ * key first read under it) would show the old value past the reveal: record
1092
+ * it for the transaction's commit replay (the `_gatedSubs` contract lanes
1093
+ * already use). An effect the transaction itself computed re-derives at its
1094
+ * commit on its own (parked run, or the contested re-derive, #3322) and is
1095
+ * not recorded — replaying it too would publish the frame twice.
1096
+ *
1097
+ * Flight twin (the pending-branch carve-out): the reader is served the
1098
+ * node's committed, pre-flight value and now observes that flight — A15:
1099
+ * async work observed by a reader settles as one unit with the writes that
1100
+ * asked it — so it joins the transaction's reporters for the node. The
1101
+ * reporter the transaction recorded when the flight started may be gone (a
1102
+ * keyed remount disposed it, #3374); a completion check that found no live
1103
+ * reporter committed the writes ahead of the answer, tearing the new
1104
+ * reader's frame (`Count: 1` beside `Details: 0`). Joins an entry the
1105
+ * transaction already holds; a flight nobody had observed yet has none (the
1106
+ * reader is its first observer — a conditional that just revealed it, #3458)
1107
+ * and is notified up the reader's own queue chain under that transaction,
1108
+ * the one sanctioned registration site (INV-3): a collecting boundary above
1109
+ * the reader consumes it as it would any pending, an unboundaried reader
1110
+ * opens the entry — and the transaction, judged complete on its other
1111
+ * flights, revealed the inputs beside the reader's pre-flight value
1112
+ * otherwise (`Count: 1 | A: 1` beside `B: 0`). A staged signal or a settled
1113
+ * node registers nothing. Every reporter dies with its reader
1114
+ * (reporterBlocksSource: the read linked it as a dep). The node's own entry
1115
+ * is the only one that can matter: a chain's intermediate memo is re-pulled
1116
+ * by the read (updateIfNecessary's retry) and enters the transaction, so the
1117
+ * reader holds through the normal path; a node with its own flight that is
1118
+ * also pending on an upstream re-ask blocks through that flight until it
1119
+ * lands, and its landing re-runs the reader into the normal path.
1120
+ */
1121
+ /** The replay half of the stale-of-foreign clause (A15 / A26): a stale reader
1122
+ * served the committed value because `txn` holds what it read re-runs at
1123
+ * txn's commit, when the value it was denied becomes the frame — unless its
1124
+ * own last value already came from that transaction. One registration for
1125
+ * the node path (heldFromStale) and the store's backing paths, which have
1126
+ * no node to carry the hold (heldFromReader, the adoption hold view). */ function recordStaleReplay(e, t) {
1127
+ const n = t.It;
1128
+ if (n == null || currentTransition(n) !== e) e.ct.add(t);
1129
+ }
1130
+
1131
+ /**
1132
+ * The ownership relation (DESIGN-CONSOLIDATION §6, ruled 2026-09-17): is
1133
+ * `hold` part of the running pass's world? A plain reader's world is the
1134
+ * transaction it runs under, through merges. A lane reader's world is its
1135
+ * lane AND the transition that owns the lane — the one asymmetry between a
1136
+ * lane and a separate transaction (a lane sees what lands from its parent as
1137
+ * its own; a separate transaction would wait for the parent to settle) —
1138
+ * see `ownsLane` in lanes.ts, built on this. One relation for the
1139
+ * stale-of-foreign clause (heldFromStale), the lane arm (readsHeldCommitted)
1140
+ * and the store's backing holds (foreignHold); `serve` has no lane arm of
1141
+ * its own, the lane's extra visibility lives here.
1142
+ */ function ownsHold(e) {
1143
+ return activeTransition !== null && currentTransition(e) === currentTransition(activeTransition);
1144
+ }
1145
+
1146
+ function heldFromStale(e, t) {
1147
+ const n = e.Ge;
1148
+ if (n === null || ownsHold(n)) return false;
1149
+ const i = currentTransition(n);
1150
+ recordStaleReplay(i, t);
1151
+ const l = i.oe.get(e);
1152
+ if (l) l.add(t); else if (e.S & STATUS_PENDING) runInTransition(i, () => t.C.notify(t, STATUS_PENDING, STATUS_PENDING, e.o._));
1153
+ return true;
1154
+ }
1155
+
1156
+ /**
1157
+ * A tracked computation about to be served a live transaction's staged value
1158
+ * derives from that transaction's world, so it enters the transaction and
1159
+ * this pass's result is held with it (A29, #3408). The write side (`setSignal`)
1160
+ * and a stamped node's recompute already enter; a reader that only now
1161
+ * starts reading the held node — a conditional memo whose branch flipped —
1162
+ * was the gap: it published a value derived from the staged world into the
1163
+ * mainline frame. Not for a probe (`isPending(() => x())` observes, it does
1164
+ * not derive) and a no-op for the ambient batch (`_transition` null) or the
1165
+ * transaction already active.
1166
+ */
1167
+ /** The transaction a pass running OUTSIDE a flush was served a staged value
1168
+ * from (A29, creation-time form). Inside a flush the pass enters through
1169
+ * initTransition; outside one — a memo or effect created from mainline code
1170
+ * while a hold is live — entering would leave `activeTransition` and the
1171
+ * batch pointed at the transaction for the rest of the synchronous block,
1172
+ * so an unrelated write made after the mount was held with someone else's
1173
+ * action. The entry is the pass's alone: recompute stages the node into the
1174
+ * transaction (born held) and mainline is never touched. */ let stagedEntry = null;
1175
+
1176
+ function enterStagedRead(e, t = e.Ge) {
1177
+ if (!t || t === activeTransition || pendingCheckActive) return;
1178
+ // A companion (the latest() shadow, the isPending() verdict signal) is the
1179
+ // engine's mirror of the flushed world — reading it, or being it, is an
1180
+ // observation, not a derivation from the hold: latest(x) never enters x's
1181
+ // transaction, and the shadow's own pass never enters either (it would
1182
+ // flip activeTransition under the reader that pulled it). (`el` is null for
1183
+ // a store backing served under a hold — no node, the transaction is the
1184
+ // fold's.)
1185
+ if (e?.o?.Ht || context?.o?.Ht) return;
1186
+ // Verdict machinery (GlobalQueue._verdictPull: companion creation and the
1187
+ // latest()/isPending() pulls — the latest() shadow is created before it is
1188
+ // marked optimistic, so the bit alone cannot tell) and optimistic nodes
1189
+ // (own lane posture, A31) read staged truth by design; they keep the
1190
+ // entering path.
1191
+ // (`context` is non-null here: every caller selected a value for a reader.)
1192
+ const n = context;
1193
+ if (activeTransition === null && !globalQueue.Kt) {
1194
+ // Verdict pulls are observations, not derivations: a latest() /
1195
+ // isPending() call from mainline must never enter a transaction (it
1196
+ // would capture the rest of the caller's synchronous block).
1197
+ if (GlobalQueue.jt) return;
1198
+ if (n.ue & REACTIVE_RECOMPUTING_DEPS && !(n.T & CONFIG_OPTIMISTIC) && (stagedEntry === null || stagedEntry === t)) {
1199
+ stagedEntry = t;
1200
+ return;
1201
+ }
1202
+ }
1203
+ globalQueue.initTransition(t);
1204
+ }
1205
+
1206
+ /**
1207
+ * Rule 1 (value selection), the full arm: does this reader see a STAGED
1208
+ * node's COMMITTED value? One implementation of the rule the fast paths
1209
+ * (readNodeFast, read's fast block) carry as their trivial ternary and that
1210
+ * every slow site — read's tail, the store's backing selection, the lane and
1211
+ * verdict arms — used to restate by hand (docs/DESIGN-CONSOLIDATION.md, move 3b). In order:
1212
+ * - no reader at all (an untracked read) — the committed frame;
1213
+ * - a reader under an optimistic lane the engine says reads committed
1214
+ * (laneReadsCommitted: another lane's hold, #3460);
1215
+ * - nothing staged;
1216
+ * - a children-forbidden reader (createTrackedEffect / onSettled: the frame,
1217
+ * never the graph — A32);
1218
+ * - a stale reader (render effect) of a FOREIGN transaction's staged write —
1219
+ * committed, no entanglement (heldFromStale registers the replay; a node
1220
+ * born held has no committed frame to fall back to, `noCommitted`);
1221
+ * - A17 for HELD truth (#3164, CONFIG_HELD_TRUTH): staged confirming truth —
1222
+ * fold-staged onto an armed family, or entangle-stolen by an awaited
1223
+ * until() — is masked from ordinary readers until its transaction's
1224
+ * reveal, the retaining transaction's own speculative recomputes included
1225
+ * (partial override coverage would otherwise compose override + staged
1226
+ * truth into a state no timeline contains). Authoritative readers
1227
+ * (until()'s predicate) and latest() see the staged truth — the tunnel that
1228
+ * keeps the hold deadlock-free.
1229
+ * False means the reader derives from the staged value and enters its
1230
+ * transaction (enterStagedRead, A29).
1231
+ */ function readerSeesCommitted(e, t, n, i) {
1232
+ return !!(!t || currentOptimisticLane !== null && GlobalQueue.Bt(e, n, t) || e.ve === NOT_PENDING || t.T & CONFIG_CHILDREN_FORBIDDEN || stale && !i && heldFromStale(e, t) || e.T & CONFIG_HELD_TRUTH && !latestReadActive && !(t.T & CONFIG_AUTHORITATIVE_READ));
1233
+ }
1234
+
1235
+ /** A28 — set when a node is staged (queuePendingNode) or a held node rewritten
1236
+ * (stashHeldRewrite) OUTSIDE a flush; cleared when the next flush begins. The
1237
+ * read sites test this one module boolean instead of `globalQueue._running`:
1238
+ * inside a flush it is false and the A28 arm costs nothing; outside, only a
1239
+ * tick with unflushed writes pays the staged-node check. */ let unflushedStaged = false;
1240
+
1241
+ function markUnflushedStaged() {
1242
+ unflushedStaged = true;
1243
+ }
1244
+
1245
+ /** A28 — a write becomes visible at flush. Outside a flush, a node holding an
1246
+ * AMBIENT staged value (no transaction stamp) was written since the last
1247
+ * flush: ambient staging commits at flush end, so nothing else leaves a node
1248
+ * in this state; a stamped value is the flushed held world, which A28 says
1249
+ * latest() serves. Inside a flush the rule does not apply (A28 (4): promoted
1250
+ * within the round). Structural — no marker on the write path. */ function unflushed(e) {
1251
+ return unflushedValue(e) !== NOT_PENDING;
1252
+ }
1253
+
1254
+ /** The value an unflushed node serves — the committed value for an ambient
1255
+ * write, the flushed staged value for a rewrite of a held node — or
1256
+ * NOT_PENDING when nothing is unflushed. Exempt: owned-write nodes (A28 (4):
1257
+ * a write issued inside a recompute is promoted at that recompute's end —
1258
+ * boundary and loading machinery, until()'s internals, signals declared for
1259
+ * in-computation writes) and engine companions (the isPending() verdict
1260
+ * signal, the latest() shadow: the system's own writes, made at the source's
1261
+ * write to mirror it, installing eagerly — A28, A8). */ function unflushedValue(e, t = e.Qe) {
1262
+ if (globalQueue.Kt || e.ve === NOT_PENDING || e.T & CONFIG_PROMOTED || e.o?.Ht) return NOT_PENDING;
1263
+ // Ambient, or adopted by a transaction before any flush carried the staging
1264
+ // (CONFIG_ADOPTED_UNFLUSHED): nothing flushed is staged — the committed
1265
+ // value answers (the caller's notion of committed: a store node's backing).
1266
+ // A held node: unflushed only if rewritten since the last flush (stash).
1267
+ if (e.Ge === null || e.T & CONFIG_ADOPTED_UNFLUSHED) return t;
1268
+ return e.o === null ? NOT_PENDING : e.o.gt;
1269
+ }
1270
+
1271
+ /** Held nodes rewritten since the last flush (setSignal); the flush clears
1272
+ * their stash — from then on latest() answers with the rewrite. */ const unflushedRewrites = [];
1273
+
1274
+ /** Nodes written inside a creation-time recompute (CONFIG_PROMOTED). */ const promotedWrites = [];
1275
+
1276
+ /** A28 (5): an optimistic write becomes the ACTIVE override at the flush that
1277
+ * carries it. `_overrideTime` is stamped with `clock` at the write and `clock`
1278
+ * advances after every flush, so "this tick, outside a flush" is unflushed. */ function unflushedOverride(e) {
1279
+ // Companions are optimistic signals written by the engine (see unflushed).
1280
+ return !globalQueue.Kt && e.o?.Ct === clock && !e.o?.Ht;
1281
+ }
1282
+
1283
+ /** Active optimistic override on an armed node (an armed slot idles at
1284
+ * NOT_PENDING; undefined = unarmed plain node). The writer's own channels —
1285
+ * the draft, `in`/keys inside the setter — compose on this regardless of
1286
+ * flush state. */ function hasActiveOverride(e) {
1287
+ const t = e.o;
1288
+ return t !== null && t.Ce !== undefined && t.Ce !== NOT_PENDING;
1289
+ }
1290
+
1291
+ /** The override a READER sees: installed, and carried by a flush (A28 (5) —
1292
+ * an optimistic write is a write; until its flush no reader sees it). One
1293
+ * implementation for read()'s override arm, the verdict channels
1294
+ * (latestRead, computePendingState) and the store's selection
1295
+ * (docs/DESIGN-CONSOLIDATION.md, move 3b). */ function visibleOverride(e) {
1296
+ return hasActiveOverride(e) && !unflushedOverride(e);
1297
+ }
1298
+
1299
+ /** A derivation served the committed value because of an unflushed write
1300
+ * (A28) must run again in the flush that carries it — the late-linker case
1301
+ * (#3337's reason to defer the walk): it linked after the write walked. */ function markLateLinker(e) {
1302
+ // The pass's own tail re-enqueues on this latch (recompute's finally) —
1303
+ // a direct enqueue here would be wiped by the pass's flag reset.
1304
+ e.ue |= REACTIVE_MISSED_WAKE;
1305
+ return true;
1306
+ }
1307
+
1308
+ /** Companion-bearing nodes written outside a flush (setSignal); the flush
1309
+ * that carries their writes re-syncs their companions (A28). */ const unflushedCompanions = [];
1310
+
1311
+ function resyncUnflushedCompanions() {
1312
+ unflushedStaged = false;
1313
+ // Length-guarded: the common flush has nothing here, and must allocate nothing.
1314
+ // Length-guarded: the common flush has nothing here and allocates nothing.
1315
+ if (unflushedRewrites.length !== 0) {
1316
+ for (const e of unflushedRewrites) e.o.gt = NOT_PENDING;
1317
+ unflushedRewrites.length = 0;
1318
+ }
1319
+ if (promotedWrites.length !== 0) {
1320
+ for (const e of promotedWrites) e.T &= ~CONFIG_PROMOTED;
1321
+ promotedWrites.length = 0;
1322
+ }
1323
+ if (unflushedCompanions.length !== 0) {
1324
+ for (const e of unflushedCompanions) GlobalQueue.me(e, e.ve !== NOT_PENDING ? e.ve : e.Qe);
1325
+ unflushedCompanions.length = 0;
1326
+ }
799
1327
  }
800
1328
 
801
1329
  function readNodeFast(e) {
802
- if (latestReadActive || pendingCheckActive || e.oe || e.rt || e.o?.Pe !== undefined || e.o?.Ye !== undefined || activeTransition !== null || currentOptimisticLane !== null || snapshotCaptureActive || false) return READ_SLOW;
1330
+ if (latestReadActive || pendingCheckActive || e.ce || e.Te || e.o?.Ce !== undefined || e.o?.nt !== undefined || activeTransition !== null || currentOptimisticLane !== null || snapshotCaptureActive ||
1331
+ // A28: a staged node read outside a flush may serve its committed value;
1332
+ // that arm lives on the slow path so this body stays inlinable.
1333
+ unflushedStaged && e.ve !== NOT_PENDING || false) return READ_SLOW;
803
1334
  let t = context;
804
- if (t?.Dt) t = t.Pt;
1335
+ if (t?.xt) t = t.Qt;
805
1336
  if (t && tracking) link(e, t);
806
1337
  // Children-forbidden readers (createTrackedEffect / onSettled callbacks) get
807
1338
  // committed visibility: like the effect half of createEffect and event
808
1339
  // handlers, effect-phase code never observes its own unsettled write — the
809
1340
  // write lands in the same flush's continuation (#3006).
810
- return !t || e.Re === NOT_PENDING || t.T & CONFIG_CHILDREN_FORBIDDEN ? e.be : e.Re;
1341
+ // A stale reader (render effect) recomputing with no transaction active is
1342
+ // mainline: a write staged by a live transaction (`_transition` stamped —
1343
+ // ambient staging never is) stays masked until that transaction's reveal,
1344
+ // the same rule the slow path applies (#3322). Without it a zombie
1345
+ // recompute, or a commit-time re-derive of a contested effect, published
1346
+ // another transaction's uncommitted value.
1347
+ return !t || e.ve === NOT_PENDING || t.T & CONFIG_CHILDREN_FORBIDDEN || stale && heldFromStale(e, t) ? e.Qe : (enterStagedRead(e),
1348
+ e.ve);
811
1349
  }
812
1350
 
813
1351
  function read(e) {
@@ -815,33 +1353,36 @@ function read(e) {
815
1353
  // Checked before isPending so that isPending(() => latest(x)) checks
816
1354
  // the _pendingSignal of _latestValueComputed (async in flight) rather
817
1355
  // than the original node (which stays "pending" while held in a transition).
818
- if (latestReadActive) return GlobalQueue.Vt(e);
1356
+ if (latestReadActive) return GlobalQueue.zt(e);
819
1357
  let t = context;
820
- if (t?.Dt) t = t.Pt;
1358
+ if (t?.xt) t = t.Qt;
821
1359
  const n = e;
822
- const i = e.rt;
823
- const u = i || e;
1360
+ const i = e.Te;
1361
+ const l = i || e;
824
1362
  // Handle isPending() mode: collect pending state while preserving normal read semantics.
825
1363
  // Probe mode is suspended while preparing the node so nested reads during a
826
1364
  // recompute don't collect into the probe.
827
1365
  if (pendingCheckActive) {
828
- GlobalQueue.bt(e, t, u, i);
829
- } else if (typeof n.oe === "function") {
1366
+ GlobalQueue.Jt(e, t, l, i);
1367
+ } else if (typeof n.ce === "function") {
830
1368
  prepareComputed(e, false);
831
1369
  }
832
- if (!n.oe && u === e && e.o?.Pe === undefined && e.o?.Ye === undefined && activeTransition === null && currentOptimisticLane === null && !snapshotCaptureActive && true) {
1370
+ if (!n.ce && l === e && e.o?.Ce === undefined && e.o?.nt === undefined && activeTransition === null && currentOptimisticLane === null && !snapshotCaptureActive && (!unflushedStaged || e.ve === NOT_PENDING) && // A28, see readNodeFast
1371
+ true) {
833
1372
  if (t && tracking) link(e, t);
834
- // Committed visibility for children-forbidden readers see readNodeFast.
835
- return !t || e.Re === NOT_PENDING || t.T & CONFIG_CHILDREN_FORBIDDEN ? e.be : e.Re;
1373
+ // Committed visibility for children-forbidden readers and for stale
1374
+ // readers of a foreign transaction's staged write see readNodeFast.
1375
+ return !t || e.ve === NOT_PENDING || t.T & CONFIG_CHILDREN_FORBIDDEN || stale && heldFromStale(e, t) ? e.Qe : (enterStagedRead(e),
1376
+ e.ve);
836
1377
  }
837
1378
  if (t && tracking) {
838
1379
  link(e, t, pendingCheckActive);
839
- if (u.oe) {
1380
+ if (l.ce) {
840
1381
  const n = queueFor(e);
841
- if (u.qe >= n.Me) {
1382
+ if (l.tt >= n.et) {
842
1383
  markNode(t);
843
1384
  markHeap(n);
844
- updateIfNecessary(u);
1385
+ updateIfNecessary(l);
845
1386
  }
846
1387
  // Fresh-pull readers (awaitable refresh's waiter) recompute a dirty
847
1388
  // source inline even when the height gate defers to the flush: the
@@ -849,66 +1390,106 @@ function read(e) {
849
1390
  // never read the PRE-re-ask value as settled. Self-guarded: a clean
850
1391
  // node no-ops and updateIfNecessary refuses disposed nodes (#2983) —
851
1392
  // a dead target serves its last value, which is already quiescent.
852
- else if (t.T & CONFIG_FRESH_READ) updateIfNecessary(u);
853
- const i = u.qe;
1393
+ else if (t.T & CONFIG_FRESH_READ) updateIfNecessary(l);
1394
+ const i = l.tt;
854
1395
  // parent check is shallow, might need to be recursive
855
- if (i >= t.qe && e.Le !== t) {
856
- t.qe = i + 1;
1396
+ if (i >= t.tt && e._parent !== t) {
1397
+ t.tt = i + 1;
857
1398
  }
858
1399
  }
859
1400
  }
860
- if (u.S & STATUS_PENDING) {
861
- if (t && !(stale && u.Ae && activeTransition !== u.Ae)) {
1401
+ if (l.S & STATUS_PENDING) {
1402
+ // A reader landing on a pending node throws the reveal that discovered
1403
+ // the flight holds on it (A15: observed async settles as one unit) — with
1404
+ // one carve-out: a stale (render) reader of a node pending in some OTHER
1405
+ // transaction keeps showing the node's committed value, no entanglement
1406
+ // (parallel transactions; the reader is recorded for that transaction's
1407
+ // commit replay, `heldFromStale`). The carve-out is sound only while the
1408
+ // committed value is coherent with the visible frame, i.e. while the
1409
+ // flight's inputs are themselves unpublished: the stamp alone does not
1410
+ // say so (it is pending-node bookkeeping), so it is refused when the
1411
+ // inputs are on screen — committed by a batch that left the flight in
1412
+ // the air (CONFIG_INPUTS_PUBLISHED, #3305) or revealed through a lane
1413
+ // (optimistic / latest, #3334) — and the reader holds instead. An
1414
+ // UNINITIALIZED node has no committed value to show and holds too
1415
+ // (firewall-backed store reads always did; plain memos since the #3043
1416
+ // port): falling through served `undefined` as if settled and stranded
1417
+ // the reader outside both transactions, so it never re-ran.
1418
+ if (t && !(stale && !(l.S & STATUS_UNINITIALIZED) && !(l.T & CONFIG_INPUTS_PUBLISHED) && !(l.T & CONFIG_HAS_LANE && GlobalQueue.Xt(l)) && heldFromStale(l, t))) {
862
1419
  // Per-lane suspension lives with the engine (a non-null lane implies it
863
1420
  // is installed): under a lane, only same-lane pending async without an
864
1421
  // active override throws — plus uninitialized sources regardless of
865
1422
  // lane (#3276); that check rides laneSuspends so floor bundles don't
866
1423
  // pay for it.
867
- if (currentOptimisticLane === null || GlobalQueue.kt(u)) {
868
- if (!tracking && e !== t) link(e, t);
869
- throw u.o?._;
1424
+ if (currentOptimisticLane === null || GlobalQueue.$t(l)) {
1425
+ // An untracked read of a pending node still re-runs its reader when
1426
+ // the node settles — unless nobody is reading (`spectating`, #3528)
1427
+ // or the reader is the node itself.
1428
+ if (!tracking && !spectating && e !== t) link(e, t);
1429
+ throw l.o?._;
870
1430
  }
871
- } else if (t && u.S & STATUS_UNINITIALIZED) {
872
- // A stale (render) reader of a node held pending in ANOTHER transition
873
- // normally keeps showing the node's committed value instead of
874
- // entangling the two transactions — but an uninitialized node has no
875
- // committed value to show. Suspend on it (firewall-backed store reads
876
- // always took this branch; plain memos now do too): the reader
877
- // registers as a reporter of that source, and its pending-node stamp
878
- // ties it to the active transaction, so the two transactions merge
879
- // when the source settles. Falling through served `undefined` as if
880
- // settled and stranded the reader outside both transactions, so it
881
- // never re-ran when either landed (#3043 port).
882
- if (!tracking && e !== t) link(e, t);
883
- throw u.o?._;
884
- } else if (!t && u.S & STATUS_UNINITIALIZED) {
885
- throw u.o?._;
1431
+ } else if (!t && l.S & STATUS_UNINITIALIZED) {
1432
+ throw l.o?._;
886
1433
  }
887
1434
  }
888
1435
  // `owner` is the computed itself, or the firewall behind a store node —
889
1436
  // firewall-backed reads follow the same rules (memo parity, #2897 ruling):
890
1437
  // an errored derive throws for every late reader instead of silently
891
1438
  // serving node values (the seed, or last-good data after a failed refetch).
892
- if (u.oe && u.S & STATUS_ERROR) {
1439
+ if (l.ce && l.S & STATUS_ERROR) {
893
1440
  // Only a genuine reactive re-read may retry an errored async source:
894
1441
  // - tracking: owned/tracked scope only (never events / `untrack` / effect side-effect phase)
895
1442
  // - !pendingCheckActive: an `isPending` probe observes the error, never refetches
896
1443
  // - owner._time < clock: only on a later cycle than the one the error was found
897
- if (tracking && !pendingCheckActive && u.Te < clock) {
898
- recompute(u);
1444
+ if (tracking && !pendingCheckActive && l.Pe < clock) {
1445
+ recompute(l);
899
1446
  return read(e);
900
- } else throw u.o?._;
1447
+ } else throw l.o?._;
901
1448
  }
902
1449
  if (snapshotCaptureActive && t && t.T & CONFIG_IN_SNAPSHOT_SCOPE) {
903
- const n = e.o?.Ye;
1450
+ const n = e.o?.nt;
904
1451
  if (n !== undefined) {
905
1452
  const i = n === NO_SNAPSHOT ? undefined : n;
906
- const u = e.Re !== NOT_PENDING ? e.Re : e.be;
907
- if (u !== i) t.ie |= REACTIVE_SNAPSHOT_STALE;
1453
+ const l = e.ve !== NOT_PENDING ? e.ve : e.Qe;
1454
+ if (l !== i) t.ue |= REACTIVE_SNAPSHOT_STALE;
908
1455
  return i;
909
1456
  }
910
1457
  }
911
- if (e.o?.Pe !== undefined && e.o?.Pe !== NOT_PENDING) {
1458
+ const u = serve(e, t, l, e.Qe);
1459
+ if (!t && l === e && typeof n.ce === "function" && e.T & CONFIG_AUTO_DISPOSE && !(l.S & STATUS_PENDING) && !e.u &&
1460
+ // An untracked read served a visible override returned before this
1461
+ // sweep registration when the arm was inline; keep that.
1462
+ !visibleOverride(e)) {
1463
+ // Deferred, not inline (#3078): an inline unobserved() here made untracked
1464
+ // reads destructive — dispose on this read, full revival recompute on the
1465
+ // next — so consecutive reads could answer differently with no write in
1466
+ // between (the revival samples the ambient transition/lane context).
1467
+ // The sweep at flush finalization re-validates and reclaims; schedule()
1468
+ // guarantees that flush happens even if nothing else is queued.
1469
+ dormantNodes.add(e);
1470
+ schedule();
1471
+ }
1472
+ return u;
1473
+ }
1474
+
1475
+ /**
1476
+ * Rule 1, the one slow implementation (DESIGN-CONSOLIDATION move 3b, step
1477
+ * 6c): the value a reader `c` (null = untracked, no pass) is served from
1478
+ * `el`, whose committed value is `committed` — the node's own `_value` for
1479
+ * a signal or memo, the BACKING for a store property node (single-home rule,
1480
+ * O6: committed truth lives in the backing and a node's `_value` is never
1481
+ * served for one). Called by read()'s slow tail and by the store's untracked
1482
+ * node path (nodeValue); the fast paths (readNodeFast, read's fast block)
1483
+ * keep their trivial ternary by design (perf, see the doc). Arms, in order:
1484
+ * - the override (A17), routed through the engine for a tracked reader under
1485
+ * a lane or a supersession (A18), an authoritative reader marked instead;
1486
+ * - the lane entanglement gate (committed, recorded for replay);
1487
+ * - a node born held has nothing for an untracked reader (A19 exception 1);
1488
+ * - an unflushed write serves committed and re-runs the reader in the
1489
+ * carrying flush (A28);
1490
+ * - readerSeesCommitted, else the staged value and the transaction (A29).
1491
+ */ function serve(e, t, n, i) {
1492
+ if (hasActiveOverride(e)) {
912
1493
  // A17: the override IS the value for every reader — except an authoritative
913
1494
  // reader (until()'s predicate carries CONFIG_AUTHORITATIVE_READ): it must
914
1495
  // observe independently-arriving truth, and serving it the caller's own
@@ -919,7 +1500,19 @@ function read(e) {
919
1500
  // authoritative — optimism never lives there); the sticky mark makes the
920
1501
  // A17-silent "landing equals override" paths notify this node's subs so
921
1502
  // the reader re-runs when truth arrives.
922
- if (!(t && t.T & CONFIG_AUTHORITATIVE_READ)) return unwrapOverride(e.o?.Pe);
1503
+ // A28 (5): an optimistic write written this tick, outside a flush, is not
1504
+ // yet the active override — fall through to the normal selection (the
1505
+ // authoritative mark below still applies: until() must wake on landing).
1506
+ if (!(t && t.T & CONFIG_AUTHORITATIVE_READ) && !unflushedOverride(e)) {
1507
+ // A tracked read of an override is the engine's selection (a lane or a
1508
+ // supersession implies the engine): a render effect OFF the override's
1509
+ // held lane sees the committed value (#3460, lanes mirror transitions);
1510
+ // a node whose own source answered with a DIFFERENT value hands a
1511
+ // tracked reader the staged truth (A18 supersession, #3331). Untracked
1512
+ // reads display the override.
1513
+ if (t && e.T & (CONFIG_HAS_LANE | CONFIG_OVERRIDE_SUPERSEDED)) return GlobalQueue.en(e, t);
1514
+ return unwrapOverride(e.o?.Ce);
1515
+ }
923
1516
  e.T |= CONFIG_AUTHORITATIVE_OBSERVED;
924
1517
  }
925
1518
  // Entanglement gate: a reader recomputing under an optimistic lane that reads
@@ -929,8 +1522,8 @@ function read(e) {
929
1522
  // _pendingValue for correct fetching. The sub is recorded for replay at commit
930
1523
  // so it re-runs with the new committed view. (Gate details live with the
931
1524
  // engine — a non-null lane implies it is installed.)
932
- if (currentOptimisticLane !== null && activeTransition !== null && t !== null && GlobalQueue.Ut(e, u, t)) {
933
- return e.be;
1525
+ if (currentOptimisticLane !== null && activeTransition !== null && t !== null && GlobalQueue.tn(e, n, t)) {
1526
+ return i;
934
1527
  }
935
1528
  // In optimistic lane context, return _value for optimistic/lane-assigned signals
936
1529
  // and for regular signals in stale mode (render effects). Non-stale readers (user
@@ -938,64 +1531,110 @@ function read(e) {
938
1531
  // (The lane-context clause lives with the engine.) Children-forbidden readers
939
1532
  // (createTrackedEffect / onSettled callbacks) get committed visibility — see
940
1533
  // readNodeFast (#3006).
941
- const l = !t || currentOptimisticLane !== null && GlobalQueue.xt(e, u, t) || e.Re === NOT_PENDING || t.T & CONFIG_CHILDREN_FORBIDDEN || stale && e.Ae && activeTransition !== e.Ae ||
942
- // A17 for HELD truth (#3164, see CONFIG_HELD_TRUTH): staged confirming
943
- // truth fold-staged onto an armed family, or entangle-stolen by an
944
- // awaited until() is masked from ordinary readers until its
945
- // transaction's reveal; the retaining transaction's own speculative
946
- // recomputes included (partial override coverage would otherwise
947
- // compose override + staged truth into a state no timeline contains).
948
- // Authoritative readers (until()'s predicate) and latest() see the
949
- // staged truth — the tunnel that keeps the hold deadlock-free.
950
- e.T & CONFIG_HELD_TRUTH && !latestReadActive && !(t.T & CONFIG_AUTHORITATIVE_READ) ? e.be : e.Re;
1534
+ // A node born held (recompute) has a staged value and no committed one: a
1535
+ // stale reader cannot fall back to the committed frame, so it takes the
1536
+ // staged value and enters like a tracked reader (A29); an untracked reader
1537
+ // has nothing to serve and holds (A19 exception 1).
1538
+ const l = e.ve !== NOT_PENDING && (e.S & STATUS_UNINITIALIZED) !== 0;
1539
+ if (l && !t) throw new NotReadyError(null);
1540
+ const u = t && unflushedStaged ? unflushedValue(e, i) : NOT_PENDING;
1541
+ if (u !== NOT_PENDING) {
1542
+ markLateLinker(t);
1543
+ if (pendingCheckActive) GlobalQueue.nn(e, u);
1544
+ return u;
1545
+ }
1546
+ const o = readerSeesCommitted(e, t, n, l) ? i : (enterStagedRead(e), e.ve);
951
1547
  // Record that this isPending() probe observed the fresh pending value, so
952
1548
  // the probe doesn't pair "pending" with the new value (#2831).
953
- if (pendingCheckActive) GlobalQueue.Lt(e, l);
954
- if (!t && u === e && typeof n.oe === "function" && e.T & CONFIG_AUTO_DISPOSE && !(u.S & STATUS_PENDING) && !e.u) {
955
- // Deferred, not inline (#3078): an inline unobserved() here made untracked
956
- // reads destructive — dispose on this read, full revival recompute on the
957
- // nextso consecutive reads could answer differently with no write in
958
- // between (the revival samples the ambient transition/lane context).
959
- // The sweep at flush finalization re-validates and reclaims; schedule()
960
- // guarantees that flush happens even if nothing else is queued.
961
- dormantNodes.add(e);
962
- schedule();
1549
+ if (pendingCheckActive) GlobalQueue.nn(e, o);
1550
+ return o;
1551
+ }
1552
+
1553
+ /** A28a rewrite of a HELD node outside a flush keeps the staged value the
1554
+ * last flush left, for latest()/verdicts, until the next flush carries it
1555
+ * (stash, cleared at that flush). Cold: only stamped nodes reach here. */ function stashHeldRewrite(e) {
1556
+ if (globalQueue.Kt) return;
1557
+ const t = ext(e);
1558
+ if (t.gt === NOT_PENDING) {
1559
+ t.gt = e.ve;
1560
+ unflushedRewrites.push(e);
1561
+ unflushedStaged = true;
963
1562
  }
964
- return l;
1563
+ }
1564
+
1565
+ /** A28 (4) — written inside a recompute that runs OUTSIDE a flush (a
1566
+ * creation-time compute): promoted at that pass's end, visible to the rest of
1567
+ * the block. Cold: only contextual writes reach here. */ function notePromotedWrite(e) {
1568
+ if (globalQueue.Kt || e.T & CONFIG_PROMOTED) return;
1569
+ e.T |= CONFIG_PROMOTED;
1570
+ promotedWrites.push(e);
965
1571
  }
966
1572
 
967
1573
  function setSignal(e, t) {
968
- if (e.Ae && activeTransition !== e.Ae) globalQueue.initTransition(e.Ae);
1574
+ // A write to a held node is a second proposal on a contested node (A34, #3494):
1575
+ // the writer's tick reveals with the hold, the same value or another. Inside
1576
+ // a flush the round enters now; from mainline the entry waits for the next
1577
+ // flush's start (batchJoins) — entering here left activeTransition set for
1578
+ // the rest of the caller's block, so a memo created after the write became
1579
+ // the transaction's instead of mainline's (A28, A29). Before the equality
1580
+ // gate below: repeating the held value proposes it too — and that repeat
1581
+ // leaves through the gate, so the join schedules its own flush here; left
1582
+ // for the next flush to find, it adopted an unrelated tick (#3519 review).
1583
+ if (e.Ge && activeTransition !== e.Ge) {
1584
+ if (globalQueue.Kt) globalQueue.initTransition(e.Ge); else {
1585
+ batchJoins.push(e.Ge);
1586
+ // dupes: a bare return in initTransition
1587
+ schedule();
1588
+ }
1589
+ }
969
1590
  // The optimistic write path lives with the engine: only optimisticSignal /
970
1591
  // optimisticComputed callers and optimistic store nodes carry an
971
1592
  // _overrideValue slot (flagged by CONFIG_OPTIMISTIC — a masked read of the
972
1593
  // always-present config instead of a missing-property probe), and every
973
1594
  // module that installs one installs the engine first.
974
- if (e.T & CONFIG_OPTIMISTIC && !projectionWriteActive) return GlobalQueue.yt(e, t);
975
- const n = e.Re === NOT_PENDING ? e.be : e.Re;
1595
+ if (e.T & CONFIG_OPTIMISTIC) {
1596
+ if (!projectionWriteActive) return GlobalQueue.ln(e, t);
1597
+ // An authoritative store landing on an override-covered node: the store
1598
+ // twin of asyncWrite's override branch, decided by the engine (#3331).
1599
+ const n = e.o?.Ce;
1600
+ if (n !== undefined && n !== NOT_PENDING) return GlobalQueue.un(e, t);
1601
+ }
1602
+ const n = e.ve === NOT_PENDING ? e.Qe : e.ve;
976
1603
  if (typeof t === "function") t = t(n);
977
1604
  // Uninitialized check first: the first commit has no previous value, so the
978
1605
  // user comparator must not run against `undefined` (matches recompute).
979
- const i = !!(e.S & STATUS_UNINITIALIZED) || !e.pe || !e.pe(n, t);
1606
+ const i = !!(e.S & STATUS_UNINITIALIZED) || !e.Fe || !e.Fe(n, t);
980
1607
  if (!i) return t;
981
- const u = e.Re !== NOT_PENDING;
982
- if (!u) queuePendingNode(e);
983
- e.Re = t;
1608
+ const l = e.ve !== NOT_PENDING;
1609
+ if (!l) queuePendingNode(e);
1610
+ // A28 arms, gated on the loads the write already pays for (a plain ambient
1611
+ // rewrite outside a flush — the write-loop shape — costs `_transition` and
1612
+ // `context` here and nothing else; the arms themselves are cold helpers so
1613
+ // setSignal stays within every setter's inlining budget, ~300 B bytecode).
1614
+ else if (e.Ge !== null) stashHeldRewrite(e);
1615
+ e.ve = t;
1616
+ if (context !== null) notePromotedWrite(e);
984
1617
  // syncCompanions only pokes _pendingSignal/_latestValueComputed — with
985
1618
  // neither companion present the call is a guaranteed no-op (companions are
986
1619
  // only ever created, never removed, and creating one installs the hook and
987
1620
  // sets CONFIG_HAS_COMPANIONS — one masked read replaces two optional-field
988
1621
  // probes on every write).
989
- e.T & CONFIG_HAS_COMPANIONS && GlobalQueue.Ue !== null && GlobalQueue.Ue(e, t);
1622
+ if (e.T & CONFIG_HAS_COMPANIONS && GlobalQueue.me !== null) {
1623
+ GlobalQueue.me(e, t);
1624
+ // A28 (2): the verdict computed here is the PRE-flush one (an unflushed
1625
+ // write is not pending); the flush that carries the write re-syncs so
1626
+ // the companions mirror the flushed world. Only companion nodes pay.
1627
+ if (!globalQueue.Kt) unflushedCompanions.push(e);
1628
+ }
990
1629
  // _time is a computed-only slot (§12e): writing it on a signal would fork
991
1630
  // the lean shape. Every read site is computed-typed.
992
- if (e.oe !== undefined) e.Te = clock;
1631
+ if (e.ce !== undefined) e.Pe = clock;
993
1632
  // Staged-rewrite fast path (§12d): a re-write to a node whose subscribers
994
1633
  // were already walked — and where nothing has recomputed or linked since
995
1634
  // (epoch) — re-stages the value and stops. The walk is idempotent (subs
996
1635
  // marked, heap entries flag-guarded, effects queued once); lane and reask
997
1636
  // contexts change what a walk MEANS, so they always walk.
998
- if (u && e.Et === notifyEpoch && currentOptimisticLane === null && !reaskArmed) return t;
1637
+ if (l && e.ht === notifyEpoch && currentOptimisticLane === null && !reaskArmed) return t;
999
1638
  insertSubs(e);
1000
1639
  schedule();
1001
1640
  return t;
@@ -1009,12 +1648,12 @@ function setSignal(e, t) {
1009
1648
  * cleanup point.
1010
1649
  */ function suppressComputedRecompute(e) {
1011
1650
  deleteFromHeap(e, queueFor(e));
1012
- if (!(e.ie & REACTIVE_MANUAL_WRITE) && e.Re === NOT_PENDING) {
1651
+ if (!(e.ue & REACTIVE_MANUAL_WRITE) && e.ve === NOT_PENDING) {
1013
1652
  queuePendingNode(e);
1014
1653
  schedule();
1015
1654
  }
1016
- e.ie = e.ie & -4 | REACTIVE_MANUAL_WRITE;
1017
- e.Qt = clock;
1655
+ e.ue = e.ue & -4 | REACTIVE_MANUAL_WRITE;
1656
+ e.sn = clock;
1018
1657
  }
1019
1658
 
1020
1659
  /**
@@ -1076,16 +1715,16 @@ function staleValues(e, t = true) {
1076
1715
  * recompute as a quiet re-ask and schedules it; no-ops for non-derived or
1077
1716
  * disposed targets and for same-tick manual writes.
1078
1717
  */ function markRefresh(e) {
1079
- if (typeof e.oe === "function" && !(e.ie & REACTIVE_DISPOSED)) {
1080
- if (e.ie & REACTIVE_MANUAL_WRITE) {
1718
+ if (typeof e.ce === "function" && !(e.ue & REACTIVE_DISPOSED)) {
1719
+ if (e.ue & REACTIVE_MANUAL_WRITE) {
1081
1720
  // A manual write in the CURRENT tick wins over the refresh (#2692).
1082
1721
  // A mask stamped in an earlier tick only survives because a
1083
1722
  // transaction (action) is holding the pending drain open; there the
1084
1723
  // refresh is a later, explicit re-ask and lifts the mask — otherwise
1085
1724
  // any setStore early in an action silently swallows every refresh()
1086
1725
  // for the rest of the transaction (#3026).
1087
- if (e.Qt === clock) return;
1088
- e.ie &= ~REACTIVE_MANUAL_WRITE;
1726
+ if (e.sn === clock) return;
1727
+ e.ue &= ~REACTIVE_MANUAL_WRITE;
1089
1728
  // The lift falls through to the re-ask classification below. The held
1090
1729
  // write's value change already rides the transaction; the refetch it
1091
1730
  // asks for is the same question with unchanged inputs. Skipping the
@@ -1102,14 +1741,14 @@ function staleValues(e, t = true) {
1102
1741
  // REACTIVE_IN_HEAP counts as dirt: insertSubs schedules subscribers by
1103
1742
  // heap insertion alone (no DIRTY/CHECK flag), so a same-batch value
1104
1743
  // change followed by refresh() must not be laundered into a quiet re-ask.
1105
- if (!(e.ie & (REACTIVE_DIRTY | REACTIVE_CHECK | REACTIVE_IN_HEAP))) {
1106
- e.ie |= REACTIVE_REASK;
1744
+ if (!(e.ue & (REACTIVE_DIRTY | REACTIVE_CHECK | REACTIVE_IN_HEAP))) {
1745
+ e.ue |= REACTIVE_REASK;
1107
1746
  armReaskClear();
1108
1747
  }
1109
- e.ie = e.ie & ~REACTIVE_CHECK | REACTIVE_DIRTY;
1748
+ e.ue = e.ue & ~REACTIVE_CHECK | REACTIVE_DIRTY;
1110
1749
  insertIntoHeap(e, queueFor(e));
1111
1750
  schedule();
1112
1751
  }
1113
1752
  }
1114
1753
 
1115
- export { READ_SLOW, clearSnapshots, computed, context, createEffectNode, currentOptimisticLane, effectStatusNotify, ext, installAuthoritativeRead, isEqual, latestReadActive, markRefresh, markSnapshotScope, notifyAuthoritativeObservers, optimisticComputed, optimisticSignal, pendingCheckActive, prepareComputed, read, readNodeFast, recompute, releaseSnapshotScope, runWithOwner, setContextInternal, setEffectStatusNotify, setLatestReadActive, setMemo, setPendingCheckActive, setSignal, setSlotUnobserved, setSnapshotCapture, signal, slotSignal, slotUnobservedHook, snapshotCaptureActive, snapshotSources, stale, staleValues, statusNotifierOf, suppressComputedRecompute, tracking, untrack };
1754
+ export { READ_SLOW, clearSnapshots, computed, context, createEffectNode, currentOptimisticLane, effectStatusNotify, enterStagedRead, ext, hasActiveOverride, installAuthoritativeRead, isEqual, latestReadActive, markLateLinker, markRefresh, markSnapshotScope, markUnflushedStaged, notifyAuthoritativeObservers, optimisticComputed, optimisticSignal, ownsHold, pendingCheckActive, prepareComputed, read, readNodeFast, readerSeesCommitted, recompute, recordStaleReplay, releaseSnapshotScope, resyncUnflushedCompanions, runWithOwner, serve, setContextInternal, setEffectStatusNotify, setLatestReadActive, setMemo, setPendingCheckActive, setSignal, setSlotUnobserved, setSnapshotCapture, signal, slotSignal, slotUnobservedHook, snapshotCaptureActive, snapshotSources, spectate, spectating, stale, staleValues, statusNotifierOf, suppressComputedRecompute, tracking, unflushed, unflushedCompanions, unflushedOverride, unflushedStaged, unflushedValue, unlinkFirewallChild, untrack, visibleOverride };