@solidjs/signals 2.0.0-rc.4 → 2.0.0-rc.6

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 (57) hide show
  1. package/dist/dev.js +1618 -232
  2. package/dist/node.cjs +2262 -1199
  3. package/dist/prod/boundaries.js +4 -1
  4. package/dist/prod/core/async.js +152 -101
  5. package/dist/prod/core/constants.js +55 -1
  6. package/dist/prod/core/core.js +305 -234
  7. package/dist/prod/core/effect.js +28 -28
  8. package/dist/prod/core/error.js +13 -1
  9. package/dist/prod/core/external.js +2 -2
  10. package/dist/prod/core/graph.js +27 -27
  11. package/dist/prod/core/heap.js +30 -30
  12. package/dist/prod/core/lanes.js +32 -32
  13. package/dist/prod/core/optimistic.js +57 -57
  14. package/dist/prod/core/owner.js +34 -34
  15. package/dist/prod/core/scheduler.js +346 -152
  16. package/dist/prod/core/verdict.js +122 -65
  17. package/dist/prod/index.js +3 -3
  18. package/dist/prod/map.js +106 -106
  19. package/dist/prod/signals.js +321 -26
  20. package/dist/prod/store/next/optimistic.js +363 -151
  21. package/dist/prod/store/next/patch.js +6 -6
  22. package/dist/prod/store/next/projection.js +25 -21
  23. package/dist/prod/store/next/store.js +223 -113
  24. package/dist/prod/store/store.js +2 -2
  25. package/dist/types/core/async.d.ts +2 -0
  26. package/dist/types/core/attribution-hooks.d.ts +11 -0
  27. package/dist/types/core/attribution.d.ts +57 -4
  28. package/dist/types/core/constants.d.ts +54 -0
  29. package/dist/types/core/core.d.ts +19 -20
  30. package/dist/types/core/dev.d.ts +8 -2
  31. package/dist/types/core/error.d.ts +9 -0
  32. package/dist/types/core/index.d.ts +2 -2
  33. package/dist/types/core/scheduler.d.ts +39 -0
  34. package/dist/types/core/types.d.ts +12 -0
  35. package/dist/types/index.d.ts +3 -3
  36. package/dist/types/signals.d.ts +108 -11
  37. package/dist/types/store/next/optimistic.d.ts +13 -10
  38. package/dist/types/store/next/projection.d.ts +1 -1
  39. package/dist/types/store/next/store.d.ts +22 -0
  40. package/dist/types/store/next/target.d.ts +25 -0
  41. package/dist/types-cjs/core/async.d.cts +2 -0
  42. package/dist/types-cjs/core/attribution-hooks.d.cts +11 -0
  43. package/dist/types-cjs/core/attribution.d.cts +57 -4
  44. package/dist/types-cjs/core/constants.d.cts +54 -0
  45. package/dist/types-cjs/core/core.d.cts +19 -20
  46. package/dist/types-cjs/core/dev.d.cts +8 -2
  47. package/dist/types-cjs/core/error.d.cts +9 -0
  48. package/dist/types-cjs/core/index.d.cts +2 -2
  49. package/dist/types-cjs/core/scheduler.d.cts +39 -0
  50. package/dist/types-cjs/core/types.d.cts +12 -0
  51. package/dist/types-cjs/index.d.cts +3 -3
  52. package/dist/types-cjs/signals.d.cts +108 -11
  53. package/dist/types-cjs/store/next/optimistic.d.cts +13 -10
  54. package/dist/types-cjs/store/next/projection.d.cts +1 -1
  55. package/dist/types-cjs/store/next/store.d.cts +22 -0
  56. package/dist/types-cjs/store/next/target.d.cts +25 -0
  57. 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, 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
 
@@ -10,9 +10,9 @@ import { sweepDormant } from "./graph.js";
10
10
 
11
11
  import { runHeap, deleteFromHeap, enqueueSub } from "./heap.js";
12
12
 
13
- import { activeLanes, assignOrMergeLane, findLane } from "./lanes.js";
13
+ import { activeLanes, assignOrMergeLane, findLane, hasActiveOverride } from "./lanes.js";
14
14
 
15
- export { getOrCreateLane, hasActiveOverride, mergeLanes, resolveLane } from "./lanes.js";
15
+ export { getOrCreateLane, mergeLanes, resolveLane } from "./lanes.js";
16
16
 
17
17
  import { devCheckFlushStart, devCheckActiveOverrides, devCensusCompanions, devCheckQuiescent } from "./invariants.js";
18
18
 
@@ -21,14 +21,14 @@ const transitions = new Set;
21
21
  const dirtyQueue = {
22
22
  eE: new Array(2e3).fill(undefined),
23
23
  tE: false,
24
- xe: 0,
24
+ Qe: 0,
25
25
  EE: 0
26
26
  };
27
27
 
28
28
  const zombieQueue = {
29
29
  eE: new Array(2e3).fill(undefined),
30
30
  tE: false,
31
- xe: 0,
31
+ Qe: 0,
32
32
  EE: 0
33
33
  };
34
34
 
@@ -63,7 +63,7 @@ const transientStoreNodes = new Set;
63
63
 
64
64
  function canUseSimpleSyncFlush(e) {
65
65
  const t = e.m;
66
- return transitions.size === 0 && activeLanes.size === 0 && e.Qt.length === 0 && t.Ke.length === 0 && t.A.length === 0 && t.Tn.size === 0 && transientStoreNodes.size === 0;
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;
67
67
  }
68
68
 
