@solidjs/signals 2.0.0-rc.0 → 2.0.0-rc.2

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 (70) hide show
  1. package/README.md +1 -1
  2. package/dist/dev.js +4287 -2803
  3. package/dist/node.cjs +4563 -3756
  4. package/dist/prod/affects.js +16 -16
  5. package/dist/prod/boundaries.js +90 -90
  6. package/dist/prod/core/action.js +3 -3
  7. package/dist/prod/core/async.js +234 -178
  8. package/dist/prod/core/constants.js +39 -1
  9. package/dist/prod/core/core.js +362 -243
  10. package/dist/prod/core/effect.js +56 -56
  11. package/dist/prod/core/external.js +4 -4
  12. package/dist/prod/core/graph.js +65 -54
  13. package/dist/prod/core/heap.js +47 -39
  14. package/dist/prod/core/invariants.js +3 -2
  15. package/dist/prod/core/lanes.js +41 -34
  16. package/dist/prod/core/optimistic.js +78 -58
  17. package/dist/prod/core/owner.js +101 -100
  18. package/dist/prod/core/scheduler.js +249 -192
  19. package/dist/prod/core/verdict.js +185 -78
  20. package/dist/prod/index.js +7 -7
  21. package/dist/prod/map.js +108 -106
  22. package/dist/prod/signals.js +20 -1
  23. package/dist/prod/store/index.js +36 -0
  24. package/dist/prod/store/next/optimistic.js +385 -0
  25. package/dist/prod/store/next/projection.js +172 -0
  26. package/dist/prod/store/next/reconcile.js +331 -0
  27. package/dist/prod/store/next/store.js +1499 -0
  28. package/dist/prod/store/next/target.js +20 -0
  29. package/dist/prod/store/store.js +126 -882
  30. package/dist/prod/store/utils.js +59 -186
  31. package/dist/types/core/attribution-hooks.d.ts +52 -0
  32. package/dist/types/core/attribution.d.ts +186 -0
  33. package/dist/types/core/constants.d.ts +26 -0
  34. package/dist/types/core/core.d.ts +16 -1
  35. package/dist/types/core/dev.d.ts +20 -3
  36. package/dist/types/core/graph.d.ts +1 -0
  37. package/dist/types/core/lanes.d.ts +4 -16
  38. package/dist/types/core/scheduler.d.ts +12 -2
  39. package/dist/types/core/types.d.ts +85 -41
  40. package/dist/types/signals.d.ts +19 -0
  41. package/dist/types/store/index.d.ts +15 -5
  42. package/dist/types/store/next/optimistic.d.ts +20 -0
  43. package/dist/types/store/next/projection.d.ts +8 -0
  44. package/dist/types/store/next/reconcile.d.ts +3 -0
  45. package/dist/types/store/next/store.d.ts +77 -0
  46. package/dist/types/store/next/target.d.ts +117 -0
  47. package/dist/types/store/store.d.ts +26 -101
  48. package/dist/types/store/utils.d.ts +0 -40
  49. package/dist/types-cjs/core/attribution-hooks.d.cts +52 -0
  50. package/dist/types-cjs/core/attribution.d.cts +186 -0
  51. package/dist/types-cjs/core/constants.d.cts +26 -0
  52. package/dist/types-cjs/core/core.d.cts +16 -1
  53. package/dist/types-cjs/core/dev.d.cts +20 -3
  54. package/dist/types-cjs/core/graph.d.cts +1 -0
  55. package/dist/types-cjs/core/lanes.d.cts +4 -16
  56. package/dist/types-cjs/core/scheduler.d.cts +12 -2
  57. package/dist/types-cjs/core/types.d.cts +85 -41
  58. package/dist/types-cjs/signals.d.cts +19 -0
  59. package/dist/types-cjs/store/index.d.cts +15 -5
  60. package/dist/types-cjs/store/next/optimistic.d.cts +20 -0
  61. package/dist/types-cjs/store/next/projection.d.cts +8 -0
  62. package/dist/types-cjs/store/next/reconcile.d.cts +3 -0
  63. package/dist/types-cjs/store/next/store.d.cts +77 -0
  64. package/dist/types-cjs/store/next/target.d.cts +117 -0
  65. package/dist/types-cjs/store/store.d.cts +26 -101
  66. package/dist/types-cjs/store/utils.d.cts +0 -40
  67. package/package.json +2 -1
  68. package/dist/prod/store/optimistic.js +0 -217
  69. package/dist/prod/store/projection.js +0 -231
  70. package/dist/prod/store/reconcile.js +0 -707
@@ -1,14 +1,14 @@
1
1
  import { handleAsync, clearStatus, parkLoadingWindow, notifyStatus, settleErroredDependents } from "./async.js";
2
2
 
3
- import { EFFECT_TRACKED, REACTIVE_OPTIMISTIC_DIRTY, NOT_PENDING, STATUS_UNINITIALIZED, STATUS_ERROR, REACTIVE_REASK, REACTIVE_RECOMPUTING_DEPS, CONFIG_SYNC, STATUS_PENDING, REACTIVE_NONE, REACTIVE_SNAPSHOT_STALE, unwrapOverride, OVERRIDE_UNDEFINED, REACTIVE_LAZY, REACTIVE_DISPOSED, CONFIG_AUTO_DISPOSE, REACTIVE_CHECK, REACTIVE_DIRTY, REACTIVE_IN_HEAP, REACTIVE_IN_HEAP_HEIGHT, defaultContext, CONFIG_IN_SNAPSHOT_SCOPE, CONFIG_TRANSPARENT, CONFIG_OWNED_WRITE, CONFIG_NO_SNAPSHOT, $REFRESH, REACTIVE_MANUAL_WRITE, NO_SNAPSHOT, STORE_SNAPSHOT_PROPS, EFFECT_USER } from "./constants.js";
3
+ import { EFFECT_TRACKED, REACTIVE_OPTIMISTIC_DIRTY, CONFIG_OPTIMISTIC, NOT_PENDING, STATUS_UNINITIALIZED, STATUS_ERROR, REACTIVE_REASK, REACTIVE_RECOMPUTING_DEPS, CONFIG_SYNC, CONFIG_HAS_LANE, STATUS_PENDING, REACTIVE_MISSED_WAKE, REACTIVE_NONE, REACTIVE_SNAPSHOT_STALE, unwrapOverride, OVERRIDE_UNDEFINED, CONFIG_HAS_COMPANIONS, REACTIVE_LAZY, REACTIVE_DISPOSED, CONFIG_AUTO_DISPOSE, CONFIG_CHILDREN_FORBIDDEN, REACTIVE_CHECK, REACTIVE_DIRTY, REACTIVE_IN_HEAP, REACTIVE_IN_HEAP_HEIGHT, defaultContext, CONFIG_IN_SNAPSHOT_SCOPE, CONFIG_TRANSPARENT, CONFIG_OWNED_WRITE, CONFIG_NO_SNAPSHOT, $REFRESH, REACTIVE_MANUAL_WRITE, CONFIG_FW_CHILDREN, NO_SNAPSHOT, CONFIG_HAS_SNAPSHOT, STORE_SNAPSHOT_PROPS, EFFECT_USER } from "./constants.js";
4
4
 
5
5
  import { NotReadyError } from "./error.js";
6
6
 
7
7
  import { trimStaleDeps, link, unobserved } from "./graph.js";
8
8
 
9
- import { deleteFromHeap, queueFor, insertIntoHeapHeight, markNode, markHeap, insertIntoHeap } from "./heap.js";
9
+ import { deleteFromHeap, queueFor, insertIntoHeapHeight, enqueueSub, markNode, markHeap, insertIntoHeap } from "./heap.js";
10
10
 
