@solidjs/signals 2.0.0-rc.3 → 2.0.0-rc.5

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 (71) hide show
  1. package/dist/dev.js +2494 -271
  2. package/dist/node.cjs +3691 -1595
  3. package/dist/prod/affects.js +13 -12
  4. package/dist/prod/boundaries.js +43 -35
  5. package/dist/prod/core/action.js +3 -3
  6. package/dist/prod/core/async.js +137 -106
  7. package/dist/prod/core/constants.js +55 -1
  8. package/dist/prod/core/core.js +354 -247
  9. package/dist/prod/core/effect.js +47 -50
  10. package/dist/prod/core/error.js +13 -1
  11. package/dist/prod/core/external.js +4 -4
  12. package/dist/prod/core/graph.js +88 -52
  13. package/dist/prod/core/heap.js +38 -38
  14. package/dist/prod/core/lanes.js +34 -34
  15. package/dist/prod/core/optimistic.js +61 -58
  16. package/dist/prod/core/owner.js +38 -38
  17. package/dist/prod/core/scheduler.js +401 -174
  18. package/dist/prod/core/verdict.js +132 -65
  19. package/dist/prod/index.js +7 -3
  20. package/dist/prod/map.js +106 -106
  21. package/dist/prod/signals.js +253 -25
  22. package/dist/prod/store/index.js +2 -0
  23. package/dist/prod/store/next/optimistic.js +314 -121
  24. package/dist/prod/store/next/patch-hooks.js +13 -0
  25. package/dist/prod/store/next/patch.js +614 -0
  26. package/dist/prod/store/next/projection.js +23 -19
  27. package/dist/prod/store/next/reconcile.js +307 -120
  28. package/dist/prod/store/next/store.js +440 -117
  29. package/dist/prod/store/next/target.js +13 -4
  30. package/dist/prod/store/store.js +5 -5
  31. package/dist/types/core/async.d.ts +2 -0
  32. package/dist/types/core/attribution.d.ts +9 -4
  33. package/dist/types/core/constants.d.ts +54 -0
  34. package/dist/types/core/core.d.ts +34 -21
  35. package/dist/types/core/dev.d.ts +8 -0
  36. package/dist/types/core/error.d.ts +9 -0
  37. package/dist/types/core/graph.d.ts +22 -0
  38. package/dist/types/core/index.d.ts +2 -2
  39. package/dist/types/core/scheduler.d.ts +46 -0
  40. package/dist/types/core/types.d.ts +12 -0
  41. package/dist/types/index.d.ts +3 -3
  42. package/dist/types/signals.d.ts +108 -0
  43. package/dist/types/store/index.d.ts +2 -0
  44. package/dist/types/store/next/optimistic.d.ts +13 -10
  45. package/dist/types/store/next/patch-hooks.d.ts +41 -0
  46. package/dist/types/store/next/patch.d.ts +91 -0
  47. package/dist/types/store/next/projection.d.ts +1 -1
  48. package/dist/types/store/next/reconcile.d.ts +14 -0
  49. package/dist/types/store/next/store.d.ts +52 -2
  50. package/dist/types/store/next/target.d.ts +73 -8
  51. package/dist/types-cjs/core/async.d.cts +2 -0
  52. package/dist/types-cjs/core/attribution.d.cts +9 -4
  53. package/dist/types-cjs/core/constants.d.cts +54 -0
  54. package/dist/types-cjs/core/core.d.cts +34 -21
  55. package/dist/types-cjs/core/dev.d.cts +8 -0
  56. package/dist/types-cjs/core/error.d.cts +9 -0
  57. package/dist/types-cjs/core/graph.d.cts +22 -0
  58. package/dist/types-cjs/core/index.d.cts +2 -2
  59. package/dist/types-cjs/core/scheduler.d.cts +46 -0
  60. package/dist/types-cjs/core/types.d.cts +12 -0
  61. package/dist/types-cjs/index.d.cts +3 -3
  62. package/dist/types-cjs/signals.d.cts +108 -0
  63. package/dist/types-cjs/store/index.d.cts +2 -0
  64. package/dist/types-cjs/store/next/optimistic.d.cts +13 -10
  65. package/dist/types-cjs/store/next/patch-hooks.d.cts +41 -0
  66. package/dist/types-cjs/store/next/patch.d.cts +91 -0
  67. package/dist/types-cjs/store/next/projection.d.cts +1 -1
  68. package/dist/types-cjs/store/next/reconcile.d.cts +14 -0
  69. package/dist/types-cjs/store/next/store.d.cts +52 -2
  70. package/dist/types-cjs/store/next/target.d.cts +73 -8
  71. package/package.json +2 -2
@@ -1,4 +1,4 @@
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";
1
+ import { EFFECT_RENDER, EFFECT_USER, STATUS_PENDING, REACTIVE_IN_HEAP_HEIGHT, REACTIVE_DISPOSED, CONFIG_HELD_TRUTH, REACTIVE_ZOMBIE, CONFIG_HAS_LANE, CONFIG_HAS_SNAPSHOT, REACTIVE_RECOMPUTING_DEPS, REACTIVE_MISSED_WAKE, CONFIG_IN_SNAPSHOT_SCOPE, REACTIVE_SNAPSHOT_STALE, REACTIVE_OPTIMISTIC_DIRTY, NOT_PENDING, CONFIG_HAS_COMPANIONS, EFFECT_TRACKED, REACTIVE_REASK, REACTIVE_MANUAL_WRITE, STATUS_UNINITIALIZED, CONFIG_AUTHORITATIVE_READ } from "./constants.js";
2
2
 
3
3
  import { currentOptimisticLane } from "./core.js";
4
4
 
@@ -6,11 +6,13 @@ import { DEV } from "./dev.js";
6
6
 
7
7
  import { NotReadyError } from "./error.js";
8
8
 
9
+ import { sweepDormant } from "./graph.js";
10
+
9
11
  import { runHeap, deleteFromHeap, enqueueSub } from "./heap.js";
10
12
 
11
- import { activeLanes, assignOrMergeLane, findLane } from "./lanes.js";
13
+ import { activeLanes, assignOrMergeLane, findLane, hasActiveOverride } from "./lanes.js";
12
14
 
13
- export { getOrCreateLane, hasActiveOverride, mergeLanes, resolveLane } from "./lanes.js";
15
+ export { getOrCreateLane, mergeLanes, resolveLane } from "./lanes.js";
14
16
 
15
17
  import { devCheckFlushStart, devCheckActiveOverrides, devCensusCompanions, devCheckQuiescent } from "./invariants.js";
16
18
 
@@ -19,14 +21,14 @@ const transitions = new Set;
19
21
  const dirtyQueue = {
20
22
  eE: new Array(2e3).fill(undefined),
21
23
  tE: false,
22
- xe: 0,
24
+ Qe: 0,
23
25
  EE: 0
24
26
  };
