@solidjs/signals 2.0.0-rc.18 → 2.0.0-rc.19

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 (65) hide show
  1. package/dist/dev-shared.js +168 -88
  2. package/dist/dev.attribution.js +16 -13
  3. package/dist/dev.js +272 -129
  4. package/dist/observe/boundaries.js +88 -67
  5. package/dist/observe/core/action.js +8 -0
  6. package/dist/observe/core/async.js +46 -34
  7. package/dist/observe/core/attribution.js +30 -28
  8. package/dist/observe/core/constants.js +21 -1
  9. package/dist/observe/core/core.js +164 -136
  10. package/dist/observe/core/effect.js +6 -6
  11. package/dist/observe/core/graph.js +3 -3
  12. package/dist/observe/core/heap.js +5 -5
  13. package/dist/observe/core/lanes.js +104 -84
  14. package/dist/observe/core/owner.js +10 -9
  15. package/dist/observe/core/scheduler.js +38 -35
  16. package/dist/observe/core/truth.js +6 -6
  17. package/dist/observe/core/verdict.js +13 -13
  18. package/dist/observe/deferred.js +39 -40
  19. package/dist/observe/index.js +2 -2
  20. package/dist/observe/map.js +22 -22
  21. package/dist/observe/reveal.js +5 -5
  22. package/dist/observe/signals.js +57 -15
  23. package/dist/observe/store/affects.js +1 -1
  24. package/dist/observe/store/hooks.js +3 -0
  25. package/dist/observe/store/optimistic.js +11 -11
  26. package/dist/observe/store/projection.js +34 -21
  27. package/dist/observe/store/reconcile.js +2 -2
  28. package/dist/observe/store/store.js +63 -53
  29. package/dist/observe/store/utils.js +7 -2
  30. package/dist/prod/boundaries.js +153 -132
  31. package/dist/prod/core/action.js +8 -0
  32. package/dist/prod/core/async.js +55 -43
  33. package/dist/prod/core/constants.js +14 -1
  34. package/dist/prod/core/core.js +162 -134
  35. package/dist/prod/core/effect.js +9 -9
  36. package/dist/prod/core/graph.js +3 -3
  37. package/dist/prod/core/heap.js +12 -12
  38. package/dist/prod/core/lanes.js +98 -78
  39. package/dist/prod/core/owner.js +15 -14
  40. package/dist/prod/core/scheduler.js +43 -40
  41. package/dist/prod/core/truth.js +6 -6
  42. package/dist/prod/core/verdict.js +17 -17
  43. package/dist/prod/deferred.js +39 -40
  44. package/dist/prod/index.js +2 -2
  45. package/dist/prod/map.js +14 -14
  46. package/dist/prod/reveal.js +8 -8
  47. package/dist/prod/signals.js +46 -4
  48. package/dist/prod/store/affects.js +1 -1
  49. package/dist/prod/store/hooks.js +3 -0
  50. package/dist/prod/store/optimistic.js +16 -16
  51. package/dist/prod/store/projection.js +22 -9
  52. package/dist/prod/store/reconcile.js +2 -2
  53. package/dist/prod/store/store.js +262 -262
  54. package/dist/prod/store/utils.js +2 -0
  55. package/dist/types/core/action.d.ts +8 -0
  56. package/dist/types/core/constants.d.ts +20 -0
  57. package/dist/types/core/core.d.ts +14 -1
  58. package/dist/types/core/dev.d.ts +1 -1
  59. package/dist/types/core/index.d.ts +1 -1
  60. package/dist/types/core/lanes.d.ts +5 -1
  61. package/dist/types/core/scheduler.d.ts +2 -0
  62. package/dist/types/index.d.ts +3 -1
  63. package/dist/types/signals.d.ts +37 -0
  64. package/dist/types/store/target.d.ts +2 -0
  65. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import { STATUS_PENDING, STATUS_ERROR, CONFIG_REDERIVE, STATUS_UNINITIALIZED, CONFIG_HELD, EFFECT_RENDER, CONFIG_VERDICT, REACTIVE_DISPOSED, REACTIVE_LANE_READ, CONFIG_OVERRIDE, EFFECT_USER, EFFECT_TRACKED, REACTIVE_ZOMBIE, CAUGHT_REARMED } from "./core/constants.js";
1
+ import { STATUS_PENDING, STATUS_ERROR, CONFIG_REDERIVE, CONFIG_HELD, CONFIG_FRAME, STATUS_UNINITIALIZED, EFFECT_RENDER, CONFIG_VERDICT, REACTIVE_DISPOSED, REACTIVE_LANE_READ, CONFIG_OVERRIDE, EFFECT_USER, EFFECT_TRACKED, REACTIVE_ZOMBIE, CAUGHT_REARMED } from "./core/constants.js";
2
2
 
