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

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 (131) hide show
  1. package/dist/dev-shared.js +5243 -0
  2. package/dist/dev.attribution.js +2333 -0
  3. package/dist/dev.js +1758 -6621
  4. package/dist/observe/affects.js +127 -0
  5. package/dist/observe/attribution.js +1 -0
  6. package/dist/observe/boundaries.js +586 -0
  7. package/dist/observe/core/action.js +167 -0
  8. package/dist/observe/core/async.js +736 -0
  9. package/dist/observe/core/attribution-hooks.js +60 -0
  10. package/dist/observe/core/attribution.js +2318 -0
  11. package/dist/{types-cjs/core/constants.d.cts → observe/core/constants.js} +135 -71
  12. package/dist/{types-cjs/core/context.d.cts → observe/core/context.js} +37 -12
  13. package/dist/observe/core/core.js +1289 -0
  14. package/dist/observe/core/dev.js +232 -0
  15. package/dist/observe/core/effect.js +179 -0
  16. package/dist/observe/core/error.js +80 -0
  17. package/dist/observe/core/external.js +98 -0
  18. package/dist/observe/core/graph.js +155 -0
  19. package/dist/observe/core/heap.js +147 -0
  20. package/dist/observe/core/invariants.js +43 -0
  21. package/dist/observe/core/lanes.js +168 -0
  22. package/dist/observe/core/optimistic.js +438 -0
  23. package/dist/observe/core/owner.js +308 -0
  24. package/dist/observe/core/scheduler.js +1190 -0
  25. package/dist/observe/core/verdict.js +551 -0
  26. package/dist/observe/index.js +55 -0
  27. package/dist/observe/map.js +482 -0
  28. package/dist/{types-cjs/signals.d.cts → observe/signals.js} +305 -317
  29. package/dist/observe/store/index.js +38 -0
  30. package/dist/observe/store/next/optimistic.js +651 -0
  31. package/dist/observe/store/next/projection.js +242 -0
  32. package/dist/observe/store/next/reconcile.js +373 -0
  33. package/dist/observe/store/next/store.js +2213 -0
  34. package/dist/observe/store/next/target.js +51 -0
  35. package/dist/observe/store/store.js +323 -0
  36. package/dist/observe/store/storePath.js +103 -0
  37. package/dist/observe/store/utils.js +210 -0
  38. package/dist/prod/attribution.js +32 -0
  39. package/dist/prod/boundaries.js +3 -1
  40. package/dist/prod/core/action.js +52 -32
  41. package/dist/prod/core/async.js +177 -128
  42. package/dist/prod/core/attribution-hooks.js +3 -0
  43. package/dist/prod/core/constants.js +32 -1
  44. package/dist/prod/core/context.js +10 -16
  45. package/dist/prod/core/core.js +505 -286
  46. package/dist/prod/core/dev.js +2 -0
  47. package/dist/prod/core/effect.js +72 -43
  48. package/dist/prod/core/external.js +2 -2
  49. package/dist/prod/core/graph.js +40 -36
  50. package/dist/prod/core/heap.js +50 -51
  51. package/dist/prod/core/lanes.js +43 -22
  52. package/dist/prod/core/optimistic.js +224 -80
  53. package/dist/prod/core/owner.js +43 -39
  54. package/dist/prod/core/scheduler.js +347 -232
  55. package/dist/prod/core/verdict.js +71 -74
  56. package/dist/prod/index.js +9 -5
  57. package/dist/prod/map.js +311 -150
  58. package/dist/prod/signals.js +52 -38
  59. package/dist/prod/store/next/optimistic.js +127 -131
  60. package/dist/prod/store/next/projection.js +3 -3
  61. package/dist/prod/store/next/reconcile.js +149 -294
  62. package/dist/prod/store/next/store.js +694 -380
  63. package/dist/prod/store/next/target.js +32 -10
  64. package/dist/prod/store/store.js +12 -14
  65. package/dist/prod/store/utils.js +36 -27
  66. package/dist/types/attribution.d.ts +15 -0
  67. package/dist/types/attribution.prod.d.ts +13 -0
  68. package/dist/types/core/async.d.ts +1 -1
  69. package/dist/types/core/attribution-hooks.d.ts +183 -6
  70. package/dist/types/core/attribution.d.ts +435 -24
  71. package/dist/types/core/constants.d.ts +31 -0
  72. package/dist/types/core/core.d.ts +19 -3
  73. package/dist/types/core/dev.d.ts +165 -26
  74. package/dist/types/core/effect.d.ts +8 -1
  75. package/dist/types/core/heap.d.ts +5 -3
  76. package/dist/types/core/index.d.ts +2 -1
  77. package/dist/types/core/invariants.d.ts +1 -1
  78. package/dist/types/core/lanes.d.ts +17 -0
  79. package/dist/types/core/scheduler.d.ts +69 -4
  80. package/dist/types/core/types.d.ts +29 -9
  81. package/dist/types/index.d.ts +9 -2
  82. package/dist/types/map.d.ts +2 -0
  83. package/dist/types/signals.d.ts +10 -0
  84. package/dist/types/store/index.d.ts +4 -6
  85. package/dist/types/store/next/optimistic.d.ts +4 -2
  86. package/dist/types/store/next/reconcile.d.ts +5 -12
  87. package/dist/types/store/next/store.d.ts +8 -9
  88. package/dist/types/store/next/target.d.ts +56 -55
  89. package/dist/types/store/store.d.ts +14 -9
  90. package/dist/types/store/utils.d.ts +7 -0
  91. package/package.json +21 -16
  92. package/dist/node.cjs +0 -11121
  93. package/dist/prod/store/next/patch-hooks.js +0 -13
  94. package/dist/prod/store/next/patch.js +0 -614
  95. package/dist/types/store/next/patch-hooks.d.ts +0 -41
  96. package/dist/types/store/next/patch.d.ts +0 -91
  97. package/dist/types-cjs/affects.d.cts +0 -47
  98. package/dist/types-cjs/boundaries.d.cts +0 -175
  99. package/dist/types-cjs/core/action.d.cts +0 -64
  100. package/dist/types-cjs/core/async.d.cts +0 -23
  101. package/dist/types-cjs/core/attribution-hooks.d.cts +0 -63
  102. package/dist/types-cjs/core/attribution.d.cts +0 -239
  103. package/dist/types-cjs/core/core.d.cts +0 -176
  104. package/dist/types-cjs/core/dev.d.cts +0 -95
  105. package/dist/types-cjs/core/effect.d.cts +0 -30
  106. package/dist/types-cjs/core/error.d.cts +0 -56
  107. package/dist/types-cjs/core/external.d.cts +0 -15
  108. package/dist/types-cjs/core/graph.d.cts +0 -28
  109. package/dist/types-cjs/core/heap.d.cts +0 -22
  110. package/dist/types-cjs/core/index.d.cts +0 -13
  111. package/dist/types-cjs/core/invariants.d.cts +0 -59
  112. package/dist/types-cjs/core/lanes.d.cts +0 -44
  113. package/dist/types-cjs/core/optimistic.d.cts +0 -6
  114. package/dist/types-cjs/core/owner.d.cts +0 -124
  115. package/dist/types-cjs/core/scheduler.d.cts +0 -231
  116. package/dist/types-cjs/core/types.d.cts +0 -204
  117. package/dist/types-cjs/core/verdict.d.cts +0 -2
  118. package/dist/types-cjs/index.d.cts +0 -10
  119. package/dist/types-cjs/map.d.cts +0 -69
  120. package/dist/types-cjs/package.json +0 -3
  121. package/dist/types-cjs/store/index.d.cts +0 -21
  122. package/dist/types-cjs/store/next/optimistic.d.cts +0 -23
  123. package/dist/types-cjs/store/next/patch-hooks.d.cts +0 -41
  124. package/dist/types-cjs/store/next/patch.d.cts +0 -91
  125. package/dist/types-cjs/store/next/projection.d.cts +0 -8
  126. package/dist/types-cjs/store/next/reconcile.d.cts +0 -17
  127. package/dist/types-cjs/store/next/store.d.cts +0 -127
  128. package/dist/types-cjs/store/next/target.d.cts +0 -192
  129. package/dist/types-cjs/store/store.d.cts +0 -138
  130. package/dist/types-cjs/store/storePath.d.cts +0 -58
  131. package/dist/types-cjs/store/utils.d.cts +0 -74