25
27
 
26
28
  const zombieQueue = {
27
29
  eE: new Array(2e3).fill(undefined),
28
30
  tE: false,
29
- xe: 0,
31
+ Qe: 0,
30
32
  EE: 0
31
33
  };
32
34
 
@@ -60,8 +62,8 @@ let projectionWriteActive = false;
60
62
  const transientStoreNodes = new Set;
61
63
 
62
64
  function canUseSimpleSyncFlush(e) {
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
+ const t = e.m;
66
+ return transitions.size === 0 && activeLanes.size === 0 && e.Qt.length === 0 && t.ze.length === 0 && t.A.length === 0 && t.En.size === 0 && transientStoreNodes.size === 0;
65
67
  }
66
68
 
67
69
  function sweepTransientStoreNodes() {
@@ -72,13 +74,13 @@ function sweepTransientStoreNodes() {
72
74
  continue;
73
75
  }
74
76
  if (e.Pe !== NOT_PENDING) continue;
75
- if (e.o?.De !== undefined && e.o?.De !== NOT_PENDING) continue;
77
+ if (e.o?.Oe !== undefined && e.o?.Oe !== NOT_PENDING) continue;
76
78
  // A live affects() mark keeps the node addressable: sweeping it would
77
79
  // detach the refcount from the slot (a fresh probe would upsert a new,
78
80
  // unmarked node for the same property).
79
81
  if (e.o?.t) continue;
80
82
  transientStoreNodes.delete(e);
81
- e.o?.ft?.();
83
+ e.o?.Et?.();
82
84
  }
83
85
  }
84
86
 