69
69
  function sweepTransientStoreNodes() {
@@ -73,14 +73,14 @@ function sweepTransientStoreNodes() {
73
73
  transientStoreNodes.delete(e);
74
74
  continue;
75
75
  }
76
- if (e.Pe !== NOT_PENDING) continue;
77
- if (e.o?.De !== undefined && e.o?.De !== NOT_PENDING) continue;
76
+ if (e.Re !== NOT_PENDING) continue;
77
+ if (e.o?.Pe !== undefined && e.o?.Pe !== NOT_PENDING) continue;
78
78
  // A live affects() mark keeps the node addressable: sweeping it would
79
79
  // detach the refcount from the slot (a fresh probe would upsert a new,
80
80
  // unmarked node for the same property).
81
81
  if (e.o?.t) continue;
82
82
  transientStoreNodes.delete(e);
83
- e.o?.ft?.();
83
+ e.o?.Et?.();
84
84
  }
85
85
  }
86
86
 
@@ -108,31 +108,31 @@ function setProjectionWriteActive(e) {
108
108
  */ function createBatch() {
109
109
  return {
110
110
  Te: clock,
111
- yt: [],
112
- Ne: new Map,
113
- Ke: [],
111
+ Lt: [],
112
+ _e: new Map,
113
+ ze: [],
114
114
  A: [],
115
- Tn: new Set,
115
+ En: new Set,
116
116
  ue: [],
117
- bt: {
118
- Lt: [ [], [] ],
117
+ Bt: {
118
+ Mt: [ [], [] ],
119
119
  Qt: []
120
120
  },
121
- fn: false,
121
+ sn: false,
122
122
  cn: new Set
123
123
  };
124
124
  }
125
125
 
126
126
  function mergeTransitionState(e, t) {
127
- t.fn = e;
127
+ t.sn = e;
128
128
  e.ue.push(...t.ue);
129
- for (const i of activeLanes) if (i._e === t) i._e = e;
130
- if (t.Ke.length) {
129
+ for (const i of activeLanes) if (i.Ae === t) i.Ae = e;
130
+ if (t.ze.length) {
131
131
  // Move (don't copy): the global queue's batch may still be the outgoing
132
132
  // transition, and the adoption pass in initTransition would re-push its
133
133
  // contents into the target — duplicating every entry.
134
- e.Ke.push(...t.Ke);
135
- t.Ke.length = 0;
134
+ e.ze.push(...t.ze);
135
+ t.ze.length = 0;
136
136
  }
137
137
  if (t.A.length) {
138
138
  // Move (don't copy): the global queue's batch may still be the outgoing
@@ -141,18 +141,18 @@ function mergeTransitionState(e, t) {
141
141
  e.A.push(...t.A);
142
142
  t.A.length = 0;
143
143
  }
144
- for (const i of t.Tn) e.Tn.add(i);
144
+ for (const i of t.En) e.En.add(i);
145
145
  // Patch-channel stash (store/next/patch.ts): entries held for the outgoing
146
146
  // transition must ride the merge like every other per-transition
147
147
  // collection — releaseBatch only reads the COMMITTING transition's stash,
148
148
  // so a stranded sidecar would silently drop its patches. Move (don't
149
149
  // copy), same aliasing rule as the collections above. The field is an
150
150
  // expando so this module stays free of patch imports (pay-for-use).
151
- const i = t.Mt;
151
+ const i = t.wt;
152
152
  if (i !== undefined) {
153
- t.Mt = undefined;
154
- let n = e.Mt;
155
- if (n !== undefined) n.push(...i); else n = e.Mt = i;
153
+ t.wt = undefined;
154
+ let n = e.wt;
155
+ if (n !== undefined) n.push(...i); else n = e.wt = i;
156
156
  // Retarget the entries' coalescing stamps to the surviving stash
157
157
  // (opaque backref contract with store/next/patch.ts): without this a
158
158
  // post-merge emission misses the stamp and pushes a SECOND entry —
@@ -162,14 +162,110 @@ function mergeTransitionState(e, t) {
162
162
  if (t !== undefined && t.qe === i[e]) t.qa = n;
163
163
  }
164
164
  }
165
- for (const [i, n] of t.Ne) {
166
- let t = e.Ne.get(i);
167
- if (!t) e.Ne.set(i, t = new Set);
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);
168
168
  for (const e of n) t.add(e);
169
169
  }
170
170
  for (const i of t.cn) e.cn.add(i);
171
171
  }
172
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 r = n.ot;
206
+ if (r.Re !== NOT_PENDING) {
207
+ const e = r.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 s = n === null ? currentBatch.Lt : n !== t && n.sn !== true ? n.Lt : null;
216
+ if (s !== null) i = stealEntangledCargo(s, 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.ge && !(t.T & CONFIG_AUTHORITATIVE_READ)) enqueueSub(t);
261
+ }
262
+ }
263
+ }
264
+ e.length = 0;
265
+ transitions.add(t);
266
+ return true;
267
+ }
268
+
173
269
  function schedule() {
174
270
  if (halted) {
175
271
  notifyHalted();
@@ -177,7 +273,7 @@ function schedule() {
177
273
  }
178
274
  if (scheduled) return;
179
275
  scheduled = true;
180
- if (!syncDepth && !globalQueue.En && !projectionWriteActive) queueMicrotask(flush);
276
+ if (!syncDepth && !globalQueue.fn && !projectionWriteActive) queueMicrotask(flush);
181
277
  }
182
278
 
183
279
  /**
@@ -212,9 +308,9 @@ let queueRunToken = 0;
212
308
 
213
309
  class Queue {
214
310
  ke=null;
215
- Lt=[ [], [] ];
311
+ Mt=[ [], [] ];
216
312
  Qt=[];
217
- Vt=0;
313
+ jt=0;
218
314
  created=clock;
219
315
  addChild(e) {
220
316
  this.Qt.push(e);
@@ -232,9 +328,9 @@ class Queue {
232
328
  return false;
233
329
  }
234
330
  run(e) {
235
- if (this.Lt[e - 1].length) {
236
- const t = this.Lt[e - 1];
237
- this.Lt[e - 1] = [];
331
+ if (this.Mt[e - 1].length) {
332
+ const t = this.Mt[e - 1];
333
+ this.Mt[e - 1] = [];
238
334
  runQueue(t, e);
239
335
  }
240
336
  // Effects run here can dispose owners, and disposal removes queues from
@@ -247,11 +343,11 @@ class Queue {
247
343
  const t = this.Qt;
248
344
  const i = ++queueRunToken;
249
345
  for (let n = 0; n < t.length; ) {
250
- const s = t[n];
251
- if (s.Vt !== i) {
252
- s.Vt = i;
253
- s.run?.(e);
254
- if (t[n] !== s) {
346
+ const r = t[n];
347
+ if (r.jt !== i) {
348
+ r.jt = i;
349
+ r.run?.(e);
350
+ if (t[n] !== r) {
255
351
  n = 0;
256
352
  continue;
257
353
  }
@@ -264,23 +360,23 @@ class Queue {
264
360
  // Route to lane's effect queue if we're in an optimistic recomputation
265
361
  if (currentOptimisticLane) {
266
362
  const i = findLane(currentOptimisticLane);
267
- i.rn[e - 1].push(t);
363
+ i.tn[e - 1].push(t);
268
364
  } else {
269
- this.Lt[e - 1].push(t);
365
+ this.Mt[e - 1].push(t);
270
366
  }
271
367
  }
272
368
  schedule();
273
369
  }
274
370
  stashQueues(e) {
275
- e.Lt[0].push(...this.Lt[0]);
276
- e.Lt[1].push(...this.Lt[1]);
277
- this.Lt = [ [], [] ];
371
+ e.Mt[0].push(...this.Mt[0]);
372
+ e.Mt[1].push(...this.Mt[1]);
373
+ this.Mt = [ [], [] ];
278
374
  for (let t = 0; t < this.Qt.length; t++) {
279
375
  let i = this.Qt[t];
280
376
  let n = e.Qt[t];
281
377
  if (!n) {
282
378
  n = {
283
- Lt: [ [], [] ],
379
+ Mt: [ [], [] ],
284
380
  Qt: []
285
381
  };
286
382
  e.Qt[t] = n;
@@ -289,8 +385,8 @@ class Queue {
289
385
  }
290
386
  }
291
387
  restoreQueues(e) {
292
- this.Lt[0].push(...e.Lt[0]);
293
- this.Lt[1].push(...e.Lt[1]);
388
+ this.Mt[0].push(...e.Mt[0]);
389
+ this.Mt[1].push(...e.Mt[1]);
294
390
  for (let t = 0; t < e.Qt.length; t++) {
295
391
  const i = e.Qt[t];
296
392
  let n = this.Qt[t];
@@ -300,14 +396,14 @@ class Queue {
300
396
  }
301
397
 
302
398
  class GlobalQueue extends Queue {
303
- En=false;
399
+ fn=false;
304
400
  // The current transaction-shaped batch: a plain ambient batch while no
305
401
  // transition is active, the active transition itself after initTransition.
306
402
  m=createBatch();
307
- static Ce;
308
403
  static Fe;
309
- static tt;
310
- static Bt=null;
404
+ static He;
405
+ static it;
406
+ static qt=null;
311
407
  // Store-side hook: drops a keyless affects() mark's identity scope when the
312
408
  // carrier node's last registration releases (wired by store.ts, mirroring
313
409
  // _clearOptimisticStore).
@@ -321,27 +417,27 @@ class GlobalQueue extends Queue {
321
417
  static N=null;
322
418
  // External-source bridge (wired by enableExternalSource(); null while no
323
419
  // config is active — including after _resetExternalSourceConfig()).
324
- static Rt=null;
325
- static Gt=null;
420
+ static Pt=null;
421
+ static ht=null;
326
422
  // Verdict-layer hooks (wired by verdict.ts when isPending()/latest() are
327
423
  // imported; null in apps that never use them). Call sites either guard for
328
424
  // null or sit behind state only the verdict layer can create (`!` is safe
329
425
  // there: `_pendingSignal`/`_latestValueComputed` are only ever assigned by
330
426
  // verdict.ts, and `pendingCheckActive`/`latestReadActive` only flip inside
331
427
  // isPending()/latest()).
332
- static Oe=null;
428
+ static Ue=null;
333
429
  static de=null;
334
- static ye=null;
430
+ static me=null;
335
431
  static un=null;
336
- static Pt=null;
337
- static Dt=null;
432
+ static gt=null;
338
433
  static Ht=null;
339
- static Je=null;
434
+ static kt=null;
435
+ static et=null;
340
436
  static k=null;
341
- static wt=null;
437
+ static Wt=null;
342
438
  // Re-asks probes whose verdict was provisionally suppressed by a fresh read
343
439
  // of a held value, once the transaction gains an async blocker (#3028).
344
- static jt=null;
440
+ static zt=null;
345
441
  // Optimistic-engine hooks (wired by core/optimistic.ts via
346
442
  // installOptimisticEngine(), called from verdict.ts / createOptimistic /
347
443
  // createOptimisticStore — every module that can create optimistic state).
@@ -349,31 +445,35 @@ class GlobalQueue extends Queue {
349
445
  // `_overrideValue` slot, a lane in `activeLanes`, an `_optimisticNodes`
350
446
  // entry, or a non-null `currentOptimisticLane`, so `!` invocations are safe
351
447
  // once the gate holds.
352
- static kt=null;
448
+ static xt=null;
449
+ static Tn=null;
353
450
  static dn=null;
354
451
  static In=null;
355
452
  static Nn=null;
356
- static _n=null;
357
453
  /** Patch-channel optimistic drain (next/patch.ts): optimistic emissions
358
454
  * apply at lane-effect timing — visible in flight, unlike the regular
359
455
  * effect queues an action stashes. Injected; null when unused. */
360
456
  static ln=null;
361
- static Ft=null;
362
- static ht=null;
363
- static gt=null;
364
- static je=null;
457
+ static vt=null;
458
+ static Vt=null;
459
+ static bt=null;
460
+ static Be=null;
365
461
  static $e=null;
366
- static ze=null;
367
- static An=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 he=null;
466
+ static Xe=null;
467
+ static _n=null;
368
468
  flush() {
369
- if (this.En) return;
469
+ if (this.fn) return;
370
470
  // Fast drain: nothing in flight but plain pending commits — no dirty
371
471
  // computeds, no queued effects, no child queues, no transitions/lanes/
372
472
  // optimistic state. Commit and go; anything a commit hook schedules
373
473
  // (companion snaps, store folds notifying subs) re-arms `scheduled`
374
474
  // below and the outer drain loop takes the full spine next round.
375
- if (activeTransition === null && dirtyQueue.EE < dirtyQueue.xe && this.Lt[0].length === 0 && this.Lt[1].length === 0 && this.Qt.length === 0 && canUseSimpleSyncFlush(this)) {
376
- this.En = 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;
377
477
  try {
378
478
  // Sweep first: unobserved() pulls swept nodes out of the dirty heap,
379
479
  // so a dormant memo dirtied in the same tick is reclaimed instead of
@@ -381,20 +481,20 @@ class GlobalQueue extends Queue {
381
481
  sweepDormant();
382
482
  commitPendingNodes();
383
483
  } finally {
384
- this.En = false;
484
+ this.fn = false;
385
485
  }
386
486
  clock++;
387
- scheduled = dirtyQueue.EE >= dirtyQueue.xe || this.Lt[0].length !== 0 || this.Lt[1].length !== 0 || this.m.yt.length !== 0;
487
+ scheduled = dirtyQueue.EE >= dirtyQueue.Qe || this.Mt[0].length !== 0 || this.Mt[1].length !== 0 || this.m.Lt.length !== 0;
388
488
  return;
389
489
  }
390
- this.En = true;
490
+ this.fn = true;
391
491
  try {
392
492
  if (false) ;
393
493
  // Before runHeap for the same reason as the fast drain above; late
394
494
  // subscribers (an effect reading a swept memo this flush) revive it,
395
495
  // which is the pay-for-use contract.
396
496
  sweepDormant();
397
- runHeap(dirtyQueue, GlobalQueue.Ce);
497
+ runHeap(dirtyQueue, GlobalQueue.Fe);
398
498
  if (activeTransition) {
399
499
  const e = transitionComplete(activeTransition);
400
500
  if (!e) {
@@ -407,7 +507,7 @@ class GlobalQueue extends Queue {
407
507
  // zombies here. Height-adjust entries still process normally: a
408
508
  // dirtied one keeps its height flag and falls through to runHeap's
409
509
  // adjustHeight path on the next pass of the bucket.
410
- runHeap(zombieQueue, this.m === e ? cancelZombieRecompute : GlobalQueue.Ce);
510
+ runHeap(zombieQueue, this.m === e ? cancelZombieRecompute : GlobalQueue.Fe);
411
511
  // Detach: the stashed transition keeps its batch; ambient work that
412
512
  // follows lands in a fresh one. If the batch is already a separate
413
513
  // ambient one — action done() restored activeTransition without
@@ -417,64 +517,64 @@ class GlobalQueue extends Queue {
417
517
  if (this.m === e) currentBatch = this.m = createBatch();
418
518
  // Run lane effects immediately (before stashing) - lanes with no pending async
419
519
  if (activeLanes.size) {
420
- GlobalQueue._n(EFFECT_RENDER);
421
- GlobalQueue._n(EFFECT_USER);
520
+ GlobalQueue.Nn(EFFECT_RENDER);
521
+ GlobalQueue.Nn(EFFECT_USER);
422
522
  }
423
- this.stashQueues(e.bt);
523
+ this.stashQueues(e.Bt);
424
524
  clock++;
425
525
  // A kept ambient batch may hold pending nodes (#2916): stay
426
526
  // scheduled so the outer drain loop commits them via the plain
427
527
  // flush path instead of leaving them until the next natural flush.
428
- scheduled = dirtyQueue.EE >= dirtyQueue.xe || this.m.yt.length > 0;
429
- reassignPendingTransition(e.yt);
528
+ scheduled = dirtyQueue.EE >= dirtyQueue.Qe || this.m.Lt.length > 0;
529
+ reassignPendingTransition(e.Lt);
430
530
  activeTransition = null;
431
531
  finalizePureQueue(null, true);
432
532
  return;
433
533
  }
434
534
  const t = activeTransition;
435
535
  const i = this.m;
436
- i !== t && i.yt.push(...t.yt);
437
- this.restoreQueues(t.bt);
536
+ i !== t && i.Lt.push(...t.Lt);
537
+ this.restoreQueues(t.Bt);
438
538
  transitions.delete(t);
439
539
  activeTransition = null;
440
- reassignPendingTransition(i.yt);
540
+ reassignPendingTransition(i.Lt);
441
541
  finalizePureQueue(t);
442
542
  if (i === t) {
443
543
  // Drop the dead Transition wrapper but keep its (drained) containers
444
544
  // as the ambient batch — late registrations during finalization live
445
545
  // there and must survive to the next flush.
446
546
  const e = createBatch();
447
- e.yt = i.yt;
448
- e.Ke = i.Ke;
547
+ e.Lt = i.Lt;
548
+ e.ze = i.ze;
449
549
  e.A = i.A;
450
- e.Tn = i.Tn;
550
+ e.En = i.En;
451
551
  currentBatch = this.m = e;
452
552
  }
453
553
  } else {
454
554
  if (canUseSimpleSyncFlush(this)) {
455
555
  commitPendingNodes();
456
- if (dirtyQueue.EE >= dirtyQueue.xe) {
457
- runHeap(dirtyQueue, GlobalQueue.Ce);
556
+ if (dirtyQueue.EE >= dirtyQueue.Qe) {
557
+ runHeap(dirtyQueue, GlobalQueue.Fe);
458
558
  commitPendingNodes();
459
559
  }
460
560
  } else {
461
- if (transitions.size) runHeap(zombieQueue, GlobalQueue.Ce);
561
+ if (transitions.size) runHeap(zombieQueue, GlobalQueue.Fe);
462
562
  finalizePureQueue();
463
563
  }
464
564
  }
465
565
  clock++;
466
566
  // Check if finalization added items to the heap (from optimistic reversion)
467
- scheduled = dirtyQueue.EE >= dirtyQueue.xe;
567
+ scheduled = dirtyQueue.EE >= dirtyQueue.Qe;
468
568
  // Run lane effects first (for ready lanes), then regular effects
469
- activeLanes.size && GlobalQueue._n(EFFECT_RENDER);
569
+ activeLanes.size && GlobalQueue.Nn(EFFECT_RENDER);
470
570
  this.run(EFFECT_RENDER);
471
- activeLanes.size && GlobalQueue._n(EFFECT_USER);
571
+ activeLanes.size && GlobalQueue.Nn(EFFECT_USER);
472
572
  this.run(EFFECT_USER);
473
573
  if (false) ;
474
574
  if (false && !scheduled && !activeTransition && transitions.size === 0 && activeLanes.size === 0) ;
475
575
  if (false) ;
476
576
  } finally {
477
- this.En = false;
577
+ this.fn = false;
478
578
  }
479
579
  }
480
580
  notify(e, t, i, n) {
@@ -489,13 +589,13 @@ class GlobalQueue extends Queue {
489
589
  if (t?.l) return true;
490
590
  if (activeTransition && t) {
491
591
  const i = t.source;
492
- let n = activeTransition.Ne.get(i);
493
- if (!n) activeTransition.Ne.set(i, n = new Set);
494
- const s = n.size;
592
+ let n = activeTransition._e.get(i);
593
+ if (!n) activeTransition._e.set(i, n = new Set);
594
+ const r = n.size;
495
595
  n.add(e);
496
- if (n.size !== s) {
596
+ if (n.size !== r) {
497
597
  schedule();
498
- GlobalQueue.jt?.(activeTransition);
598
+ GlobalQueue.zt?.(activeTransition);
499
599
  }
500
600
  }
501
601
  }
@@ -504,8 +604,17 @@ class GlobalQueue extends Queue {
504
604
  return false;
505
605
  }
506
606
  initTransition(e) {
507
- if (e) e = currentTransition(e);
508
- 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
+ }
509
618
  if (!e && activeTransition && activeTransition.Te === clock) return;
510
619
  if (!activeTransition) {
511
620
  activeTransition = e ?? createBatch();
@@ -526,18 +635,18 @@ class GlobalQueue extends Queue {
526
635
  // must not entangle unrelated writes to it; the same rule holds one hop
527
636
  // downstream: propagation never queues pended subscribers as pending
528
637
  // nodes, see propagateAffectsMark, #2893.
529
- for (let e = 0; e < t.yt.length; e++) {
530
- const i = t.yt[e];
531
- i._e = activeTransition;
532
- 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);
533
642
  }
534
- for (let e = 0; e < t.Ke.length; e++) {
535
- const i = t.Ke[e];
536
- i._e = activeTransition;
537
- 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);
538
647
  }
539
648
  if (t.A.length) activeTransition.A.push(...t.A);
540
- for (const e of t.Tn) activeTransition.Tn.add(e);
649
+ for (const e of t.En) activeTransition.En.add(e);
541
650
  // Gated readers recorded against the ambient batch move with it: their
542
651
  // replay-at-commit now happens at the transaction's completion.
543
652
  if (t.cn.size) {
@@ -547,13 +656,22 @@ class GlobalQueue extends Queue {
547
656
  currentBatch = this.m = activeTransition;
548
657
  }
549
658
  for (const e of activeLanes) {
550
- if (!e._e) e._e = activeTransition;
659
+ if (!e.Ae) e.Ae = activeTransition;
551
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();
552
670
  }
553
671
  }
554
672
 
555
673
  function queuePendingNode(e) {
556
- currentBatch.yt.push(e);
674
+ currentBatch.Lt.push(e);
557
675
  }
558
676
 
559
677
  // Sticky: flips true on the first refresh() ever (the only setter of
@@ -577,7 +695,7 @@ function armReaskClear() {
577
695
  function insertSubs(e, t = false) {
578
696
  // §12d: stamp before walking — setSignal's staged-rewrite fast path skips
579
697
  // the next walk for this node while the epoch holds (marking is idempotent).
580
- e._t = notifyEpoch;
698
+ e.It = notifyEpoch;
581
699
  // Get source lane: prefer node's own lane over current context
582
700
  // This is important for isPending signals which need their own lane to flush immediately
583
701
  // Presence bits gate the optional-slot probes (see constants.ts): one
@@ -585,14 +703,14 @@ function insertSubs(e, t = false) {
585
703
  // lookups in the hottest notify loop. Bits are sticky — the field read
586
704
  // stays authoritative when a bit is set.
587
705
  const i = e.T;
588
- const n = (i & CONFIG_HAS_LANE ? e.o?.Be : undefined) || currentOptimisticLane;
589
- const s = (i & CONFIG_HAS_SNAPSHOT) !== 0 && e.o?.Qe !== undefined;
590
- const o = reaskArmed;
706
+ const n = (i & CONFIG_HAS_LANE ? e.o?.Je : undefined) || currentOptimisticLane;
707
+ const r = (i & CONFIG_HAS_SNAPSHOT) !== 0 && e.o?.We !== undefined;
708
+ const s = reaskArmed;
591
709
  for (let i = e.u; i !== null; i = i.ae) {
592
710
  const e = i.ce;
593
711
  // A value-change notification is a new question for the subscriber: any
594
712
  // pending re-ask mark (refresh) it carried is superseded.
595
- if (o) e.ie &= ~REACTIVE_REASK;
713
+ if (s) e.ie &= ~REACTIVE_REASK;
596
714
  // Missed-wake latch (#3037): this write is landing while the subscriber
597
715
  // is mid-recompute (a nested pull committing beneath its reads), and the
598
716
  // heap refuses RECOMPUTING nodes. A gen-current link means the pass
@@ -601,8 +719,8 @@ function insertSubs(e, t = false) {
601
719
  // pass either re-reads them fresh or trims them), and neither does the
602
720
  // tail link: it is the read IN FLIGHT — read() links before it pulls, so
603
721
  // this very commit is what that read returns.
604
- if (e.ie & REACTIVE_RECOMPUTING_DEPS && i.nn === e.Ze && i !== e.Ye) e.ie |= REACTIVE_MISSED_WAKE;
605
- if (s && e.T & CONFIG_IN_SNAPSHOT_SCOPE) {
722
+ if (e.ie & REACTIVE_RECOMPUTING_DEPS && i.Ft === e.Ke && i !== e.je) e.ie |= REACTIVE_MISSED_WAKE;
723
+ if (r && e.T & CONFIG_IN_SNAPSHOT_SCOPE) {
606
724
  e.ie |= REACTIVE_SNAPSHOT_STALE;
607
725
  continue;
608
726
  }
@@ -612,7 +730,7 @@ function insertSubs(e, t = false) {
612
730
  } else if (t) {
613
731
  e.ie |= REACTIVE_OPTIMISTIC_DIRTY;
614
732
  // No source lane means reversion - clear subscriber's lane so effects go to regular queue
615
- if (e.o) e.o.Be = undefined;
733
+ if (e.o) e.o.Je = undefined;
616
734
  }
617
735
  enqueueSub(e);
618
736
  }
@@ -621,26 +739,32 @@ function insertSubs(e, t = false) {
621
739
  function commitPendingNode(e) {
622
740
  const t = e;
623
741
  if (!t.oe) {
624
- if (e.Pe !== NOT_PENDING) {
625
- e.be = e.Pe;
626
- e.Pe = NOT_PENDING;
742
+ if (e.Re !== NOT_PENDING) {
743
+ e.be = e.Re;
744
+ e.Re = NOT_PENDING;
627
745
  }
628
746
  if (e.T & CONFIG_HAS_COMPANIONS) GlobalQueue.un(e);
629
747
  return;
630
748
  }
631
- if (e.Pe !== NOT_PENDING) {
632
- e.be = e.Pe;
633
- e.Pe = NOT_PENDING;
749
+ if (e.Re !== NOT_PENDING) {
750
+ e.be = e.Re;
751
+ e.Re = NOT_PENDING;
634
752
  // Set _modified for effects, but not for tracked effects (they handle their own scheduling)
635
- if (e.Re && e.Re !== EFFECT_TRACKED) e.Xe = true;
753
+ if (e.ge && e.ge !== EFFECT_TRACKED) e.tt = true;
754
+ // A quiet re-ask classification preserved through a held landing dies
755
+ // with the value commit — the commit IS the reveal (#3178). Gated on the
756
+ // staged value: status propagation queues pending nodes whose windows
757
+ // are still OPEN (no staged value), and their live classification must
758
+ // survive this sweep.
759
+ if (e.o) e.o.De = false;
636
760
  }
637
761
  // The committed hold is the first observable answer for a loading-window
638
762
  // node — the window closes here, not at compute time (#2990). Unconditional
639
763
  // store to an always-present computed slot.
640
- t.Ie = false;
764
+ t.Ne = false;
641
765
  t.ie &= ~REACTIVE_MANUAL_WRITE;
642
766
  if (!(t.S & STATUS_PENDING)) t.S &= ~STATUS_UNINITIALIZED;
643
- if (t.o != null && (t.o.qe !== null || t.o.We !== null)) GlobalQueue.Fe(t, false, true);
767
+ if (t.o != null && (t.o.Ye !== null || t.o.qe !== null)) GlobalQueue.He(t, false, true);
644
768
  if (e.T & CONFIG_HAS_COMPANIONS) GlobalQueue.un(e);
645
769
  }
646
770
 
@@ -666,10 +790,34 @@ function setPatchCommitHook(e) {
666
790
  patchCommitHook = e;
667
791
  }
668
792
 
793
+ /** Held truth committed this finalize, awaiting its post-revert wake (see
794
+ * finalizePureQueue): the commit IS the reveal, but subscribers must not
795
+ * re-derive until the settling transaction's optimistic overrides have
796
+ * reverted — a commit-time wake recomputes them in the window where
797
+ * confirming truth is committed and the override still displays, a torn
798
+ * frame no timeline contains. */ const heldRevealed = [];
799
+
669
800
  function commitPendingNodes() {
670
- const e = currentBatch.yt;
801
+ const e = currentBatch.Lt;
671
802
  for (let t = 0; t < e.length; t++) {
672
- commitPendingNode(e[t]);
803
+ const i = e[t];
804
+ commitPendingNode(i);
805
+ // The stamp dies with the commit (#3143) — symmetric with
806
+ // resolveOptimisticNodes clearing optimistic stamps. A stamp outliving
807
+ // its transaction let any later write (even a value-equal no-op, which
808
+ // re-opens before the equality bail) resurrect the finished transaction;
809
+ // a boundary flag rewritten every finalize pass then spun the drain loop
810
+ // forever (#3140). The held-truth mark dies the same death — the commit
811
+ // IS the reveal — but its wake defers to the post-revert pass: ordinary
812
+ // subscribers were masked to committed all hold (some re-derived against
813
+ // that old view and cached it), and commits are otherwise silent
814
+ // (staging already notified), so without a wake they'd hold the old
815
+ // world forever.
816
+ i.Ae = null;
817
+ if (i.T & CONFIG_HELD_TRUTH) {
818
+ i.T &= ~CONFIG_HELD_TRUTH;
819
+ heldRevealed.push(i);
820
+ }
673
821
  }
674
822
  e.length = 0;
675
823
  storeCommitHook?.();
@@ -682,15 +830,15 @@ function finalizePureQueue(e = null, t = false) {
682
830
  const i = !t;
683
831
  if (i) commitPendingNodes();
684
832
  if (!t && globalQueue.Qt.length) checkBoundaryChildren(globalQueue);
685
- const n = dirtyQueue.EE >= dirtyQueue.xe;
686
- if (n) runHeap(dirtyQueue, GlobalQueue.Ce);
833
+ const n = dirtyQueue.EE >= dirtyQueue.Qe;
834
+ if (n) runHeap(dirtyQueue, GlobalQueue.Fe);
687
835
  if (i) {
688
836
  if (n) commitPendingNodes();
689
837
  // The settling batch: the completing transaction's, or the ambient one.
690
838
  const t = e ?? globalQueue.m;
691
839
  // Optimistic reversion: a non-empty batch means _optimisticWrite ran,
692
840
  // which installed the engine's hooks.
693
- if (t.Ke.length) GlobalQueue.dn(t.Ke);
841
+ if (t.ze.length) GlobalQueue.Tn(t.ze);
694
842
  // Replay entanglement: subs recorded by the read-time gate get rescheduled
695
843
  // so they re-run with the now-committed values visible. The ambient batch
696
844
  // replays too — laneReadsCommitted records readers whose committed-view
@@ -720,10 +868,26 @@ function finalizePureQueue(e = null, t = false) {
720
868
  // hook; the hook iterates, clears, and schedules (keeping the loop out of
721
869
  // core lets esbuild shake it — rollup already folds the null guard). The
722
870
  // completing transition scopes the clear to its own layer keys (#2899).
723
- if (t.Tn.size) GlobalQueue.Bt(t.Tn, e);
871
+ if (t.En.size) GlobalQueue.qt(t.En, e);
872
+ // Held-truth reveal wake (#3164), post-revert by construction: this
873
+ // finalize committed confirming truth whose subscribers were masked all
874
+ // hold — some re-derived against the committed view (the staging, or a
875
+ // confirming carrier's landing, notified them as a plain write) and
876
+ // cached it, and stash-restored applies may carry those torn values.
877
+ // Waking and recomputing HERE — after _resolveOptimistic and the store
878
+ // clears above — means every apply paints the settled view; a wake at
879
+ // commit time would recompute them in the window where truth is
880
+ // committed but the settling transaction's overrides still display.
881
+ if (heldRevealed.length !== 0) {
882
+ while (heldRevealed.length) insertSubs(heldRevealed.pop());
883
+ if (dirtyQueue.EE >= dirtyQueue.Qe) {
884
+ runHeap(dirtyQueue, GlobalQueue.Fe);
885
+ commitPendingNodes();
886
+ }
887
+ }
724
888
  sweepTransientStoreNodes();
725
889
  // Lanes only enter activeLanes through the engine's getOrCreateLane.
726
- if (activeLanes.size) GlobalQueue.Nn(e);
890
+ if (activeLanes.size) GlobalQueue.In(e);
727
891
  }
728
892
  }
729
893
 
@@ -752,7 +916,7 @@ function checkBoundaryChildren(e) {
752
916
 
753
917
  function reassignPendingTransition(e) {
754
918
  for (let t = 0; t < e.length; t++) {
755
- e[t]._e = activeTransition;
919
+ e[t].Ae = activeTransition;
756
920
  }
757
921
  }
758
922
 
@@ -780,7 +944,7 @@ function flush(e) {
780
944
  }
781
945
  }
782
946
  }
783
- if (globalQueue.En) {
947
+ if (globalQueue.fn) {
784
948
  return;
785
949
  }
786
950
  if (halted) return;
@@ -798,30 +962,30 @@ function runQueue(e, t) {
798
962
  function reporterBlocksSource(e, t) {
799
963
  if (e.ie & (REACTIVE_ZOMBIE | REACTIVE_DISPOSED)) return false;
800
964
  if (e.o?.le?.has(t)) return true;
801
- for (let i = e.nt; i; i = i.it) {
802
- let e = i.ut;
965
+ for (let i = e.ut; i; i = i.lt) {
966
+ let e = i.ot;
803
967
  while (e) {
804
- if (e === t || e.lt === t) return true;
805
- e = e.o?.Et;
968
+ if (e === t || e.st === t) return true;
969
+ e = e.o?.Tt;
806
970
  }
807
971
  }
808
972
  return !!(e.S & STATUS_PENDING && e.o?._ instanceof NotReadyError && e.o?._.source === t);
809
973
  }
810
974
 
811
975
  function transitionComplete(e) {
812
- if (e.fn) return true;
976
+ if (e.sn) return true;
813
977
  if (e.ue.length) return false;
814
978
  let t = true;
815
- for (const [i, n] of e.Ne) {
816
- let s = false;
979
+ for (const [i, n] of e._e) {
980
+ let r = false;
817
981
  for (const e of n) {
818
982
  if (reporterBlocksSource(e, i)) {
819
- s = true;
983
+ r = true;
820
984
  break;
821
985
  }
822
986
  n.delete(e);
823
987
  }
824
- if (!s) e.Ne.delete(i); else if (i.S & STATUS_PENDING && i.o?._?.source === i) {
988
+ if (!r) e._e.delete(i); else if (i.S & STATUS_PENDING && i.o?._?.source === i) {
825
989
  t = false;
826
990
  break;
827
991
  }
@@ -829,13 +993,20 @@ function transitionComplete(e) {
829
993
  // Override blockage lives with the engine (absent hook = "no optimistic
830
994
  // blockage"); the hook's loops over _optimisticNodes/_optimisticStores are
831
995
  // no-ops when the transition holds neither, so no pre-check is needed.
832
- if (t && GlobalQueue.In?.(e)) t = false;
833
- t && (e.fn = true);
996
+ if (t && GlobalQueue.dn?.(e)) t = false;
997
+ t && (e.sn = true);
834
998
  return t;
835
999
  }
836
1000
 
1001
+ /** A fresh, unentered transaction (#3146): the optimistic store's truth
1002
+ * flight DECLARES an owned transaction instead of relying on whatever the
1003
+ * ambient adoption machinery stamped on its firewall. Activate it with
1004
+ * initTransition; it is a plain batch until then. */ function createTransition() {
1005
+ return createBatch();
1006
+ }
1007
+
837
1008
  function currentTransition(e) {
838
- while (e.fn && typeof e.fn === "object") e = e.fn;
1009
+ while (e.sn && typeof e.sn === "object") e = e.sn;
839
1010
  return e;
840
1011
  }
841
1012
 
@@ -849,4 +1020,27 @@ function runInTransition(e, t) {
849
1020
  }
850
1021
  }
851
1022
 
852
- export { GlobalQueue, Queue, activeAffectsMarks, activeLanes, activeTransition, armReaskClear, assignOrMergeLane, bumpNotifyEpoch, clock, currentTransition, dirtyQueue, enforceLoadingBoundary, finalizePureQueue, findLane, flush, globalQueue, haltReactivity, insertSubs, notifyEpoch, patchCommitHook, projectionWriteActive, queuePendingNode, reaskArmed, resetErrorHalt, runInTransition, schedule, setPatchCommitHook, setProjectionWriteActive, setStoreCommitHook, shiftAffectsMarks, storeCommitHook, zombieQueue };
1023
+ /** Run `fn` with `transition` as BOTH the ambient transaction and the
1024
+ * registration batch, restoring both after. runInTransition alone is not
1025
+ * enough for code that WRITES on behalf of a transaction from inside someone
1026
+ * else's window (optimistic replay re-arming a still-open action's edits
1027
+ * during a landing commit, #3123): registrations route through the queue's
1028
+ * batch pointer, and a bare activeTransition swap leaves them in the ambient
1029
+ * batch — a plain batch "completes" at the next flush and reverts optimistic
1030
+ * registrations that were supposed to live with the transaction.
1031
+ * initTransition is the wrong tool here: it MERGES the currently ambient
1032
+ * transaction into the target, entangling whatever the interrupted window
1033
+ * belonged to. */ function runAsTransitionBatch(e, t) {
1034
+ const i = activeTransition;
1035
+ const n = globalQueue.m;
1036
+ try {
1037
+ activeTransition = currentTransition(e);
1038
+ currentBatch = globalQueue.m = activeTransition;
1039
+ return t();
1040
+ } finally {
1041
+ activeTransition = i;
1042
+ currentBatch = globalQueue.m = n;
1043
+ }
1044
+ }
1045
+
1046
+ export { GlobalQueue, Queue, activeAffectsMarks, activeLanes, activeTransition, armReaskClear, assignOrMergeLane, bumpNotifyEpoch, clock, createTransition, 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 };