@@ -1,6 +1,6 @@
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";
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_SLOT_NODE, CONFIG_HAS_COMPANIONS, EFFECT_TRACKED, CONFIG_INPUTS_PUBLISHED, REACTIVE_REASK, REACTIVE_MANUAL_WRITE, STATUS_UNINITIALIZED, CONFIG_AUTHORITATIVE_READ } from "./constants.js";
2
2
 
3
- import { currentOptimisticLane } from "./core.js";
3
+ import { currentOptimisticLane, slotUnobservedHook } from "./core.js";
4
4
 
5
5
  import { DEV } from "./dev.js";
6
6
 
@@ -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
- Qe: 0,
24
+ Ke: 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
- Qe: 0,
31
+ Ke: 0,
32
32
  EE: 0
33
33
  };
34
34
 
@@ -55,6 +55,17 @@ let syncDepth = 0;
55
55
 
56
56
  let projectionWriteActive = false;
57
57
 
58
+ /** > 0 while an action's generator body is on the stack (the synchronous
59
+ * slice between yields). Maintained by action.ts around `it.next()`. */ let actionStepDepth = 0;
60
+
61
+ function enterActionStep() {
62
+ actionStepDepth++;
63
+ }
64
+
65
+ function exitActionStep() {
66
+ actionStepDepth--;
67
+ }
68
+
58
69
  // Store property nodes that were created solely to carry a pending write (no
59
70
  // subscribers at write time). Swept after each flush that commits pending
60
71
  // values — any still without subs get disposed via their `_unobserved` hook,
@@ -63,7 +74,7 @@ const transientStoreNodes = new Set;
63
74
 
64
75
  function canUseSimpleSyncFlush(e) {
65
76
  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;
77
+ return transitions.size === 0 && activeLanes.size === 0 && e.Xt.length === 0 && t.it.length === 0 && t.A.length === 0 && t.En.size === 0 && transientStoreNodes.size === 0;
67
78
  }
68
79
 
69
80
  function sweepTransientStoreNodes() {
@@ -73,14 +84,14 @@ function sweepTransientStoreNodes() {
73
84
  transientStoreNodes.delete(e);
74
85
  continue;
75
86
  }
76
- if (e.Re !== NOT_PENDING) continue;
77
- if (e.o?.Pe !== undefined && e.o?.Pe !== NOT_PENDING) continue;
87
+ if (e.Ge !== NOT_PENDING) continue;
88
+ if (e.o?.be !== undefined && e.o?.be !== NOT_PENDING) continue;
78
89
  // A live affects() mark keeps the node addressable: sweeping it would
79
90
  // detach the refcount from the slot (a fresh probe would upsert a new,
80
91
  // unmarked node for the same property).
81
92
  if (e.o?.t) continue;
82
93
  transientStoreNodes.delete(e);
83
- e.o?.Et?.();
94
+ if (e.T & CONFIG_SLOT_NODE) slotUnobservedHook(e); else e.o?.Ct?.();
84
95
  }
85
96
  }
86
97
 