3
3
  import { signal, runWithOwner, computed, read, setSignal, recompute } from "./core/core.js";
4
4
 
@@ -12,7 +12,7 @@ import { enqueueSub } from "./core/heap.js";
12
12
 
13
13
  import { createOwner, getOwner, cleanup, dispose } from "./core/owner.js";
14
14
 
15
- import { globalQueue, haltReactivityAt, haltReactivity, passLane, joinFuture, GlobalQueue, flushTransaction, heldUnder, setPassLane, txOf, unhold, laneStageHook, schedule, installBoundaryHooks } from "./core/scheduler.js";
15
+ import { globalQueue, haltReactivityAt, haltReactivity, schedule, passLane, joinFuture, GlobalQueue, flushTransaction, heldUnder, setPassLane, txOf, unhold, laneStageHook, installBoundaryHooks } from "./core/scheduler.js";
16
16
 
17
17
  import { flatten } from "./flatten.js";
18
18
 
@@ -65,6 +65,9 @@ import { accessor } from "./signals.js";
65
65
  * slot of (reveal.ts); a boundary clears it for its content — only direct
66
66
  * children are slots. */ const REVEAL = Symbol("");
67
67
 
68
+ // Every call site checks the hooks themselves first (not only the slot or
69
+ // controller, which are never set without them): a build that does not
70
+ // import `createRevealOrder` folds the reveal-order arms away.
68
71
  let revealHooks = null;
69
72
 