11
- import { GlobalQueue, activeTransition, globalQueue, clock, insertSubs, queuePendingNode, runInTransition, schedule, dirtyQueue, projectionWriteActive, armReaskClear } from "./scheduler.js";
11
+ import { GlobalQueue, activeTransition, globalQueue, clock, insertSubs, queuePendingNode, runInTransition, schedule, notifyEpoch, dirtyQueue, bumpNotifyEpoch, projectionWriteActive, reaskArmed, armReaskClear } from "./scheduler.js";
12
12
 
13
13
  import "./invariants.js";
14
14
 
@@ -16,7 +16,7 @@ import { disposeChildren, markDisposal, inheritId } from "./owner.js";
16
16
 
17
17
  GlobalQueue.Ce = recompute;
18
18
 
19
- GlobalQueue.me = disposeChildren;
19
+ GlobalQueue.Fe = disposeChildren;
20
20
 
21
21
  let tracking = false;
22
22
 
@@ -48,8 +48,8 @@ let snapshotSources = null;
48
48
 
49
49
  function ownerInSnapshotScope(e) {
50
50
  while (e) {
51
- if (e.ve) return true;
52
- e = e.ke;
51
+ if (e.He) return true;
52
+ e = e.ve;
53
53
  }
54
54
  return false;
55
55
  }
@@ -60,41 +60,41 @@ function setSnapshotCapture(e) {
60
60
  }
61
61
 
62
62
  function markSnapshotScope(e) {
63
- e.ve = true;
63
+ e.He = true;
64
64
  }
65
65
 
66
66
  function releaseSnapshotScope(e) {
67
- e.ve = false;
67
+ e.He = false;
68
68
  releaseSubtree(e);
69
69
  schedule();
70
70
  }
71
71
 