@@ -107,32 +118,33 @@ function setProjectionWriteActive(e) {
107
118
  * land there directly — no per-field aliasing.
108
119
  */ function createBatch() {
109
120
  return {
110
- Te: clock,
111
- Lt: [],
112
- _e: new Map,
113
- ze: [],
121
+ _e: clock,
122
+ $t: [],
123
+ Re: new Map,
124
+ it: [],
114
125
  A: [],
115
126
  En: new Set,
116
127
  ue: [],
117
- Bt: {
118
- Mt: [ [], [] ],
119
- Qt: []
128
+ ei: {
129
+ ti: [ [], [] ],
130
+ Xt: []
120
131
  },
121
- sn: false,
122
- cn: new Set
132
+ ft: false,
133
+ lt: new Set,
134
+ Et: null
123
135
  };
124
136
  }
125
137
 
126
138
  function mergeTransitionState(e, t) {
127
- t.sn = e;
139
+ t.ft = e;
128
140
  e.ue.push(...t.ue);
129
- for (const i of activeLanes) if (i.Ae === t) i.Ae = e;
130
- if (t.ze.length) {
141
+ for (const i of activeLanes) if (i.ge === t) i.ge = e;
142
+ if (t.it.length) {
131
143
  // Move (don't copy): the global queue's batch may still be the outgoing
132
144
  // transition, and the adoption pass in initTransition would re-push its
133
145
  // contents into the target — duplicating every entry.
134
- e.ze.push(...t.ze);
135
- t.ze.length = 0;
146
+ e.it.push(...t.it);
147
+ t.it.length = 0;
136
148
  }
137
149
  if (t.A.length) {
138
150
  // Move (don't copy): the global queue's batch may still be the outgoing
@@ -142,32 +154,13 @@ function mergeTransitionState(e, t) {
142
154
  t.A.length = 0;
143
155
  }
144
156
  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);
157
+ for (const [i, n] of t.Re) {
158
+ let t = e.Re.get(i);
159
+ if (!t) e.Re.set(i, t = new Set);
168
160
  for (const e of n) t.add(e);
169
161
  }
170
- for (const i of t.cn) e.cn.add(i);
162
+ for (const i of t.lt) e.lt.add(i);
163
+ if (t.Et) (e.Et ??= []).push(...t.Et);
171
164
  }
172
165
 
173
166
  /**
@@ -188,7 +181,7 @@ function mergeTransitionState(e, t) {
188
181
  * `until()` retain this.
189
182
  */ function entangleConfirmingTransitions(e, t) {
190
183
  t = currentTransition(t);
191
- if (t.sn === true) return;
184
+ if (t.ft === true) return;
192
185
  // The confirming evidence is a dep whose value is STAGED (pending,
193
186
  // uncommitted) at flip evaluation — committed deps are public already and
194
187
  // carry nothing to entangle. A staged dep lives in one of two carriers: a
@@ -201,10 +194,10 @@ function mergeTransitionState(e, t) {
201
194
  // the awaiting transaction to landings that haven't happened; a merged
202
195
  // reporter deadlocked exactly that way).
203
196
  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;
197
+ for (let n = e.fe; n !== null; n = n.ae) {
198
+ const r = n.Se;
199
+ if (r.Ge !== NOT_PENDING) {
200
+ const e = r.ge;
208
201
  const n = e != null ? currentTransition(e) : null;
209
202
  // Skip the awaiting transaction's own cargo (t === target: a
210
203
  // fold-staged landing or a write the action itself issued — hold and
@@ -212,10 +205,10 @@ function mergeTransitionState(e, t) {
212
205
  // (t === null) must leave the batch NOW — it commits at this flush's
213
206
  // end, which would reveal the confirmation under the live optimism
214
207
  // it just confirmed.
215
- const s = n === null ? currentBatch.Lt : n !== t && n.sn !== true ? n.Lt : null;
208
+ const s = n === null ? currentBatch.$t : n !== t && n.ft !== true ? n.$t : null;
216
209
  if (s !== null) i = stealEntangledCargo(s, t) || i;
217
210
  }
218
- if (n === e.je) break;
211
+ if (n === e.et) break;
219
212
  }
220
213
  // The steal never activates the awaiting transaction: the predicate can
221
214
  // flip inside another transaction's finalize heap, and adopting the queue
@@ -244,20 +237,20 @@ function mergeTransitionState(e, t) {
244
237
  * committed value — while re-running them would re-derive the OLD world
245
238
  * and re-stage it over the held truth. The reveal re-notifies
246
239
  * (commitPendingNodes), which is when they re-derive for real. */ function stealEntangledCargo(e, t) {
247
- if (e === t.Lt || e.length === 0) return false;
240
+ if (e === t.$t || e.length === 0) return false;
248
241
  for (let i = 0; i < e.length; i++) {
249
242
  const n = e[i];
250
- n.Ae = t;
251
- t.Lt.push(n);
243
+ n.ge = t;
244
+ t.$t.push(n);
252
245
  // Override-covered nodes stay silent AND unmasked: the override is the
253
246
  // display (A17 — its staging never notified, its revert will), so their
254
247
  // subs saw nothing and re-running one would break the silence with a
255
248
  // duplicate fire of an unchanged view.
256
249
  if (!hasActiveOverride(n)) {
257
250
  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);
251
+ for (let e = n.u; e !== null; e = e.Te) {
252
+ const t = e.Ie;
253
+ if (t.Ce && !(t.T & CONFIG_AUTHORITATIVE_READ)) enqueueSub(t);
261
254
  }
262
255
  }
263
256
  }
@@ -273,7 +266,7 @@ function schedule() {
273
266
  }
274
267
  if (scheduled) return;
275
268
  scheduled = true;
276
- if (!syncDepth && !globalQueue.fn && !projectionWriteActive) queueMicrotask(flush);
269
+ if (!syncDepth && !globalQueue.sn && !projectionWriteActive) queueMicrotask(flush);
277
270
  }
278
271
 
279
272
  /**
@@ -284,10 +277,18 @@ function schedule() {
284
277
  if (halted) return;
285
278
  halted = true;
286
279
  let t = "[REACTIVITY_HALTED]";
287
- // Log the cause here too: callers rethrow it, but a creation-time throw
280
+ // Surface the cause here too: callers rethrow it, but a creation-time throw
288
281
  // unwinds through ancestor recomputes that convert it to status instead of
289
282
  // surfacing it (#2884), so the rethrow alone cannot guarantee visibility.
290
- e === undefined ? console.error(t) : console.error(t, e);
283
+ // Where the platform has one, hand the cause to its uncaught-error channel
284
+ // (`reportError` → `error` event → window.onerror / error monitoring): a
285
+ // halt that only reaches console.error leaves a page that LOOKS alive with
286
+ // nothing an app or its telemetry can act on (#3338 — an uncaught throw
287
+ // during the hydration render). The rethrow may reach the top as well in
288
+ // the non-swallowed cases; a duplicate report beats a silent one.
289
+ const i = e !== undefined && globalThis.reportError;
290
+ i || e === undefined ? console.error(t) : console.error(t, e);
291
+ i && i(e);
291
292
  }
292
293
 
293
294
  // Logs on the first write after a halt so a frozen interaction is traceable.
@@ -307,30 +308,30 @@ function notifyHalted() {
307
308
  let queueRunToken = 0;
308
309
 
309
310
  class Queue {
310
- ke=null;
311
- Mt=[ [], [] ];
312
- Qt=[];
313
- jt=0;
311
+ qe=null;
312
+ ti=[ [], [] ];
313
+ Xt=[];
314
+ ii=0;
314
315
  created=clock;
315
316
  addChild(e) {
316
- this.Qt.push(e);
317
- e.ke = this;
317
+ this.Xt.push(e);
318
+ e.qe = this;
318
319
  }
319
320
  removeChild(e) {
320
- const t = this.Qt.indexOf(e);
321
+ const t = this.Xt.indexOf(e);
321
322
  if (t >= 0) {
322
- this.Qt.splice(t, 1);
323
- e.ke = null;
323
+ this.Xt.splice(t, 1);
324
+ e.qe = null;
324
325
  }
325
326
  }
326
327
  notify(e, t, i, n) {
327
- if (this.ke) return this.ke.notify(e, t, i, n);
328
+ if (this.qe) return this.qe.notify(e, t, i, n);
328
329
  return false;
329
330
  }
330
331
  run(e) {
331
- if (this.Mt[e - 1].length) {
332
- const t = this.Mt[e - 1];
333
- this.Mt[e - 1] = [];
332
+ if (this.ti[e - 1].length) {
333
+ const t = this.ti[e - 1];
334
+ this.ti[e - 1] = [];
334
335
  runQueue(t, e);
335
336
  }
336
337
  // Effects run here can dispose owners, and disposal removes queues from
@@ -340,12 +341,12 @@ class Queue {
340
341
  // can be recovered by rescanning from the front and every child still runs
341
342
  // exactly once. Children appended mid-pass carry a stale stamp and run,
342
343
  // matching the previous live-array behaviour.
343
- const t = this.Qt;
344
+ const t = this.Xt;
344
345
  const i = ++queueRunToken;
345
346
  for (let n = 0; n < t.length; ) {
346
347
  const r = t[n];
347
- if (r.jt !== i) {
348
- r.jt = i;
348
+ if (r.ii !== i) {
349
+ r.ii = i;
349
350
  r.run?.(e);
350
351
  if (t[n] !== r) {
351
352
  n = 0;
@@ -362,48 +363,48 @@ class Queue {
362
363
  const i = findLane(currentOptimisticLane);
363
364
  i.tn[e - 1].push(t);
364
365
  } else {
365
- this.Mt[e - 1].push(t);
366
+ this.ti[e - 1].push(t);
366
367
  }
367
368
  }
368
369
  schedule();
369
370
  }
370
371
  stashQueues(e) {
371
- e.Mt[0].push(...this.Mt[0]);
372
- e.Mt[1].push(...this.Mt[1]);
373
- this.Mt = [ [], [] ];
374
- for (let t = 0; t < this.Qt.length; t++) {
375
- let i = this.Qt[t];
376
- let n = e.Qt[t];
372
+ e.ti[0].push(...this.ti[0]);
373
+ e.ti[1].push(...this.ti[1]);
374
+ this.ti = [ [], [] ];
375
+ for (let t = 0; t < this.Xt.length; t++) {
376
+ let i = this.Xt[t];
377
+ let n = e.Xt[t];
377
378
  if (!n) {
378
379
  n = {
379
- Mt: [ [], [] ],
380
- Qt: []
380
+ ti: [ [], [] ],
381
+ Xt: []
381
382
  };
382
- e.Qt[t] = n;
383
+ e.Xt[t] = n;
383
384
  }
384
385
  i.stashQueues(n);
385
386
  }
386
387
  }
387
388
  restoreQueues(e) {
388
- this.Mt[0].push(...e.Mt[0]);
389
- this.Mt[1].push(...e.Mt[1]);
390
- for (let t = 0; t < e.Qt.length; t++) {
391
- const i = e.Qt[t];
392
- let n = this.Qt[t];
389
+ this.ti[0].push(...e.ti[0]);
390
+ this.ti[1].push(...e.ti[1]);
391
+ for (let t = 0; t < e.Xt.length; t++) {
392
+ const i = e.Xt[t];
393
+ let n = this.Xt[t];
393
394
  if (n) n.restoreQueues(i);
394
395
  }
395
396
  }
396
397
  }
397
398
 
398
399
  class GlobalQueue extends Queue {
399
- fn=false;
400
+ sn=false;
400
401
  // The current transaction-shaped batch: a plain ambient batch while no
401
402
  // transition is active, the active transition itself after initTransition.
402
403
  m=createBatch();
403
404
  static Fe;
404
- static He;
405
- static it;
406
- static qt=null;
405
+ static We;
406
+ static ct;
407
+ static ni=null;
407
408
  // Store-side hook: drops a keyless affects() mark's identity scope when the
408
409
  // carrier node's last registration releases (wired by store.ts, mirroring
409
410
  // _clearOptimisticStore).
@@ -417,27 +418,27 @@ class GlobalQueue extends Queue {
417
418
  static N=null;
418
419
  // External-source bridge (wired by enableExternalSource(); null while no
419
420
  // config is active — including after _resetExternalSourceConfig()).
420
- static Pt=null;
421
- static ht=null;
421
+ static kt=null;
422
+ static Lt=null;
422
423
  // Verdict-layer hooks (wired by verdict.ts when isPending()/latest() are
423
424
  // imported; null in apps that never use them). Call sites either guard for
424
425
  // null or sit behind state only the verdict layer can create (`!` is safe
425
426
  // there: `_pendingSignal`/`_latestValueComputed` are only ever assigned by
426
427
  // verdict.ts, and `pendingCheckActive`/`latestReadActive` only flip inside
427
428
  // isPending()/latest()).
428
- static Ue=null;
429
- static de=null;
430
- static me=null;
429
+ static pe=null;
430
+ static Ne=null;
431
+ static ke=null;
431
432
  static un=null;
432
- static gt=null;
433
- static Ht=null;
434
- static kt=null;
435
- static et=null;
436
- static k=null;
433
+ static wt=null;
437
434
  static Wt=null;
435
+ static jt=null;
436
+ static st=null;
437
+ static k=null;
438
+ static ri=null;
438
439
  // Re-asks probes whose verdict was provisionally suppressed by a fresh read
439
440
  // of a held value, once the transaction gains an async blocker (#3028).
440
- static zt=null;
441
+ static si=null;
441
442
  // Optimistic-engine hooks (wired by core/optimistic.ts via
442
443
  // installOptimisticEngine(), called from verdict.ts / createOptimistic /
443
444
  // createOptimisticStore — every module that can create optimistic state).
@@ -445,35 +446,61 @@ class GlobalQueue extends Queue {
445
446
  // `_overrideValue` slot, a lane in `activeLanes`, an `_optimisticNodes`
446
447
  // entry, or a non-null `currentOptimisticLane`, so `!` invocations are safe
447
448
  // once the gate holds.
448
- static xt=null;
449
- static Tn=null;
449
+ static Bt=null;
450
+ static fn=null;
451
+ static cn=null;
450
452
  static dn=null;
451
453
  static In=null;
452
- static Nn=null;
453
454
  /** Patch-channel optimistic drain (next/patch.ts): optimistic emissions
454
455
  * apply at lane-effect timing — visible in flight, unlike the regular
455
456
  * effect queues an action stashes. Injected; null when unused. */
456
457
  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 he=null;
466
- static Xe=null;
467
- static _n=null;
458
+ static Zt=null;
459
+ static qt=null;
460
+ /** Is the node routed through a LIVE lane (`resolveLane`)? read()'s reveal
461
+ * carve-out asks before showing a foreign-held pending node's committed
462
+ * value: a lane-derived flight's inputs are already revealed through the
463
+ * lane (#3334). Gated on CONFIG_HAS_LANE, which only the engine sets. */
464
+ static Mt=null;
465
+ static Kt=null;
466
+ static nt=null;
467
+ static ot=null;
468
+ /** Authoritative-view reader wakeup: installed by until() and refresh() before
469
+ * their first read. Call sites are gated by CONFIG_AUTHORITATIVE_OBSERVED, which
470
+ * only such a reader's carve-out read can set, so `!` invocations are safe once
471
+ * the gate holds (#3303). */
472
+ static Qt=null;
473
+ static ut=null;
474
+ /** A18 supersession (#3331): own-source truth `value` landed under an active
475
+ * override. The engine decides whether the graph re-derives — the value
476
+ * differs from the override and is not a stale (older-action) answer (mark
477
+ * the node, demote its lane cascade, notify), or returns to it after an
478
+ * earlier differing arrival (clear the mark, notify) — and owns the
479
+ * authoritative-observer wake for a silent confirm. Installed with the
480
+ * optimistic engine; only reachable on a node that has an override. */
481
+ static ye=null;
482
+ /** read()'s value for a TRACKED reader of a superseded node (#3331): the
483
+ * staged truth, unless the reader is a stale (render) reader of another
484
+ * transaction — then the displayed override, as it keeps a foreign
485
+ * transaction's committed value over its staged write. */
486
+ static Yt=null;
487
+ /** setSignal's authoritative (projection-write) landing on an override-
488
+ * covered node (#3331 store twin): stage the truth for its transaction's
489
+ * commit whatever its relation to the committed value — a landing equal to
490
+ * committed still differs from the override — then _supersedeOverride
491
+ * decides. Installed with the optimistic engine; only reachable on a node
492
+ * that has an override. */
493
+ static zt=null;
494
+ static Nn=null;
468
495
  flush() {
469
- if (this.fn) return;
496
+ if (this.sn) return;
470
497
  // Fast drain: nothing in flight but plain pending commits — no dirty
471
498
  // computeds, no queued effects, no child queues, no transitions/lanes/
472
499
  // optimistic state. Commit and go; anything a commit hook schedules
473
500
  // (companion snaps, store folds notifying subs) re-arms `scheduled`
474
501
  // below and the outer drain loop takes the full spine next round.
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;
502
+ if (activeTransition === null && dirtyQueue.EE < dirtyQueue.Ke && this.ti[0].length === 0 && this.ti[1].length === 0 && this.Xt.length === 0 && canUseSimpleSyncFlush(this)) {
503
+ this.sn = true;
477
504
  try {
478
505
  // Sweep first: unobserved() pulls swept nodes out of the dirty heap,
479
506
  // so a dormant memo dirtied in the same tick is reclaimed instead of
@@ -481,13 +508,13 @@ class GlobalQueue extends Queue {
481
508
  sweepDormant();
482
509
  commitPendingNodes();
483
510
  } finally {
484
- this.fn = false;
511
+ this.sn = false;
485
512
  }
486
513
  clock++;
487
- scheduled = dirtyQueue.EE >= dirtyQueue.Qe || this.Mt[0].length !== 0 || this.Mt[1].length !== 0 || this.m.Lt.length !== 0;
514
+ scheduled = dirtyQueue.EE >= dirtyQueue.Ke || this.ti[0].length !== 0 || this.ti[1].length !== 0 || this.m.$t.length !== 0;
488
515
  return;
489
516
  }
490
- this.fn = true;
517
+ this.sn = true;
491
518
  try {
492
519
  if (false) ;
493
520
  // Before runHeap for the same reason as the fast drain above; late
@@ -517,35 +544,35 @@ class GlobalQueue extends Queue {
517
544
  if (this.m === e) currentBatch = this.m = createBatch();
518
545
  // Run lane effects immediately (before stashing) - lanes with no pending async
519
546
  if (activeLanes.size) {
520
- GlobalQueue.Nn(EFFECT_RENDER);
521
- GlobalQueue.Nn(EFFECT_USER);
547
+ GlobalQueue.In(EFFECT_RENDER);
548
+ GlobalQueue.In(EFFECT_USER);
522
549
  }
523
- this.stashQueues(e.Bt);
550
+ this.stashQueues(e.ei);
524
551
  clock++;
525
552
  // A kept ambient batch may hold pending nodes (#2916): stay
526
553
  // scheduled so the outer drain loop commits them via the plain
527
554
  // flush path instead of leaving them until the next natural flush.
528
- scheduled = dirtyQueue.EE >= dirtyQueue.Qe || this.m.Lt.length > 0;
529
- reassignPendingTransition(e.Lt);
555
+ scheduled = dirtyQueue.EE >= dirtyQueue.Ke || this.m.$t.length > 0;
556
+ reassignPendingTransition(e.$t);
530
557
  activeTransition = null;
531
558
  finalizePureQueue(null, true);
532
559
  return;
533
560
  }
534
561
  const t = activeTransition;
535
562
  const i = this.m;
536
- i !== t && i.Lt.push(...t.Lt);
537
- this.restoreQueues(t.Bt);
563
+ i !== t && i.$t.push(...t.$t);
564
+ this.restoreQueues(t.ei);
538
565
  transitions.delete(t);
539
566
  activeTransition = null;
540
- reassignPendingTransition(i.Lt);
567
+ reassignPendingTransition(i.$t);
541
568
  finalizePureQueue(t);
542
569
  if (i === t) {
543
570
  // Drop the dead Transition wrapper but keep its (drained) containers
544
571
  // as the ambient batch — late registrations during finalization live
545
572
  // there and must survive to the next flush.
546
573
  const e = createBatch();
547
- e.Lt = i.Lt;
548
- e.ze = i.ze;
574
+ e.$t = i.$t;
575
+ e.it = i.it;
549
576
  e.A = i.A;
550
577
  e.En = i.En;
551
578
  currentBatch = this.m = e;
@@ -553,7 +580,7 @@ class GlobalQueue extends Queue {
553
580
  } else {
554
581
  if (canUseSimpleSyncFlush(this)) {
555
582
  commitPendingNodes();
556
- if (dirtyQueue.EE >= dirtyQueue.Qe) {
583
+ if (dirtyQueue.EE >= dirtyQueue.Ke) {
557
584
  runHeap(dirtyQueue, GlobalQueue.Fe);
558
585
  commitPendingNodes();
559
586
  }
@@ -563,39 +590,54 @@ class GlobalQueue extends Queue {
563
590
  }
564
591
  }
565
592
  clock++;
566
- // Check if finalization added items to the heap (from optimistic reversion)
567
- scheduled = dirtyQueue.EE >= dirtyQueue.Qe;
593
+ // Check if finalization added items to the heap (from optimistic reversion).
594
+ // Finalization may also have ENTERED a transaction (a commit hook, boundary
595
+ // sweep or recompute wrote a node it owns): effects computed under it
596
+ // since are its to apply, not this flush's — runEffect leaves them queued
597
+ // and the next pass parks them with it (#3319). Everything computed
598
+ // mainline applies now.
599
+ scheduled = dirtyQueue.EE >= dirtyQueue.Ke || activeTransition !== null;
568
600
  // Run lane effects first (for ready lanes), then regular effects
569
- activeLanes.size && GlobalQueue.Nn(EFFECT_RENDER);
601
+ activeLanes.size && GlobalQueue.In(EFFECT_RENDER);
570
602
  this.run(EFFECT_RENDER);
571
- activeLanes.size && GlobalQueue.Nn(EFFECT_USER);
603
+ activeLanes.size && GlobalQueue.In(EFFECT_USER);
572
604
  this.run(EFFECT_USER);
573
605
  if (false) ;
574
606
  if (false && !scheduled && !activeTransition && transitions.size === 0 && activeLanes.size === 0) ;
575
607
  if (false) ;
576
608
  } finally {
577
- this.fn = false;
609
+ this.sn = false;
578
610
  }
579
611
  }
580
612
  notify(e, t, i, n) {
581
613
  // Only track async if the boundary is propagating STATUS_PENDING (not caught by boundary)
582
614
  if (t & STATUS_PENDING) {
583
615
  if (i & STATUS_PENDING) {
584
- const t = n !== undefined ? n : e.o?._;
616
+ // Callers pass either nothing or this node's own `_x._error`, so `??`
617
+ // is exact (a null error falls back to the same null).
618
+ const t = n ?? e.o?._;
585
619
  // A visibility-only mark notification (the affects() boundary
586
620
  // channel) updates display state on its way up but must be invisible
587
621
  // to completion accounting BY CONSTRUCTION: it never registers a
588
622
  // reporter and never counts toward the loading-boundary diagnostic.
589
623
  if (t?.l) return true;
590
- if (activeTransition && t) {
591
- const i = t.source;
592
- let n = activeTransition._e.get(i);
593
- if (!n) activeTransition._e.set(i, n = new Set);
594
- const r = n.size;
595
- n.add(e);
596
- if (n.size !== r) {
597
- schedule();
598
- GlobalQueue.zt?.(activeTransition);
624
+ if (t) {
625
+ // A reveal can discover a flight started in an earlier flush. Hold
626
+ // the staged writes with that reader (A15), even if the reader is
627
+ // new. Fresh/reset loading boundaries consume pending before it
628
+ // reaches here. A reader already parked in a transition must not
629
+ // open a second one.
630
+ if (!activeTransition && !e.ge && currentBatch.$t.length) this.initTransition();
631
+ if (activeTransition) {
632
+ const i = t.source;
633
+ let n = activeTransition.Re.get(i);
634
+ if (!n) activeTransition.Re.set(i, n = new Set);
635
+ const r = n.size;
636
+ n.add(e);
637
+ if (n.size !== r) {
638
+ schedule();
639
+ GlobalQueue.si?.(activeTransition);
640
+ }
599
641
  }
600
642
  }
601
643
  }
@@ -613,19 +655,24 @@ class GlobalQueue extends Queue {
613
655
  // new transaction identity each pass. Stamps are cleared at commit, so
614
656
  // this is a belt for paths that hand over a chased-dead reference
615
657
  // (merged chains, async settles racing completion).
616
- if (e.sn === true || e === activeTransition) return;
658
+ if (e.ft === true || e === activeTransition) return;
617
659
  }
618
- if (!e && activeTransition && activeTransition.Te === clock) return;
660
+ if (!e && activeTransition && activeTransition._e === clock) return;
619
661
  if (!activeTransition) {
620
662
  activeTransition = e ?? createBatch();
621
663
  } else if (e) {
622
664
  const t = activeTransition;
623
665
  mergeTransitionState(e, t);
666
+ // Effects the outgoing transaction parked belong to the surviving one
667
+ // now: back onto the live queue, where this flush parks them under
668
+ // `transition` or runs them at its completion. The outgoing stash is
669
+ // never read again — the transaction is dead (#3310).
670
+ this.restoreQueues(t.ei);
624
671
  transitions.delete(t);
625
672
  activeTransition = e;
626
673
  }
627
674
  transitions.add(activeTransition);
628
- activeTransition.Te = clock;
675
+ activeTransition._e = clock;
629
676
  const t = this.m;
630
677
  if (t !== activeTransition) {
631
678
  // Adopt the ambient batch into the transaction, then make the
@@ -635,28 +682,28 @@ class GlobalQueue extends Queue {
635
682
  // must not entangle unrelated writes to it; the same rule holds one hop
636
683
  // downstream: propagation never queues pended subscribers as pending
637
684
  // nodes, see propagateAffectsMark, #2893.
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);
685
+ for (let e = 0; e < t.$t.length; e++) {
686
+ const i = t.$t[e];
687
+ i.ge = activeTransition;
688
+ activeTransition.$t.push(i);
642
689
  }
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);
690
+ for (let e = 0; e < t.it.length; e++) {
691
+ const i = t.it[e];
692
+ i.ge = activeTransition;
693
+ activeTransition.it.push(i);
647
694
  }
648
695
  if (t.A.length) activeTransition.A.push(...t.A);
649
696
  for (const e of t.En) activeTransition.En.add(e);
650
697
  // Gated readers recorded against the ambient batch move with it: their
651
698
  // replay-at-commit now happens at the transaction's completion.
652
- if (t.cn.size) {
653
- for (const e of t.cn) activeTransition.cn.add(e);
654
- t.cn.clear();
699
+ if (t.lt.size) {
700
+ for (const e of t.lt) activeTransition.lt.add(e);
701
+ t.lt.clear();
655
702
  }
656
703
  currentBatch = this.m = activeTransition;
657
704
  }
658
705
  for (const e of activeLanes) {
659
- if (!e.Ae) e.Ae = activeTransition;
706
+ if (!e.ge) e.ge = activeTransition;
660
707
  }
661
708
  // A transaction's ambient window is one flush. Entering must therefore
662
709
  // guarantee a flush: a transaction opened with no writes (an action whose
@@ -671,7 +718,7 @@ class GlobalQueue extends Queue {
671
718
  }
672
719
 
673
720
  function queuePendingNode(e) {
674
- currentBatch.Lt.push(e);
721
+ currentBatch.$t.push(e);
675
722
  }
676
723
 
677
724
  // Sticky: flips true on the first refresh() ever (the only setter of
@@ -692,10 +739,30 @@ function armReaskClear() {
692
739
  reaskArmed = true;
693
740
  }
694
741
 
742
+ /** Provenance of the work currently running (A18 supersession, #3331): the
743
+ * invocation sequence of the action whose ambient window this is — set by
744
+ * action() for each slice; the flush that ends the window clears it — or,
745
+ * inside an async landing, the sequence captured when that flight was
746
+ * registered (asyncWrite sets it for the landing's synchronous propagation,
747
+ * so a sync recompute downstream of the landing — an optimistic wrapper over
748
+ * the async source — derives under the flight's provenance, and flights it
749
+ * registers inherit it). 0 is mainline: no action, always the current
750
+ * question. An override stamps this at its write (`_overrideStamp`); an
751
+ * answer whose flight an OLDER action issued is a stale question the user
752
+ * has since changed — it holds silently to commit instead of superseding. A
753
+ * slow source must not leak back in over a newer intent. Transactions merge,
754
+ * so the transition object cannot say WHICH action asked; this can. */ let origin = 0;
755
+
756
+ function setOrigin(e) {
757
+ const t = origin;
758
+ origin = e;
759
+ return t;
760
+ }
761
+
695
762
  function insertSubs(e, t = false) {
696
763
  // §12d: stamp before walking — setSignal's staged-rewrite fast path skips
697
764
  // the next walk for this node while the epoch holds (marking is idempotent).
698
- e.It = notifyEpoch;
765
+ e.At = notifyEpoch;
699
766
  // Get source lane: prefer node's own lane over current context
700
767
  // This is important for isPending signals which need their own lane to flush immediately
701
768
  // Presence bits gate the optional-slot probes (see constants.ts): one
@@ -703,11 +770,11 @@ function insertSubs(e, t = false) {
703
770
  // lookups in the hottest notify loop. Bits are sticky — the field read
704
771
  // stays authoritative when a bit is set.
705
772
  const i = e.T;
706
- const n = (i & CONFIG_HAS_LANE ? e.o?.Je : undefined) || currentOptimisticLane;
707
- const r = (i & CONFIG_HAS_SNAPSHOT) !== 0 && e.o?.We !== undefined;
773
+ const n = (i & CONFIG_HAS_LANE ? e.o?.Oe : undefined) || currentOptimisticLane;
774
+ const r = (i & CONFIG_HAS_SNAPSHOT) !== 0 && e.o?.ze !== undefined;
708
775
  const s = reaskArmed;
709
- for (let i = e.u; i !== null; i = i.ae) {
710
- const e = i.ce;
776
+ for (let i = e.u; i !== null; i = i.Te) {
777
+ const e = i.Ie;
711
778
  // A value-change notification is a new question for the subscriber: any
712
779
  // pending re-ask mark (refresh) it carried is superseded.
713
780
  if (s) e.ie &= ~REACTIVE_REASK;
@@ -719,7 +786,7 @@ function insertSubs(e, t = false) {
719
786
  // pass either re-reads them fresh or trims them), and neither does the
720
787
  // tail link: it is the read IN FLIGHT — read() links before it pulls, so
721
788
  // this very commit is what that read returns.
722
- if (e.ie & REACTIVE_RECOMPUTING_DEPS && i.Ft === e.Ke && i !== e.je) e.ie |= REACTIVE_MISSED_WAKE;
789
+ if (e.ie & REACTIVE_RECOMPUTING_DEPS && i.yt === e.tt && i !== e.et) e.ie |= REACTIVE_MISSED_WAKE;
723
790
  if (r && e.T & CONFIG_IN_SNAPSHOT_SCOPE) {
724
791
  e.ie |= REACTIVE_SNAPSHOT_STALE;
725
792
  continue;
@@ -730,7 +797,7 @@ function insertSubs(e, t = false) {
730
797
  } else if (t) {
731
798
  e.ie |= REACTIVE_OPTIMISTIC_DIRTY;
732
799
  // No source lane means reversion - clear subscriber's lane so effects go to regular queue
733
- if (e.o) e.o.Je = undefined;
800
+ if (e.o) e.o.Oe = undefined;
734
801
  }
735
802
  enqueueSub(e);
736
803
  }
@@ -739,32 +806,36 @@ function insertSubs(e, t = false) {
739
806
  function commitPendingNode(e) {
740
807
  const t = e;
741
808
  if (!t.oe) {
742
- if (e.Re !== NOT_PENDING) {
743
- e.be = e.Re;
744
- e.Re = NOT_PENDING;
809
+ if (e.Ge !== NOT_PENDING) {
810
+ e.me = e.Ge;
811
+ e.Ge = NOT_PENDING;
745
812
  }
746
813
  if (e.T & CONFIG_HAS_COMPANIONS) GlobalQueue.un(e);
747
814
  return;
748
815
  }
749
- if (e.Re !== NOT_PENDING) {
750
- e.be = e.Re;
751
- e.Re = NOT_PENDING;
816
+ if (e.Ge !== NOT_PENDING) {
817
+ e.me = e.Ge;
818
+ e.Ge = NOT_PENDING;
752
819
  // Set _modified for effects, but not for tracked effects (they handle their own scheduling)
753
- if (e.ge && e.ge !== EFFECT_TRACKED) e.tt = true;
820
+ if (e.Ce && e.Ce !== EFFECT_TRACKED) e.He = true;
754
821
  // A quiet re-ask classification preserved through a held landing dies
755
822
  // with the value commit — the commit IS the reveal (#3178). Gated on the
756
823
  // staged value: status propagation queues pending nodes whose windows
757
824
  // are still OPEN (no staged value), and their live classification must
758
825
  // survive this sweep.
759
- if (e.o) e.o.De = false;
826
+ if (e.o) e.o.Ue = false;
760
827
  }
761
828
  // The committed hold is the first observable answer for a loading-window
762
829
  // node — the window closes here, not at compute time (#2990). Unconditional
763
830
  // store to an always-present computed slot.
764
- t.Ne = false;
831
+ t.Ae = false;
765
832
  t.ie &= ~REACTIVE_MANUAL_WRITE;
766
833
  if (!(t.S & STATUS_PENDING)) t.S &= ~STATUS_UNINITIALIZED;
767
- if (t.o != null && (t.o.Ye !== null || t.o.qe !== null)) GlobalQueue.He(t, false, true);
834
+ // A flight this commit leaves in the air (unobserved, or observed only by
835
+ // a boundary) now has PUBLISHED inputs: its committed value is stale
836
+ // against the frame. read()'s reveal carve-out keys on the mark (#3305).
837
+ else e.T |= CONFIG_INPUTS_PUBLISHED;
838
+ if (t.o != null && (t.o.Xe !== null || t.o.Je !== null)) GlobalQueue.We(t, false, true);
768
839
  if (e.T & CONFIG_HAS_COMPANIONS) GlobalQueue.un(e);
769
840
  }
770
841
 
@@ -779,17 +850,6 @@ function setStoreCommitHook(e) {
779
850
  storeCommitHook = e;
780
851
  }
781
852
 
782
- /** Patch-channel release hook (next/patch.ts): transition-stamped patch
783
- * emissions are released when THEIR batch commits. Transitions never
784
- * abort: failed actions still commit (only optimistic overrides revert),
785
- * and merged-away transitions hand their stash to the survivor
786
- * (mergeTransitionState) — every stash drains exactly once. Injected like
787
- * storeCommitHook to stay tree-shakeable. */ let patchCommitHook = null;
788
-
789
- function setPatchCommitHook(e) {
790
- patchCommitHook = e;
791
- }
792
-
793
853
  /** Held truth committed this finalize, awaiting its post-revert wake (see
794
854
  * finalizePureQueue): the commit IS the reveal, but subscribers must not
795
855
  * re-derive until the settling transaction's optimistic overrides have
@@ -798,7 +858,7 @@ function setPatchCommitHook(e) {
798
858
  * frame no timeline contains. */ const heldRevealed = [];
799
859
 
800
860
  function commitPendingNodes() {
801
- const e = currentBatch.Lt;
861
+ const e = currentBatch.$t;
802
862
  for (let t = 0; t < e.length; t++) {
803
863
  const i = e[t];
804
864
  commitPendingNode(i);
@@ -813,7 +873,7 @@ function commitPendingNodes() {
813
873
  // that old view and cached it), and commits are otherwise silent
814
874
  // (staging already notified), so without a wake they'd hold the old
815
875
  // world forever.
816
- i.Ae = null;
876
+ i.ge = null;
817
877
  if (i.T & CONFIG_HELD_TRUTH) {
818
878
  i.T &= ~CONFIG_HELD_TRUTH;
819
879
  heldRevealed.push(i);
@@ -821,34 +881,61 @@ function commitPendingNodes() {
821
881
  }
822
882
  e.length = 0;
823
883
  storeCommitHook?.();
824
- patchCommitHook?.(currentBatch);
825
884
  }
826
885
 
827
886
  function finalizePureQueue(e = null, t = false) {
828
887
  // For incomplete transitions, skip pending resolution and optimistic reversion
829
888
  // For completing transitions or no-transition, resolve pending and revert optimistic
830
- const i = !t;
831
- if (i) commitPendingNodes();
832
- if (!t && globalQueue.Qt.length) checkBoundaryChildren(globalQueue);
833
- const n = dirtyQueue.EE >= dirtyQueue.Qe;
834
- if (n) runHeap(dirtyQueue, GlobalQueue.Fe);
835
- if (i) {
836
- if (n) commitPendingNodes();
889
+ const i = currentBatch;
890
+ const n = !t;
891
+ if (n) commitPendingNodes();
892
+ if (!t && globalQueue.Xt.length) checkBoundaryChildren(globalQueue);
893
+ // Contested effects (#3322) re-derive from the world this commit just
894
+ // produced. Ahead of the heap run — not the post-heap gated replay — so
895
+ // the recompute and the effect phase land in this same pass and the value
896
+ // the other transaction wrote into the slot is never published.
897
+ // (No clear: a completed transition is never finalized again.)
898
+ // A transaction whose settle reverts optimism re-derives them post-revert
899
+ // instead (below, with the gated replay): between commitPendingNodes and
900
+ // _resolveOptimistic the truth is committed but the overrides still
901
+ // display, and a re-derive here would compose the two — the #3164 tear,
902
+ // one window later. The slot meanwhile holds the frame that is on screen.
903
+ const r = e?.Et;
904
+ const s = n && (e ?? i).it.length !== 0;
905
+ if (r && !s) for (const e of r) if (!(e.ie & REACTIVE_DISPOSED)) enqueueSub(e);
906
+ const o = dirtyQueue.EE >= dirtyQueue.Ke;
907
+ if (o) runHeap(dirtyQueue, GlobalQueue.Fe);
908
+ if (n) {
909
+ // Boundary checks, commit hooks and recomputes can enter a transaction,
910
+ // which adopts the batch this finalize was settling: nothing batch-derived
911
+ // may be committed or reverted here — the entered transaction owns it now
912
+ // (#3319). A completing transaction's OWN containers are a different
913
+ // matter: when the ambient batch was separate from it (the #2916 shape),
914
+ // adoption never touched them and it must still settle them; when the
915
+ // batch WAS the completing transaction, adoption re-stamped its contents
916
+ // into the entered one and there is nothing left to settle.
917
+ if (currentBatch !== i) {
918
+ if (e === null || e === i) return;
919
+ } else if (o) commitPendingNodes();
837
920
  // The settling batch: the completing transaction's, or the ambient one.
838
- const t = e ?? globalQueue.m;
921
+ const t = e ?? i;
839
922
  // Optimistic reversion: a non-empty batch means _optimisticWrite ran,
840
923
  // which installed the engine's hooks.
841
- if (t.ze.length) GlobalQueue.Tn(t.ze);
924
+ if (t.it.length) GlobalQueue.fn(t.it);
925
+ if (r && s) {
926
+ for (const e of r) if (!(e.ie & REACTIVE_DISPOSED)) enqueueSub(e);
927
+ schedule();
928
+ }
842
929
  // Replay entanglement: subs recorded by the read-time gate get rescheduled
843
930
  // so they re-run with the now-committed values visible. The ambient batch
844
931
  // replays too — laneReadsCommitted records readers whose committed-view
845
932
  // read hid a same-tick plain write that just committed above (#2963).
846
- if (t.cn.size) {
847
- for (const e of t.cn) {
933
+ if (t.lt.size) {
934
+ for (const e of t.lt) {
848
935
  if (e.ie & REACTIVE_DISPOSED) continue;
849
936
  enqueueSub(e);
850
937
  }
851
- t.cn.clear();
938
+ t.lt.clear();
852
939
  // A completing transition keeps the outer flush loop alive by itself;
853
940
  // the ambient batch needs the re-arm or the replay sits in the heap
854
941
  // until the next unrelated write.
@@ -862,13 +949,13 @@ function finalizePureQueue(e = null, t = false) {
862
949
  // (the earlier sweep above ran while the marks were still live).
863
950
  if (t.A.length) {
864
951
  GlobalQueue.G(t.A);
865
- if (globalQueue.Qt.length) checkBoundaryChildren(globalQueue);
952
+ if (globalQueue.Xt.length) checkBoundaryChildren(globalQueue);
866
953
  }
867
954
  // A non-empty set means trackOptimisticStore ran, which installed the
868
955
  // hook; the hook iterates, clears, and schedules (keeping the loop out of
869
956
  // core lets esbuild shake it — rollup already folds the null guard). The
870
957
  // completing transition scopes the clear to its own layer keys (#2899).
871
- if (t.En.size) GlobalQueue.qt(t.En, e);
958
+ if (t.En.size) GlobalQueue.ni(t.En, e);
872
959
  // Held-truth reveal wake (#3164), post-revert by construction: this
873
960
  // finalize committed confirming truth whose subscribers were masked all
874
961
  // hold — some re-derived against the committed view (the staging, or a
@@ -880,19 +967,19 @@ function finalizePureQueue(e = null, t = false) {
880
967
  // committed but the settling transaction's overrides still display.
881
968
  if (heldRevealed.length !== 0) {
882
969
  while (heldRevealed.length) insertSubs(heldRevealed.pop());
883
- if (dirtyQueue.EE >= dirtyQueue.Qe) {
970
+ if (dirtyQueue.EE >= dirtyQueue.Ke) {
884
971
  runHeap(dirtyQueue, GlobalQueue.Fe);
885
972
  commitPendingNodes();
886
973
  }
887
974
  }
888
975
  sweepTransientStoreNodes();
889
976
  // Lanes only enter activeLanes through the engine's getOrCreateLane.
890
- if (activeLanes.size) GlobalQueue.In(e);
977
+ if (activeLanes.size) GlobalQueue.dn(e);
891
978
  }
892
979
  }
893
980
 
894
981
  function checkBoundaryChildren(e) {
895
- for (const t of e.Qt) {
982
+ for (const t of e.Xt) {
896
983
  t.se?.();
897
984
  checkBoundaryChildren(t);
898
985
  }
@@ -916,7 +1003,7 @@ function checkBoundaryChildren(e) {
916
1003
 
917
1004
  function reassignPendingTransition(e) {
918
1005
  for (let t = 0; t < e.length; t++) {
919
- e[t].Ae = activeTransition;
1006
+ e[t].ge = activeTransition;
920
1007
  }
921
1008
  }
922
1009
 
@@ -930,6 +1017,16 @@ const globalQueue = new GlobalQueue;
930
1017
  let currentBatch = globalQueue.m;
931
1018
 
932
1019
  function flush(e) {
1020
+ // Inside an action body the drain is incoherent (#3333): the action's
1021
+ // writes are held by its transaction until it settles, so a drain can't
1022
+ // reveal them — and the loop below only exits once `activeTransition` is
1023
+ // null, so it would PARK the transaction mid-slice and every write after it
1024
+ // in the body would land as a plain, committed write. The reporter's
1025
+ // "leading flush()" workaround was exactly that leak. Prod: run `fn` if
1026
+ // given (its writes stay in the transaction) and skip the drain.
1027
+ if (actionStepDepth > 0) {
1028
+ return e ? e() : undefined;
1029
+ }
933
1030
  if (e) {
934
1031
  syncDepth++;
935
1032
  try {
@@ -944,7 +1041,7 @@ function flush(e) {
944
1041
  }
945
1042
  }
946
1043
  }
947
- if (globalQueue.fn) {
1044
+ if (globalQueue.sn) {
948
1045
  return;
949
1046
  }
950
1047
  if (halted) return;
@@ -953,6 +1050,9 @@ function flush(e) {
953
1050
  while (scheduled || activeTransition) {
954
1051
  globalQueue.flush();
955
1052
  }
1053
+ // Provenance ends with the drain: every ambient window (an action's first
1054
+ // slice, a landing's propagation) runs to this flush.
1055
+ origin = 0;
956
1056
  }
957
1057
 
958
1058
  function runQueue(e, t) {
@@ -962,21 +1062,23 @@ function runQueue(e, t) {
962
1062
  function reporterBlocksSource(e, t) {
963
1063
  if (e.ie & (REACTIVE_ZOMBIE | REACTIVE_DISPOSED)) return false;
964
1064
  if (e.o?.le?.has(t)) return true;
965
- for (let i = e.ut; i; i = i.lt) {
966
- let e = i.ot;
1065
+ for (let i = e.fe; i; i = i.ae) {
1066
+ let e = i.Se;
967
1067
  while (e) {
968
- if (e === t || e.st === t) return true;
969
- e = e.o?.Tt;
1068
+ if (e === t || e.ce === t) return true;
1069
+ e = e.o?.Gt;
970
1070
  }
971
1071
  }
972
1072
  return !!(e.S & STATUS_PENDING && e.o?._ instanceof NotReadyError && e.o?._.source === t);
973
1073
  }
974
1074
 
975
1075
  function transitionComplete(e) {
976
- if (e.sn) return true;
977
- if (e.ue.length) return false;
1076
+ if (e.ft) return true;
1077
+ if (e.ue.length) {
1078
+ return false;
1079
+ }
978
1080
  let t = true;
979
- for (const [i, n] of e._e) {
1081
+ for (const [i, n] of e.Re) {
980
1082
  let r = false;
981
1083
  for (const e of n) {
982
1084
  if (reporterBlocksSource(e, i)) {
@@ -985,7 +1087,7 @@ function transitionComplete(e) {
985
1087
  }
986
1088
  n.delete(e);
987
1089
  }
988
- if (!r) e._e.delete(i); else if (i.S & STATUS_PENDING && i.o?._?.source === i) {
1090
+ if (!r) e.Re.delete(i); else if (i.S & STATUS_PENDING && i.o?._?.source === i) {
989
1091
  t = false;
990
1092
  break;
991
1093
  }
@@ -993,8 +1095,8 @@ function transitionComplete(e) {
993
1095
  // Override blockage lives with the engine (absent hook = "no optimistic
994
1096
  // blockage"); the hook's loops over _optimisticNodes/_optimisticStores are
995
1097
  // no-ops when the transition holds neither, so no pre-check is needed.
996
- if (t && GlobalQueue.dn?.(e)) t = false;
997
- t && (e.sn = true);
1098
+ if (t && GlobalQueue.cn?.(e)) t = false;
1099
+ t && (e.ft = true);
998
1100
  return t;
999
1101
  }
1000
1102
 
@@ -1006,10 +1108,23 @@ function transitionComplete(e) {
1006
1108
  }
1007
1109
 
1008
1110
  function currentTransition(e) {
1009
- while (e.sn && typeof e.sn === "object") e = e.sn;
1111
+ while (e.ft && typeof e.ft === "object") e = e.ft;
1010
1112
  return e;
1011
1113
  }
1012
1114
 
1115
+ /**
1116
+ * The live transition blocked on `source` — the one whose render reader
1117
+ * observed it pending (INV-3 records the observation in whichever transaction
1118
+ * was active when the reader was notified). The observation is a fact about
1119
+ * the node, so a hold check must not assume it was recorded in the transaction
1120
+ * it happens to hold — lanes merge across transactions (#2912), and a merged
1121
+ * root's transaction knows nothing of the async its members' transactions
1122
+ * observed (#3335). Null when nobody is waiting.
1123
+ */ function waitingTransition(e) {
1124
+ for (const t of transitions) if (t.Re.has(e)) return t;
1125
+ return null;
1126
+ }
1127
+
1013
1128
  function runInTransition(e, t) {
1014
1129
  const i = activeTransition;
1015
1130
  try {
@@ -1043,4 +1158,4 @@ function runInTransition(e, t) {
1043
1158
  }
1044
1159
  }
1045
1160
 
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 };
1161
+ export { GlobalQueue, Queue, actionStepDepth, activeAffectsMarks, activeLanes, activeTransition, armReaskClear, assignOrMergeLane, bumpNotifyEpoch, clock, createTransition, currentTransition, dirtyQueue, enforceLoadingBoundary, entangleConfirmingTransitions, enterActionStep, exitActionStep, finalizePureQueue, findLane, flush, globalQueue, haltReactivity, hasActiveOverride, insertSubs, notifyEpoch, origin, projectionWriteActive, queuePendingNode, reaskArmed, resetErrorHalt, runAsTransitionBatch, runInTransition, schedule, setOrigin, setProjectionWriteActive, setStoreCommitHook, shiftAffectsMarks, storeCommitHook, waitingTransition, zombieQueue };