@solidjs/signals 2.0.0-rc.1 → 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 (50) hide show
  1. package/dist/dev.js +1661 -361
  2. package/dist/node.cjs +1939 -1331
  3. package/dist/prod/affects.js +16 -16
  4. package/dist/prod/boundaries.js +90 -90
  5. package/dist/prod/core/action.js +3 -3
  6. package/dist/prod/core/async.js +74 -72
  7. package/dist/prod/core/constants.js +39 -1
  8. package/dist/prod/core/core.js +332 -224
  9. package/dist/prod/core/effect.js +56 -56
  10. package/dist/prod/core/external.js +4 -4
  11. package/dist/prod/core/graph.js +65 -54
  12. package/dist/prod/core/heap.js +52 -44
  13. package/dist/prod/core/invariants.js +3 -2
  14. package/dist/prod/core/lanes.js +41 -34
  15. package/dist/prod/core/optimistic.js +63 -60
  16. package/dist/prod/core/owner.js +97 -96
  17. package/dist/prod/core/scheduler.js +243 -194
  18. package/dist/prod/core/verdict.js +184 -77
  19. package/dist/prod/map.js +104 -104
  20. package/dist/prod/signals.js +1 -1
  21. package/dist/prod/store/next/optimistic.js +31 -23
  22. package/dist/prod/store/next/projection.js +3 -3
  23. package/dist/prod/store/next/reconcile.js +78 -74
  24. package/dist/prod/store/next/store.js +357 -90
  25. package/dist/prod/store/store.js +7 -7
  26. package/dist/types/core/attribution-hooks.d.ts +52 -0
  27. package/dist/types/core/attribution.d.ts +186 -0
  28. package/dist/types/core/constants.d.ts +26 -0
  29. package/dist/types/core/core.d.ts +8 -1
  30. package/dist/types/core/dev.d.ts +20 -3
  31. package/dist/types/core/graph.d.ts +1 -0
  32. package/dist/types/core/lanes.d.ts +4 -16
  33. package/dist/types/core/scheduler.d.ts +8 -0
  34. package/dist/types/core/types.d.ts +85 -41
  35. package/dist/types/store/next/projection.d.ts +0 -16
  36. package/dist/types/store/next/store.d.ts +6 -0
  37. package/dist/types/store/next/target.d.ts +18 -0
  38. package/dist/types-cjs/core/attribution-hooks.d.cts +52 -0
  39. package/dist/types-cjs/core/attribution.d.cts +186 -0
  40. package/dist/types-cjs/core/constants.d.cts +26 -0
  41. package/dist/types-cjs/core/core.d.cts +8 -1
  42. package/dist/types-cjs/core/dev.d.cts +20 -3
  43. package/dist/types-cjs/core/graph.d.cts +1 -0
  44. package/dist/types-cjs/core/lanes.d.cts +4 -16
  45. package/dist/types-cjs/core/scheduler.d.cts +8 -0
  46. package/dist/types-cjs/core/types.d.cts +85 -41
  47. package/dist/types-cjs/store/next/projection.d.cts +0 -16
  48. package/dist/types-cjs/store/next/store.d.cts +6 -0
  49. package/dist/types-cjs/store/next/target.d.cts +18 -0
  50. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import { EFFECT_RENDER, EFFECT_USER, STATUS_PENDING, REACTIVE_IN_HEAP_HEIGHT, REACTIVE_DISPOSED, REACTIVE_ZOMBIE, NOT_PENDING, EFFECT_TRACKED, CONFIG_IN_SNAPSHOT_SCOPE, REACTIVE_SNAPSHOT_STALE, REACTIVE_OPTIMISTIC_DIRTY, REACTIVE_REASK, REACTIVE_MANUAL_WRITE, STATUS_UNINITIALIZED } from "./constants.js";
1
+ import { EFFECT_RENDER, EFFECT_USER, STATUS_PENDING, REACTIVE_IN_HEAP_HEIGHT, REACTIVE_DISPOSED, REACTIVE_ZOMBIE, NOT_PENDING, CONFIG_HAS_COMPANIONS, EFFECT_TRACKED, CONFIG_HAS_LANE, CONFIG_HAS_SNAPSHOT, REACTIVE_RECOMPUTING_DEPS, REACTIVE_MISSED_WAKE, CONFIG_IN_SNAPSHOT_SCOPE, REACTIVE_SNAPSHOT_STALE, REACTIVE_OPTIMISTIC_DIRTY, REACTIVE_REASK, REACTIVE_MANUAL_WRITE, STATUS_UNINITIALIZED } from "./constants.js";
2
2
 
3
3
  import { currentOptimisticLane } from "./core.js";
4
4
 
@@ -19,23 +19,23 @@ const transitions = new Set;
19
19
  const dirtyQueue = {
20
20
  eE: new Array(2e3).fill(undefined),
21
21
  tE: false,
22
- Ve: 0,
22
+ xe: 0,
23
23
  EE: 0
24
24
  };
25
25
 
26
26
  const zombieQueue = {
27
27
  eE: new Array(2e3).fill(undefined),
28
28
  tE: false,
29
- Ve: 0,
29
+ xe: 0,
30
30
  EE: 0
31
31
  };
32
32
 