72
72
  function releaseSubtree(e) {
73
- let t = e.Fe;
73
+ let t = e.ke;
74
74
  while (t) {
75
- if (t.ve) {
76
- t = t.He;
75
+ if (t.He) {
76
+ t = t.Ve;
77
77
  continue;
78
78
  }
79
- if (t.ce) {
79
+ if (t.Se) {
80
80
  const e = t;
81
81
  e.T &= ~CONFIG_IN_SNAPSHOT_SCOPE;
82
- if (e.se & REACTIVE_SNAPSHOT_STALE) {
83
- e.se &= ~REACTIVE_SNAPSHOT_STALE;
84
- e.se |= REACTIVE_DIRTY;
85
- if (dirtyQueue.Ve > e.xe) dirtyQueue.Ve = e.xe;
82
+ if (e.ie & REACTIVE_SNAPSHOT_STALE) {
83
+ e.ie &= ~REACTIVE_SNAPSHOT_STALE;
84
+ e.ie |= REACTIVE_DIRTY;
85
+ if (dirtyQueue.xe > e.Le) dirtyQueue.xe = e.Le;
86
86
  insertIntoHeap(e, dirtyQueue);
87
87
  }
88
88
  }
89
89
  releaseSubtree(t);
90
- t = t.He;
90
+ t = t.Ve;
91
91
  }
92
92
  }
93
93
 
94
94
  function clearSnapshots() {
95
95
  if (snapshotSources) {
96
96
  for (const e of snapshotSources) {
97
- delete e.Le;
97
+ delete e.o?.Qe;
98
98
  // StoreNode targets share one pre-initialized hidden class (see
99
99
  // createStoreProxy) — assign undefined instead of deleting, and only
100
100
  // when present so signal-node sources don't grow the field.
@@ -106,46 +106,50 @@ function clearSnapshots() {
106
106
  }
107
107
 
108
108
  function recompute(e, t = false) {
109
+ // §12d: any recompute can clean a marked subscriber — invalidate skips.
110
+ bumpNotifyEpoch();
109
111
  const n = e.Re;
110
112
  if (!t) {
111
- if (e.Ne && (!n || activeTransition) && activeTransition !== e.Ne) globalQueue.initTransition(e.Ne);
113
+ if (e._e && (!n || activeTransition) && activeTransition !== e._e) globalQueue.initTransition(e._e);
112
114
  deleteFromHeap(e, queueFor(e));
113
- e.Te = null;
115
+ if (e.o !== null) e.o.Ee = null;
114
116
  // Tracked effects run after finalizePureQueue, so dispose immediately instead of deferring
115
- if (e.Ne || n === EFFECT_TRACKED) disposeChildren(e); else if (e.Fe !== null || e.ye !== null) {
117
+ if (e._e || n === EFFECT_TRACKED) disposeChildren(e); else if (e.ke !== null || e.he !== null) {
116
118
  markDisposal(e);
117
- e.Qe = e.ye;
118
- e.We = e.Fe;
119
- e.ye = null;
120
- e.Fe = null;
121
- e.qe = 0;
119
+ const t = ext(e);
120
+ t.We = e.he;
121
+ t.qe = e.ke;
122
+ e.he = null;
123
+ e.ke = null;
124
+ e.Me = 0;
122
125
  } else ;
123
126
  }
124
- let i = !!(e.se & REACTIVE_OPTIMISTIC_DIRTY);
125
- const l = e._e !== undefined && e._e !== NOT_PENDING;
126
- const u = !!(e.S & STATUS_UNINITIALIZED);
127
+ let i = !!(e.ie & REACTIVE_OPTIMISTIC_DIRTY);
128
+ const u = (e.T & CONFIG_OPTIMISTIC) !== 0 && e.o?.De !== NOT_PENDING && e.o?.De !== undefined;
129
+ const l = !!(e.S & STATUS_UNINITIALIZED);
127
130
  // Outgoing error, captured before the compute clears status: if this run
128
131
  // recovers to an unchanged value, dependents still holding this object must
129
132
  // be swept (settleErroredDependents, #2949).
130
- const o = e.S & STATUS_ERROR ? e._ : undefined;
133
+ const o = e.S & STATUS_ERROR ? e.o?._ : undefined;
131
134
  // Re-ask classification lives in the verdict module; capture the flag before
132
135
  // the recompute wipes _flags below.
133
- const s = (e.se & REACTIVE_REASK) !== 0;
136
+ const s = (e.ie & REACTIVE_REASK) !== 0;
134
137
  // Captured before the compute clears it on a sync landing: if that landing
135
138
  // is transition-held below, the window must stay open until the hold
136
139
  // commits (commitPendingNode) — a closed window plus a held value reads as
137
140
  // a pending frame to live observers of the verdict (#2990).
138
- const a = e.Ee;
141
+ const a = e.Ie;
139
142
  const r = context;
140
143
  context = e;
141
144
  e.Ye = null;
142
145
  e.Ze++;
143
- e.se = REACTIVE_RECOMPUTING_DEPS;
144
- e.de = clock;
145
- let c = e.De === NOT_PENDING ? e.Ue : e.De;
146
- let _ = e.xe;
147
- let f = tracking;
148
- let E = currentOptimisticLane;
146
+ e.ie = REACTIVE_RECOMPUTING_DEPS;
147
+ e.Te = clock;
148
+ let c = e.Pe === NOT_PENDING ? e.be : e.Pe;
149
+ let _ = e.Le;
150
+ let f = false;
151
+ let E = tracking;
152
+ let I = currentOptimisticLane;
149
153
  tracking = true;
150
154
  // A computed's fn establishes its OWN dependencies, so it must never run
151
155
  // inside a latest() read window: read() short-circuits through the
@@ -153,7 +157,7 @@ function recompute(e, t = false) {
153
157
  // computed) inside latest(fn) came out permanently dependency-less (#2926).
154
158
  // latestRead() already suspends the flag for its pull-recomputes; this
155
159
  // covers creation-time computes and flushes that run inside the window.
156
- const T = latestReadActive;
160
+ const N = latestReadActive;
157
161
  latestReadActive = false;
158
162
  // Lane posture lives with the engine: OPTIMISTIC_DIRTY is only ever set by
159
163
  // engine-driven paths, and _optimisticNodes is only pushed by
@@ -161,7 +165,11 @@ function recompute(e, t = false) {
161
165
  if (i) {
162
166
  const t = GlobalQueue.je(e, true);
163
167
  if (t) currentOptimisticLane = t;
164
- } else if (activeTransition && !t && activeTransition.Me.length) {
168
+ // `false` = wake-only lane demotion: recompute plain so a mid-tick
169
+ // latest()/isPending() pull stages instead of direct-committing (#3009).
170
+ // The predicate lives with the engine (recomputeLane).
171
+ else if (t === false) i = false;
172
+ } else if (activeTransition && !t && activeTransition.Ke.length) {
165
173
  // Lane adoption: parent-deeper-than-owned-child can run before its OPT-dirty
166
174
  // child propagates. Walk deps once and inherit the OPT lane so this node
167
175
  // recomputes under the right posture and propagates correctly.
@@ -171,43 +179,45 @@ function recompute(e, t = false) {
171
179
  currentOptimisticLane = t;
172
180
  }
173
181
  }
174
- const N = n && n !== EFFECT_USER;
182
+ const T = n && n !== EFFECT_USER;
175
183
  const d = stale;
176
- if (N) stale = true;
184
+ if (T) stale = true;
177
185
  try {
178
186
  if (!false && e.T & CONFIG_SYNC) {
179
- c = e.ce(c);
180
- e.Te = null;
181
- e.Ee = false;
187
+ c = e.Se(c);
188
+ if (e.o !== null) e.o.Ee = null;
189
+ e.Ie = false;
182
190
  } else {
183
191
  // Snapshot `_inFlight` so we can detect whether `_fn` self-registered an async
184
192
  // subscription (e.g. `createProjection` calls `handleAsync` from inside its body
185
193
  // with a setter callback). In that case, the outer `handleAsync` call below would
186
194
  // clobber the fresh subscription, so we skip it and let the internally-registered
187
195
  // iteration drive updates.
188
- const t = e.Te;
189
- const n = e.ce(c);
196
+ const t = e.o?.Ee;
197
+ const n = e.Se(c);
190
198
  const i = typeof n === "object" && n !== null;
191
- const l = e.Te !== t;
192
- c = l || !i ? n : handleAsync(e, n);
193
- if (!l && !i) {
194
- e.Te = null;
199
+ const u = e.o?.Ee !== t;
200
+ c = u || !i ? n : handleAsync(e, n);
201
+ if (!u && !i) {
202
+ if (e.o !== null) e.o.Ee = null;
195
203
  // A sync (non-object) return is the first real answer; async-shaped
196
204
  // results clear inside handleAsync at their own landing points, and a
197
205
  // self-registered flight (inFlightChanged — projections) clears when
198
206
  // its internal handleAsync lands.
199
- e.Ee = false;
207
+ e.Ie = false;
200
208
  }
201
209
  }
202
- // On a status-free node clearStatus is a guaranteed no-op: every branch
203
- // in its body is gated on one of these fields, and with _statusFlags === 0
204
- // the flags write (create or not) stores the 0 already there.
205
- if (e.S !== 0 || e.i !== undefined || e._ || e.he || e.ue || e.oe !== undefined || e.ge !== undefined || e.pe !== undefined || e.u !== null) clearStatus(e, t);
206
- // _optimisticLane is only ever assigned by engine paths.
207
- if (e.Ke) GlobalQueue.ze(e);
210
+ // On a status-free node clearStatus is a guaranteed no-op: every field
211
+ // its body gates on is either _statusFlags or lives in the cold
212
+ // extension no extension, no status to clear. (_x from an unrelated
213
+ // installer just makes clearStatus a cheap re-verified no-op.)
214
+ if (e.S !== 0 || e.o !== null) clearStatus(e, t);
215
+ // _optimisticLane is only ever assigned by engine paths (CONFIG_HAS_LANE
216
+ // is their sticky presence mark).
217
+ if (e.T & CONFIG_HAS_LANE && e.o?.Be) GlobalQueue.ze(e);
208
218
  } catch (t) {
209
219
  const n = t instanceof NotReadyError;
210
- if (n && e.Ee) {
220
+ if (n && e.Ie) {
211
221
  // Loading window with an unready sync dependency: register for the
212
222
  // source's settle (the settlePendingSource walk runs off
213
223
  // _pendingSources + _blocked alone) but take NO read-visible pending
@@ -222,25 +232,31 @@ function recompute(e, t = false) {
222
232
  if (n && currentOptimisticLane) GlobalQueue.$e(e);
223
233
  let i = false;
224
234
  if (n) {
225
- e.ue = true;
226
- if (GlobalQueue.Be !== null) i = GlobalQueue.Be(e, s);
235
+ ext(e).ue = true;
236
+ if (GlobalQueue.Je !== null) i = GlobalQueue.Je(e, s);
227
237
  }
228
- notifyStatus(e, n ? STATUS_PENDING : STATUS_ERROR, t, undefined, n ? e.Ke : undefined);
229
- if (i) GlobalQueue.k(e);
238
+ notifyStatus(e, n ? STATUS_PENDING : STATUS_ERROR, t, undefined, n ? e.o?.Be : undefined);
239
+ if (i) GlobalQueue.p(e);
230
240
  }
231
241
  } finally {
232
- tracking = f;
233
- latestReadActive = T;
234
- if (N) stale = d;
235
- e.se = REACTIVE_NONE | (t ? e.se & REACTIVE_SNAPSHOT_STALE : 0);
242
+ tracking = E;
243
+ latestReadActive = N;
244
+ if (T) stale = d;
245
+ // Consume the missed-wake latch (#3037, set by insertSubs): a dep write
246
+ // landed beneath this pass on a link it had already validated. The wipe
247
+ // below must not key off DIRTY/CHECK — the read-time pull protocol
248
+ // (markNode(c) in read()) marks the running node as part of ordinary
249
+ // bookkeeping, and those marks are correctly discarded here.
250
+ f = (e.ie & REACTIVE_MISSED_WAKE) !== 0;
251
+ e.ie = REACTIVE_NONE | (t ? e.ie & REACTIVE_SNAPSHOT_STALE : 0);
236
252
  context = r;
237
253
  }
238
- if (!e._) {
254
+ if (!e.o?._) {
239
255
  trimStaleDeps(e);
240
- const s = l ? unwrapOverride(e._e) : e.De === NOT_PENDING ? e.Ue : e.De;
256
+ const s = u ? unwrapOverride(e.o?.De) : e.Pe === NOT_PENDING ? e.be : e.Pe;
241
257
  let r = false;
242
258
  try {
243
- r = !n && u || !e.be || !e.be(s, c);
259
+ r = !n && l || !e.Ue || !e.Ue(s, c);
244
260
  } catch (t) {
245
261
  // A throwing user comparator is an error of this node's computation.
246
262
  // Route it through the same status path as a compute-phase throw so
@@ -254,91 +270,105 @@ function recompute(e, t = false) {
254
270
  // gate: the explicit recompute(node, true) inside effect() does not enqueue, so
255
271
  // effect() can call its runner synchronously for the first run.
256
272
  if (n && r) {
257
- e.Je = !e._;
273
+ e.Xe = !e.o?._;
258
274
  // Reuse one bound runner per effect — runEffect no-ops on a stale
259
275
  // `_modified`, so re-enqueueing the same function is harmless.
260
- if (!t) e.C.enqueue(n, e.Xe ??= GlobalQueue.et.bind(null, e));
276
+ if (!t) e.C.enqueue(n, e.et ??= GlobalQueue.tt.bind(null, e));
261
277
  }
262
- if (e._) ; else if (r) {
263
- const u = l ? e._e : undefined;
278
+ if (e.o?._) ; else if (r) {
279
+ const l = u ? e.o?.De : undefined;
264
280
  if (t ||
265
281
  // Plain sync flush (no transition on either side) commits effect
266
282
  // values directly — the pending round-trip (queuePendingNode +
267
283
  // commitPendingNodes) exists to sequence transition reveals, and
268
284
  // paying it per effect on the plain path is pure overhead.
269
- n && (activeTransition !== e.Ne || activeTransition === null) || i) {
270
- e.Ue = c;
285
+ n && (activeTransition !== e._e || activeTransition === null) || i) {
286
+ e.be = c;
271
287
  // Lane-propagated correction: upstream data is fresh, correct the
272
288
  // override unconditionally. The direct _value commit is the lane's
273
289
  // own reveal schedule; drop any superseded older hold so its queued
274
290
  // commit can't clobber the fresh value.
275
- if (l && i) {
276
- e._e = c === undefined ? OVERRIDE_UNDEFINED : c;
277
- e.De = NOT_PENDING;
291
+ if (u && i) {
292
+ ext(e).De = c === undefined ? OVERRIDE_UNDEFINED : c;
293
+ e.Pe = NOT_PENDING;
278
294
  }
279
295
  } else {
280
- e.De = c;
296
+ e.Pe = c;
281
297
  // A window landing that gets held re-opens the window until the hold
282
298
  // commits — the verdict's held-value branch is window-gated (#2990).
283
- if (a) e.Ee = true;
299
+ if (a) e.Ie = true;
284
300
  // Transition-held sync recompute is a write path like setSignal/asyncWrite,
285
301
  // so sync derivations of held sources stay visible to isPending()/latest()
286
302
  // (#2831). Both companion writes are transition-scoped (optimistic) and
287
303
  // auto-revert/re-derive at commit. Skipped for plain flushes where the
288
304
  // pending value commits before effects run.
289
- if ((activeTransition || e.Ne) && GlobalQueue.Pe !== null) GlobalQueue.Pe(e, c);
305
+ if ((activeTransition || e._e) && GlobalQueue.Oe !== null) GlobalQueue.Oe(e, c);
290
306
  }
291
307
  // insertSubs only walks _subs (no scheduling of its own), so a
292
308
  // subscriber-less node has nothing to notify.
293
- if (e.o !== null && (!l || i || e._e !== u)) insertSubs(e, i || l);
294
- } else if (l) {
309
+ if (e.u !== null && (!u || i || e.o?.De !== l)) insertSubs(e, i || u);
310
+ } else if (u) {
295
311
  // Unchanged value (equals the override) recomputed while the override
296
312
  // is active: _value may still be stale, so hold the authoritative value
297
313
  // for commit on its own transition's schedule — invisibly (A17/A18).
298
- if (e.De === NOT_PENDING) queuePendingNode(e);
299
- e.De = c;
300
- if (a) e.Ee = true;
314
+ if (e.Pe === NOT_PENDING) queuePendingNode(e);
315
+ e.Pe = c;
316
+ if (a) e.Ie = true;
301
317
  // see the held branch above (#2990)
302
- } else if (e.xe != _) {
303
- for (let t = e.o; t !== null; t = t.le) {
304
- insertIntoHeapHeight(t.fe, queueFor(t.fe));
318
+ } else if (e.Le != _) {
319
+ for (let t = e.u; t !== null; t = t.fe) {
320
+ insertIntoHeapHeight(t.ae, queueFor(t.ae));
305
321
  }
306
322
  }
307
323
  // Silent recovery: errored → unchanged value fires no notification, but
308
324
  // dependents still holding the propagated error consumed their dirty flag
309
325
  // in an errored run and may sit on stale commits (#2949). Changed-value
310
326
  // recoveries ride insertSubs above; a comparator throw re-errored the node
311
- // (el._error re-set), so this only runs on a genuinely clean recovery.
312
- if (o !== undefined && !r && !e._) settleErroredDependents(e, o);
327
+ // (el._x?._error re-set), so this only runs on a genuinely clean recovery.
328
+ if (o !== undefined && !r && !e.o?._) settleErroredDependents(e, o);
313
329
  }
314
- currentOptimisticLane = E;
315
- const I = e.De !== NOT_PENDING || e.We !== null || e.Qe !== null || (e.S & (STATUS_PENDING | STATUS_UNINITIALIZED)) !== 0;
330
+ currentOptimisticLane = I;
331
+ const S = e.Pe !== NOT_PENDING || e.o !== null && (e.o.qe !== null || e.o.We !== null) || (e.S & (STATUS_PENDING | STATUS_UNINITIALIZED)) !== 0;
316
332
  // Override-covered holds (hasOverride) always queue: their commit belongs
317
333
  // to their own transition's schedule (A18 re-rule) and is unobservable
318
334
  // under the override (A17). Revert no longer commits anything, so an
319
335
  // unqueued covered hold would leak (INV-7) once the revert clears
320
336
  // _transition.
321
- I && (!t || e.S & STATUS_PENDING) && (!e.Ne || l) && queuePendingNode(e);
322
- e.Ne && n && activeTransition !== e.Ne && runInTransition(e.Ne, () => recompute(e));
337
+ S && (!t || e.S & STATUS_PENDING) && (!e._e || u) && queuePendingNode(e);
338
+ e._e && n && activeTransition !== e._e && runInTransition(e._e, () => recompute(e));
339
+ // Missed-wake reschedule (see the finally above): values this pass read
340
+ // before the nested commit are stale, so run again now that the heap will
341
+ // accept the node. Equality gates stop same-value landings from cascading,
342
+ // and a re-run only latches again if another nested commit changes a dep
343
+ // beneath it — convergent unless deps genuinely keep changing.
344
+ if (f) {
345
+ enqueueSub(e);
346
+ schedule();
347
+ }
323
348
  }
324
349
 
325
350
  function updateIfNecessary(e) {
326
- if (e.se & REACTIVE_CHECK) {
327
- for (let t = e.tt; t; t = t.nt) {
328
- const n = t.it;
351
+ // Never re-enter a node that is currently computing: its dep bookkeeping
352
+ // (_depsTail/_depGen) is live, and a nested recompute would corrupt it.
353
+ // A mid-pass mark stays latched for recompute's own tail to reschedule
354
+ // (#3037); readers meanwhile serve the values the pass has so far.
355
+ if (e.ie & REACTIVE_RECOMPUTING_DEPS) return;
356
+ if (e.ie & REACTIVE_CHECK) {
357
+ for (let t = e.nt; t; t = t.it) {
358
+ const n = t.ut;
329
359
  const i = n.lt || n;
330
- if (i.ce) {
360
+ if (i.Se) {
331
361
  updateIfNecessary(i);
332
362
  }
333
- if (e.se & REACTIVE_DIRTY) {
363
+ if (e.ie & REACTIVE_DIRTY) {
334
364
  break;
335
365
  }
336
366
  }
337
367
  }
338
- if (e.se & (REACTIVE_DIRTY | REACTIVE_OPTIMISTIC_DIRTY) || e._ && e.de < clock && !e.Te) {
368
+ if (e.ie & (REACTIVE_DIRTY | REACTIVE_OPTIMISTIC_DIRTY) || e.o?._ && e.Te < clock && !e.o?.Ee) {
339
369
  recompute(e);
340
370
  }
341
- e.se = e.se & (REACTIVE_SNAPSHOT_STALE | REACTIVE_IN_HEAP | REACTIVE_IN_HEAP_HEIGHT);
371
+ e.ie = e.ie & (REACTIVE_SNAPSHOT_STALE | REACTIVE_IN_HEAP | REACTIVE_IN_HEAP_HEIGHT);
342
372
  }
343
373
 
344
374
  function computed(e, t) {
@@ -347,44 +377,72 @@ function computed(e, t) {
347
377
  // `T | undefined` node is a real commit #0. The typeof guard tolerates
348
378
  // non-object option values that older call shapes force through `as any`.
349
379
  const i = t !== null && typeof t === "object" && "loadingValue" in t;
350
- const l = {
380
+ const u = {
351
381
  id: inheritId(t, n, context),
352
- 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),
353
- be: t?.equals != null ? t.equals : isEqual,
354
- ut: t?.unobserved,
355
- ye: null,
382
+ T: (n ? CONFIG_TRANSPARENT : 0) | (t?.ownedWrite ? CONFIG_OWNED_WRITE : 0) | (!context || t?.lazy ? CONFIG_AUTO_DISPOSE : 0) | (t?.sync ? CONFIG_SYNC : 0) | (t?.H ? CONFIG_NO_SNAPSHOT : 0) | (snapshotCaptureActive && ownerInSnapshotScope(context) ? CONFIG_IN_SNAPSHOT_SCOPE : 0),
383
+ Ue: t?.equals != null ? t.equals : isEqual,
384
+ he: null,
356
385
  C: context?.C ?? globalQueue,
357
386
  we: context?.we ?? defaultContext,
358
- qe: 0,
359
- ce: e,
360
- Ue: i ? t.loadingValue : undefined,
361
- xe: 0,
362
- u: null,
387
+ Me: 0,
388
+ Se: e,
389
+ be: i ? t.loadingValue : undefined,
390
+ Le: 0,
363
391
  ot: undefined,
364
392
  st: null,
365
- tt: null,
393
+ nt: null,
366
394
  Ye: null,
367
395
  Ze: 0,
368
- o: null,
396
+ u: null,
369
397
  rt: null,
370
- ke: context,
371
- He: null,
398
+ ve: context,
399
+ Ve: null,
372
400
  ct: null,
373
- Fe: null,
374
- se: t?.lazy ? REACTIVE_LAZY : REACTIVE_NONE,
401
+ ke: null,
402
+ ie: t?.lazy ? REACTIVE_LAZY : REACTIVE_NONE,
375
403
  // A loadingValue node is born committed: commit #0 is already in _value.
376
404
  S: i ? 0 : STATUS_UNINITIALIZED,
377
- de: clock,
378
- De: NOT_PENDING,
379
- Qe: null,
405
+ Te: clock,
406
+ Pe: NOT_PENDING,
407
+ _e: null,
408
+ _t: -1,
409
+ Ie: i,
410
+ // Cold machinery (async/transition/optimistic/verdict slots) lives one
411
+ // hop away in the lazily-allocated extension — the core literal MUST
412
+ // stay under V8's in-object boundary (§12: past ~39 fields every
413
+ // allocation spills to a backing store and creation cost ~4x's).
414
+ o: null
415
+ };
416
+ if (t?.unobserved) ext(u).ft = t.unobserved;
417
+ setupComputedNode(u, t);
418
+ return u;
419
+ }
420
+
421
+ /** Lazily allocate a node's cold extension (ONE shape for signals and
422
+ * computeds — `_x` access stays monomorphic). Installers write through
423
+ * this; hot paths read `el._x?._field` gated by the _config presence bits.
424
+ * Never call ext() just to store a field's default. */ function ext(e) {
425
+ return e.o ??= {
426
+ De: undefined,
427
+ Et: undefined,
428
+ Be: undefined,
429
+ Ge: undefined,
430
+ ge: undefined,
431
+ It: undefined,
432
+ t: 0,
433
+ Ee: null,
434
+ _: undefined,
435
+ ue: undefined,
436
+ le: undefined,
437
+ A: undefined,
438
+ pe: false,
439
+ l: null,
440
+ ft: undefined,
441
+ Qe: undefined,
380
442
  We: null,
381
- Te: null,
382
- Ne: null,
383
- he: false,
384
- Ee: i
443
+ qe: null,
444
+ Nt: undefined
385
445
  };
386
- setupComputedNode(l, t);
387
- return l;
388
446
  }
389
447
 
390
448
  /**
@@ -392,50 +450,48 @@ function computed(e, t) {
392
450
  * so V8 sees the full hidden class shape at construction time. Effects always run in lazy
393
451
  * mode (recompute is called explicitly by `effect()`), so we hardcode the lazy bits and skip
394
452
  * the auto-dispose CONFIG bit (effect() previously cleared it post-construction).
395
- */ function createEffectNode(e, t, n, i, l, u) {
396
- const o = u?.transparent ?? false;
453
+ */ function createEffectNode(e, t, n, i, u, l) {
454
+ const o = l?.transparent ?? false;
397
455
  const s = {
398
- id: inheritId(u, o, context),
399
- T: (o ? CONFIG_TRANSPARENT : 0) | (u?.ownedWrite ? CONFIG_OWNED_WRITE : 0) | (u?.sync ? CONFIG_SYNC : 0) | (snapshotCaptureActive && ownerInSnapshotScope(context) ? CONFIG_IN_SNAPSHOT_SCOPE : 0),
400
- be: false,
401
- ut: u?.unobserved,
402
- ye: null,
456
+ id: inheritId(l, o, context),
457
+ T: (o ? CONFIG_TRANSPARENT : 0) | (l?.ownedWrite ? CONFIG_OWNED_WRITE : 0) | (l?.sync ? CONFIG_SYNC : 0) | (snapshotCaptureActive && ownerInSnapshotScope(context) ? CONFIG_IN_SNAPSHOT_SCOPE : 0),
458
+ Ue: false,
459
+ he: null,
403
460
  C: context?.C ?? globalQueue,
404
461
  we: context?.we ?? defaultContext,
405
- qe: 0,
406
- ce: e,
407
- Ue: undefined,
408
- xe: 0,
409
- u: null,
462
+ Me: 0,
463
+ Se: e,
464
+ be: undefined,
465
+ Le: 0,
410
466
  ot: undefined,
411
467
  st: null,
412
- tt: null,
468
+ nt: null,
413
469
  Ye: null,
414
470
  Ze: 0,
415
- o: null,
471
+ u: null,
416
472
  rt: null,
417
- ke: context,
418
- He: null,
473
+ ve: context,
474
+ Ve: null,
419
475
  ct: null,
420
- Fe: null,
421
- se: REACTIVE_LAZY,
476
+ ke: null,
477
+ ie: REACTIVE_LAZY,
422
478
  S: STATUS_UNINITIALIZED,
423
- de: clock,
424
- De: NOT_PENDING,
425
- Qe: null,
426
- We: null,
427
- Te: null,
428
- Ne: null,
429
- he: false,
430
- Ee: false,
431
- Je: false,
432
- _t: undefined,
433
- ft: t,
434
- Et: n,
479
+ Te: clock,
480
+ Pe: NOT_PENDING,
481
+ _e: null,
482
+ _t: -1,
483
+ Ie: false,
484
+ Xe: false,
435
485
  Tt: undefined,
486
+ dt: t,
487
+ St: n,
488
+ At: undefined,
436
489
  Re: i,
437
- i: l
490
+ o: null
438
491
  };
492
+ // Boundary effects carry a status channel; most effects never touch _x.
493
+ if (u !== undefined) ext(s).A = u;
494
+ if (l?.unobserved) ext(s).ft = l.unobserved;
439
495
  setupComputedNode(s, lazyOptions);
440
496
  return s;
441
497
  }
@@ -446,23 +502,24 @@ const lazyOptions = {
446
502
 
447
503
  function setupComputedNode(e, t) {
448
504
  e.st = e;
449
- const n = context?.Nt ? context.dt : context;
505
+ const n = context?.Ct ? context.Ot : context;
450
506
  if (context) {
451
- const t = context.Fe;
507
+ const t = context.ke;
452
508
  if (t === null) {
453
- context.Fe = e;
509
+ context.ke = e;
454
510
  } else {
455
- e.He = t;
511
+ e.Ve = t;
456
512
  t.ct = e;
457
- context.Fe = e;
513
+ context.ke = e;
458
514
  }
459
515
  }
460
- if (n) e.xe = n.xe + 1;
461
- if (GlobalQueue.It !== null) GlobalQueue.It(e);
516
+ if (n) e.Le = n.Le + 1;
517
+ if (GlobalQueue.Rt !== null) GlobalQueue.Rt(e);
462
518
  !t?.lazy && recompute(e, true);
463
519
  if (snapshotCaptureActive && !t?.lazy) {
464
520
  if (!(e.S & STATUS_PENDING) && !(e.T & CONFIG_NO_SNAPSHOT)) {
465
- e.Le = e.Ue === undefined ? NO_SNAPSHOT : e.Ue;
521
+ ext(e).Qe = e.be === undefined ? NO_SNAPSHOT : e.be;
522
+ e.T |= CONFIG_HAS_SNAPSHOT;
466
523
  snapshotSources.add(e);
467
524
  }
468
525
  }
@@ -470,20 +527,32 @@ function setupComputedNode(e, t) {
470
527
 
471
528
  function signal(e, t, n = null) {
472
529
  const i = {
473
- be: t?.equals != null ? t.equals : isEqual,
474
- T: (t?.ownedWrite ? CONFIG_OWNED_WRITE : 0) | (t?.V ? CONFIG_NO_SNAPSHOT : 0),
475
- ut: t?.unobserved,
476
- Ue: e,
477
- o: null,
530
+ Ue: t?.equals != null ? t.equals : isEqual,
531
+ T: (t?.ownedWrite ? CONFIG_OWNED_WRITE : 0) | (t?.H ? CONFIG_NO_SNAPSHOT : 0),
532
+ be: e,
533
+ u: null,
478
534
  rt: null,
479
- de: clock,
535
+ Te: clock,
480
536
  lt: n,
481
- ae: n?.u || null,
482
- De: NOT_PENDING
537
+ ce: n?.o?.l || null,
538
+ Pe: NOT_PENDING,
539
+ // Signal-literal diet (§12e): NO _time/_fn/_statusFlags slots. Stores
540
+ // materialize one signal per touched leaf, so signal bytes are store
541
+ // bytes. _time is write-only on signals (every read site is computed-
542
+ // typed error-retry gating); _fn/_statusFlags read falsy-identically as
543
+ // missing properties on the shared paths (undefined masks to 0).
544
+ _e: null,
545
+ _t: -1,
546
+ o: null
483
547
  };
484
- n && (n.u = i);
548
+ if (t?.unobserved) ext(i).ft = t.unobserved;
549
+ if (n) {
550
+ ext(n).l = i;
551
+ n.T |= CONFIG_FW_CHILDREN;
552
+ }
485
553
  if (snapshotCaptureActive && !(i.T & CONFIG_NO_SNAPSHOT) && !((n?.S ?? 0) & STATUS_PENDING)) {
486
- i.Le = e === undefined ? NO_SNAPSHOT : e;
554
+ ext(i).Qe = e === undefined ? NO_SNAPSHOT : e;
555
+ i.T |= CONFIG_HAS_SNAPSHOT;
487
556
  snapshotSources.add(i);
488
557
  }
489
558
  return i;
@@ -491,13 +560,15 @@ function signal(e, t, n = null) {
491
560
 
492
561
  function optimisticSignal(e, t) {
493
562
  const n = signal(e, t);
494
- n._e = NOT_PENDING;
563
+ ext(n).De = NOT_PENDING;
564
+ n.T |= CONFIG_OPTIMISTIC;
495
565
  return n;
496
566
  }
497
567
 
498
568
  function optimisticComputed(e, t) {
499
569
  const n = computed(e, t);
500
- n._e = NOT_PENDING;
570
+ ext(n).De = NOT_PENDING;
571
+ n.T |= CONFIG_OPTIMISTIC;
501
572
  return n;
502
573
  }
503
574
 
@@ -527,11 +598,11 @@ function isEqual(e, t) {
527
598
  * );
528
599
  * ```
529
600
  */ function untrack(e, t) {
530
- if (GlobalQueue.St === null && !tracking && true) return e();
601
+ if (GlobalQueue.Gt === null && !tracking && true) return e();
531
602
  const n = tracking;
532
603
  tracking = false;
533
604
  try {
534
- if (GlobalQueue.St !== null) return GlobalQueue.St(e);
605
+ if (GlobalQueue.Gt !== null) return GlobalQueue.Gt(e);
535
606
  return e();
536
607
  } finally {
537
608
  tracking = n;
@@ -543,11 +614,17 @@ function isEqual(e, t) {
543
614
  * an isPending() probe (`refresh`) additionally pulls the node fully up to
544
615
  * date so its status flags reflect the current graph.
545
616
  */ function prepareComputed(e, t) {
546
- if (e.se & REACTIVE_LAZY) {
547
- e.se &= ~REACTIVE_LAZY;
548
- recompute(e, true);
549
- } else if (e.se & REACTIVE_DISPOSED) {
617
+ if (e.ie & REACTIVE_LAZY) {
618
+ e.ie &= ~REACTIVE_LAZY;
550
619
  recompute(e, true);
620
+ } else if (e.ie & REACTIVE_DISPOSED) {
621
+ // Two disposal lifecycles share the flag (#3024). Observation-lifecycle
622
+ // nodes (CONFIG_AUTO_DISPOSE) are dormant — torn down by unobserved()
623
+ // when the last subscriber left — and reads reawaken them; that is the
624
+ // pay-for-use contract. Owner-lifecycle nodes are dead: recomputing would
625
+ // re-run user code in a torn-down tree (and discard manual writes on
626
+ // derived-writable signals), so reads return the last committed value.
627
+ if (e.T & CONFIG_AUTO_DISPOSE) recompute(e, true);
551
628
  } else if (t) {
552
629
  updateIfNecessary(e);
553
630
  }
@@ -567,11 +644,15 @@ function isEqual(e, t) {
567
644
  * snapshot / transition / lane / dev-strictRead state all take the full
568
645
  * resolution. Anything slow returns READ_SLOW; the caller then calls read().
569
646
  */ function readNodeFast(e) {
570
- if (latestReadActive || pendingCheckActive || e.ce || e.lt || e._e !== undefined || e.Le !== undefined || activeTransition !== null || currentOptimisticLane !== null || snapshotCaptureActive || false) return READ_SLOW;
647
+ if (latestReadActive || pendingCheckActive || e.Se || e.lt || e.o?.De !== undefined || e.o?.Qe !== undefined || activeTransition !== null || currentOptimisticLane !== null || snapshotCaptureActive || false) return READ_SLOW;
571
648
  let t = context;
572
- if (t?.Nt) t = t.dt;
649
+ if (t?.Ct) t = t.Ot;
573
650
  if (t && tracking) link(e, t);
574
- return !t || e.De === NOT_PENDING ? e.Ue : e.De;
651
+ // Children-forbidden readers (createTrackedEffect / onSettled callbacks) get
652
+ // committed visibility: like the effect half of createEffect and event
653
+ // handlers, effect-phase code never observes its own unsettled write — the
654
+ // write lands in the same flush's continuation (#3006).
655
+ return !t || e.Pe === NOT_PENDING || t.T & CONFIG_CHILDREN_FORBIDDEN ? e.be : e.Pe;
575
656
  }
576
657
 
577
658
  function read(e) {
@@ -579,82 +660,93 @@ function read(e) {
579
660
  // Checked before isPending so that isPending(() => latest(x)) checks
580
661
  // the _pendingSignal of _latestValueComputed (async in flight) rather
581
662
  // than the original node (which stays "pending" while held in a transition).
582
- if (latestReadActive) return GlobalQueue.At(e);
663
+ if (latestReadActive) return GlobalQueue.Pt(e);
583
664
  let t = context;
584
- if (t?.Nt) t = t.dt;
665
+ if (t?.Ct) t = t.Ot;
585
666
  const n = e;
586
667
  const i = e.lt;
587
- const l = i || e;
668
+ const u = i || e;
588
669
  // Handle isPending() mode: collect pending state while preserving normal read semantics.
589
670
  // Probe mode is suspended while preparing the node so nested reads during a
590
671
  // recompute don't collect into the probe.
591
672
  if (pendingCheckActive) {
592
- GlobalQueue.Ct(e, t, l, i);
593
- } else if (typeof n.ce === "function") {
673
+ GlobalQueue.Dt(e, t, u, i);
674
+ } else if (typeof n.Se === "function") {
594
675
  prepareComputed(e, false);
595
676
  }
596
- if (!n.ce && l === e && e._e === undefined && e.Le === undefined && activeTransition === null && currentOptimisticLane === null && !snapshotCaptureActive && true) {
677
+ if (!n.Se && u === e && e.o?.De === undefined && e.o?.Qe === undefined && activeTransition === null && currentOptimisticLane === null && !snapshotCaptureActive && true) {
597
678
  if (t && tracking) link(e, t);
598
- return !t || e.De === NOT_PENDING ? e.Ue : e.De;
679
+ // Committed visibility for children-forbidden readers see readNodeFast.
680
+ return !t || e.Pe === NOT_PENDING || t.T & CONFIG_CHILDREN_FORBIDDEN ? e.be : e.Pe;
599
681
  }
600
682
  if (t && tracking) {
601
683
  link(e, t, pendingCheckActive);
602
- if (l.ce) {
684
+ if (u.Se) {
603
685
  const n = queueFor(e);
604
- if (l.xe >= n.Ve) {
686
+ if (u.Le >= n.xe) {
605
687
  markNode(t);
606
688
  markHeap(n);
607
- updateIfNecessary(l);
689
+ updateIfNecessary(u);
608
690
  }
609
- const i = l.xe;
691
+ const i = u.Le;
610
692
  // parent check is shallow, might need to be recursive
611
- if (i >= t.xe && e.ke !== t) {
612
- t.xe = i + 1;
693
+ if (i >= t.Le && e.ve !== t) {
694
+ t.Le = i + 1;
613
695
  }
614
696
  }
615
697
  }
616
- if (l.S & STATUS_PENDING) {
617
- if (t && !(stale && l.Ne && activeTransition !== l.Ne)) {
698
+ if (u.S & STATUS_PENDING) {
699
+ if (t && !(stale && u._e && activeTransition !== u._e)) {
618
700
  // Per-lane suspension lives with the engine (a non-null lane implies it
619
701
  // is installed): under a lane, only same-lane pending async without an
620
702
  // active override throws.
621
- if (currentOptimisticLane === null || GlobalQueue.Ot(l)) {
703
+ if (currentOptimisticLane === null || GlobalQueue.ht(u)) {
622
704
  if (!tracking && e !== t) link(e, t);
623
- throw l._;
705
+ throw u.o?._;
624
706
  }
625
- } else if (t && l !== e && l.S & STATUS_UNINITIALIZED) {
707
+ } else if (t && u.S & STATUS_UNINITIALIZED) {
708
+ // A stale (render) reader of a node held pending in ANOTHER transition
709
+ // normally keeps showing the node's committed value instead of
710
+ // entangling the two transactions — but an uninitialized node has no
711
+ // committed value to show. Suspend on it (firewall-backed store reads
712
+ // always took this branch; plain memos now do too): the reader
713
+ // registers as a reporter of that source, and its pending-node stamp
714
+ // ties it to the active transaction, so the two transactions merge
715
+ // when the source settles. Falling through served `undefined` as if
716
+ // settled and stranded the reader outside both transactions, so it
717
+ // never re-ran when either landed (#3043 port).
626
718
  if (!tracking && e !== t) link(e, t);
627
- throw l._;
628
- } else if (!t && l.S & STATUS_UNINITIALIZED) {
629
- throw l._;
719
+ throw u.o?._;
720
+ } else if (!t && u.S & STATUS_UNINITIALIZED) {
721
+ throw u.o?._;
630
722
  }
631
723
  }
632
724
  // `owner` is the computed itself, or the firewall behind a store node —
633
725
  // firewall-backed reads follow the same rules (memo parity, #2897 ruling):
634
726
  // an errored derive throws for every late reader instead of silently
635
727
  // serving node values (the seed, or last-good data after a failed refetch).
636
- if (l.ce && l.S & STATUS_ERROR) {
728
+ if (u.Se && u.S & STATUS_ERROR) {
637
729
  // Only a genuine reactive re-read may retry an errored async source:
638
730
  // - tracking: owned/tracked scope only (never events / `untrack` / effect side-effect phase)
639
731
  // - !pendingCheckActive: an `isPending` probe observes the error, never refetches
640
732
  // - owner._time < clock: only on a later cycle than the one the error was found
641
- if (tracking && !pendingCheckActive && l.de < clock) {
642
- recompute(l);
733
+ if (tracking && !pendingCheckActive && u.Te < clock) {
734
+ recompute(u);
643
735
  return read(e);
644
- } else throw l._;
736
+ } else throw u.o?._;
645
737
  }
646
738
  if (snapshotCaptureActive && t && t.T & CONFIG_IN_SNAPSHOT_SCOPE) {
647
- const n = e.Le;
739
+ const n = e.o?.Qe;
648
740
  if (n !== undefined) {
649
741
  const i = n === NO_SNAPSHOT ? undefined : n;
650
- const l = e.De !== NOT_PENDING ? e.De : e.Ue;
651
- if (l !== i) t.se |= REACTIVE_SNAPSHOT_STALE;
742
+ const u = e.Pe !== NOT_PENDING ? e.Pe : e.be;
743
+ if (u !== i) t.ie |= REACTIVE_SNAPSHOT_STALE;
652
744
  return i;
653
745
  }
654
746
  }
655
- if (e._e !== undefined && e._e !== NOT_PENDING) {
747
+ if (e.o?.De !== undefined && e.o?.De !== NOT_PENDING) {
656
748
  // A17: the override IS the value for every reader.
657
- return unwrapOverride(e._e);
749
+ return unwrapOverride(e.o?.De);
658
750
  }
659
751
  // Entanglement gate: a reader recomputing under an optimistic lane that reads
660
752
  // a pending mid-transition write sees the committed value. Projection-store
@@ -663,43 +755,57 @@ function read(e) {
663
755
  // _pendingValue for correct fetching. The sub is recorded for replay at commit
664
756
  // so it re-runs with the new committed view. (Gate details live with the
665
757
  // engine — a non-null lane implies it is installed.)
666
- if (currentOptimisticLane !== null && activeTransition !== null && t !== null && GlobalQueue.Rt(e, l, t)) {
667
- return e.Ue;
758
+ if (currentOptimisticLane !== null && activeTransition !== null && t !== null && GlobalQueue.Ft(e, u, t)) {
759
+ return e.be;
668
760
  }
669
761
  // In optimistic lane context, return _value for optimistic/lane-assigned signals
670
762
  // and for regular signals in stale mode (render effects). Non-stale readers (user
671
763
  // effects) see _pendingValue so that latest() and direct reads stay consistent.
672
- // (The lane-context clause lives with the engine.)
673
- const u = !t || currentOptimisticLane !== null && GlobalQueue.Pt(e, l, t) || e.De === NOT_PENDING || stale && e.Ne && activeTransition !== e.Ne ? e.Ue : e.De;
764
+ // (The lane-context clause lives with the engine.) Children-forbidden readers
765
+ // (createTrackedEffect / onSettled callbacks) get committed visibility see
766
+ // readNodeFast (#3006).
767
+ const l = !t || currentOptimisticLane !== null && GlobalQueue.gt(e, u, t) || e.Pe === NOT_PENDING || t.T & CONFIG_CHILDREN_FORBIDDEN || stale && e._e && activeTransition !== e._e ? e.be : e.Pe;
674
768
  // Record that this isPending() probe observed the fresh pending value, so
675
769
  // the probe doesn't pair "pending" with the new value (#2831).
676
- if (pendingCheckActive) GlobalQueue.ht(e, u);
677
- if (!t && l === e && typeof n.ce === "function" && e.T & CONFIG_AUTO_DISPOSE && !(l.S & STATUS_PENDING) && !e.o) {
770
+ if (pendingCheckActive) GlobalQueue.Ht(e, l);
771
+ if (!t && u === e && typeof n.Se === "function" && e.T & CONFIG_AUTO_DISPOSE && !(u.S & STATUS_PENDING) && !e.u) {
678
772
  unobserved(e);
679
773
  }
680
- return u;
774
+ return l;
681
775
  }
682
776
 
683
777
  function setSignal(e, t) {
684
- if (e.Ne && activeTransition !== e.Ne) globalQueue.initTransition(e.Ne);
778
+ if (e._e && activeTransition !== e._e) globalQueue.initTransition(e._e);
685
779
  // The optimistic write path lives with the engine: only optimisticSignal /
686
780
  // optimisticComputed callers and optimistic store nodes carry an
687
- // _overrideValue slot, and every module that creates one installs the
688
- // engine first.
689
- if (e._e !== undefined && !projectionWriteActive) return GlobalQueue.Gt(e, t);
690
- const n = e.De === NOT_PENDING ? e.Ue : e.De;
781
+ // _overrideValue slot (flagged by CONFIG_OPTIMISTIC a masked read of the
782
+ // always-present config instead of a missing-property probe), and every
783
+ // module that installs one installs the engine first.
784
+ if (e.T & CONFIG_OPTIMISTIC && !projectionWriteActive) return GlobalQueue.vt(e, t);
785
+ const n = e.Pe === NOT_PENDING ? e.be : e.Pe;
691
786
  if (typeof t === "function") t = t(n);
692
787
  // Uninitialized check first: the first commit has no previous value, so the
693
788
  // user comparator must not run against `undefined` (matches recompute).
694
- const i = !!(e.S & STATUS_UNINITIALIZED) || !e.be || !e.be(n, t);
789
+ const i = !!(e.S & STATUS_UNINITIALIZED) || !e.Ue || !e.Ue(n, t);
695
790
  if (!i) return t;
696
- if (e.De === NOT_PENDING) queuePendingNode(e);
697
- e.De = t;
791
+ const u = e.Pe !== NOT_PENDING;
792
+ if (!u) queuePendingNode(e);
793
+ e.Pe = t;
698
794
  // syncCompanions only pokes _pendingSignal/_latestValueComputed — with
699
795
  // neither companion present the call is a guaranteed no-op (companions are
700
- // only ever created, never removed, and creating one installs the hook).
701
- (e.ge !== undefined || e.pe !== undefined) && GlobalQueue.Pe !== null && GlobalQueue.Pe(e, t);
702
- e.de = clock;
796
+ // only ever created, never removed, and creating one installs the hook and
797
+ // sets CONFIG_HAS_COMPANIONS one masked read replaces two optional-field
798
+ // probes on every write).
799
+ e.T & CONFIG_HAS_COMPANIONS && GlobalQueue.Oe !== null && GlobalQueue.Oe(e, t);
800
+ // _time is a computed-only slot (§12e): writing it on a signal would fork
801
+ // the lean shape. Every read site is computed-typed.
802
+ if (e.Se !== undefined) e.Te = clock;
803
+ // Staged-rewrite fast path (§12d): a re-write to a node whose subscribers
804
+ // were already walked — and where nothing has recomputed or linked since
805
+ // (epoch) — re-stages the value and stops. The walk is idempotent (subs
806
+ // marked, heap entries flag-guarded, effects queued once); lane and reask
807
+ // contexts change what a walk MEANS, so they always walk.
808
+ if (u && e._t === notifyEpoch && currentOptimisticLane === null && !reaskArmed) return t;
703
809
  insertSubs(e);
704
810
  schedule();
705
811
  return t;
@@ -713,11 +819,12 @@ function setSignal(e, t) {
713
819
  * cleanup point.
714
820
  */ function suppressComputedRecompute(e) {
715
821
  deleteFromHeap(e, queueFor(e));
716
- if (!(e.se & REACTIVE_MANUAL_WRITE) && e.De === NOT_PENDING) {
822
+ if (!(e.ie & REACTIVE_MANUAL_WRITE) && e.Pe === NOT_PENDING) {
717
823
  queuePendingNode(e);
718
824
  schedule();
719
825
  }
720
- e.se = e.se & -4 | REACTIVE_MANUAL_WRITE;
826
+ e.ie = e.ie & -4 | REACTIVE_MANUAL_WRITE;
827
+ e.kt = clock;
721
828
  }
722
829
 
723
830
  /**
@@ -796,7 +903,19 @@ function staleValues(e, t = true) {
796
903
  if (!t) {
797
904
  return;
798
905
  }
799
- if (typeof t.ce === "function" && !(t.se & (REACTIVE_DISPOSED | REACTIVE_MANUAL_WRITE))) {
906
+ if (typeof t.Se === "function" && !(t.ie & REACTIVE_DISPOSED)) {
907
+ if (t.ie & REACTIVE_MANUAL_WRITE) {
908
+ // A manual write in the CURRENT tick wins over the refresh (#2692).
909
+ // A mask stamped in an earlier tick only survives because a
910
+ // transaction (action) is holding the pending drain open; there the
911
+ // refresh is a later, explicit re-ask and lifts the mask — otherwise
912
+ // any setStore early in an action silently swallows every refresh()
913
+ // for the rest of the transaction (#3026).
914
+ if (t.kt === clock) return;
915
+ t.ie &= ~REACTIVE_MANUAL_WRITE;
916
+ // No REASK below: the batch carries a manual value change, so the
917
+ // recompute is not a quiet re-ask of an unchanged question.
918
+ }
800
919
  // A refresh with no value-change dirt already queued is a re-ask of the
801
920
  // same question: mark it so the recompute classifies any resulting
802
921
  // pending window as quiet (not pending). If the node is already dirty
@@ -804,14 +923,14 @@ function staleValues(e, t = true) {
804
923
  // REACTIVE_IN_HEAP counts as dirt: insertSubs schedules subscribers by
805
924
  // heap insertion alone (no DIRTY/CHECK flag), so a same-batch value
806
925
  // change followed by refresh() must not be laundered into a quiet re-ask.
807
- if (!(t.se & (REACTIVE_DIRTY | REACTIVE_CHECK | REACTIVE_IN_HEAP))) {
808
- t.se |= REACTIVE_REASK;
926
+ else if (!(t.ie & (REACTIVE_DIRTY | REACTIVE_CHECK | REACTIVE_IN_HEAP))) {
927
+ t.ie |= REACTIVE_REASK;
809
928
  armReaskClear();
810
929
  }
811
- t.se = t.se & ~REACTIVE_CHECK | REACTIVE_DIRTY;
930
+ t.ie = t.ie & ~REACTIVE_CHECK | REACTIVE_DIRTY;
812
931
  insertIntoHeap(t, queueFor(t));
813
932
  schedule();
814
933
  }
815
934
  }
816
935
 
817
- export { READ_SLOW, clearSnapshots, computed, context, createEffectNode, currentOptimisticLane, isEqual, latestReadActive, markSnapshotScope, optimisticComputed, optimisticSignal, pendingCheckActive, prepareComputed, read, readNodeFast, recompute, refresh, releaseSnapshotScope, runWithOwner, setContextInternal, setLatestReadActive, setMemo, setPendingCheckActive, setSignal, setSnapshotCapture, signal, snapshotCaptureActive, snapshotSources, stale, staleValues, suppressComputedRecompute, tracking, untrack };
936
+ export { READ_SLOW, clearSnapshots, computed, context, createEffectNode, currentOptimisticLane, ext, isEqual, latestReadActive, markSnapshotScope, optimisticComputed, optimisticSignal, pendingCheckActive, prepareComputed, read, readNodeFast, recompute, refresh, releaseSnapshotScope, runWithOwner, setContextInternal, setLatestReadActive, setMemo, setPendingCheckActive, setSignal, setSnapshotCapture, signal, snapshotCaptureActive, snapshotSources, stale, staleValues, suppressComputedRecompute, tracking, untrack };