70
73
  function setRevealHooks(e) {
@@ -78,33 +81,33 @@ const boundaryOf = e => e.A[BOUNDARY];
78
81
  /** Catches what reaches it: an error boundary always; a loading one until it
79
82
  * has shown content, or while re-armed. */ const isCollecting = e => e.i === STATUS_ERROR || !e.D || e.h;
80
83
 
81
- /** A reader `b` waits on: recorded for its `on`, and `b` visited at the seam. */ function record(e, n) {
82
- e.U.add(n);
84
+ /** A reader `b` waits on: recorded for its `on`, and `b` visited at the seam. */ function record(e, r) {
85
+ e.F.add(r);
83
86
  collecting.add(e);
84
87
  }
85
88
 
86
89
  /** The reader goes to the fallback: recorded, and the output re-derives
87
90
  * (a no-op while the output's own pass is running — its post-check
88
- * decides). */ function caught(e, n, r) {
91
+ * decides). */ function caught(e, r, n) {
89
92
  if (e.i === STATUS_ERROR) {
90
93
  // An error boundary waits on what THREW (`StatusError.source`), not on
91
94
  // the reader that saw it: the reader recovers the moment a refetch
92
95
  // starts (its boundary catches the pending), while the error is settled
93
96
  // only when its source is neither errored nor in flight. `reset` re-runs
94
97
  // the same node.
95
- r ??= n.T?.F;
96
- n = r?.source ?? n;
97
- const t = unwrapStatusError(r);
98
- setSignal(e.F, t);
99
- reportClientError(t, e.L, n);
98
+ n ??= r.T?.U;
99
+ r = n?.source ?? r;
100
+ const o = unwrapStatusError(n);
101
+ setSignal(e.U, o);
102
+ reportClientError(o, e.H, r);
100
103
  }
101
- record(e, n);
104
+ record(e, r);
102
105
  if (e.h) flip(e);
103
106
  // Already showing the fallback: one more reader to wait on changes
104
107
  // nothing on screen. The fallback is instantiated once per show — a later
105
108
  // error reaches it through the `error` accessor, which is why it is one.
106
- if (!e.H) redraw(e);
107
- if (e.V !== null) revealHooks.P(e.V);
109
+ if (!e.L) redraw(e);
110
+ if (revealHooks && e.v !== null) revealHooks.V(e.v);
108
111
  }
109
112
 
110
113
  /** A re-armed boundary found something pending: fresh again, and the swap
@@ -113,10 +116,10 @@ const boundaryOf = e => e.A[BOUNDARY];
113
116
  * transaction holding its pending content, which would entangle that
114
117
  * transaction with the arming frame's (#3540 `on: () => latest(dep)`). */ function flip(e) {
115
118
  e.D = false;
116
- e.v = e.B;
117
- const n = e.j;
118
- if (!e.v && n.l & CONFIG_HELD && txOf(n) !== flushTransaction) unhold(n);
119
- if (e.v !== null && e.j !== null) laneStageHook(e.j, e.v, true, false);
119
+ e.P = e.B;
120
+ const r = e.j;
121
+ if (!e.P && r.l & CONFIG_HELD && txOf(r) !== flushTransaction) unhold(r);
122
+ if (laneStageHook && e.P !== null && e.j !== null) laneStageHook(e.j, e.P, true, false);
120
123
  }
121
124
 
122
125
  function redraw(e) {
@@ -128,8 +131,8 @@ function redraw(e) {
128
131
 
129
132
  /** `catchHook` (scheduler.ts): status from a frame reader, nearest boundary first.
130
133
  * A loading boundary on the way records a pending reader whether or not it
131
- * catches it (its `on` may collect it later). */ function catchStatus(e, n, r) {
132
- if (n === 0) {
134
+ * catches it (its `on` may collect it later). */ function catchStatus(e, r, n) {
135
+ if (r === 0) {
133
136
  // A status cleared. Judged by the node's status now, against each
134
137
  // boundary's own rule (`unsettled`) — the error path clears pending
135
138
  // first and the node is still errored then. The last reader a fallback
@@ -137,31 +140,31 @@ function redraw(e) {
137
140
  // held reader's landing has joined its transaction, so the content
138
141
  // lands with its run, not a round after). Others still waiting: nothing
139
142
  // on screen changes, the fallback stays as it is.
140
- if (collecting.size !== 0) for (let n = boundaryOf(e); n; n = n._parent) if (!(e.m & unsettled(n)) && n.U.delete(e) && n.U.size === 0) {
141
- if (n.H) redraw(n);
142
- if (n.V !== null) revealHooks.P(n.V);
143
+ if (collecting.size !== 0) for (let r = boundaryOf(e); r; r = r._parent) if (!(e.m & unsettled(r)) && r.F.delete(e) && r.F.size === 0) {
144
+ if (r.L) redraw(r);
145
+ if (revealHooks && r.v !== null) revealHooks.V(r.v);
143
146
  }
144
147
  return false;
145
148
  }
146
- for (let t = boundaryOf(e); t; t = t._parent) {
147
- if (!(t.i & n)) continue;
148
- if (isCollecting(t)) {
149
- caught(t, e, r);
150
- return t.h ? CAUGHT_REARMED : true;
149
+ for (let o = boundaryOf(e); o; o = o._parent) {
150
+ if (!(o.i & r)) continue;
151
+ if (isCollecting(o)) {
152
+ caught(o, e, n);
153
+ return o.h ? CAUGHT_REARMED : true;
151
154
  }
152
- record(t, e);
155
+ record(o, e);
153
156
  }
154
157
  return false;
155
158
  }
156
159
 
157
160
  /** The nearest loading boundary that would catch `node` was re-armed this
158
161
  * flush. */ function armedOver(e) {
159
- for (let n = boundaryOf(e); n; n = n._parent) if (n.i & STATUS_PENDING && isCollecting(n)) return n.h;
162
+ for (let r = boundaryOf(e); r; r = r._parent) if (r.i & STATUS_PENDING && isCollecting(r)) return r.h;
160
163
  }
161
164
 
162
165
  /** The nearest boundary on `r`'s chain showing its fallback, if any
163
166
  * (`hiddenHook`: `r` is behind a fallback). */ function hiddenBy(e) {
164
- for (let n = boundaryOf(e); n; n = n._parent) if (n.H) return n;
167
+ for (let r = boundaryOf(e); r; r = r._parent) if (r.L) return r;
165
168
  return undefined;
166
169
  }
167
170
 
@@ -176,21 +179,21 @@ function redraw(e) {
176
179
  * synchronous on creation and never comes here. The run's `_modified` stays
177
180
  * set; the reveal re-queues it by type (`release`), and a boundary above
178
181
  * still showing its fallback holds it again then. */ function heldRun(e) {
179
- const n = hiddenBy(e);
180
- if (n === undefined) return false;
181
- (n.Z ??= new Set).add(e);
182
+ const r = hiddenBy(e);
183
+ if (r === undefined) return false;
184
+ (r.M ??= new Set).add(e);
182
185
  return true;
183
186
  }
184
187
 
185
188
  /** The reveal: the runs held behind the fallback are this flush's, each in
186
189
  * its own phase — render updates with the frame that attaches the content,
187
190
  * user effects after it. */ function release(e) {
188
- if (e.Z === null) return;
189
- for (const n of e.Z) {
190
- const e = n.i;
191
- globalQueue.enqueue(e === EFFECT_TRACKED ? EFFECT_USER : e, e === EFFECT_TRACKED ? n.M : n.W ??= GlobalQueue.K.bind(null, n));
191
+ if (e.M === null) return;
192
+ for (const r of e.M) {
193
+ const e = r.i;
194
+ globalQueue.enqueue(e === EFFECT_TRACKED ? EFFECT_USER : e, e === EFFECT_TRACKED ? r.Z : r.W ??= GlobalQueue.K.bind(null, r));
192
195
  }
193
- e.Z = null;
196
+ e.M = null;
194
197
  }
195
198
 
196
199
  /** Drop the readers that settled or died. Settled: a loading reader no
@@ -200,11 +203,11 @@ function redraw(e) {
200
203
  * it and enters the transaction itself (its committed value shows until the
201
204
  * landing). */ const unsettled = e => e.i === STATUS_ERROR ? STATUS_ERROR | STATUS_PENDING : STATUS_PENDING;
202
205
 
203
- function prune(e, n) {
204
- const r = unsettled(e);
205
- for (const t of e.U) {
206
- if (t.R & REACTIVE_DISPOSED) e.U.delete(t); else if (!(t.m & r)) {
207
- if ((t !== e.Y || e.h) && (t.l & (CONFIG_HELD | CONFIG_OVERRIDE)) === CONFIG_HELD) {
206
+ function prune(e, r) {
207
+ const n = unsettled(e);
208
+ for (const o of e.F) {
209
+ if (o.R & REACTIVE_DISPOSED) e.F.delete(o); else if (!(o.m & n)) {
210
+ if ((o !== e.Y || e.h) && (o.l & (CONFIG_HELD | CONFIG_OVERRIDE)) === CONFIG_HELD) {
208
211
  // Landed, held: the content is that transaction's — the output's
209
212
  // pass enters it and the reveal lands with the reader's run. The
210
213
  // seam, past its park decision, waits for the commit instead. (A
@@ -212,13 +215,13 @@ function prune(e, n) {
212
215
  // has not shown content catches it instead: the fallback stays until
213
216
  // the reader is committed (#3540: what a fresh boundary catches holds
214
217
  // nothing outside it).
215
- if (!n || !e.D) continue;
216
- joinFuture(txOf(t));
218
+ if (!r || !e.D) continue;
219
+ joinFuture(txOf(o));
217
220
  }
218
- e.U.delete(t);
221
+ e.F.delete(o);
219
222
  }
220
223
  }
221
- return e.U.size;
224
+ return e.F.size;
222
225
  }
223
226
 
224
227
  /** Reveal order: a slot is ready when its content can show — it has shown
@@ -231,24 +234,24 @@ function prune(e, n) {
231
234
  * or landed and committed are dropped; a fallback with none left reveals
232
235
  * next round. An arm resolves: nothing collected, nothing happened. */ function boundarySeam() {
233
236
  for (const e of collecting) {
234
- const n = e.j.R;
235
- if (n & REACTIVE_DISPOSED) {
237
+ const r = e.j.R;
238
+ if (r & REACTIVE_DISPOSED) {
236
239
  collecting.delete(e);
237
240
  continue;
238
241
  }
239
242
  // A parked frame's boundary (zombie) keeps its state for its revival.
240
- if (n & REACTIVE_ZOMBIE) continue;
243
+ if (r & REACTIVE_ZOMBIE) continue;
241
244
  prune(e, false);
242
- const r = e.j;
243
- if (e.h && !e.D && r.l & CONFIG_HELD && [ ...e.U ].every(e => !(e.m & STATUS_PENDING) && e.l & CONFIG_HELD && txOf(e) === txOf(r))) {
244
- e.U.clear();
245
+ const n = e.j;
246
+ if (e.h && !e.D && n.l & CONFIG_HELD && [ ...e.F ].every(e => !(e.m & STATUS_PENDING) && e.l & CONFIG_HELD && txOf(e) === txOf(n))) {
247
+ e.F.clear();
245
248
  e.D = true;
246
249
  }
247
250
  e.h = false;
248
- if (e.U.size === 0) {
251
+ if (e.F.size === 0) {
249
252
  collecting.delete(e);
250
- if (e.H) redraw(e);
251
- if (e.V !== null) revealHooks.P(e.V);
253
+ if (e.L) redraw(e);
254
+ if (revealHooks && e.v !== null) revealHooks.V(e.v);
252
255
  }
253
256
  }
254
257
  }
@@ -262,7 +265,7 @@ function prune(e, n) {
262
265
  // holder's verdict lane's — verdict.ts), and the swap it asked for is
263
266
  // display-ahead after all (#3528). Re-home the swap: the output re-runs
264
267
  // as the lane's, shown now beside the held frame.
265
- if (e.H && e.v === null && e.B === null && passLane !== null) {
268
+ if (e.L && e.P === null && e.B === null && passLane !== null) {
266
269
  e.B = passLane;
267
270
  // The swap the frame's pass staged (held with the frame) is void: the
268
271
  // lane's pass stages it anew (`flip` lists the output in the lane,
@@ -274,27 +277,29 @@ function prune(e, n) {
274
277
  return;
275
278
  }
276
279
  e.h = true;
280
+ // The swap is the change's work: the output joins it (D-13, #3540).
281
+ e.j.l &= ~CONFIG_FRAME;
277
282
  // A verdict read in `on` (`isPending`, or a memo of one — #3528's
278
283
  // `createMemo(() => isPending(m2))`: the mark carries, core.ts `read`)
279
284
  // whose frame has no transaction yet: the swap is still display-ahead — the frame gets one now (it lands
280
285
  // at this very seam if nothing holds it), and the swap is its verdict
281
286
  // lane's work, shown ahead of the frame's own runs (#3528).
282
- let n = passLane;
283
- if (n === null && globalQueue.q && getOwner().l & CONFIG_VERDICT) {
287
+ let r = passLane;
288
+ if (r === null && globalQueue.q && getOwner().l & CONFIG_VERDICT) {
284
289
  joinFuture(null);
285
- n = GlobalQueue.J(flushTransaction);
290
+ r = GlobalQueue.J(flushTransaction);
286
291
  }
287
- e.B = n;
292
+ e.B = r;
288
293
  collecting.add(e);
289
- const r = e.U.size;
290
- heldUnder(e.L, flushTransaction, n => record(e, n));
291
- for (const n of e.U) {
292
- if (e.U.size !== r || n.m & STATUS_PENDING && !(n.R & REACTIVE_DISPOSED)) {
294
+ const n = e.F.size;
295
+ heldUnder(e.H, flushTransaction, r => record(e, r));
296
+ for (const r of e.F) {
297
+ if (e.F.size !== n || r.m & STATUS_PENDING && !(r.R & REACTIVE_DISPOSED)) {
293
298
  // What it collects was forwarded through it to the boundaries above
294
299
  // too (`catchStatus` records a reader in every boundary on the way).
295
300
  // It is this boundary's alone now (B5, the nearest catcher): an outer
296
301
  // re-armed by the same tick does not wait for it (D-14, U-4).
297
- for (let n = e._parent; n !== null; n = n._parent) for (const r of e.U) n.U.delete(r);
302
+ for (let r = e._parent; r !== null; r = r._parent) for (const n of e.F) r.F.delete(n);
298
303
  flip(e);
299
304
  redraw(e);
300
305
  return;
@@ -305,47 +310,47 @@ function prune(e, n) {
305
310
  /** The error boundary's `reset`: re-run what threw (a comparator throw
306
311
  * names a signal — nothing to re-run). */ function reset(e) {
307
312
  // A snapshot: a source failing again is caught — and re-added — mid-walk.
308
- for (const n of [ ...e.U ]) if (n.X !== undefined && !(n.R & REACTIVE_DISPOSED)) recompute(n);
313
+ for (const r of [ ...e.F ]) if (r.X !== undefined && !(r.R & REACTIVE_DISPOSED)) recompute(r);
309
314
  schedule();
310
315
  }
311
316
 
312
- function createBoundary(e, n, r, t) {
313
- const o = createOwner();
317
+ function createBoundary(e, r, n, o) {
318
+ const t = createOwner();
314
319
  const l = {
315
320
  i: e,
316
- _parent: boundaryOf(o) ?? null,
317
- L: o,
321
+ _parent: boundaryOf(t) ?? null,
322
+ H: t,
318
323
  Y: null,
319
324
  j: null,
320
- U: new Set,
325
+ F: new Set,
321
326
  D: false,
322
327
  h: false,
323
328
  B: null,
329
+ P: null,
330
+ L: false,
331
+ M: null,
324
332
  v: null,
325
- H: false,
326
- Z: null,
327
- V: null,
328
333
  $: false,
329
334
  ee: false,
330
- F: e === STATUS_ERROR ? signal(undefined, ERROR_SIGNAL) : null,
331
- ne: r,
332
- re: null
335
+ U: e === STATUS_ERROR ? signal(undefined, ERROR_SIGNAL) : null,
336
+ re: n,
337
+ ne: null
333
338
  };
334
- const u = e === STATUS_PENDING ? o.A[REVEAL] : null;
339
+ const u = e === STATUS_PENDING ? t.A[REVEAL] : null;
335
340
  if (getOwner()) cleanup(() => {
336
341
  collecting.delete(l);
337
- if (u) revealHooks.te(u, l);
342
+ if (revealHooks && u) revealHooks.oe(u, l);
338
343
  });
339
344
  // The `on` dependencies live OUTSIDE the boundary, as the condition of a
340
345
  // `<Show>` wrapping it would: created before the owner's context names it.
341
346
  // A source of it going pending re-runs it (CONFIG_REDERIVE) rather than
342
347
  // marking it: the pass catches the NotReady — it is a notification, it
343
348
  // does not suspend the parent — and re-arms.
344
- if (t) {
349
+ if (o) {
345
350
  let e = false;
346
- runWithOwner(o, () => computed(() => {
351
+ runWithOwner(t, () => computed(() => {
347
352
  try {
348
- t();
353
+ o();
349
354
  } catch (e) {
350
355
  if (!(e instanceof NotReadyError)) {
351
356
  if (!globalQueue.notify(getOwner(), STATUS_ERROR, STATUS_ERROR, e)) {
@@ -359,15 +364,15 @@ function createBoundary(e, n, r, t) {
359
364
  })).l |= CONFIG_REDERIVE;
360
365
  }
361
366
  const a = {
362
- ...o.A,
367
+ ...t.A,
363
368
  [BOUNDARY]: l
364
369
  };
365
370
  if (revealHooks !== null) a[REVEAL] = null;
366
- o.A = a;
367
- const i = runWithOwner(o, () => {
368
- const e = false ? computed(n, {
371
+ t.A = a;
372
+ const i = runWithOwner(t, () => {
373
+ const e = false ? computed(r, {
369
374
  name: "children"
370
- }) : computed(n);
375
+ }) : computed(r);
371
376
  return false ? computed(() => flatten(read(e)), {
372
377
  name: "boundary"
373
378
  }) : computed(() => flatten(read(e)));
@@ -375,23 +380,30 @@ function createBoundary(e, n, r, t) {
375
380
  l.Y = i;
376
381
  // A slot of the reveal order in its context (reveal.ts): gated until the
377
382
  // controller releases it — before the output's first pass.
378
- if (u) revealHooks.oe(u, l);
383
+ if (revealHooks && u) revealHooks.te(u, l);
379
384
  const f = computed(() => {
385
+ // Structure, not a derivation of its content (CONFIG_FRAME, #4283): it
386
+ // reads a hold it is not part of as the screen, as the render effect
387
+ // flattening the same children without a boundary would. Not while a
388
+ // re-arm's swap is the change's work (`arm`), until that swap commits
389
+ // (D-13, #3540).
390
+ const r = getOwner();
391
+ if (!l.h && !(r.l & CONFIG_HELD)) r.l |= CONFIG_FRAME;
380
392
  // Reveal order (reveal.ts): a gated slot shows its fallback — or, in a
381
393
  // collapsed tail, nothing — whatever its content's state; the
382
394
  // controller is told the pass ran (its readiness may have changed) and
383
395
  // releases it in turn.
384
- if (l.$) {
396
+ if (l.$) {
385
397
  // Linked without being read: the tree settling (or changing)
386
398
  // re-derives this pass, which is how the controller hears of it.
387
399
  // It may release this very slot (the frontier whose content just
388
400
  // settled): the pass goes on to show it — the redraw it asks for is
389
401
  // refused mid-pass and not needed.
390
- link(i, getOwner());
391
- if (l.V !== null) revealHooks.P(l.V);
402
+ link(i, r);
403
+ if (revealHooks && l.v !== null) revealHooks.V(l.v);
392
404
  if (l.$) {
393
405
  if (l.ee) {
394
- l.H = true;
406
+ l.L = true;
395
407
  return undefined;
396
408
  }
397
409
  return fallback(l);
@@ -406,7 +418,7 @@ function createBoundary(e, n, r, t) {
406
418
  // once clean.
407
419
  const n = i.m & (STATUS_PENDING | STATUS_ERROR);
408
420
  if (n !== 0) {
409
- link(i, getOwner());
421
+ link(i, r);
410
422
  if (n & e) {
411
423
  if (isCollecting(l)) {
412
424
  caught(l, i, undefined);
@@ -418,7 +430,7 @@ function createBoundary(e, n, r, t) {
418
430
  }
419
431
  // A pending passes an error boundary by (A6); an error, a loading
420
432
  // one; a loading boundary showing content forwards its pending.
421
- throw i.T.F;
433
+ throw i.T.U;
422
434
  }
423
435
  if (isCollecting(l)) {
424
436
  // A29's boundary exemption (#3540): a boundary MOUNTED over a held
@@ -432,19 +444,18 @@ function createBoundary(e, n, r, t) {
432
444
  // enters: the outside sees its committed value until the landing,
433
445
  // which reveals the content — a fallback staged earlier is replaced
434
446
  // ahead of the commit and never shown.
435
- const e = getOwner();
436
- if (l.U.size !== 0 && e.m & STATUS_UNINITIALIZED && !(e.l & CONFIG_HELD)) return fallback(l);
447
+ if (l.F.size !== 0 && r.m & STATUS_UNINITIALIZED && !(r.l & CONFIG_HELD)) return fallback(l);
437
448
  // Readers under it still unready: the fallback, the tree untouched.
438
449
  // The seam re-derives this pass when they settle.
439
450
  if (prune(l, true) !== 0) return fallback(l);
440
451
  }
441
- let r;
452
+ let o;
442
453
  try {
443
- r = read(i);
444
- } catch (n) {
445
- if (n instanceof NotReadyError ? e === STATUS_PENDING : e === STATUS_ERROR) {
454
+ o = read(i);
455
+ } catch (r) {
456
+ if (r instanceof NotReadyError ? e === STATUS_PENDING : e === STATUS_ERROR) {
446
457
  if (isCollecting(l)) {
447
- caught(l, i, n);
458
+ caught(l, i, r);
448
459
  return fallback(l);
449
460
  }
450
461
  // Forwarded: a loading boundary showing content keeps it and
@@ -452,43 +463,53 @@ function createBoundary(e, n, r, t) {
452
463
  record(l, i);
453
464
  }
454
465
  // A pending passes an error boundary by (A6).
455
- throw n;
466
+ throw r;
456
467
  }
457
468
  // Readers under it still unready (caught before, or while the read
458
469
  // ran the content at a mount): the fallback.
459
- if (isCollecting(l) && l.U.size !== 0) return fallback(l);
470
+ if (isCollecting(l) && l.F.size !== 0) return fallback(l);
460
471
  l.D = true;
461
- l.H = false;
472
+ l.L = false;
462
473
  // The fallback dies when the content that replaces it is on screen —
463
474
  // with this flush's commits when the pass is the frame's, at the
464
475
  // landing when the frame parks (the run is held with it, as the old
465
476
  // children of a held pass are) — not now: a held swap keeps the
466
477
  // fallback on screen, live, until it commits. A pass back to the
467
- // fallback before then finds it still built and keeps it.
468
- const t = l.re;
469
- if (t !== null) globalQueue.enqueue(EFFECT_RENDER, () => {
470
- if (!l.H && l.re === t) {
478
+ // fallback before then finds it still built and keeps it. A fallback
479
+ // the screen does not show (a swap staged with a held frame, replaced
480
+ // before it commits) dies now: what it reads holds nothing (S-22 row
481
+ // 3′, #4268).
482
+ const t = l.ne;
483
+ const drop = () => {
484
+ if (!l.L && l.ne === t) {
485
+ // A fallback that threw (a rethrow the boundary does not
486
+ // handle) is what an enclosing boundary waits on: it died, and
487
+ // the seam of another round drops it there (#4270).
488
+ if (t.m & STATUS_ERROR) schedule();
471
489
  dispose(t);
472
- l.re = null;
490
+ l.ne = null;
473
491
  }
474
- });
492
+ };
493
+ if (t !== null) l.j.le !== t.le ? drop() : globalQueue.enqueue(EFFECT_RENDER, drop);
475
494
  release(l);
476
495
  // Shown: the slot leaves its reveal order (a later re-arm gates nobody
477
496
  // else; the frontier moves on).
478
- if (l.V !== null) {
479
- const e = l.V;
480
- l.V = null;
481
- revealHooks.P(e);
497
+ if (revealHooks && l.v !== null) {
498
+ const e = l.v;
499
+ l.v = null;
500
+ revealHooks.V(e);
482
501
  }
483
- return r;
502
+ return o;
484
503
  },
485
504
  // Boundary structure, not a user source: its value is fallback-or-content
486
505
  // and legitimately swaps mid-hydration (resume), so it must never be
487
506
  // frozen by snapshot capture.
488
507
  {
489
- le: true
508
+ ue: true
490
509
  });
491
510
  f.l |= CONFIG_REDERIVE;
511
+ // The tree is structure too (the output's pass marks the output).
512
+ i.l |= CONFIG_FRAME;
492
513
  l.j = f;
493
514
  return accessor(f);
494
515
  }
@@ -497,12 +518,12 @@ function fallback(e) {
497
518
  // The swap a display-ahead re-arm asked for: this pass is the lane's — a
498
519
  // lane read by construction (REACTIVE_LANE_READ: `recompute`'s tail keeps
499
520
  // the pass in the lane).
500
- if (e.v !== null) {
501
- setPassLane(e.v);
521
+ if (e.P !== null) {
522
+ setPassLane(e.P);
502
523
  e.j.R |= REACTIVE_LANE_READ;
503
- e.v = null;
524
+ e.P = null;
504
525
  }
505
- e.H = true;
526
+ e.L = true;
506
527
  // Built in its own scope, like `Show`'s function child, once per show
507
528
  // (D-39: the fallback is the display until a new answer replaces the
508
529
  // error): a re-derive of the output while it shows — a second reader
@@ -514,18 +535,18 @@ function fallback(e) {
514
535
  // outside the boundary, as the output is: its errors and runs are the
515
536
  // enclosing boundary's — with the output's id, so what it builds gets the
516
537
  // ids a build under the output would; disposed once content is on screen.
517
- if (e.re === null) {
518
- const n = getOwner();
519
- e.re = runWithOwner(n._parent, () => computed(() => e.ne(e), {
520
- id: n.id
538
+ if (e.ne === null) {
539
+ const r = getOwner();
540
+ e.ne = runWithOwner(r._parent, () => computed(() => e.re(e), {
541
+ id: r.id
521
542
  }));
522
543
  }
523
- return read(e.re);
544
+ return read(e.ne);
524
545
  }
525
546
 
526
547
  const ERROR_SIGNAL = {
527
548
  ownedWrite: true,
528
- le: true
549
+ ue: true
529
550
  };
530
551
 
531
552
  /**
@@ -569,8 +590,8 @@ const ERROR_SIGNAL = {
569
590
  * ) as unknown as JSX.Element;
570
591
  * }
571
592
  * ```
572
- */ function createLoadingBoundary(e, n, r) {
573
- return createBoundary(STATUS_PENDING, e, () => n(), r?.on);
593
+ */ function createLoadingBoundary(e, r, n) {
594
+ return createBoundary(STATUS_PENDING, e, () => r(), n?.on);
574
595
  }
575
596
 
576
597
  /**
@@ -595,12 +616,12 @@ const ERROR_SIGNAL = {
595
616
  * ) as unknown as JSX.Element;
596
617
  * }
597
618
  * ```
598
- */ function createErrorBoundary(e, n) {
599
- return createBoundary(STATUS_ERROR, e, e => n(accessor(e.F), () => reset(e)));
619
+ */ function createErrorBoundary(e, r) {
620
+ return createBoundary(STATUS_ERROR, e, e => r(accessor(e.U), () => reset(e)));
600
621
  }
601
622
 
602
623
  // Installed at module evaluation — present exactly when something imports a
603
624
  // boundary. An app without one pays the three null checks and nothing else.
604
- installBoundaryHooks(catchStatus, (e, n) => !n && (e.m & STATUS_UNINITIALIZED ? catchStatus(e, STATUS_PENDING) : armedOver(e)), hiddenBy, boundarySeam, heldRun);
625
+ installBoundaryHooks(catchStatus, (e, r) => !r && (e.m & STATUS_UNINITIALIZED ? catchStatus(e, STATUS_PENDING) : armedOver(e)), hiddenBy, boundarySeam, heldRun);
605
626
 
606
627
  export { REVEAL, createErrorBoundary, createLoadingBoundary, prune, ready, redraw, revealHooks, setRevealHooks };
@@ -66,6 +66,14 @@ import { joinFuture, newTransaction, enterAction, exitAction, resolveTx, schedul
66
66
  * yield until(() => todos.some(t => t.id === saved.id));
67
67
  * ```
68
68
  *
69
+ * The same holds inside an async helper the action yields
70
+ * (`yield (async () => { ... })()`): everything after its first `await` runs
71
+ * outside the transaction. An `await refresh(x)` or `await until(...)` there
72
+ * waits for this action's commit, which waits on the helper, so it hangs
73
+ * whenever `x` re-ran during the action (#4263) and works otherwise. Call
74
+ * `refresh(x)` in the generator once the helper returns; `yield` it only when
75
+ * the action needs the fresh value.
76
+ *
69
77
  * (For the same reason, don't call `flush()` inside an action body — it
70
78
  * cannot reveal the held writes and is refused.)
71
79
  *