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