@@ -106,46 +108,162 @@ function setProjectionWriteActive(e) {
106
108
  */ function createBatch() {
107
109
  return {
108
110
  Te: clock,
109
- yt: [],
110
- Ne: new Map,
111
- Ke: [],
112
- m: [],
113
- cn: new Set,
114
- oe: [],
115
- bt: {
116
- Lt: [ [], [] ],
111
+ Lt: [],
112
+ _e: new Map,
113
+ ze: [],
114
+ A: [],
115
+ En: new Set,
116
+ ue: [],
117
+ Bt: {
118
+ Mt: [ [], [] ],
117
119
  Qt: []
118
120
  },
119
- an: false,
120
- ln: new Set
121
+ sn: false,
122
+ cn: new Set
121
123
  };
122
124
  }
123
125
 
124
126
  function mergeTransitionState(e, t) {
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) {
127
+ t.sn = e;
128
+ e.ue.push(...t.ue);
129
+ for (const i of activeLanes) if (i.Ae === t) i.Ae = e;
130
+ if (t.ze.length) {
129
131
  // Move (don't copy): the global queue's batch may still be the outgoing
130
132
  // transition, and the adoption pass in initTransition would re-push its
131
133
  // contents into the target — duplicating every entry.
132
- e.Ke.push(...t.Ke);
133
- t.Ke.length = 0;
134
+ e.ze.push(...t.ze);
135
+ t.ze.length = 0;
134
136
  }
135
- if (t.m.length) {
137
+ if (t.A.length) {
136
138
  // Move (don't copy): the global queue's batch may still be the outgoing
137
139
  // transition, and the adoption pass in initTransition would re-push its
138
140
  // contents into the target — double-releasing every mark.
139
- e.m.push(...t.m);
140
- t.m.length = 0;
141
+ e.A.push(...t.A);
142
+ t.A.length = 0;
141
143
  }
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);
144
+ for (const i of t.En) e.En.add(i);
145
+ // Patch-channel stash (store/next/patch.ts): entries held for the outgoing
146
+ // transition must ride the merge like every other per-transition
147
+ // collection releaseBatch only reads the COMMITTING transition's stash,
148
+ // so a stranded sidecar would silently drop its patches. Move (don't
149
+ // copy), same aliasing rule as the collections above. The field is an
150
+ // expando so this module stays free of patch imports (pay-for-use).
151
+ const i = t.wt;
152
+ if (i !== undefined) {
153
+ t.wt = undefined;
154
+ let n = e.wt;
155
+ if (n !== undefined) n.push(...i); else n = e.wt = i;
156
+ // Retarget the entries' coalescing stamps to the surviving stash
157
+ // (opaque backref contract with store/next/patch.ts): without this a
158
+ // post-merge emission misses the stamp and pushes a SECOND entry —
159
+ // the record's patch applies twice at commit (re-audit 5, P1-2).
160
+ for (let e = 0; e < i.length; e++) {
161
+ const t = i[e].pc;
162
+ if (t !== undefined && t.qe === i[e]) t.qa = n;
163
+ }
164
+ }
165
+ for (const [i, n] of t._e) {
166
+ let t = e._e.get(i);
167
+ if (!t) e._e.set(i, t = new Set);
146
168
  for (const e of n) t.add(e);
147
169
  }
148
- for (const i of t.ln) e.ln.add(i);
170
+ for (const i of t.cn) e.cn.add(i);
171
+ }
172
+
173
+ /**
174
+ * Flip-entanglement (#3164 follow-up): `until()` is a declaration of
175
+ * relatedness — the predicate names the condition that confirms the awaiting
176
+ * transaction. When the predicate settles truthy, every live foreign
177
+ * transition whose staged write it read IS the confirming event by the
178
+ * user's own definition, so it merges into the awaiting transaction and
179
+ * reveals at the joint settle — the cross-primitive twin of the family fold
180
+ * (a landing on an optimism-carrying family joins the retaining
181
+ * transaction). Non-flipping updates never pass through here: falsy
182
+ * evaluations don't entangle, so unrelated traffic on the watched sources
183
+ * reveals freely on its own schedule.
184
+ *
185
+ * Runs inside the predicate's compute (pure phase) — the confirming
186
+ * transition's stamps are still live and its commit decision hasn't run, so
187
+ * the merge lands before any reveal. Only the tree-shaken graphs that call
188
+ * `until()` retain this.
189
+ */ function entangleConfirmingTransitions(e, t) {
190
+ t = currentTransition(t);
191
+ if (t.sn === true) return;
192
+ // The confirming evidence is a dep whose value is STAGED (pending,
193
+ // uncommitted) at flip evaluation — committed deps are public already and
194
+ // carry nothing to entangle. A staged dep lives in one of two carriers: a
195
+ // stamped transition, or the queue's current batch (ambient registrations
196
+ // don't stamp; "ambient work IS a transaction" — the batch is the
197
+ // carrier). The entangle STEALS the carrier's staged cargo — its pending
198
+ // nodes move (re-stamped) into the awaiting transaction and reveal at its
199
+ // settle — but never the carrier itself: its async reporters, actions,
200
+ // and stashes are its own future (a live stream's flight must not chain
201
+ // the awaiting transaction to landings that haven't happened; a merged
202
+ // reporter deadlocked exactly that way).
203
+ let i = false;
204
+ for (let n = e.ut; n !== null; n = n.lt) {
205
+ const s = n.ot;
206
+ if (s.Pe !== NOT_PENDING) {
207
+ const e = s.Ae;
208
+ const n = e != null ? currentTransition(e) : null;
209
+ // Skip the awaiting transaction's own cargo (t === target: a
210
+ // fold-staged landing or a write the action itself issued — hold and
211
+ // reveal already correct) and dead carriers. Ambient-batch staging
212
+ // (t === null) must leave the batch NOW — it commits at this flush's
213
+ // end, which would reveal the confirmation under the live optimism
214
+ // it just confirmed.
215
+ const r = n === null ? currentBatch.Lt : n !== t && n.sn !== true ? n.Lt : null;
216
+ if (r !== null) i = stealEntangledCargo(r, t) || i;
217
+ }
218
+ if (n === e.je) break;
219
+ }
220
+ // The steal never activates the awaiting transaction: the predicate can
221
+ // flip inside another transaction's finalize heap, and adopting the queue
222
+ // batch there hands the stolen cargo to that finalize's commit sweep — a
223
+ // premature reveal at a foreign settle. Subscribers that computed against
224
+ // the pre-steal world were re-dirtied by the steal itself, so this
225
+ // flush's applies paint the masked (mid-hold) view; the cargo commits at
226
+ // the awaiting transaction's own settle.
227
+ }
228
+
229
+ /** Move a confirming carrier's staged nodes into the awaiting transaction:
230
+ * re-stamp and arm the held-truth mask (override-covered nodes skip it —
231
+ * the override already hides their staged value per A17, and is usually
232
+ * the very optimism this confirmation settles); the mask's commit
233
+ * registers the settle-side post-revert wake. The carrier's array is
234
+ * emptied so its own commit point commits none of the stolen cargo.
235
+ *
236
+ * EFFECT subs of stolen nodes re-run: any that recomputed against the
237
+ * staging BEFORE the steal (the carrier's landing notified them as a plain
238
+ * write) hold a private torn result — override composed with confirming
239
+ * truth — that the next paint gate (stash-point lane run, a foreign
240
+ * flush's completion drain) would show. Re-running them under the mask
241
+ * re-derives the mid-hold view in this same heap pass. PURE computeds are
242
+ * deliberately NOT re-run: a torn staged value of theirs is itself stolen
243
+ * cargo — masked at read, so ordinary readers already serve their
244
+ * committed value — while re-running them would re-derive the OLD world
245
+ * and re-stage it over the held truth. The reveal re-notifies
246
+ * (commitPendingNodes), which is when they re-derive for real. */ function stealEntangledCargo(e, t) {
247
+ if (e === t.Lt || e.length === 0) return false;
248
+ for (let i = 0; i < e.length; i++) {
249
+ const n = e[i];
250
+ n.Ae = t;
251
+ t.Lt.push(n);
252
+ // Override-covered nodes stay silent AND unmasked: the override is the
253
+ // display (A17 — its staging never notified, its revert will), so their
254
+ // subs saw nothing and re-running one would break the silence with a
255
+ // duplicate fire of an unchanged view.
256
+ if (!hasActiveOverride(n)) {
257
+ n.T |= CONFIG_HELD_TRUTH;
258
+ for (let e = n.u; e !== null; e = e.ae) {
259
+ const t = e.ce;
260
+ if (t.he && !(t.T & CONFIG_AUTHORITATIVE_READ)) enqueueSub(t);
261
+ }
262
+ }
263
+ }
264
+ e.length = 0;
265
+ transitions.add(t);
266
+ return true;
149
267
  }
150
268
 
151
269
  function schedule() {
@@ -155,7 +273,7 @@ function schedule() {
155
273
  }
156
274
  if (scheduled) return;
157
275
  scheduled = true;
158
- if (!syncDepth && !globalQueue.sn && !projectionWriteActive) queueMicrotask(flush);
276
+ if (!syncDepth && !globalQueue.fn && !projectionWriteActive) queueMicrotask(flush);
159
277
  }
160
278
 
161
279
  /**
@@ -189,30 +307,30 @@ function notifyHalted() {
189
307
  let queueRunToken = 0;
190
308
 
191
309
  class Queue {
192
- ve=null;
193
- Lt=[ [], [] ];
310
+ ke=null;
311
+ Mt=[ [], [] ];
194
312
  Qt=[];
195
- Mt=0;
313
+ jt=0;
196
314
  created=clock;
197
315
  addChild(e) {
198
316
  this.Qt.push(e);
199
- e.ve = this;
317
+ e.ke = this;
200
318
  }
201
319
  removeChild(e) {
202
320
  const t = this.Qt.indexOf(e);
203
321
  if (t >= 0) {
204
322
  this.Qt.splice(t, 1);
205
- e.ve = null;
323
+ e.ke = null;
206
324
  }
207
325
  }
208
326
  notify(e, t, i, n) {
209
- if (this.ve) return this.ve.notify(e, t, i, n);
327
+ if (this.ke) return this.ke.notify(e, t, i, n);
210
328
  return false;
211
329
  }
212
330
  run(e) {
213
- if (this.Lt[e - 1].length) {
214
- const t = this.Lt[e - 1];
215
- this.Lt[e - 1] = [];
331
+ if (this.Mt[e - 1].length) {
332
+ const t = this.Mt[e - 1];
333
+ this.Mt[e - 1] = [];
216
334
  runQueue(t, e);
217
335
  }
218
336
  // Effects run here can dispose owners, and disposal removes queues from
@@ -226,8 +344,8 @@ class Queue {
226
344
  const i = ++queueRunToken;
227
345
  for (let n = 0; n < t.length; ) {
228
346
  const s = t[n];
229
- if (s.Mt !== i) {
230
- s.Mt = i;
347
+ if (s.jt !== i) {
348
+ s.jt = i;
231
349
  s.run?.(e);
232
350
  if (t[n] !== s) {
233
351
  n = 0;
@@ -242,23 +360,23 @@ class Queue {
242
360
  // Route to lane's effect queue if we're in an optimistic recomputation
243
361
  if (currentOptimisticLane) {
244
362
  const i = findLane(currentOptimisticLane);
245
- i.en[e - 1].push(t);
363
+ i.tn[e - 1].push(t);
246
364
  } else {
247
- this.Lt[e - 1].push(t);
365
+ this.Mt[e - 1].push(t);
248
366
  }
249
367
  }
250
368
  schedule();
251
369
  }
252
370
  stashQueues(e) {
253
- e.Lt[0].push(...this.Lt[0]);
254
- e.Lt[1].push(...this.Lt[1]);
255
- this.Lt = [ [], [] ];
371
+ e.Mt[0].push(...this.Mt[0]);
372
+ e.Mt[1].push(...this.Mt[1]);
373
+ this.Mt = [ [], [] ];
256
374
  for (let t = 0; t < this.Qt.length; t++) {
257
375
  let i = this.Qt[t];
258
376
  let n = e.Qt[t];
259
377
  if (!n) {
260
378
  n = {
261
- Lt: [ [], [] ],
379
+ Mt: [ [], [] ],
262
380
  Qt: []
263
381
  };
264
382
  e.Qt[t] = n;
@@ -267,8 +385,8 @@ class Queue {
267
385
  }
268
386
  }
269
387
  restoreQueues(e) {
270
- this.Lt[0].push(...e.Lt[0]);
271
- this.Lt[1].push(...e.Lt[1]);
388
+ this.Mt[0].push(...e.Mt[0]);
389
+ this.Mt[1].push(...e.Mt[1]);
272
390
  for (let t = 0; t < e.Qt.length; t++) {
273
391
  const i = e.Qt[t];
274
392
  let n = this.Qt[t];
@@ -278,48 +396,48 @@ class Queue {
278
396
  }
279
397
 
280
398
  class GlobalQueue extends Queue {
281
- sn=false;
399
+ fn=false;
282
400
  // The current transaction-shaped batch: a plain ambient batch while no
283
401
  // transition is active, the active transition itself after initTransition.
284
- k=createBatch();
285
- static Ce;
402
+ m=createBatch();
286
403
  static Fe;
287
- static tt;
288
- static Vt=null;
404
+ static He;
405
+ static it;
406
+ static qt=null;
289
407
  // Store-side hook: drops a keyless affects() mark's identity scope when the
290
408
  // carrier node's last registration releases (wired by store.ts, mirroring
291
409
  // _clearOptimisticStore).
292
- static G=null;
410
+ static p=null;
293
411
  // affects()-side hooks (wired by affects.ts, mirroring _update): the mark
294
412
  // engine — count/register/release — lives with the feature. Every call site
295
413
  // is gated by state only that module creates, so `!` invocations are safe
296
414
  // once the gate holds.
415
+ static G=null;
297
416
  static M=null;
298
- static h=null;
299
- static j=null;
417
+ static N=null;
300
418
  // External-source bridge (wired by enableExternalSource(); null while no
301
419
  // config is active — including after _resetExternalSourceConfig()).
302
- static Rt=null;
303
- static Gt=null;
420
+ static Pt=null;
421
+ static ht=null;
304
422
  // Verdict-layer hooks (wired by verdict.ts when isPending()/latest() are
305
423
  // imported; null in apps that never use them). Call sites either guard for
306
424
  // null or sit behind state only the verdict layer can create (`!` is safe
307
425
  // there: `_pendingSignal`/`_latestValueComputed` are only ever assigned by
308
426
  // verdict.ts, and `pendingCheckActive`/`latestReadActive` only flip inside
309
427
  // isPending()/latest()).
310
- static Oe=null;
428
+ static Re=null;
311
429
  static de=null;
312
- static ye=null;
430
+ static me=null;
313
431
  static un=null;
314
- static Pt=null;
315
- static Dt=null;
432
+ static gt=null;
316
433
  static Ht=null;
317
- static Je=null;
318
- static p=null;
319
- static Bt=null;
434
+ static kt=null;
435
+ static et=null;
436
+ static k=null;
437
+ static Wt=null;
320
438
  // Re-asks probes whose verdict was provisionally suppressed by a fresh read
321
439
  // of a held value, once the transaction gains an async blocker (#3028).
322
- static wt=null;
440
+ static zt=null;
323
441
  // Optimistic-engine hooks (wired by core/optimistic.ts via
324
442
  // installOptimisticEngine(), called from verdict.ts / createOptimistic /
325
443
  // createOptimisticStore — every module that can create optimistic state).
@@ -327,40 +445,56 @@ class GlobalQueue extends Queue {
327
445
  // `_overrideValue` slot, a lane in `activeLanes`, an `_optimisticNodes`
328
446
  // entry, or a non-null `currentOptimisticLane`, so `!` invocations are safe
329
447
  // once the gate holds.
330
- static vt=null;
331
- static fn=null;
332
- static En=null;
448
+ static xt=null;
333
449
  static Tn=null;
334
450
  static dn=null;
335
- static Ft=null;
336
- static ht=null;
337
- static gt=null;
338
- static je=null;
339
- static $e=null;
340
- static ze=null;
341
451
  static In=null;
452
+ static Nn=null;
453
+ /** Patch-channel optimistic drain (next/patch.ts): optimistic emissions
454
+ * apply at lane-effect timing — visible in flight, unlike the regular
455
+ * effect queues an action stashes. Injected; null when unused. */
456
+ static ln=null;
457
+ static vt=null;
458
+ static Vt=null;
459
+ static bt=null;
460
+ static Be=null;
461
+ static $e=null;
462
+ /** Authoritative-view reader wakeup (until()): installed at first until() call.
463
+ * Call sites are gated by CONFIG_AUTHORITATIVE_OBSERVED, which only until()'s
464
+ * carve-out read can set, so `!` invocations are safe once the gate holds. */
465
+ static Ue=null;
466
+ static Xe=null;
467
+ static _n=null;
342
468
  flush() {
343
- if (this.sn) return;
469
+ if (this.fn) return;
344
470
  // Fast drain: nothing in flight but plain pending commits — no dirty
345
471
  // computeds, no queued effects, no child queues, no transitions/lanes/
346
472
  // optimistic state. Commit and go; anything a commit hook schedules
347
473
  // (companion snaps, store folds notifying subs) re-arms `scheduled`
348
474
  // 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;
475
+ if (activeTransition === null && dirtyQueue.EE < dirtyQueue.Qe && this.Mt[0].length === 0 && this.Mt[1].length === 0 && this.Qt.length === 0 && canUseSimpleSyncFlush(this)) {
476
+ this.fn = true;
351
477
  try {
478
+ // Sweep first: unobserved() pulls swept nodes out of the dirty heap,
479
+ // so a dormant memo dirtied in the same tick is reclaimed instead of
480
+ // recomputed (matching the old inline dispose-on-read counts).
481
+ sweepDormant();
352
482
  commitPendingNodes();
353
483
  } finally {
354
- this.sn = false;
484
+ this.fn = false;
355
485
  }
356
486
  clock++;
357
- scheduled = dirtyQueue.EE >= dirtyQueue.xe || this.Lt[0].length !== 0 || this.Lt[1].length !== 0 || this.k.yt.length !== 0;
487
+ scheduled = dirtyQueue.EE >= dirtyQueue.Qe || this.Mt[0].length !== 0 || this.Mt[1].length !== 0 || this.m.Lt.length !== 0;
358
488
  return;
359
489
  }
360
- this.sn = true;
490
+ this.fn = true;
361
491
  try {
362
492
  if (false) ;
363
- runHeap(dirtyQueue, GlobalQueue.Ce);
493
+ // Before runHeap for the same reason as the fast drain above; late
494
+ // subscribers (an effect reading a swept memo this flush) revive it,
495
+ // which is the pay-for-use contract.
496
+ sweepDormant();
497
+ runHeap(dirtyQueue, GlobalQueue.Fe);
364
498
  if (activeTransition) {
365
499
  const e = transitionComplete(activeTransition);
366
500
  if (!e) {
@@ -373,74 +507,74 @@ class GlobalQueue extends Queue {
373
507
  // zombies here. Height-adjust entries still process normally: a
374
508
  // dirtied one keeps its height flag and falls through to runHeap's
375
509
  // adjustHeight path on the next pass of the bucket.
376
- runHeap(zombieQueue, this.k === e ? cancelZombieRecompute : GlobalQueue.Ce);
510
+ runHeap(zombieQueue, this.m === e ? cancelZombieRecompute : GlobalQueue.Fe);
377
511
  // Detach: the stashed transition keeps its batch; ambient work that
378
512
  // follows lands in a fresh one. If the batch is already a separate
379
513
  // ambient one — action done() restored activeTransition without
380
514
  // adopting the batch, and an ordinary write landed there before
381
515
  // the scheduled flush (#2916) — keep it: replacing it would strand
382
516
  // its queued pending nodes with held _pendingValues forever.
383
- if (this.k === e) currentBatch = this.k = createBatch();
517
+ if (this.m === e) currentBatch = this.m = createBatch();
384
518
  // Run lane effects immediately (before stashing) - lanes with no pending async
385
519
  if (activeLanes.size) {
386
- GlobalQueue.dn(EFFECT_RENDER);
387
- GlobalQueue.dn(EFFECT_USER);
520
+ GlobalQueue.Nn(EFFECT_RENDER);
521
+ GlobalQueue.Nn(EFFECT_USER);
388
522
  }
389
- this.stashQueues(e.bt);
523
+ this.stashQueues(e.Bt);
390
524
  clock++;
391
525
  // A kept ambient batch may hold pending nodes (#2916): stay
392
526
  // scheduled so the outer drain loop commits them via the plain
393
527
  // flush path instead of leaving them until the next natural flush.
394
- scheduled = dirtyQueue.EE >= dirtyQueue.xe || this.k.yt.length > 0;
395
- reassignPendingTransition(e.yt);
528
+ scheduled = dirtyQueue.EE >= dirtyQueue.Qe || this.m.Lt.length > 0;
529
+ reassignPendingTransition(e.Lt);
396
530
  activeTransition = null;
397
531
  finalizePureQueue(null, true);
398
532
  return;
399
533
  }
400
534
  const t = activeTransition;
401
- const i = this.k;
402
- i !== t && i.yt.push(...t.yt);
403
- this.restoreQueues(t.bt);
535
+ const i = this.m;
536
+ i !== t && i.Lt.push(...t.Lt);
537
+ this.restoreQueues(t.Bt);
404
538
  transitions.delete(t);
405
539
  activeTransition = null;
406
- reassignPendingTransition(i.yt);
540
+ reassignPendingTransition(i.Lt);
407
541
  finalizePureQueue(t);
408
542
  if (i === t) {
409
543
  // Drop the dead Transition wrapper but keep its (drained) containers
410
544
  // as the ambient batch — late registrations during finalization live
411
545
  // there and must survive to the next flush.
412
546
  const e = createBatch();
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;
547
+ e.Lt = i.Lt;
548
+ e.ze = i.ze;
549
+ e.A = i.A;
550
+ e.En = i.En;
551
+ currentBatch = this.m = e;
418
552
  }
419
553
  } else {
420
554
  if (canUseSimpleSyncFlush(this)) {
421
555
  commitPendingNodes();
422
- if (dirtyQueue.EE >= dirtyQueue.xe) {
423
- runHeap(dirtyQueue, GlobalQueue.Ce);
556
+ if (dirtyQueue.EE >= dirtyQueue.Qe) {
557
+ runHeap(dirtyQueue, GlobalQueue.Fe);
424
558
  commitPendingNodes();
425
559
  }
426
560
  } else {
427
- if (transitions.size) runHeap(zombieQueue, GlobalQueue.Ce);
561
+ if (transitions.size) runHeap(zombieQueue, GlobalQueue.Fe);
428
562
  finalizePureQueue();
429
563
  }
430
564
  }
431
565
  clock++;
432
566
  // Check if finalization added items to the heap (from optimistic reversion)
433
- scheduled = dirtyQueue.EE >= dirtyQueue.xe;
567
+ scheduled = dirtyQueue.EE >= dirtyQueue.Qe;
434
568
  // Run lane effects first (for ready lanes), then regular effects
435
- activeLanes.size && GlobalQueue.dn(EFFECT_RENDER);
569
+ activeLanes.size && GlobalQueue.Nn(EFFECT_RENDER);
436
570
  this.run(EFFECT_RENDER);
437
- activeLanes.size && GlobalQueue.dn(EFFECT_USER);
571
+ activeLanes.size && GlobalQueue.Nn(EFFECT_USER);
438
572
  this.run(EFFECT_USER);
439
573
  if (false) ;
440
574
  if (false && !scheduled && !activeTransition && transitions.size === 0 && activeLanes.size === 0) ;
441
575
  if (false) ;
442
576
  } finally {
443
- this.sn = false;
577
+ this.fn = false;
444
578
  }
445
579
  }
446
580
  notify(e, t, i, n) {
@@ -452,16 +586,16 @@ class GlobalQueue extends Queue {
452
586
  // channel) updates display state on its way up but must be invisible
453
587
  // to completion accounting BY CONSTRUCTION: it never registers a
454
588
  // reporter and never counts toward the loading-boundary diagnostic.
455
- if (t?.i) return true;
589
+ if (t?.l) return true;
456
590
  if (activeTransition && t) {
457
591
  const i = t.source;
458
- let n = activeTransition.Ne.get(i);
459
- if (!n) activeTransition.Ne.set(i, n = new Set);
592
+ let n = activeTransition._e.get(i);
593
+ if (!n) activeTransition._e.set(i, n = new Set);
460
594
  const s = n.size;
461
595
  n.add(e);
462
596
  if (n.size !== s) {
463
597
  schedule();
464
- GlobalQueue.wt?.(activeTransition);
598
+ GlobalQueue.zt?.(activeTransition);
465
599
  }
466
600
  }
467
601
  }
@@ -470,8 +604,17 @@ class GlobalQueue extends Queue {
470
604
  return false;
471
605
  }
472
606
  initTransition(e) {
473
- if (e) e = currentTransition(e);
474
- if (e && e === activeTransition) return;
607
+ if (e) {
608
+ e = currentTransition(e);
609
+ // A finished transaction cannot be re-entered: its state is committed
610
+ // or reverted, so "rejoining" it (A26) is meaningless and re-activating
611
+ // it spins the drain loop (#3140). The refusal must be a bare return —
612
+ // redirecting the caller to a fresh batch would re-arm the loop with a
613
+ // new transaction identity each pass. Stamps are cleared at commit, so
614
+ // this is a belt for paths that hand over a chased-dead reference
615
+ // (merged chains, async settles racing completion).
616
+ if (e.sn === true || e === activeTransition) return;
617
+ }
475
618
  if (!e && activeTransition && activeTransition.Te === clock) return;
476
619
  if (!activeTransition) {
477
620
  activeTransition = e ?? createBatch();
@@ -483,7 +626,7 @@ class GlobalQueue extends Queue {
483
626
  }
484
627
  transitions.add(activeTransition);
485
628
  activeTransition.Te = clock;
486
- const t = this.k;
629
+ const t = this.m;
487
630
  if (t !== activeTransition) {
488
631
  // Adopt the ambient batch into the transaction, then make the
489
632
  // transaction the batch so later registrations land there directly.
@@ -492,34 +635,43 @@ class GlobalQueue extends Queue {
492
635
  // must not entangle unrelated writes to it; the same rule holds one hop
493
636
  // downstream: propagation never queues pended subscribers as pending
494
637
  // nodes, see propagateAffectsMark, #2893.
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);
638
+ for (let e = 0; e < t.Lt.length; e++) {
639
+ const i = t.Lt[e];
640
+ i.Ae = activeTransition;
641
+ activeTransition.Lt.push(i);
499
642
  }
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);
643
+ for (let e = 0; e < t.ze.length; e++) {
644
+ const i = t.ze[e];
645
+ i.Ae = activeTransition;
646
+ activeTransition.ze.push(i);
504
647
  }
505
- if (t.m.length) activeTransition.m.push(...t.m);
506
- for (const e of t.cn) activeTransition.cn.add(e);
648
+ if (t.A.length) activeTransition.A.push(...t.A);
649
+ for (const e of t.En) activeTransition.En.add(e);
507
650
  // Gated readers recorded against the ambient batch move with it: their
508
651
  // replay-at-commit now happens at the transaction's completion.
509
- if (t.ln.size) {
510
- for (const e of t.ln) activeTransition.ln.add(e);
511
- t.ln.clear();
652
+ if (t.cn.size) {
653
+ for (const e of t.cn) activeTransition.cn.add(e);
654
+ t.cn.clear();
512
655
  }
513
- currentBatch = this.k = activeTransition;
656
+ currentBatch = this.m = activeTransition;
514
657
  }
515
658
  for (const e of activeLanes) {
516
- if (!e._e) e._e = activeTransition;
659
+ if (!e.Ae) e.Ae = activeTransition;
517
660
  }
661
+ // A transaction's ambient window is one flush. Entering must therefore
662
+ // guarantee a flush: a transaction opened with no writes (an action whose
663
+ // first statements only await) otherwise leaves activeTransition and the
664
+ // adopted batch armed across the async gap, and the next unrelated work
665
+ // to arrive — an optimistic store's authoritative landing, a plain async
666
+ // settle — is adopted into a transaction it has nothing to do with
667
+ // (#3141). The scheduled flush parks the incomplete transaction through
668
+ // the normal machinery and detaches the ambient slots first.
669
+ schedule();
518
670
  }
519
671
  }
520
672
 
521
673
  function queuePendingNode(e) {
522
- currentBatch.yt.push(e);
674
+ currentBatch.Lt.push(e);
523
675
  }
524
676
 
525
677
  // Sticky: flips true on the first refresh() ever (the only setter of
@@ -543,7 +695,7 @@ function armReaskClear() {
543
695
  function insertSubs(e, t = false) {
544
696
  // §12d: stamp before walking — setSignal's staged-rewrite fast path skips
545
697
  // the next walk for this node while the epoch holds (marking is idempotent).
546
- e._t = notifyEpoch;
698
+ e.It = notifyEpoch;
547
699
  // Get source lane: prefer node's own lane over current context
548
700
  // This is important for isPending signals which need their own lane to flush immediately
549
701
  // Presence bits gate the optional-slot probes (see constants.ts): one
@@ -551,14 +703,14 @@ function insertSubs(e, t = false) {
551
703
  // lookups in the hottest notify loop. Bits are sticky — the field read
552
704
  // stays authoritative when a bit is set.
553
705
  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;
706
+ const n = (i & CONFIG_HAS_LANE ? e.o?.Je : undefined) || currentOptimisticLane;
707
+ const s = (i & CONFIG_HAS_SNAPSHOT) !== 0 && e.o?.We !== undefined;
708
+ const r = reaskArmed;
709
+ for (let i = e.u; i !== null; i = i.ae) {
710
+ const e = i.ce;
559
711
  // A value-change notification is a new question for the subscriber: any
560
712
  // pending re-ask mark (refresh) it carried is superseded.
561
- if (o) e.ie &= ~REACTIVE_REASK;
713
+ if (r) e.ie &= ~REACTIVE_REASK;
562
714
  // Missed-wake latch (#3037): this write is landing while the subscriber
563
715
  // is mid-recompute (a nested pull committing beneath its reads), and the
564
716
  // heap refuses RECOMPUTING nodes. A gen-current link means the pass
@@ -567,7 +719,7 @@ function insertSubs(e, t = false) {
567
719
  // pass either re-reads them fresh or trims them), and neither does the
568
720
  // tail link: it is the read IN FLIGHT — read() links before it pulls, so
569
721
  // 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;
722
+ if (e.ie & REACTIVE_RECOMPUTING_DEPS && i.Ft === e.Ke && i !== e.je) e.ie |= REACTIVE_MISSED_WAKE;
571
723
  if (s && e.T & CONFIG_IN_SNAPSHOT_SCOPE) {
572
724
  e.ie |= REACTIVE_SNAPSHOT_STALE;
573
725
  continue;
@@ -578,7 +730,7 @@ function insertSubs(e, t = false) {
578
730
  } else if (t) {
579
731
  e.ie |= REACTIVE_OPTIMISTIC_DIRTY;
580
732
  // No source lane means reversion - clear subscriber's lane so effects go to regular queue
581
- if (e.o) e.o.Be = undefined;
733
+ if (e.o) e.o.Je = undefined;
582
734
  }
583
735
  enqueueSub(e);
584
736
  }
@@ -586,7 +738,7 @@ function insertSubs(e, t = false) {
586
738
 
587
739
  function commitPendingNode(e) {
588
740
  const t = e;
589
- if (!t.Se) {
741
+ if (!t.oe) {
590
742
  if (e.Pe !== NOT_PENDING) {
591
743
  e.be = e.Pe;
592
744
  e.Pe = NOT_PENDING;
@@ -598,15 +750,15 @@ function commitPendingNode(e) {
598
750
  e.be = e.Pe;
599
751
  e.Pe = NOT_PENDING;
600
752
  // Set _modified for effects, but not for tracked effects (they handle their own scheduling)
601
- if (e.Re && e.Re !== EFFECT_TRACKED) e.Xe = true;
753
+ if (e.he && e.he !== EFFECT_TRACKED) e.tt = true;
602
754
  }
603
755
  // The committed hold is the first observable answer for a loading-window
604
756
  // node — the window closes here, not at compute time (#2990). Unconditional
605
757
  // store to an always-present computed slot.
606
- t.Ie = false;
758
+ t.Ne = false;
607
759
  t.ie &= ~REACTIVE_MANUAL_WRITE;
608
760
  if (!(t.S & STATUS_PENDING)) t.S &= ~STATUS_UNINITIALIZED;
609
- if (t.o != null && (t.o.qe !== null || t.o.We !== null)) GlobalQueue.Fe(t, false, true);
761
+ if (t.o != null && (t.o.Ye !== null || t.o.qe !== null)) GlobalQueue.He(t, false, true);
610
762
  if (e.T & CONFIG_HAS_COMPANIONS) GlobalQueue.un(e);
611
763
  }
612
764
 
@@ -621,13 +773,49 @@ function setStoreCommitHook(e) {
621
773
  storeCommitHook = e;
622
774
  }
623
775
 
776
+ /** Patch-channel release hook (next/patch.ts): transition-stamped patch
777
+ * emissions are released when THEIR batch commits. Transitions never
778
+ * abort: failed actions still commit (only optimistic overrides revert),
779
+ * and merged-away transitions hand their stash to the survivor
780
+ * (mergeTransitionState) — every stash drains exactly once. Injected like
781
+ * storeCommitHook to stay tree-shakeable. */ let patchCommitHook = null;
782
+
783
+ function setPatchCommitHook(e) {
784
+ patchCommitHook = e;
785
+ }
786
+
787
+ /** Held truth committed this finalize, awaiting its post-revert wake (see
788
+ * finalizePureQueue): the commit IS the reveal, but subscribers must not
789
+ * re-derive until the settling transaction's optimistic overrides have
790
+ * reverted — a commit-time wake recomputes them in the window where
791
+ * confirming truth is committed and the override still displays, a torn
792
+ * frame no timeline contains. */ const heldRevealed = [];
793
+
624
794
  function commitPendingNodes() {
625
- const e = currentBatch.yt;
795
+ const e = currentBatch.Lt;
626
796
  for (let t = 0; t < e.length; t++) {
627
- commitPendingNode(e[t]);
797
+ const i = e[t];
798
+ commitPendingNode(i);
799
+ // The stamp dies with the commit (#3143) — symmetric with
800
+ // resolveOptimisticNodes clearing optimistic stamps. A stamp outliving
801
+ // its transaction let any later write (even a value-equal no-op, which
802
+ // re-opens before the equality bail) resurrect the finished transaction;
803
+ // a boundary flag rewritten every finalize pass then spun the drain loop
804
+ // forever (#3140). The held-truth mark dies the same death — the commit
805
+ // IS the reveal — but its wake defers to the post-revert pass: ordinary
806
+ // subscribers were masked to committed all hold (some re-derived against
807
+ // that old view and cached it), and commits are otherwise silent
808
+ // (staging already notified), so without a wake they'd hold the old
809
+ // world forever.
810
+ i.Ae = null;
811
+ if (i.T & CONFIG_HELD_TRUTH) {
812
+ i.T &= ~CONFIG_HELD_TRUTH;
813
+ heldRevealed.push(i);
814
+ }
628
815
  }
629
816
  e.length = 0;
630
817
  storeCommitHook?.();
818
+ patchCommitHook?.(currentBatch);
631
819
  }
632
820
 
633
821
  function finalizePureQueue(e = null, t = false) {
@@ -636,25 +824,25 @@ function finalizePureQueue(e = null, t = false) {
636
824
  const i = !t;
637
825
  if (i) commitPendingNodes();
638
826
  if (!t && globalQueue.Qt.length) checkBoundaryChildren(globalQueue);
639
- const n = dirtyQueue.EE >= dirtyQueue.xe;
640
- if (n) runHeap(dirtyQueue, GlobalQueue.Ce);
827
+ const n = dirtyQueue.EE >= dirtyQueue.Qe;
828
+ if (n) runHeap(dirtyQueue, GlobalQueue.Fe);
641
829
  if (i) {
642
830
  if (n) commitPendingNodes();
643
831
  // The settling batch: the completing transaction's, or the ambient one.
644
- const t = e ?? globalQueue.k;
832
+ const t = e ?? globalQueue.m;
645
833
  // Optimistic reversion: a non-empty batch means _optimisticWrite ran,
646
834
  // which installed the engine's hooks.
647
- if (t.Ke.length) GlobalQueue.fn(t.Ke);
835
+ if (t.ze.length) GlobalQueue.Tn(t.ze);
648
836
  // Replay entanglement: subs recorded by the read-time gate get rescheduled
649
837
  // so they re-run with the now-committed values visible. The ambient batch
650
838
  // replays too — laneReadsCommitted records readers whose committed-view
651
839
  // read hid a same-tick plain write that just committed above (#2963).
652
- if (t.ln.size) {
653
- for (const e of t.ln) {
840
+ if (t.cn.size) {
841
+ for (const e of t.cn) {
654
842
  if (e.ie & REACTIVE_DISPOSED) continue;
655
843
  enqueueSub(e);
656
844
  }
657
- t.ln.clear();
845
+ t.cn.clear();
658
846
  // A completing transition keeps the outer flush loop alive by itself;
659
847
  // the ambient batch needs the re-arm or the replay sits in the heap
660
848
  // until the next unrelated write.
@@ -666,18 +854,34 @@ function finalizePureQueue(e = null, t = false) {
666
854
  // held boundary display state through the visual channel, and their
667
855
  // release is the display-state update point — re-run the boundary sweep
668
856
  // (the earlier sweep above ran while the marks were still live).
669
- if (t.m.length) {
670
- GlobalQueue.M(t.m);
857
+ if (t.A.length) {
858
+ GlobalQueue.G(t.A);
671
859
  if (globalQueue.Qt.length) checkBoundaryChildren(globalQueue);
672
860
  }
673
861
  // A non-empty set means trackOptimisticStore ran, which installed the
674
862
  // hook; the hook iterates, clears, and schedules (keeping the loop out of
675
863
  // core lets esbuild shake it — rollup already folds the null guard). The
676
864
  // completing transition scopes the clear to its own layer keys (#2899).
677
- if (t.cn.size) GlobalQueue.Vt(t.cn, e);
865
+ if (t.En.size) GlobalQueue.qt(t.En, e);
866
+ // Held-truth reveal wake (#3164), post-revert by construction: this
867
+ // finalize committed confirming truth whose subscribers were masked all
868
+ // hold — some re-derived against the committed view (the staging, or a
869
+ // confirming carrier's landing, notified them as a plain write) and
870
+ // cached it, and stash-restored applies may carry those torn values.
871
+ // Waking and recomputing HERE — after _resolveOptimistic and the store
872
+ // clears above — means every apply paints the settled view; a wake at
873
+ // commit time would recompute them in the window where truth is
874
+ // committed but the settling transaction's overrides still display.
875
+ if (heldRevealed.length !== 0) {
876
+ while (heldRevealed.length) insertSubs(heldRevealed.pop());
877
+ if (dirtyQueue.EE >= dirtyQueue.Qe) {
878
+ runHeap(dirtyQueue, GlobalQueue.Fe);
879
+ commitPendingNodes();
880
+ }
881
+ }
678
882
  sweepTransientStoreNodes();
679
883
  // Lanes only enter activeLanes through the engine's getOrCreateLane.
680
- if (activeLanes.size) GlobalQueue.Tn(e);
884
+ if (activeLanes.size) GlobalQueue.In(e);
681
885
  }
682
886
  }
683
887
 
@@ -706,7 +910,7 @@ function checkBoundaryChildren(e) {
706
910
 
707
911
  function reassignPendingTransition(e) {
708
912
  for (let t = 0; t < e.length; t++) {
709
- e[t]._e = activeTransition;
913
+ e[t].Ae = activeTransition;
710
914
  }
711
915
  }
712
916
 
@@ -717,7 +921,7 @@ const globalQueue = new GlobalQueue;
717
921
  // property hop through `_batch` was a measured instruction-count regression
718
922
  // (CodSpeed update1to1, PR #2905). The field stays authoritative for
719
923
  // cross-module readers; every `_batch` assignment updates both.
720
- let currentBatch = globalQueue.k;
924
+ let currentBatch = globalQueue.m;
721
925
 
722
926
  function flush(e) {
723
927
  if (e) {
@@ -734,7 +938,7 @@ function flush(e) {
734
938
  }
735
939
  }
736
940
  }
737
- if (globalQueue.sn) {
941
+ if (globalQueue.fn) {
738
942
  return;
739
943
  }
740
944
  if (halted) return;
@@ -752,21 +956,21 @@ function runQueue(e, t) {
752
956
  function reporterBlocksSource(e, t) {
753
957
  if (e.ie & (REACTIVE_ZOMBIE | REACTIVE_DISPOSED)) return false;
754
958
  if (e.o?.le?.has(t)) return true;
755
- for (let i = e.nt; i; i = i.it) {
756
- let e = i.ut;
959
+ for (let i = e.ut; i; i = i.lt) {
960
+ let e = i.ot;
757
961
  while (e) {
758
- if (e === t || e.lt === t) return true;
759
- e = e.o?.It;
962
+ if (e === t || e.st === t) return true;
963
+ e = e.o?.Tt;
760
964
  }
761
965
  }
762
966
  return !!(e.S & STATUS_PENDING && e.o?._ instanceof NotReadyError && e.o?._.source === t);
763
967
  }
764
968
 
765
969
  function transitionComplete(e) {
766
- if (e.an) return true;
767
- if (e.oe.length) return false;
970
+ if (e.sn) return true;
971
+ if (e.ue.length) return false;
768
972
  let t = true;
769
- for (const [i, n] of e.Ne) {
973
+ for (const [i, n] of e._e) {
770
974
  let s = false;
771
975
  for (const e of n) {
772
976
  if (reporterBlocksSource(e, i)) {
@@ -775,7 +979,7 @@ function transitionComplete(e) {
775
979
  }
776
980
  n.delete(e);
777
981
  }
778
- if (!s) e.Ne.delete(i); else if (i.S & STATUS_PENDING && i.o?._?.source === i) {
982
+ if (!s) e._e.delete(i); else if (i.S & STATUS_PENDING && i.o?._?.source === i) {
779
983
  t = false;
780
984
  break;
781
985
  }
@@ -783,13 +987,13 @@ function transitionComplete(e) {
783
987
  // Override blockage lives with the engine (absent hook = "no optimistic
784
988
  // blockage"); the hook's loops over _optimisticNodes/_optimisticStores are
785
989
  // no-ops when the transition holds neither, so no pre-check is needed.
786
- if (t && GlobalQueue.En?.(e)) t = false;
787
- t && (e.an = true);
990
+ if (t && GlobalQueue.dn?.(e)) t = false;
991
+ t && (e.sn = true);
788
992
  return t;
789
993
  }
790
994
 
791
995
  function currentTransition(e) {
792
- while (e.an && typeof e.an === "object") e = e.an;
996
+ while (e.sn && typeof e.sn === "object") e = e.sn;
793
997
  return e;
794
998
  }
795
999
 
@@ -803,4 +1007,27 @@ function runInTransition(e, t) {
803
1007
  }
804
1008
  }
805
1009
 
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 };
1010
+ /** Run `fn` with `transition` as BOTH the ambient transaction and the
1011
+ * registration batch, restoring both after. runInTransition alone is not
1012
+ * enough for code that WRITES on behalf of a transaction from inside someone
1013
+ * else's window (optimistic replay re-arming a still-open action's edits
1014
+ * during a landing commit, #3123): registrations route through the queue's
1015
+ * batch pointer, and a bare activeTransition swap leaves them in the ambient
1016
+ * batch — a plain batch "completes" at the next flush and reverts optimistic
1017
+ * registrations that were supposed to live with the transaction.
1018
+ * initTransition is the wrong tool here: it MERGES the currently ambient
1019
+ * transaction into the target, entangling whatever the interrupted window
1020
+ * belonged to. */ function runAsTransitionBatch(e, t) {
1021
+ const i = activeTransition;
1022
+ const n = globalQueue.m;
1023
+ try {
1024
+ activeTransition = currentTransition(e);
1025
+ currentBatch = globalQueue.m = activeTransition;
1026
+ return t();
1027
+ } finally {
1028
+ activeTransition = i;
1029
+ currentBatch = globalQueue.m = n;
1030
+ }
1031
+ }
1032
+
1033
+ export { GlobalQueue, Queue, activeAffectsMarks, activeLanes, activeTransition, armReaskClear, assignOrMergeLane, bumpNotifyEpoch, clock, currentTransition, dirtyQueue, enforceLoadingBoundary, entangleConfirmingTransitions, finalizePureQueue, findLane, flush, globalQueue, haltReactivity, hasActiveOverride, insertSubs, notifyEpoch, patchCommitHook, projectionWriteActive, queuePendingNode, reaskArmed, resetErrorHalt, runAsTransitionBatch, runInTransition, schedule, setPatchCommitHook, setProjectionWriteActive, setStoreCommitHook, shiftAffectsMarks, storeCommitHook, zombieQueue };