33
33
  /** runHeap callback that discards a queued zombie recompute instead of running
34
34
  * it: unlink pure recompute entries; strip just the recompute bit from dirtied
35
35
  * height-adjust entries so their height work still happens. */ function cancelZombieRecompute(e) {
36
- if (e.se & REACTIVE_IN_HEAP_HEIGHT) e.se &= -12; else {
36
+ if (e.ie & REACTIVE_IN_HEAP_HEIGHT) e.ie &= -12; else {
37
37
  deleteFromHeap(e, zombieQueue);
38
- e.se &= -4;
38
+ e.ie &= -4;
39
39
  }
40
40
  }
41
41
 
@@ -60,25 +60,25 @@ let projectionWriteActive = false;
60
60
  const transientStoreNodes = new Set;
61
61
 
62
62
  function canUseSimpleSyncFlush(e) {
63
- const t = e.m;
64
- return transitions.size === 0 && activeLanes.size === 0 && e.vt.length === 0 && t.Me.length === 0 && t.A.length === 0 && t.dn.size === 0 && transientStoreNodes.size === 0;
63
+ const t = e.k;
64
+ return transitions.size === 0 && activeLanes.size === 0 && e.Qt.length === 0 && t.Ke.length === 0 && t.m.length === 0 && t.cn.size === 0 && transientStoreNodes.size === 0;
65
65
  }
66
66
 
67
67
  function sweepTransientStoreNodes() {
68
68
  if (transientStoreNodes.size === 0) return;
69
69
  for (const e of transientStoreNodes) {
70
- if (e.o !== null) {
70
+ if (e.u !== null) {
71
71
  transientStoreNodes.delete(e);
72
72
  continue;
73
73
  }
74
- if (e._e !== NOT_PENDING) continue;
75
- if (e.De !== undefined && e.De !== NOT_PENDING) continue;
74
+ if (e.Pe !== NOT_PENDING) continue;
75
+ if (e.o?.De !== undefined && e.o?.De !== NOT_PENDING) continue;
76
76
  // A live affects() mark keeps the node addressable: sweeping it would
77
77
  // detach the refcount from the slot (a fresh probe would upsert a new,
78
78
  // unmarked node for the same property).
79
- if (e.t) continue;
79
+ if (e.o?.t) continue;
80
80
  transientStoreNodes.delete(e);
81
- e.ut?.();
81
+ e.o?.ft?.();
82
82
  }
83
83
  }
84
84
 
@@ -105,44 +105,44 @@ function setProjectionWriteActive(e) {
105
105
  * land there directly — no per-field aliasing.
106
106
  */ function createBatch() {
107
107
  return {
108
- de: clock,
109
- Qt: [],
110
- Ie: new Map,
111
- Me: [],
112
- A: [],
113
- dn: new Set,
114
- ie: [],
115
- yt: {
116
- bt: [ [], [] ],
117
- vt: []
108
+ Te: clock,
109
+ yt: [],
110
+ Ne: new Map,
111
+ Ke: [],
112
+ m: [],
113
+ cn: new Set,
114
+ oe: [],
115
+ bt: {
116
+ Lt: [ [], [] ],
117
+ Qt: []
118
118
  },
119
- fn: false,
119
+ an: false,
120
120
  ln: new Set
121
121
  };
122
122
  }
123
123
 
124
124
  function mergeTransitionState(e, t) {
125
- t.fn = e;
126
- e.ie.push(...t.ie);
127
- for (const i of activeLanes) if (i.Ne === t) i.Ne = e;
128
- if (t.Me.length) {
125
+ t.an = e;
126
+ e.oe.push(...t.oe);
127
+ for (const i of activeLanes) if (i._e === t) i._e = e;
128
+ if (t.Ke.length) {
129
129
  // Move (don't copy): the global queue's batch may still be the outgoing
130
130
  // transition, and the adoption pass in initTransition would re-push its
131
131
  // contents into the target — duplicating every entry.
132
- e.Me.push(...t.Me);
133
- t.Me.length = 0;
132
+ e.Ke.push(...t.Ke);
133
+ t.Ke.length = 0;
134
134
  }
135
- if (t.A.length) {
135
+ if (t.m.length) {
136
136
  // Move (don't copy): the global queue's batch may still be the outgoing
137
137
  // transition, and the adoption pass in initTransition would re-push its
138
138
  // contents into the target — double-releasing every mark.
139
- e.A.push(...t.A);
140
- t.A.length = 0;
139
+ e.m.push(...t.m);
140
+ t.m.length = 0;
141
141
  }
142
- for (const i of t.dn) e.dn.add(i);
143
- for (const [i, n] of t.Ie) {
144
- let t = e.Ie.get(i);
145
- if (!t) e.Ie.set(i, t = new Set);
142
+ for (const i of t.cn) e.cn.add(i);
143
+ for (const [i, n] of t.Ne) {
144
+ let t = e.Ne.get(i);
145
+ if (!t) e.Ne.set(i, t = new Set);
146
146
  for (const e of n) t.add(e);
147
147
  }
148
148
  for (const i of t.ln) e.ln.add(i);
@@ -155,7 +155,7 @@ function schedule() {
155
155
  }
156
156
  if (scheduled) return;
157
157
  scheduled = true;
158
- if (!syncDepth && !globalQueue.cn && !projectionWriteActive) queueMicrotask(flush);
158
+ if (!syncDepth && !globalQueue.sn && !projectionWriteActive) queueMicrotask(flush);
159
159
  }
160
160
 
161
161
  /**
@@ -189,30 +189,30 @@ function notifyHalted() {
189
189
  let queueRunToken = 0;
190
190
 
191
191
  class Queue {
192
- Fe=null;
193
- bt=[ [], [] ];
194
- vt=[];
195
- kt=0;
192
+ ve=null;
193
+ Lt=[ [], [] ];
194
+ Qt=[];
195
+ Mt=0;
196
196
  created=clock;
197
197
  addChild(e) {
198
- this.vt.push(e);
199
- e.Fe = this;
198
+ this.Qt.push(e);
199
+ e.ve = this;
200
200
  }
201
201
  removeChild(e) {
202
- const t = this.vt.indexOf(e);
202
+ const t = this.Qt.indexOf(e);
203
203
  if (t >= 0) {
204
- this.vt.splice(t, 1);
205
- e.Fe = null;
204
+ this.Qt.splice(t, 1);
205
+ e.ve = null;
206
206
  }
207
207
  }
208
208
  notify(e, t, i, n) {
209
- if (this.Fe) return this.Fe.notify(e, t, i, n);
209
+ if (this.ve) return this.ve.notify(e, t, i, n);
210
210
  return false;
211
211
  }
212
212
  run(e) {
213
- if (this.bt[e - 1].length) {
214
- const t = this.bt[e - 1];
215
- this.bt[e - 1] = [];
213
+ if (this.Lt[e - 1].length) {
214
+ const t = this.Lt[e - 1];
215
+ this.Lt[e - 1] = [];
216
216
  runQueue(t, e);
217
217
  }
218
218
  // Effects run here can dispose owners, and disposal removes queues from
@@ -222,12 +222,12 @@ class Queue {
222
222
  // can be recovered by rescanning from the front and every child still runs
223
223
  // exactly once. Children appended mid-pass carry a stale stamp and run,
224
224
  // matching the previous live-array behaviour.
225
- const t = this.vt;
225
+ const t = this.Qt;
226
226
  const i = ++queueRunToken;
227
227
  for (let n = 0; n < t.length; ) {
228
228
  const s = t[n];
229
- if (s.kt !== i) {
230
- s.kt = i;
229
+ if (s.Mt !== i) {
230
+ s.Mt = i;
231
231
  s.run?.(e);
232
232
  if (t[n] !== s) {
233
233
  n = 0;
@@ -242,81 +242,84 @@ class Queue {
242
242
  // Route to lane's effect queue if we're in an optimistic recomputation
243
243
  if (currentOptimisticLane) {
244
244
  const i = findLane(currentOptimisticLane);
245
- i.rn[e - 1].push(t);
245
+ i.en[e - 1].push(t);
246
246
  } else {
247
- this.bt[e - 1].push(t);
247
+ this.Lt[e - 1].push(t);
248
248
  }
249
249
  }
250
250
  schedule();
251
251
  }
252
252
  stashQueues(e) {
253
- e.bt[0].push(...this.bt[0]);
254
- e.bt[1].push(...this.bt[1]);
255
- this.bt = [ [], [] ];
256
- for (let t = 0; t < this.vt.length; t++) {
257
- let i = this.vt[t];
258
- let n = e.vt[t];
253
+ e.Lt[0].push(...this.Lt[0]);
254
+ e.Lt[1].push(...this.Lt[1]);
255
+ this.Lt = [ [], [] ];
256
+ for (let t = 0; t < this.Qt.length; t++) {
257
+ let i = this.Qt[t];
258
+ let n = e.Qt[t];
259
259
  if (!n) {
260
260
  n = {
261
- bt: [ [], [] ],
262
- vt: []
261
+ Lt: [ [], [] ],
262
+ Qt: []
263
263
  };
264
- e.vt[t] = n;
264
+ e.Qt[t] = n;
265
265
  }
266
266
  i.stashQueues(n);
267
267
  }
268
268
  }
269
269
  restoreQueues(e) {
270
- this.bt[0].push(...e.bt[0]);
271
- this.bt[1].push(...e.bt[1]);
272
- for (let t = 0; t < e.vt.length; t++) {
273
- const i = e.vt[t];
274
- let n = this.vt[t];
270
+ this.Lt[0].push(...e.Lt[0]);
271
+ this.Lt[1].push(...e.Lt[1]);
272
+ for (let t = 0; t < e.Qt.length; t++) {
273
+ const i = e.Qt[t];
274
+ let n = this.Qt[t];
275
275
  if (n) n.restoreQueues(i);
276
276
  }
277
277
  }
278
278
  }
279
279
 
280
280
  class GlobalQueue extends Queue {
281
- cn=false;
281
+ sn=false;
282
282
  // The current transaction-shaped batch: a plain ambient batch while no
283
283
  // transition is active, the active transition itself after initTransition.
284
- m=createBatch();
284
+ k=createBatch();
285
285
  static Ce;
286
- static me;
287
- static et;
288
- static gt=null;
286
+ static Fe;
287
+ static tt;
288
+ static Vt=null;
289
289
  // Store-side hook: drops a keyless affects() mark's identity scope when the
290
290
  // carrier node's last registration releases (wired by store.ts, mirroring
291
291
  // _clearOptimisticStore).
292
- static p=null;
292
+ static G=null;
293
293
  // affects()-side hooks (wired by affects.ts, mirroring _update): the mark
294
294
  // engine — count/register/release — lives with the feature. Every call site
295
295
  // is gated by state only that module creates, so `!` invocations are safe
296
296
  // once the gate holds.
297
- static G=null;
298
297
  static M=null;
299
298
  static h=null;
299
+ static j=null;
300
300
  // External-source bridge (wired by enableExternalSource(); null while no
301
301
  // config is active — including after _resetExternalSourceConfig()).
302
- static dt=null;
303
- static St=null;
302
+ static Rt=null;
303
+ static Gt=null;
304
304
  // Verdict-layer hooks (wired by verdict.ts when isPending()/latest() are
305
305
  // imported; null in apps that never use them). Call sites either guard for
306
306
  // null or sit behind state only the verdict layer can create (`!` is safe
307
307
  // there: `_pendingSignal`/`_latestValueComputed` are only ever assigned by
308
308
  // verdict.ts, and `pendingCheckActive`/`latestReadActive` only flip inside
309
309
  // isPending()/latest()).
310
- static Pe=null;
311
- static Se=null;
312
- static Ge=null;
310
+ static Oe=null;
311
+ static de=null;
312
+ static ye=null;
313
313
  static un=null;
314
- static At=null;
315
- static Ct=null;
316
314
  static Pt=null;
317
- static $e=null;
318
- static k=null;
319
- static Lt=null;
315
+ static Dt=null;
316
+ static Ht=null;
317
+ static Je=null;
318
+ static p=null;
319
+ static Bt=null;
320
+ // Re-asks probes whose verdict was provisionally suppressed by a fresh read
321
+ // of a held value, once the transaction gains an async blocker (#3028).
322
+ static wt=null;
320
323
  // Optimistic-engine hooks (wired by core/optimistic.ts via
321
324
  // installOptimisticEngine(), called from verdict.ts / createOptimistic /
322
325
  // createOptimisticStore — every module that can create optimistic state).
@@ -324,21 +327,37 @@ class GlobalQueue extends Queue {
324
327
  // `_overrideValue` slot, a lane in `activeLanes`, an `_optimisticNodes`
325
328
  // entry, or a non-null `currentOptimisticLane`, so `!` invocations are safe
326
329
  // once the gate holds.
327
- static Gt=null;
330
+ static vt=null;
331
+ static fn=null;
332
+ static dn=null;
328
333
  static En=null;
329
334
  static Tn=null;
330
- static In=null;
331
- static Nn=null;
332
- static Rt=null;
333
- static Ot=null;
334
- static Dt=null;
335
+ static Ft=null;
336
+ static ht=null;
337
+ static gt=null;
335
338
  static je=null;
339
+ static $e=null;
336
340
  static ze=null;
337
- static Be=null;
338
- static Qn=null;
341
+ static Nn=null;
339
342
  flush() {
340
- if (this.cn) return;
341
- this.cn = true;
343
+ if (this.sn) return;
344
+ // Fast drain: nothing in flight but plain pending commits — no dirty
345
+ // computeds, no queued effects, no child queues, no transitions/lanes/
346
+ // optimistic state. Commit and go; anything a commit hook schedules
347
+ // (companion snaps, store folds notifying subs) re-arms `scheduled`
348
+ // below and the outer drain loop takes the full spine next round.
349
+ if (activeTransition === null && dirtyQueue.EE < dirtyQueue.xe && this.Lt[0].length === 0 && this.Lt[1].length === 0 && this.Qt.length === 0 && canUseSimpleSyncFlush(this)) {
350
+ this.sn = true;
351
+ try {
352
+ commitPendingNodes();
353
+ } finally {
354
+ this.sn = false;
355
+ }
356
+ clock++;
357
+ scheduled = dirtyQueue.EE >= dirtyQueue.xe || this.Lt[0].length !== 0 || this.Lt[1].length !== 0 || this.k.yt.length !== 0;
358
+ return;
359
+ }
360
+ this.sn = true;
342
361
  try {
343
362
  if (false) ;
344
363
  runHeap(dirtyQueue, GlobalQueue.Ce);
@@ -354,53 +373,53 @@ class GlobalQueue extends Queue {
354
373
  // zombies here. Height-adjust entries still process normally: a
355
374
  // dirtied one keeps its height flag and falls through to runHeap's
356
375
  // adjustHeight path on the next pass of the bucket.
357
- runHeap(zombieQueue, this.m === e ? cancelZombieRecompute : GlobalQueue.Ce);
376
+ runHeap(zombieQueue, this.k === e ? cancelZombieRecompute : GlobalQueue.Ce);
358
377
  // Detach: the stashed transition keeps its batch; ambient work that
359
378
  // follows lands in a fresh one. If the batch is already a separate
360
379
  // ambient one — action done() restored activeTransition without
361
380
  // adopting the batch, and an ordinary write landed there before
362
381
  // the scheduled flush (#2916) — keep it: replacing it would strand
363
382
  // its queued pending nodes with held _pendingValues forever.
364
- if (this.m === e) currentBatch = this.m = createBatch();
383
+ if (this.k === e) currentBatch = this.k = createBatch();
365
384
  // Run lane effects immediately (before stashing) - lanes with no pending async
366
385
  if (activeLanes.size) {
367
- GlobalQueue.Nn(EFFECT_RENDER);
368
- GlobalQueue.Nn(EFFECT_USER);
386
+ GlobalQueue.Tn(EFFECT_RENDER);
387
+ GlobalQueue.Tn(EFFECT_USER);
369
388
  }
370
- this.stashQueues(e.yt);
389
+ this.stashQueues(e.bt);
371
390
  clock++;
372
391
  // A kept ambient batch may hold pending nodes (#2916): stay
373
392
  // scheduled so the outer drain loop commits them via the plain
374
393
  // flush path instead of leaving them until the next natural flush.
375
- scheduled = dirtyQueue.EE >= dirtyQueue.Ve || this.m.Qt.length > 0;
376
- reassignPendingTransition(e.Qt);
394
+ scheduled = dirtyQueue.EE >= dirtyQueue.xe || this.k.yt.length > 0;
395
+ reassignPendingTransition(e.yt);
377
396
  activeTransition = null;
378
397
  finalizePureQueue(null, true);
379
398
  return;
380
399
  }
381
400
  const t = activeTransition;
382
- const i = this.m;
383
- i !== t && i.Qt.push(...t.Qt);
384
- this.restoreQueues(t.yt);
401
+ const i = this.k;
402
+ i !== t && i.yt.push(...t.yt);
403
+ this.restoreQueues(t.bt);
385
404
  transitions.delete(t);
386
405
  activeTransition = null;
387
- reassignPendingTransition(i.Qt);
406
+ reassignPendingTransition(i.yt);
388
407
  finalizePureQueue(t);
389
408
  if (i === t) {
390
409
  // Drop the dead Transition wrapper but keep its (drained) containers
391
410
  // as the ambient batch — late registrations during finalization live
392
411
  // there and must survive to the next flush.
393
412
  const e = createBatch();
394
- e.Qt = i.Qt;
395
- e.Me = i.Me;
396
- e.A = i.A;
397
- e.dn = i.dn;
398
- currentBatch = this.m = e;
413
+ e.yt = i.yt;
414
+ e.Ke = i.Ke;
415
+ e.m = i.m;
416
+ e.cn = i.cn;
417
+ currentBatch = this.k = e;
399
418
  }
400
419
  } else {
401
420
  if (canUseSimpleSyncFlush(this)) {
402
421
  commitPendingNodes();
403
- if (dirtyQueue.EE >= dirtyQueue.Ve) {
422
+ if (dirtyQueue.EE >= dirtyQueue.xe) {
404
423
  runHeap(dirtyQueue, GlobalQueue.Ce);
405
424
  commitPendingNodes();
406
425
  }
@@ -411,36 +430,39 @@ class GlobalQueue extends Queue {
411
430
  }
412
431
  clock++;
413
432
  // Check if finalization added items to the heap (from optimistic reversion)
414
- scheduled = dirtyQueue.EE >= dirtyQueue.Ve;
433
+ scheduled = dirtyQueue.EE >= dirtyQueue.xe;
415
434
  // Run lane effects first (for ready lanes), then regular effects
416
- activeLanes.size && GlobalQueue.Nn(EFFECT_RENDER);
435
+ activeLanes.size && GlobalQueue.Tn(EFFECT_RENDER);
417
436
  this.run(EFFECT_RENDER);
418
- activeLanes.size && GlobalQueue.Nn(EFFECT_USER);
437
+ activeLanes.size && GlobalQueue.Tn(EFFECT_USER);
419
438
  this.run(EFFECT_USER);
420
439
  if (false) ;
421
440
  if (false && !scheduled && !activeTransition && transitions.size === 0 && activeLanes.size === 0) ;
422
441
  if (false) ;
423
442
  } finally {
424
- this.cn = false;
443
+ this.sn = false;
425
444
  }
426
445
  }
427
446
  notify(e, t, i, n) {
428
447
  // Only track async if the boundary is propagating STATUS_PENDING (not caught by boundary)
429
448
  if (t & STATUS_PENDING) {
430
449
  if (i & STATUS_PENDING) {
431
- const t = n !== undefined ? n : e._;
450
+ const t = n !== undefined ? n : e.o?._;
432
451
  // A visibility-only mark notification (the affects() boundary
433
452
  // channel) updates display state on its way up but must be invisible
434
453
  // to completion accounting BY CONSTRUCTION: it never registers a
435
454
  // reporter and never counts toward the loading-boundary diagnostic.
436
- if (t?.l) return true;
455
+ if (t?.i) return true;
437
456
  if (activeTransition && t) {
438
457
  const i = t.source;
439
- let n = activeTransition.Ie.get(i);
440
- if (!n) activeTransition.Ie.set(i, n = new Set);
458
+ let n = activeTransition.Ne.get(i);
459
+ if (!n) activeTransition.Ne.set(i, n = new Set);
441
460
  const s = n.size;
442
461
  n.add(e);
443
- if (n.size !== s) schedule();
462
+ if (n.size !== s) {
463
+ schedule();
464
+ GlobalQueue.wt?.(activeTransition);
465
+ }
444
466
  }
445
467
  }
446
468
  return true;
@@ -450,7 +472,7 @@ class GlobalQueue extends Queue {
450
472
  initTransition(e) {
451
473
  if (e) e = currentTransition(e);
452
474
  if (e && e === activeTransition) return;
453
- if (!e && activeTransition && activeTransition.de === clock) return;
475
+ if (!e && activeTransition && activeTransition.Te === clock) return;
454
476
  if (!activeTransition) {
455
477
  activeTransition = e ?? createBatch();
456
478
  } else if (e) {
@@ -460,8 +482,8 @@ class GlobalQueue extends Queue {
460
482
  activeTransition = e;
461
483
  }
462
484
  transitions.add(activeTransition);
463
- activeTransition.de = clock;
464
- const t = this.m;
485
+ activeTransition.Te = clock;
486
+ const t = this.k;
465
487
  if (t !== activeTransition) {
466
488
  // Adopt the ambient batch into the transaction, then make the
467
489
  // transaction the batch so later registrations land there directly.
@@ -470,34 +492,34 @@ class GlobalQueue extends Queue {
470
492
  // must not entangle unrelated writes to it; the same rule holds one hop
471
493
  // downstream: propagation never queues pended subscribers as pending
472
494
  // nodes, see propagateAffectsMark, #2893.
473
- for (let e = 0; e < t.Qt.length; e++) {
474
- const i = t.Qt[e];
475
- i.Ne = activeTransition;
476
- activeTransition.Qt.push(i);
495
+ for (let e = 0; e < t.yt.length; e++) {
496
+ const i = t.yt[e];
497
+ i._e = activeTransition;
498
+ activeTransition.yt.push(i);
477
499
  }
478
- for (let e = 0; e < t.Me.length; e++) {
479
- const i = t.Me[e];
480
- i.Ne = activeTransition;
481
- activeTransition.Me.push(i);
500
+ for (let e = 0; e < t.Ke.length; e++) {
501
+ const i = t.Ke[e];
502
+ i._e = activeTransition;
503
+ activeTransition.Ke.push(i);
482
504
  }
483
- if (t.A.length) activeTransition.A.push(...t.A);
484
- for (const e of t.dn) activeTransition.dn.add(e);
505
+ if (t.m.length) activeTransition.m.push(...t.m);
506
+ for (const e of t.cn) activeTransition.cn.add(e);
485
507
  // Gated readers recorded against the ambient batch move with it: their
486
508
  // replay-at-commit now happens at the transaction's completion.
487
509
  if (t.ln.size) {
488
510
  for (const e of t.ln) activeTransition.ln.add(e);
489
511
  t.ln.clear();
490
512
  }
491
- currentBatch = this.m = activeTransition;
513
+ currentBatch = this.k = activeTransition;
492
514
  }
493
515
  for (const e of activeLanes) {
494
- if (!e.Ne) e.Ne = activeTransition;
516
+ if (!e._e) e._e = activeTransition;
495
517
  }
496
518
  }
497
519
  }
498
520
 
499
521
  function queuePendingNode(e) {
500
- currentBatch.Qt.push(e);
522
+ currentBatch.yt.push(e);
501
523
  }
502
524
 
503
525
  // Sticky: flips true on the first refresh() ever (the only setter of
@@ -505,60 +527,87 @@ function queuePendingNode(e) {
505
527
  // clear entirely in apps that never refresh.
506
528
  let reaskArmed = false;
507
529
 
530
+ /** §12d: bumped by every recompute and every new subscriber edge. A node's
531
+ * staged-rewrite skip is sound only while NOTHING recomputed or linked since
532
+ * its last notify — a mid-batch pull can clean a marked subscriber, and a
533
+ * skipped re-write would leave it stale. */ let notifyEpoch = 0;
534
+
535
+ function bumpNotifyEpoch() {
536
+ notifyEpoch++;
537
+ }
538
+
508
539
  function armReaskClear() {
509
540
  reaskArmed = true;
510
541
  }
511
542
 
512
543
  function insertSubs(e, t = false) {
544
+ // §12d: stamp before walking — setSignal's staged-rewrite fast path skips
545
+ // the next walk for this node while the epoch holds (marking is idempotent).
546
+ e._t = notifyEpoch;
513
547
  // Get source lane: prefer node's own lane over current context
514
548
  // This is important for isPending signals which need their own lane to flush immediately
515
- const i = e.Ke || currentOptimisticLane;
516
- const n = e.xe !== undefined;
517
- const s = reaskArmed;
518
- for (let r = e.o; r !== null; r = r.ue) {
549
+ // Presence bits gate the optional-slot probes (see constants.ts): one
550
+ // masked read of the always-present _config instead of missing-property
551
+ // lookups in the hottest notify loop. Bits are sticky — the field read
552
+ // stays authoritative when a bit is set.
553
+ const i = e.T;
554
+ const n = (i & CONFIG_HAS_LANE ? e.o?.Be : undefined) || currentOptimisticLane;
555
+ const s = (i & CONFIG_HAS_SNAPSHOT) !== 0 && e.o?.Qe !== undefined;
556
+ const o = reaskArmed;
557
+ for (let i = e.u; i !== null; i = i.fe) {
558
+ const e = i.ae;
519
559
  // A value-change notification is a new question for the subscriber: any
520
560
  // pending re-ask mark (refresh) it carried is superseded.
521
- if (s) r.fe.se &= ~REACTIVE_REASK;
522
- if (n && r.fe.T & CONFIG_IN_SNAPSHOT_SCOPE) {
523
- r.fe.se |= REACTIVE_SNAPSHOT_STALE;
561
+ if (o) e.ie &= ~REACTIVE_REASK;
562
+ // Missed-wake latch (#3037): this write is landing while the subscriber
563
+ // is mid-recompute (a nested pull committing beneath its reads), and the
564
+ // heap refuses RECOMPUTING nodes. A gen-current link means the pass
565
+ // already validated this dep — the value it read is now stale — so latch
566
+ // for recompute's tail to reschedule. Untouched links need no latch (the
567
+ // pass either re-reads them fresh or trims them), and neither does the
568
+ // tail link: it is the read IN FLIGHT — read() links before it pulls, so
569
+ // this very commit is what that read returns.
570
+ if (e.ie & REACTIVE_RECOMPUTING_DEPS && i.ll === e.Ze && i !== e.Ye) e.ie |= REACTIVE_MISSED_WAKE;
571
+ if (s && e.T & CONFIG_IN_SNAPSHOT_SCOPE) {
572
+ e.ie |= REACTIVE_SNAPSHOT_STALE;
524
573
  continue;
525
574
  }
526
- if (t && i) {
527
- r.fe.se |= REACTIVE_OPTIMISTIC_DIRTY;
528
- assignOrMergeLane(r.fe, i);
575
+ if (t && n) {
576
+ e.ie |= REACTIVE_OPTIMISTIC_DIRTY;
577
+ assignOrMergeLane(e, n);
529
578
  } else if (t) {
530
- r.fe.se |= REACTIVE_OPTIMISTIC_DIRTY;
579
+ e.ie |= REACTIVE_OPTIMISTIC_DIRTY;
531
580
  // No source lane means reversion - clear subscriber's lane so effects go to regular queue
532
- r.fe.Ke = undefined;
581
+ if (e.o) e.o.Be = undefined;
533
582
  }
534
- enqueueSub(r.fe);
583
+ enqueueSub(e);
535
584
  }
536
585
  }
537
586
 
538
587
  function commitPendingNode(e) {
539
588
  const t = e;
540
- if (!t.ce) {
541
- if (e._e !== NOT_PENDING) {
542
- e.be = e._e;
543
- e._e = NOT_PENDING;
589
+ if (!t.Se) {
590
+ if (e.Pe !== NOT_PENDING) {
591
+ e.be = e.Pe;
592
+ e.Pe = NOT_PENDING;
544
593
  }
545
- if (e.Oe || e.ge) GlobalQueue.un(e);
594
+ if (e.T & CONFIG_HAS_COMPANIONS) GlobalQueue.un(e);
546
595
  return;
547
596
  }
548
- if (e._e !== NOT_PENDING) {
549
- e.be = e._e;
550
- e._e = NOT_PENDING;
597
+ if (e.Pe !== NOT_PENDING) {
598
+ e.be = e.Pe;
599
+ e.Pe = NOT_PENDING;
551
600
  // Set _modified for effects, but not for tracked effects (they handle their own scheduling)
552
- if (e.Re && e.Re !== EFFECT_TRACKED) e.Je = true;
601
+ if (e.Re && e.Re !== EFFECT_TRACKED) e.Xe = true;
553
602
  }
554
603
  // The committed hold is the first observable answer for a loading-window
555
604
  // node — the window closes here, not at compute time (#2990). Unconditional
556
605
  // store to an always-present computed slot.
557
- t.Ee = false;
558
- t.se &= ~REACTIVE_MANUAL_WRITE;
606
+ t.Ie = false;
607
+ t.ie &= ~REACTIVE_MANUAL_WRITE;
559
608
  if (!(t.S & STATUS_PENDING)) t.S &= ~STATUS_UNINITIALIZED;
560
- if (t.We !== null || t.Qe !== null) GlobalQueue.me(t, false, true);
561
- if (e.Oe || e.ge) GlobalQueue.un(e);
609
+ if (t.o != null && (t.o.qe !== null || t.o.We !== null)) GlobalQueue.Fe(t, false, true);
610
+ if (e.T & CONFIG_HAS_COMPANIONS) GlobalQueue.un(e);
562
611
  }
563
612
 
564
613
  // Store commit hook (INTERNALS-STORE-STATE.md §3): installed by the store
@@ -573,7 +622,7 @@ function setStoreCommitHook(e) {
573
622
  }
574
623
 
575
624
  function commitPendingNodes() {
576
- const e = currentBatch.Qt;
625
+ const e = currentBatch.yt;
577
626
  for (let t = 0; t < e.length; t++) {
578
627
  commitPendingNode(e[t]);
579
628
  }
@@ -586,23 +635,23 @@ function finalizePureQueue(e = null, t = false) {
586
635
  // For completing transitions or no-transition, resolve pending and revert optimistic
587
636
  const i = !t;
588
637
  if (i) commitPendingNodes();
589
- if (!t && globalQueue.vt.length) checkBoundaryChildren(globalQueue);
590
- const n = dirtyQueue.EE >= dirtyQueue.Ve;
638
+ if (!t && globalQueue.Qt.length) checkBoundaryChildren(globalQueue);
639
+ const n = dirtyQueue.EE >= dirtyQueue.xe;
591
640
  if (n) runHeap(dirtyQueue, GlobalQueue.Ce);
592
641
  if (i) {
593
642
  if (n) commitPendingNodes();
594
643
  // The settling batch: the completing transaction's, or the ambient one.
595
- const t = e ?? globalQueue.m;
644
+ const t = e ?? globalQueue.k;
596
645
  // Optimistic reversion: a non-empty batch means _optimisticWrite ran,
597
646
  // which installed the engine's hooks.
598
- if (t.Me.length) GlobalQueue.En(t.Me);
647
+ if (t.Ke.length) GlobalQueue.fn(t.Ke);
599
648
  // Replay entanglement: subs recorded by the read-time gate get rescheduled
600
649
  // so they re-run with the now-committed values visible. The ambient batch
601
650
  // replays too — laneReadsCommitted records readers whose committed-view
602
651
  // read hid a same-tick plain write that just committed above (#2963).
603
652
  if (t.ln.size) {
604
653
  for (const e of t.ln) {
605
- if (e.se & REACTIVE_DISPOSED) continue;
654
+ if (e.ie & REACTIVE_DISPOSED) continue;
606
655
  enqueueSub(e);
607
656
  }
608
657
  t.ln.clear();
@@ -617,24 +666,24 @@ function finalizePureQueue(e = null, t = false) {
617
666
  // held boundary display state through the visual channel, and their
618
667
  // release is the display-state update point — re-run the boundary sweep
619
668
  // (the earlier sweep above ran while the marks were still live).
620
- if (t.A.length) {
621
- GlobalQueue.G(t.A);
622
- if (globalQueue.vt.length) checkBoundaryChildren(globalQueue);
669
+ if (t.m.length) {
670
+ GlobalQueue.M(t.m);
671
+ if (globalQueue.Qt.length) checkBoundaryChildren(globalQueue);
623
672
  }
624
673
  // A non-empty set means trackOptimisticStore ran, which installed the
625
674
  // hook; the hook iterates, clears, and schedules (keeping the loop out of
626
675
  // core lets esbuild shake it — rollup already folds the null guard). The
627
676
  // completing transition scopes the clear to its own layer keys (#2899).
628
- if (t.dn.size) GlobalQueue.gt(t.dn, e);
677
+ if (t.cn.size) GlobalQueue.Vt(t.cn, e);
629
678
  sweepTransientStoreNodes();
630
679
  // Lanes only enter activeLanes through the engine's getOrCreateLane.
631
- if (activeLanes.size) GlobalQueue.In(e);
680
+ if (activeLanes.size) GlobalQueue.En(e);
632
681
  }
633
682
  }
634
683
 
635
684
  function checkBoundaryChildren(e) {
636
- for (const t of e.vt) {
637
- t.ne?.();
685
+ for (const t of e.Qt) {
686
+ t.se?.();
638
687
  checkBoundaryChildren(t);
639
688
  }
640
689
  }
@@ -657,7 +706,7 @@ function checkBoundaryChildren(e) {
657
706
 
658
707
  function reassignPendingTransition(e) {
659
708
  for (let t = 0; t < e.length; t++) {
660
- e[t].Ne = activeTransition;
709
+ e[t]._e = activeTransition;
661
710
  }
662
711
  }
663
712
 
@@ -668,7 +717,7 @@ const globalQueue = new GlobalQueue;
668
717
  // property hop through `_batch` was a measured instruction-count regression
669
718
  // (CodSpeed update1to1, PR #2905). The field stays authoritative for
670
719
  // cross-module readers; every `_batch` assignment updates both.
671
- let currentBatch = globalQueue.m;
720
+ let currentBatch = globalQueue.k;
672
721
 
673
722
  function flush(e) {
674
723
  if (e) {
@@ -685,7 +734,7 @@ function flush(e) {
685
734
  }
686
735
  }
687
736
  }
688
- if (globalQueue.cn) {
737
+ if (globalQueue.sn) {
689
738
  return;
690
739
  }
691
740
  if (halted) return;
@@ -701,23 +750,23 @@ function runQueue(e, t) {
701
750
  }
702
751
 
703
752
  function reporterBlocksSource(e, t) {
704
- if (e.se & (REACTIVE_ZOMBIE | REACTIVE_DISPOSED)) return false;
705
- if (e.oe?.has(t)) return true;
706
- for (let i = e.tt; i; i = i.nt) {
707
- let e = i.it;
753
+ if (e.ie & (REACTIVE_ZOMBIE | REACTIVE_DISPOSED)) return false;
754
+ if (e.o?.le?.has(t)) return true;
755
+ for (let i = e.nt; i; i = i.it) {
756
+ let e = i.ut;
708
757
  while (e) {
709
758
  if (e === t || e.lt === t) return true;
710
- e = e.nn;
759
+ e = e.o?.It;
711
760
  }
712
761
  }
713
- return !!(e.S & STATUS_PENDING && e._ instanceof NotReadyError && e._.source === t);
762
+ return !!(e.S & STATUS_PENDING && e.o?._ instanceof NotReadyError && e.o?._.source === t);
714
763
  }
715
764
 
716
765
  function transitionComplete(e) {
717
- if (e.fn) return true;
718
- if (e.ie.length) return false;
766
+ if (e.an) return true;
767
+ if (e.oe.length) return false;
719
768
  let t = true;
720
- for (const [i, n] of e.Ie) {
769
+ for (const [i, n] of e.Ne) {
721
770
  let s = false;
722
771
  for (const e of n) {
723
772
  if (reporterBlocksSource(e, i)) {
@@ -726,7 +775,7 @@ function transitionComplete(e) {
726
775
  }
727
776
  n.delete(e);
728
777
  }
729
- if (!s) e.Ie.delete(i); else if (i.S & STATUS_PENDING && i._?.source === i) {
778
+ if (!s) e.Ne.delete(i); else if (i.S & STATUS_PENDING && i.o?._?.source === i) {
730
779
  t = false;
731
780
  break;
732
781
  }
@@ -734,13 +783,13 @@ function transitionComplete(e) {
734
783
  // Override blockage lives with the engine (absent hook = "no optimistic
735
784
  // blockage"); the hook's loops over _optimisticNodes/_optimisticStores are
736
785
  // no-ops when the transition holds neither, so no pre-check is needed.
737
- if (t && GlobalQueue.Tn?.(e)) t = false;
738
- t && (e.fn = true);
786
+ if (t && GlobalQueue.dn?.(e)) t = false;
787
+ t && (e.an = true);
739
788
  return t;
740
789
  }
741
790
 
742
791
  function currentTransition(e) {
743
- while (e.fn && typeof e.fn === "object") e = e.fn;
792
+ while (e.an && typeof e.an === "object") e = e.an;
744
793
  return e;
745
794
  }
746
795
 
@@ -754,4 +803,4 @@ function runInTransition(e, t) {
754
803
  }
755
804
  }
756
805
 
757
- export { GlobalQueue, Queue, activeAffectsMarks, activeLanes, activeTransition, armReaskClear, assignOrMergeLane, clock, currentTransition, dirtyQueue, enforceLoadingBoundary, finalizePureQueue, findLane, flush, globalQueue, haltReactivity, insertSubs, projectionWriteActive, queuePendingNode, resetErrorHalt, runInTransition, schedule, setProjectionWriteActive, setStoreCommitHook, shiftAffectsMarks, storeCommitHook, zombieQueue };
806
+ export { GlobalQueue, Queue, activeAffectsMarks, activeLanes, activeTransition, armReaskClear, assignOrMergeLane, bumpNotifyEpoch, clock, currentTransition, dirtyQueue, enforceLoadingBoundary, finalizePureQueue, findLane, flush, globalQueue, haltReactivity, insertSubs, notifyEpoch, projectionWriteActive, queuePendingNode, reaskArmed, resetErrorHalt, runInTransition, schedule, setProjectionWriteActive, setStoreCommitHook, shiftAffectsMarks, storeCommitHook, zombieQueue };