@solidjs/signals 2.0.0-rc.13 → 2.0.0-rc.15

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 (121) hide show
  1. package/dist/dev-shared.js +2059 -3768
  2. package/dist/dev.attribution.js +237 -101
  3. package/dist/dev.js +7466 -8497
  4. package/dist/observe/affects.js +128 -104
  5. package/dist/observe/boundaries.js +492 -665
  6. package/dist/observe/core/action.js +63 -66
  7. package/dist/observe/core/async.js +243 -379
  8. package/dist/observe/core/attribution-queries.js +1 -1
  9. package/dist/observe/core/attribution.js +394 -253
  10. package/dist/observe/core/constants.js +120 -187
  11. package/dist/observe/core/context.js +3 -3
  12. package/dist/observe/core/core.js +874 -1440
  13. package/dist/observe/core/dev.js +3 -3
  14. package/dist/observe/core/effect.js +73 -84
  15. package/dist/observe/core/external.js +5 -5
  16. package/dist/observe/core/graph.js +62 -71
  17. package/dist/observe/core/heap.js +75 -98
  18. package/dist/observe/core/invariants.js +1 -34
  19. package/dist/observe/core/lanes.js +750 -181
  20. package/dist/observe/core/owner.js +80 -101
  21. package/dist/observe/core/scheduler.js +719 -1356
  22. package/dist/observe/core/verdict.js +314 -621
  23. package/dist/observe/flatten.js +79 -0
  24. package/dist/observe/index.js +15 -13
  25. package/dist/observe/map.js +232 -235
  26. package/dist/observe/reveal.js +212 -0
  27. package/dist/observe/signals.js +144 -216
  28. package/dist/observe/store/affects.js +142 -0
  29. package/dist/observe/store/hooks.js +13 -0
  30. package/dist/observe/store/index.js +27 -24
  31. package/dist/observe/store/optimistic.js +571 -0
  32. package/dist/observe/store/projection.js +308 -0
  33. package/dist/observe/store/reconcile.js +259 -0
  34. package/dist/observe/store/store.js +1988 -277
  35. package/dist/observe/store/storePath.js +1 -1
  36. package/dist/{prod/store/next → observe/store}/target.js +17 -18
  37. package/dist/observe/store/types.js +73 -0
  38. package/dist/observe/store/utils.js +1 -13
  39. package/dist/prod/affects.js +130 -106
  40. package/dist/prod/boundaries.js +476 -654
  41. package/dist/prod/core/action.js +71 -74
  42. package/dist/prod/core/async.js +306 -444
  43. package/dist/prod/core/constants.js +117 -186
  44. package/dist/prod/core/context.js +3 -3
  45. package/dist/prod/core/core.js +888 -1462
  46. package/dist/prod/core/effect.js +88 -99
  47. package/dist/prod/core/external.js +5 -5
  48. package/dist/prod/core/graph.js +62 -71
  49. package/dist/prod/core/heap.js +75 -98
  50. package/dist/prod/core/invariants.js +1 -32
  51. package/dist/prod/core/lanes.js +712 -181
  52. package/dist/prod/core/owner.js +75 -96
  53. package/dist/prod/core/scheduler.js +686 -1335
  54. package/dist/prod/core/verdict.js +309 -621
  55. package/dist/prod/flatten.js +77 -0
  56. package/dist/prod/index.js +15 -13
  57. package/dist/prod/map.js +142 -145
  58. package/dist/prod/reveal.js +212 -0
  59. package/dist/prod/signals.js +155 -227
  60. package/dist/prod/store/affects.js +142 -0
  61. package/dist/prod/store/hooks.js +13 -0
  62. package/dist/prod/store/index.js +26 -19
  63. package/dist/prod/store/optimistic.js +566 -0
  64. package/dist/prod/store/projection.js +301 -0
  65. package/dist/prod/store/reconcile.js +259 -0
  66. package/dist/prod/store/store.js +1883 -272
  67. package/dist/prod/store/storePath.js +1 -1
  68. package/dist/{observe/store/next → prod/store}/target.js +13 -17
  69. package/dist/prod/store/types.js +73 -0
  70. package/dist/prod/store/utils.js +1 -11
  71. package/dist/types/affects.d.ts +11 -30
  72. package/dist/types/boundaries.d.ts +66 -176
  73. package/dist/types/carved.d.ts +1 -0
  74. package/dist/types/core/action.d.ts +5 -6
  75. package/dist/types/core/async.d.ts +10 -5
  76. package/dist/types/core/attribution-hooks.d.ts +20 -1
  77. package/dist/types/core/attribution.d.ts +26 -11
  78. package/dist/types/core/constants.d.ts +130 -180
  79. package/dist/types/core/core.d.ts +134 -264
  80. package/dist/types/core/dev.d.ts +2 -2
  81. package/dist/types/core/effect.d.ts +0 -7
  82. package/dist/types/core/graph.d.ts +7 -1
  83. package/dist/types/core/heap.d.ts +5 -7
  84. package/dist/types/core/index.d.ts +5 -5
  85. package/dist/types/core/invariants.d.ts +0 -29
  86. package/dist/types/core/lanes.d.ts +74 -86
  87. package/dist/types/core/owner.d.ts +3 -3
  88. package/dist/types/core/scheduler.d.ts +255 -331
  89. package/dist/types/core/types.d.ts +45 -85
  90. package/dist/types/core/verdict.d.ts +3 -0
  91. package/dist/types/flatten.d.ts +8 -0
  92. package/dist/types/index.d.ts +6 -4
  93. package/dist/types/reveal.d.ts +19 -0
  94. package/dist/types/signals.d.ts +35 -91
  95. package/dist/types/store/affects.d.ts +12 -0
  96. package/dist/types/store/hooks.d.ts +48 -0
  97. package/dist/types/store/index.d.ts +34 -15
  98. package/dist/types/store/optimistic.d.ts +23 -0
  99. package/dist/types/store/projection.d.ts +18 -0
  100. package/dist/types/store/reconcile.d.ts +3 -0
  101. package/dist/types/store/store.d.ts +132 -145
  102. package/dist/types/store/storePath.d.ts +1 -1
  103. package/dist/types/store/target.d.ts +151 -0
  104. package/dist/types/store/types.d.ts +84 -0
  105. package/package.json +1 -1
  106. package/dist/observe/core/optimistic.js +0 -669
  107. package/dist/observe/store/next/optimistic.js +0 -717
  108. package/dist/observe/store/next/projection.js +0 -266
  109. package/dist/observe/store/next/reconcile.js +0 -375
  110. package/dist/observe/store/next/store.js +0 -2608
  111. package/dist/prod/core/optimistic.js +0 -658
  112. package/dist/prod/store/next/optimistic.js +0 -710
  113. package/dist/prod/store/next/projection.js +0 -257
  114. package/dist/prod/store/next/reconcile.js +0 -373
  115. package/dist/prod/store/next/store.js +0 -2484
  116. package/dist/types/core/optimistic.d.ts +0 -6
  117. package/dist/types/store/next/optimistic.d.ts +0 -25
  118. package/dist/types/store/next/projection.d.ts +0 -27
  119. package/dist/types/store/next/reconcile.d.ts +0 -10
  120. package/dist/types/store/next/store.d.ts +0 -145
  121. package/dist/types/store/next/target.d.ts +0 -196
@@ -1,567 +1,523 @@
1
- import { runWithOwner, signal, spectate, computed, read, untrack, setSignal, notifyOnLane, recompute, currentOptimisticLane, ext } from "./core/core.js";
1
+ import { STATUS_PENDING, STATUS_ERROR, CONFIG_REDERIVE, REACTIVE_DISPOSED, STATUS_UNINITIALIZED, CONFIG_HELD, EFFECT_RENDER, REACTIVE_ZOMBIE, CONFIG_VERDICT, REACTIVE_LANE_READ, CONFIG_OVERRIDE, EFFECT_USER, EFFECT_TRACKED } from "./core/constants.js";
2
2
 
3
- import { NotReadyError, unwrapStatusError } from "./core/error.js";
4
-
5
- import { createOwner, cleanup } from "./core/owner.js";
6
-
7
- import { Queue, transitions, reporterBlocksSource, wakeParked, schedule, queueRearm, haltReactivity } from "./core/scheduler.js";
3
+ import { signal, runWithOwner, computed, read, setSignal, recompute } from "./core/core.js";
8
4
 
9
- import { getContext, setContext, createContext } from "./core/context.js";
5
+ import { NotReadyError, unwrapStatusError } from "./core/error.js";
10
6
 
11
- import { STATUS_PENDING, STATUS_ERROR, REACTIVE_DISPOSED, REACTIVE_ZOMBIE, STATUS_UNINITIALIZED, NOT_PENDING, CONFIG_AUTO_DISPOSE } from "./core/constants.js";
7
+ import { reportClientError } from "./core/error-hooks.js";
12
8
 
13
- import "./core/invariants.js";
9
+ import { link } from "./core/graph.js";
14
10
 
15
11
  import { enqueueSub } from "./core/heap.js";
16
12
 
17
- import "./core/verdict.js";
13
+ import { createOwner, getOwner, cleanup, dispose } from "./core/owner.js";
18
14
 
19
- import "./core/effect.js";
15
+ import { globalQueue, haltReactivity, passLane, joinFuture, GlobalQueue, flushTransaction, schedule, setPassLane, txOf } from "./core/scheduler.js";
20
16
 
21
- import { reportClientError } from "./core/error-hooks.js";
17
+ import { flatten } from "./flatten.js";
22
18
 
23
19
  import { accessor } from "./signals.js";
24
20
 
25
- function boundaryComputed(e, t) {
26
- const r = computed(e, {
27
- lazy: true
28
- });
29
- ext(r).S = (e, t) => {
30
- // Use passed values if provided, otherwise read from node
31
- const n = e !== undefined ? e : r.h;
32
- const i = t !== undefined ? t : r.o?._;
33
- // Notify both status dimensions like a render effect does; the queue chain
34
- // consumes this boundary's own type and forwards the remainder upward until
35
- // a boundary that handles it is found.
36
- r.h &= ~r.R;
37
- const s = r.T.notify(r, STATUS_PENDING | STATUS_ERROR, n, i);
38
- // The queue is the only propagation channel: a foreign status must not stay
39
- // reader-visible on the tree, or reads re-throw it across the boundary and
40
- // link unrelated ambient contexts (the #2809 nested-boundary loop). Deps are
41
- // untouched, so the tree still recomputes when the foreign source settles.
42
- const o = n & ~r.R & (STATUS_PENDING | STATUS_ERROR);
43
- if (o) {
44
- r.h &= ~o;
45
- if (r.o?._ === i && !(r.h & (STATUS_PENDING | STATUS_ERROR))) if (r.o !== null) r.o._ = undefined;
46
- }
47
- // An ERROR the chain could not deliver to any boundary is uncaught. The
48
- // scrub above already removed it from reader-visible state, so without
49
- // escalation here it would vanish entirely (#2884) — halt-and-throw,
50
- // exactly like an unhandled effect error.
51
- if (!s && n & STATUS_ERROR) {
52
- haltReactivity(unwrapStatusError(i));
53
- throw i;
54
- }
55
- };
56
- r.R = t;
57
- r.C &= ~CONFIG_AUTO_DISPOSE;
58
- recompute(r, true);
59
- return r;
60
- }
61
-
62
21
  /**
63
- * A boundary's `on` dependencies (#3540): `onFn` runs tracked, as a
64
- * computation whose value is discarded — what it READS is the point. Every
65
- * run after the first is a notification (a source it read was written, went
66
- * pending, or landed; an optimistic write notifies like any other) and
67
- * queues the boundary for re-arming once this pass's heap has run
68
- * (scheduler.ts `pendingRearms`: the re-arm needs what the notifying write
69
- * put in flight, which this pass — at the height of its reads — runs ahead
70
- * of). The value is never compared: a thunk that returns a fresh object per
71
- * run but reads nothing reactive never re-arms, and one that returns the
72
- * same constant re-arms whenever a read source changes. A zero-argument
73
- * function is an accessor, tracked like any other.
22
+ * Boundaries — the display consumers between a frame reader and the root:
23
+ * `createLoadingBoundary` (`<Loading>`) and `createErrorBoundary`
24
+ * (`<Errored>`).
74
25
  *
75
- * The pass's posture is the notification's. A plain pass derives from the
76
- * frame the write belongs to, and the re-arm follows that frame. A pass
77
- * under a lane read display-ahead state — `latest()` (its shadow is an
78
- * optimistic computed), an optimistic write — and the re-arm shows the
79
- * fallback through that lane (`_rearmLane`): now, beside whatever frame a
80
- * transaction still holds.
26
+ * A boundary is an owner whose context names it (`BOUNDARY`), so every node
27
+ * created under it finds it in O(1) and the enclosing boundaries through
28
+ * `_parent`; a `tree` (the content, flattened) and an `output` that shows
29
+ * the tree or the fallback. The output is a frame reader (CONFIG_REDERIVE):
30
+ * status reaching it from its content re-derives it instead of marking it,
31
+ * and its pass decides.
81
32
  *
82
- * Created under `owner` while the owner's queue is still the parent's, so
83
- * the node belongs to the parent boundary, not to this one — as the
84
- * condition of a `<Show>` wrapping the boundary would. Two status rules set
85
- * it apart from a plain memo:
86
- * - Pending is not the parent's: a source of `on` that is not ready is a
87
- * notification for this boundary (it re-arms: the fallback shows here, not
88
- * in an outer boundary). Propagation marks the node without a pass, so the
89
- * channel scrubs the mark and re-derives it; the pass reads the source
90
- * (linking to its landing) and catches. The node never registers as a
91
- * reporter: `on` reading a pending source holds no frame.
92
- * - An error IS the parent's, as a wrapping `<Show>` condition's would be:
93
- * forwarded up the queue chain like a render effect's; uncaught, it halts
94
- * (#2884).
95
- */ function onNode(e, t, r) {
96
- let n = false;
97
- const i = runWithOwner(e, () => computed(() => {
98
- try {
99
- r();
100
- } catch (e) {
101
- if (!(e instanceof NotReadyError)) throw e;
102
- }
103
- if (n) {
104
- if (currentOptimisticLane !== null) t.O = currentOptimisticLane;
105
- queueRearm(t);
106
- } else n = true;
107
- }, {
108
- lazy: true
109
- }));
110
- ext(i).S = (e, t) => {
111
- const r = e !== undefined ? e : i.h;
112
- if (r & STATUS_PENDING) {
113
- i.h &= ~STATUS_PENDING;
114
- if (i.o?._ instanceof NotReadyError) i.o._ = undefined;
115
- enqueueSub(i);
116
- schedule();
117
- }
118
- if (r & STATUS_ERROR) {
119
- const e = t !== undefined ? t : i.o?._;
120
- i.h &= ~STATUS_ERROR;
121
- if (i.o?._ === e && i.o !== null) i.o._ = undefined;
122
- if (!i.T.notify(i, STATUS_ERROR, r, e)) {
123
- haltReactivity(unwrapStatusError(e));
124
- throw e;
125
- }
126
- }
127
- };
128
- i.C &= ~CONFIG_AUTO_DISPOSE;
129
- recompute(i, true);
130
- return i;
131
- }
33
+ * Status protocol (B5). A frame reader under a boundary that goes pending or
34
+ * errors tells `GlobalQueue.notify`, which asks `catchStatus` before the
35
+ * root: the nearest boundary of that status that is collecting — an error
36
+ * boundary always; a loading boundary that has not shown content yet, or
37
+ * whose `on` just changed — records the reader and shows its fallback, and
38
+ * the root never hears of it (no transaction is opened: A33, a fallback-
39
+ * caught flight holds nothing). A loading boundary showing content forwards:
40
+ * the root holds the frame as for any reader. The tree's own status takes
41
+ * the same route from the output's pass (`read(tree)` throws it).
42
+ *
43
+ * The hold (`blocked`, scheduler.ts) asks `hidden`: a reader behind a
44
+ * fallback — any boundary on its chain whose last pass chose the fallback —
45
+ * is not on screen and holds nothing. An error boundary flipping to its
46
+ * fallback is the same as a loading one resetting: every reader under it
47
+ * stops counting, and a flight whose only visible reader that was loses its
48
+ * hold (maintainer, 2026-10-01).
49
+ *
50
+ * Reveal. The seam (`boundarySeam`) drops readers that settled, died, or
51
+ * landed and committed; a fallback with none left re-derives its output,
52
+ * which reads the tree again. A reader that landed but is held (CONFIG_HELD,
53
+ * its value staged in a transaction) is not settled: the content reveals at
54
+ * the commit, with the rest of the frame (#3540).
55
+ *
56
+ * `on` (loading only): a tracked function whose reads re-arm the boundary.
57
+ * A pass of it after the mount arms the boundary for the flush: anything
58
+ * pending under it — already (its forwarded readers), or going pending later
59
+ * in the same heap — is collected and the fallback shows now, with the
60
+ * change's frame; nothing pending, and the arm is a no-op at the seam.
61
+ */
62
+ /** Context key: the nearest boundary of a node, inherited at creation. */ const BOUNDARY = Symbol("");
132
63
 
133
- function createBoundChildren(e, t, r, n) {
134
- const i = e.T;
135
- i.addChild(e.T = r);
136
- cleanup(() => i.removeChild(e.T));
137
- // Named for the observe tier's owner paths: user content under a boundary
138
- // is owned by `children`, and the boundary's own two nodes read as
139
- // structure rather than as anonymous `computed`s between `<Loading>` and
140
- // the content (`<App> › <Loading> › children › <Feed>`).
141
- return runWithOwner(e, () => {
142
- // The call, not the argument, is gated: `computed(fn, void 0)` would keep
143
- // a trailing argument in the prod artifact.
144
- const e = false ? computed(t, {
145
- name: "children"
146
- }) : computed(t);
147
- return boundaryComputed(() => flatten(read(e)), n);
148
- });
149
- }
64
+ /** Context key: the reveal controller a Loading boundary created here is a
65
+ * slot of (reveal.ts); a boundary clears it for its content — only direct
66
+ * children are slots. */ const REVEAL = Symbol("");
150
67
 
151
- const RevealControllerContext = /* @__PURE__ */ createContext(null);
68
+ let revealHooks = null;
152
69
 
153
- let _revealUsed = false;
70
+ function setRevealHooks(e) {
71
+ revealHooks = e;
72
+ }
154
73
 
155
- const FALSE_ACCESSOR = () => false;
74
+ /** Boundaries with readers to sweep (or an arm to resolve) at the seam. */ const collecting = new Set;
156
75
 
157
- const SEQUENTIAL_ACCESSOR = () => "sequential";
76
+ const boundaryOf = e => e.D[BOUNDARY];
158
77
 
159
- function isRevealController(e) {
160
- return e instanceof RevealController;
161
- }
78
+ /** Catches what reaches it: an error boundary always; a loading one until it
79
+ * has shown content, or while re-armed. */ const isCollecting = e => e.u === STATUS_ERROR || !e.h || e.U;
162
80
 
163
- function isSlotReady(e) {
164
- return isRevealController(e) ? e.I() : e.U.size === 0 && !e.v;
81
+ /** The reader goes to the fallback: recorded, and the output re-derives
82
+ * (a no-op while the output's own pass is running — its post-check
83
+ * decides). */ function caught(e, n, r) {
84
+ if (e.u === STATUS_ERROR) {
85
+ // An error boundary waits on what THREW (`StatusError.source`), not on
86
+ // the reader that saw it: the reader recovers the moment a refetch
87
+ // starts (its boundary catches the pending), while the error is settled
88
+ // only when its source is neither errored nor in flight. `reset` re-runs
89
+ // the same node.
90
+ r ??= n.m?.F;
91
+ n = r?.source ?? n;
92
+ const t = unwrapStatusError(r);
93
+ setSignal(e.F, t);
94
+ reportClientError(t, e.L, n);
95
+ }
96
+ e.V.add(n);
97
+ collecting.add(e);
98
+ if (e.U) flip(e);
99
+ // Already showing the fallback: one more reader to wait on changes
100
+ // nothing on screen. The fallback is instantiated once per show — a later
101
+ // error reaches it through the `error` accessor, which is why it is one.
102
+ if (!e.v) redraw(e);
103
+ if (e.H !== null) revealHooks.P(e.H);
165
104
  }
166
105
 
167
- function isSlotMinimallyReady(e) {
168
- return isRevealController(e) ? e.D() : isSlotReady(e);
106
+ /** A re-armed boundary found something pending: fresh again, and the swap
107
+ * the arming pass asked for is that pass's lane's (if any): the output is
108
+ * the lane's work from its next pass's head — not a re-pass of the
109
+ * transaction holding its pending content, which would entangle that
110
+ * transaction with the arming frame's (#3540 `on: () => latest(dep)`). */ function flip(e) {
111
+ e.h = false;
112
+ e.B = e.j;
113
+ if (e.B !== null && e.Z !== null) GlobalQueue.W(e.Z, e.B, true, false);
169
114
  }
170
115
 
171
- function setSlotState(e, t, r, n) {
172
- setSignal(e.P, r);
173
- setSignal(e.L, n);
174
- if (isRevealController(e)) {
175
- if (!r && e.j === t) e.j = undefined;
176
- return e.B(r, n);
116
+ function redraw(e) {
117
+ if (e.Z !== null && !(e.Z.R & (REACTIVE_DISPOSED | REACTIVE_ZOMBIE))) {
118
+ enqueueSub(e.Z);
119
+ schedule();
177
120
  }
178
- if (!r && e.W === t && e.q) e.W = undefined;
179
121
  }
180
122
 
181
- class RevealController {
182
- V;
183
- F;
184
- Z=[];
185
- j;
186
- P=signal(false, {
187
- ownedWrite: true,
188
- H: true
189
- });
190
- L=signal(false, {
191
- ownedWrite: true,
192
- H: true
193
- });
194
- J=true;
195
- K=true;
196
- X=false;
197
- constructor(e, t) {
198
- this.V = e;
199
- this.F = t;
200
- }
201
- Y(e) {
202
- for (let t = 0; t < this.Z.length; t++) {
203
- const r = this.Z[t];
204
- if ((isRevealController(r) ? r.j : r.W) !== this) continue;
205
- if (e(r) === false) return false;
206
- }
207
- return true;
208
- }
209
- I() {
210
- return this.Y(isSlotReady);
211
- }
212
- /**
213
- * "Minimally ready" = this group has something visible to show under its own policy.
214
- * Used by an enclosing `together` group to decide when it can release.
215
- * - `together`: every direct slot is minimally ready.
216
- * - `sequential`: the first owned slot is minimally ready (frontier can advance).
217
- * - `natural`: any owned slot is minimally ready.
218
- */ D() {
219
- const e = untrack(this.V);
220
- if (e === "together") return this.Y(isSlotMinimallyReady);
221
- if (e === "natural") {
222
- let e = false;
223
- let t = false;
224
- this.Y(r => {
225
- e = true;
226
- if (isSlotMinimallyReady(r)) {
227
- t = true;
228
- return false;
229
- }
230
- });
231
- return !e || t;
123
+ /** GlobalQueue._catch: status from a frame reader, nearest boundary first.
124
+ * A loading boundary on the way records a pending reader whether or not it
125
+ * catches it (its `on` may collect it later). */ function catchStatus(e, n, r) {
126
+ if (n === 0) {
127
+ // A status cleared. Judged by the node's status now, against each
128
+ // boundary's own rule (`unsettled`) — the error path clears pending
129
+ // first and the node is still errored then. The last reader a fallback
130
+ // waited on settling re-derives the output now, in the same heap (a
131
+ // held reader's landing has joined its transaction, so the content
132
+ // lands with its run, not a round after). Others still waiting: nothing
133
+ // on screen changes, the fallback stays as it is.
134
+ if (collecting.size !== 0) for (let n = boundaryOf(e); n !== undefined; n = n._parent ?? undefined) if (!(e.S & unsettled(n)) && n.V.delete(e) && n.V.size === 0) {
135
+ if (n.v) redraw(n);
136
+ if (n.H !== null) revealHooks.P(n.H);
232
137
  }
233
- // sequential: only the first owned slot matters.
234
- let t = true;
235
- this.Y(e => {
236
- t = isSlotMinimallyReady(e);
237
- return false;
238
- });
239
- return t;
240
- }
241
- $(e) {
242
- if (this.Z.includes(e)) return;
243
- this.Z.push(e);
244
- const t = untrack(this.V);
245
- setSignal(e.P, true), setSignal(e.L, t === "sequential" ? !!untrack(this.F) : false);
246
- untrack(() => this.B());
247
- }
248
- ee(e) {
249
- const t = this.Z.indexOf(e);
250
- if (t >= 0) this.Z.splice(t, 1);
251
- untrack(() => this.B());
138
+ return false;
252
139
  }
253
- B(e, t) {
254
- if (this.X) return;
255
- this.X = true;
256
- const r = this.J;
257
- const n = this.K;
258
- try {
259
- const r = e ?? read(this.P), n = untrack(this.V), i = n === "sequential" && !!untrack(this.F), s = t ?? i;
260
- if (r) {
261
- // Held by an outer group. Propagate the hold (and whatever collapsed policy
262
- // the outer asked for) down the whole subtree. Inner order is ignored while
263
- // held; it resumes once the outer releases us.
264
- this.Y(e => setSlotState(e, this, true, s));
265
- } else if (n === "natural") {
266
- // Each child reveals based on its own readiness. A nested controller slot
267
- // is released to run its own order locally — we bypass setSlotState for it
268
- // so the parent backpointer survives for upward readiness notifications.
269
- this.Y(e => {
270
- if (isRevealController(e)) {
271
- setSignal(e.L, false);
272
- setSignal(e.P, false);
273
- e.B(false, false);
274
- } else {
275
- setSlotState(e, this, !isSlotReady(e), false);
276
- }
277
- });
278
- } else if (n === "together") {
279
- // Release when every direct slot is minimally ready (has something to show
280
- // under its own order). A fully-ready inner together is minimally ready;
281
- // sequential's first slot being ready is minimally ready; natural having any
282
- // ready child is minimally ready. This lets `together` guarantee a single
283
- // cohesive reveal without waiting for every grandchild.
284
- const e = this.Y(isSlotMinimallyReady);
285
- this.Y(t => setSlotState(t, this, !e, false));
286
- } else {
287
- let e = false;
288
- this.Y(t => {
289
- if (e) return setSlotState(t, this, true, i);
290
- if (isSlotReady(t)) return setSlotState(t, this, false, false);
291
- e = true;
292
- // Frontier slot. For a leaf, holding `_disabled=true` is what keeps its
293
- // fallback visible. For a composite, we instead release it so it runs
294
- // its own order locally — its leaves will each show their own fallback
295
- // until their data lands. Outer still waits on full readiness before
296
- // advancing past this slot, and we bypass setSlotState so the parent
297
- // backpointer survives for upward readiness notifications.
298
- if (isRevealController(t)) {
299
- setSignal(t.L, false);
300
- setSignal(t.P, false);
301
- t.B(false, false);
302
- } else {
303
- setSlotState(t, this, true, false);
304
- }
305
- });
306
- }
307
- } finally {
308
- this.J = this.I();
309
- this.K = this.D();
310
- this.X = false;
140
+ for (let t = boundaryOf(e); t !== undefined; t = t._parent ?? undefined) {
141
+ if (!(t.u & n)) continue;
142
+ if (isCollecting(t)) {
143
+ caught(t, e, r);
144
+ return true;
311
145
  }
312
- if (this.j && (r !== this.J || n !== this.K)) this.j.B();
146
+ t.V.add(e);
147
+ collecting.add(t);
313
148
  }
149
+ return false;
314
150
  }
315
151
 
316
- class CollectionQueue extends Queue {
317
- te;
318
- U=new Set;
319
- re;
320
- /** The output pass — fallback or content (createCollectionBoundary). */
321
- ne;
322
- v=true;
323
- P=signal(false, {
324
- ownedWrite: true,
325
- H: true
326
- });
327
- _;
328
- L=signal(false, {
329
- ownedWrite: true,
330
- H: true
331
- });
332
- W;
333
- q=false;
334
- /** The boundary's owner — where a `caught` report locates itself, set before the children are built (a creation-time throw arrives before `_tree`). */
335
- ie;
336
- /** The lane the `on` pass that queued this re-arm ran under (onNode), if
337
- * any: the fallback swap is display-ahead — shown through the lane. */
338
- O=null;
339
- constructor(e) {
340
- super();
341
- this.te = e;
342
- }
343
- run(e) {
344
- if (!e || read(this.P) && (!_revealUsed || read(this.L))) return;
345
- return super.run(e);
152
+ /** The nearest boundary on `r`'s chain showing its fallback, if any. */ function hiddenBy(e) {
153
+ for (let n = boundaryOf(e); n !== undefined; n = n._parent ?? undefined) if (n.v) return n;
154
+ return undefined;
155
+ }
156
+
157
+ /** GlobalQueue._hidden: behind a fallback. */ function hidden(e) {
158
+ return hiddenBy(e) !== undefined;
159
+ }
160
+
161
+ /** GlobalQueue._heldRun: a queued run behind a fallback waits for the reveal
162
+ * (maintainer, 2026-10-02: the queue is held, the synchronous first render
163
+ * goes through). A user effect's, which would read a DOM that is not
164
+ * attached ("many depend on reading the DOM, something that can't happen
165
+ * off screen"); a render effect's update too — a Portal or a head-tag
166
+ * library writes outside the hidden subtree, and a queued render run is
167
+ * what mounts them. A user effect's first run is scheduled like every other,
168
+ * so a mount under a fallback defers it too; a render effect's first run is
169
+ * synchronous on creation and never comes here. The run's `_modified` stays
170
+ * set; the reveal re-queues it by type (`release`), and a boundary above
171
+ * still showing its fallback holds it again then. */ function heldRun(e) {
172
+ const n = hiddenBy(e);
173
+ if (n === undefined) return false;
174
+ (n.K ??= new Set).add(e);
175
+ return true;
176
+ }
177
+
178
+ /** The reveal: the runs held behind the fallback are this flush's, each in
179
+ * its own phase — render updates with the frame that attaches the content,
180
+ * user effects after it. */ function release(e) {
181
+ if (e.K === null) return;
182
+ for (const n of e.K) {
183
+ const e = n.u;
184
+ globalQueue.enqueue(e === EFFECT_TRACKED ? EFFECT_USER : e, e === EFFECT_TRACKED ? n.M : n.Y ??= GlobalQueue.q.bind(null, n));
346
185
  }
347
- /** An `on` dependency notified (onNode → scheduler `pendingRearms`);
348
- * drained after the heap, before the verdict, under the notifying write's
349
- * transaction (#3540). A boundary showing content is fresh again: it
350
- * releases its hold now and, if anything under it is still pending, swaps
351
- * to its fallback. The swap is staged, so it lands with the write's frame
352
- * — at once when nothing else holds it, with the rest of the new page
353
- * when something outside the boundary does; if the pending lands first,
354
- * `_checkSources` clears it and no fallback is shown. An `on` that read a
355
- * lane (`latest()`, an optimistic write) asked for the change now:
356
- * `_rearmLane` shows the swap through the lane, beside the held frame.
357
- * Children stay alive behind the fallback. */ se() {
358
- const e = this.O;
359
- this.O = null;
360
- if (this.re === undefined || this.re.oe & REACTIVE_DISPOSED) return;
361
- if (!this.q) return;
362
- // Readers forwarded while this boundary showed content are what it
363
- // would wait on now. They never re-notify (status propagation dedupes on
364
- // the reader's `_pendingSources`), so the re-arm collects it from their
365
- // registrations — the one place a forwarded reader is recorded (INV-3)
366
- // — or a sibling reader's flight that lands first reveals them stale
367
- // (A33, #3459). Before the verdict, a registration may have stopped
368
- // counting without being pruned yet (its flight landed this pass):
369
- // `reporterBlocksSource` is the verdict's own test.
370
- const t = new Set;
371
- for (const e of transitions) for (const [r, n] of e.le) {
372
- for (const e of n) if (this.ae(e) && reporterBlocksSource(e, r)) {
373
- t.add(r);
374
- e.o?.ue?.forEach(e => t.add(e));
186
+ e.K = null;
187
+ }
188
+
189
+ /** Drop the readers that settled or died. Settled: a loading reader no
190
+ * longer pending; an error's source neither errored nor in flight. A reader
191
+ * that landed but is held (its value staged in a transaction) is not: the
192
+ * content reveals at the commit, with its run. The tree is: the output reads
193
+ * it and enters the transaction itself (its committed value shows until the
194
+ * landing). */ const unsettled = e => e.u === STATUS_ERROR ? STATUS_ERROR | STATUS_PENDING : STATUS_PENDING;
195
+
196
+ function prune(e, n) {
197
+ const r = unsettled(e);
198
+ for (const t of e.V) {
199
+ if (t.R & REACTIVE_DISPOSED) e.V.delete(t); else if (!(t.S & r)) {
200
+ if (t !== e.J && (t.i & (CONFIG_HELD | CONFIG_OVERRIDE)) === CONFIG_HELD) {
201
+ // Landed, held: the content is that transaction's — the output's
202
+ // pass enters it and the reveal lands with the reader's run. The
203
+ // seam, past its park decision, waits for the commit instead. (A
204
+ // lane's reader reveals with the lane, this round.) A boundary that
205
+ // has not shown content catches it instead: the fallback stays until
206
+ // the reader is committed (#3540: what a fresh boundary catches holds
207
+ // nothing outside it).
208
+ if (!n || !e.h) continue;
209
+ joinFuture(txOf(t));
375
210
  }
211
+ e.V.delete(t);
376
212
  }
377
- if (!t.size) return;
378
- this.q = false;
379
- this.U = t;
380
- this.v = true;
381
- this.fe(e);
382
- // Those readers are behind the fallback now: they stop blocking
383
- // (`reporterBlocksSource`), and the transactions they were holding must
384
- // be re-judged for it (A33, #3375) — the active one by the verdict that
385
- // follows this drain, parked ones by the wake. A live action keeps its
386
- // transaction parked regardless (transitionComplete): its batch commits
387
- // when it settles, intact.
388
- wakeParked();
389
213
  }
390
- /** Show the fallback: the swap the output pass selects on. Staged, it is
391
- * the frame's and lands with its commit. Re-armed from a lane pass
392
- * (`lane`), it is the current frame's — committed outright, as the lane's
393
- * view already is on screen — and shown through the lane: the output pass
394
- * publishes a derived override and its readers run from the lane's queue,
395
- * at the park, ahead of the transaction (a lane pass reads staged plain
396
- * writes committed, so a staged swap would be invisible to it). */ fe(e) {
397
- if (e === null) setSignal(this.P, true); else {
398
- this.P.ce = true;
399
- notifyOnLane(this.P, e);
400
- }
401
- }
402
- /** Retry the collected failures of an error boundary: recompute each
403
- * source that threw, so the boundary can recover. */ Se() {
404
- for (const e of this.U) {
405
- // Non-computed sources (patch-channel registrations under plain
406
- // owners) are not recomputable — their reset is the record's next
407
- // transition re-applying the patch (re-audit 2, P1-4).
408
- if (e.he !== undefined) recompute(e);
214
+ return e.V.size;
215
+ }
216
+
217
+ /** Reveal order: a slot is ready when its content can show — it has shown
218
+ * content, or nothing under it is unready (the tree neither pending nor
219
+ * born held, no reader waited on). */ function ready(e) {
220
+ return e.h || !(e.J.S & STATUS_PENDING) && !(e.J.i & CONFIG_HELD && (e.Z === null || e.Z.S & STATUS_UNINITIALIZED)) && prune(e, false) === 0;
221
+ }
222
+
223
+ /** GlobalQueue._boundarySeam, end of the seam: readers that settled, died,
224
+ * or landed and committed are dropped; a fallback with none left reveals
225
+ * next round. An arm resolves: nothing collected, nothing happened. */ function boundarySeam() {
226
+ for (const e of collecting) {
227
+ const n = e.Z.R;
228
+ if (n & REACTIVE_DISPOSED) {
229
+ collecting.delete(e);
230
+ continue;
409
231
  }
410
- schedule();
411
- }
412
- notify(e, t, r, n) {
413
- if (!(t & this.te)) return super.notify(e, t, r, n);
414
- // Routing is dimension-independent: each boundary consumes only its own
415
- // status dimension from the mask (`type &= ~collectionType` below) and
416
- // forwards the remainder up the queue chain. An error inside a `Loading`
417
- // needs no special rule — the ERROR dimension survives consumption here and
418
- // reaches the `Errored` that catches it natively, and `flags & collectionType`
419
- // keeps this boundary from collecting a node that isn't actually pending.
420
- // Symmetrically, a pending inside an `Errored` forwards on the PENDING
421
- // dimension, while a status already caught by an inner boundary arrives with
422
- // its dimension consumed from the mask and is correctly not re-routed
423
- // (the Loading > Errored > content composition escape, #2856).
424
- if (this.te & STATUS_PENDING && this.q) return super.notify(e, t, r, n);
425
- if (r & this.te) {
426
- this.v = true;
427
- const t = n?.source || e.o?._?.source;
428
- if (t) {
429
- const r = this.U.size === 0;
430
- this.U.add(t);
431
- // A collecting boundary waits on everything the effect is pending on,
432
- // not only the source this notification carries. Status propagation
433
- // dedupes on the effect's `_pendingSources`: a source it already
434
- // carries (a flight that started before an `on` reset cleared the
435
- // set) is never re-reported, and that source's later re-flight
436
- // stays invisible — the boundary revealed when its one collected
437
- // source settled while the effect was still pending (#3375).
438
- if (this.te & STATUS_PENDING) e.o?.ue?.forEach(e => this.U.add(e));
439
- if (r) {
440
- setSignal(this.P, true);
441
- }
442
- if (this.te & STATUS_ERROR) {
443
- const e = unwrapStatusError(t.o?._);
444
- setSignal(this._, e);
445
- // The client error hook: this boundary renders its fallback for
446
- // it — the one road a rendered failure took that no global handler
447
- // ever saw. `source` is the computation that threw (the status
448
- // wrapper's, made at the first landing and kept downstream), so the
449
- // hook hears where it broke as well as where it was met. Once per
450
- // error object; a `reset()` re-collecting the same failure says
451
- // nothing new.
452
- reportClientError(e, this.ie, t);
453
- }
454
- }
232
+ // A parked frame's boundary (zombie) keeps its state for its revival.
233
+ if (n & REACTIVE_ZOMBIE) continue;
234
+ prune(e, false);
235
+ e.U = false;
236
+ if (e.V.size === 0) {
237
+ collecting.delete(e);
238
+ if (e.v) redraw(e);
239
+ if (e.H !== null) revealHooks.P(e.H);
455
240
  }
456
- t &= ~this.te;
457
- return t ? super.notify(e, t, r, n) : true;
458
241
  }
459
- /** Is `reporter` live and routed to this boundary — under it, with no
460
- * collecting pending-type boundary in between (`reporterBlocksSource`'s test)? */ ae(e) {
461
- if (e.oe & (REACTIVE_ZOMBIE | REACTIVE_DISPOSED)) return false;
462
- for (let t = e.T; t; t = t._parent) {
463
- if (t === this) return true;
464
- if (t.te & STATUS_PENDING && !t.q) return false;
242
+ }
243
+
244
+ /** The `on` pass is a verdict's: it probed itself (CONFIG_VERDICT), or it
245
+ * read a memo that did (#3528's `createMemo(() => isPending(m2))`) — its
246
+ * swap is display-ahead either way. */ function verdictDerived(e) {
247
+ if (e.i & CONFIG_VERDICT) return true;
248
+ for (let n = e.p; n !== null; n = n._) if (n.I.i & CONFIG_VERDICT) return true;
249
+ return false;
250
+ }
251
+
252
+ /** `on` changed after the mount: fresh again if anything under it is
253
+ * pending now; collecting for the rest of the flush either way. */ function arm(e) {
254
+ if (!e.h) {
255
+ // Already re-armed and on its fallback, as the frame's work — and the
256
+ // `on` pass arms again as lane work: its verdict was provisional (a
257
+ // memo of `isPending(m2)` is a watcher until the flush parks, then the
258
+ // holder's verdict lane's — verdict.ts), and the swap it asked for is
259
+ // display-ahead after all (#3528). Re-home the swap: the output re-runs
260
+ // as the lane's, shown now beside the held frame.
261
+ if (e.v && e.B === null && e.j === null && passLane !== null) {
262
+ e.j = passLane;
263
+ // The swap the frame's pass staged (held with the frame) is void: the
264
+ // lane's pass stages it anew (`flip` lists the output in the lane,
265
+ // which voids its staging), as a watcher's provisional value is
266
+ // voided at the seam (verdict.ts `verdictSeam`).
267
+ flip(e);
268
+ redraw(e);
465
269
  }
466
- return false;
467
- }
468
- /** Has a collected source stopped counting for this boundary? A source
469
- * with a live affects() mark holds display state for the mark's lifetime
470
- * (the visual channel): the marked node carries no status of its own, so
471
- * the count is the liveness test. The release sweep (finalizePureQueue
472
- * after mark release) re-runs this check. A source born held under this
473
- * boundary (recompute, #3540) carries no status either: it is collected
474
- * while it has a staged value and no committed one, and released by the
475
- * commit that initializes it. */ de(e) {
476
- return !!(e.oe & REACTIVE_DISPOSED || !e.o?.t && !(e.h & this.te) && !(this.te & STATUS_ERROR && e.h & STATUS_PENDING) && !(this.te & STATUS_PENDING && e.h & STATUS_UNINITIALIZED && e._e !== NOT_PENDING));
270
+ return;
477
271
  }
478
- /** The pre-verdict sweep (scheduler run(), #3540): a collecting boundary
479
- * whose OUTPUT is pending — its fallback read something not ready — is
480
- * judged here, under the transaction. That output is what an initialized
481
- * parent holds the frame on, and it derives from `_disabled`, not the
482
- * tree: the tree settling never re-runs it, only a sweep does, and the
483
- * commit sweep runs after the verdict the output's own read keeps parking
484
- * — the content waited for the fallback's flight. Judged ready here, the
485
- * boundary stages `_disabled` false with the frame and the output re-runs
486
- * in this heap: it reads the tree and drops the fallback's read (recompute
487
- * settles a pass's outgoing pending sources), so the verdict sees the
488
- * release. A boundary showing a ready fallback parks nothing and keeps the
489
- * commit sweep's reveal. */ Re() {
490
- if (this.q || !(this.ne.h & STATUS_PENDING)) return;
491
- this.Ee();
272
+ e.U = true;
273
+ // A verdict read in `on` (`isPending`) whose frame has no transaction
274
+ // yet: the swap is still display-ahead — the frame gets one now (it lands
275
+ // at this very seam if nothing holds it), and the swap is its verdict
276
+ // lane's work, shown ahead of the frame's own runs (#3528).
277
+ let n = passLane;
278
+ if (n === null && globalQueue.X && verdictDerived(getOwner())) {
279
+ joinFuture(null);
280
+ n = GlobalQueue.$(flushTransaction);
492
281
  }
493
- Ee() {
494
- for (const e of this.U) if (this.de(e)) this.U.delete(e);
495
- if (!this.U.size) {
496
- if (this.te & STATUS_PENDING && this.v && !this.q && this.re) {
497
- this.v = !!(this.re.h & this.te);
498
- } else {
499
- this.v = false;
500
- }
501
- if (!this.v) {
502
- setSignal(this.P, false);
503
- }
282
+ e.j = n;
283
+ collecting.add(e);
284
+ for (const n of e.V) {
285
+ if (n.S & STATUS_PENDING && !(n.R & REACTIVE_DISPOSED)) {
286
+ flip(e);
287
+ redraw(e);
288
+ return;
504
289
  }
505
- if (_revealUsed) this.W?.B();
506
290
  }
507
291
  }
508
292
 
509
- function createCollectionBoundary(e, t, r, n) {
510
- const i = createOwner();
511
- if (_revealUsed) setContext(RevealControllerContext, null, i);
512
- const s = new CollectionQueue(e);
513
- s.ie = i;
514
- if (e === STATUS_ERROR) s._ = signal(undefined, {
515
- ownedWrite: true,
516
- H: true
293
+ /** The error boundary's `reset`: re-run what threw (a comparator throw
294
+ * names a signal — nothing to re-run). */ function reset(e) {
295
+ // A snapshot: a source failing again is caught — and re-added — mid-walk.
296
+ for (const n of [ ...e.V ]) if (n.ee !== undefined && !(n.R & REACTIVE_DISPOSED)) recompute(n);
297
+ schedule();
298
+ }
299
+
300
+ function createBoundary(e, n, r, t) {
301
+ const l = createOwner();
302
+ const o = {
303
+ u: e,
304
+ _parent: boundaryOf(l) ?? null,
305
+ L: l,
306
+ J: null,
307
+ Z: null,
308
+ V: new Set,
309
+ h: false,
310
+ U: false,
311
+ j: null,
312
+ B: null,
313
+ v: false,
314
+ K: null,
315
+ H: null,
316
+ ne: false,
317
+ re: false,
318
+ F: e === STATUS_ERROR ? signal(undefined, ERROR_SIGNAL) : null,
319
+ te: r,
320
+ le: null
321
+ };
322
+ const u = e === STATUS_PENDING ? l.D[REVEAL] : null;
323
+ if (getOwner()) cleanup(() => {
324
+ collecting.delete(o);
325
+ if (u) revealHooks.oe(u, o);
517
326
  });
518
327
  // The `on` dependencies live OUTSIDE the boundary, as the condition of a
519
- // `<Show>` wrapping it would: created before the owner's queue becomes
520
- // this boundary's (onNode).
521
- n && onNode(i, s, n);
522
- const o = s.re = createBoundChildren(i, t, s, e);
523
- // Prime source tracking so reveal registration sees pending sources. A
524
- // bookkeeping read (`spectate`): the mounting pass derives nothing from
525
- // the tree — it must not be linked to it, nor enter the transaction a
526
- // tree born held (A29 under a boundary, #3540) was staged into.
527
- spectate(() => {
528
- let t = false;
328
+ // `<Show>` wrapping it would: created before the owner's context names it.
329
+ // A source of it going pending re-runs it (CONFIG_REDERIVE) rather than
330
+ // marking it: the pass catches the NotReady — it is a notification, it
331
+ // does not suspend the parent — and re-arms.
332
+ if (t) {
333
+ let e = false;
334
+ runWithOwner(l, () => computed(() => {
335
+ try {
336
+ t();
337
+ } catch (e) {
338
+ if (!(e instanceof NotReadyError)) {
339
+ if (!globalQueue.notify(getOwner(), STATUS_ERROR, STATUS_ERROR, e)) {
340
+ haltReactivity(e);
341
+ throw e;
342
+ }
343
+ return;
344
+ }
345
+ }
346
+ if (e) arm(o); else e = true;
347
+ })).i |= CONFIG_REDERIVE;
348
+ }
349
+ const a = {
350
+ ...l.D,
351
+ [BOUNDARY]: o
352
+ };
353
+ if (revealHooks !== null) a[REVEAL] = null;
354
+ l.D = a;
355
+ const i = runWithOwner(l, () => {
356
+ const e = false ? computed(n, {
357
+ name: "children"
358
+ }) : computed(n);
359
+ return false ? computed(() => flatten(read(e)), {
360
+ name: "boundary"
361
+ }) : computed(() => flatten(read(e)));
362
+ });
363
+ o.J = i;
364
+ // A slot of the reveal order in its context (reveal.ts): gated until the
365
+ // controller releases it — before the output's first pass.
366
+ if (u) revealHooks.ue(u, o);
367
+ const f = computed(() => {
368
+ // Reveal order (reveal.ts): a gated slot shows its fallback — or, in a
369
+ // collapsed tail, nothing — whatever its content's state; the
370
+ // controller is told the pass ran (its readiness may have changed) and
371
+ // releases it in turn.
372
+ if (o.ne) {
373
+ // Linked without being read: the tree settling (or changing)
374
+ // re-derives this pass, which is how the controller hears of it.
375
+ // It may release this very slot (the frontier whose content just
376
+ // settled): the pass goes on to show it — the redraw it asks for is
377
+ // refused mid-pass and not needed.
378
+ link(i, getOwner());
379
+ if (o.H !== null) revealHooks.P(o.H);
380
+ if (o.ne) {
381
+ if (o.re) {
382
+ o.v = true;
383
+ return undefined;
384
+ }
385
+ return fallback(o);
386
+ }
387
+ }
388
+ // A tree carrying a status is not read: an errored memo's reactive
389
+ // re-read retries it (`read`), and neither a fallback re-rendering nor
390
+ // passing the error by may re-run the content; a pending one read as
391
+ // lane work would serve its committed value (A31) — the swap a
392
+ // display-ahead `on` causes is the lane's, and must still be the
393
+ // fallback. Linked, so its recovery re-derives this pass; read again
394
+ // once clean.
395
+ const n = i.S & (STATUS_PENDING | STATUS_ERROR);
396
+ if (n !== 0) {
397
+ link(i, getOwner());
398
+ if (n & e) {
399
+ if (isCollecting(o)) {
400
+ caught(o, i, undefined);
401
+ return fallback(o);
402
+ }
403
+ // Forwarded: a loading boundary showing content keeps it and
404
+ // holds the frame like any reader (recorded for its `on`).
405
+ o.V.add(i);
406
+ collecting.add(o);
407
+ }
408
+ // A pending passes an error boundary by (A6); an error, a loading
409
+ // one; a loading boundary showing content forwards its pending.
410
+ throw i.m.F;
411
+ }
412
+ if (isCollecting(o)) {
413
+ // A29's boundary exemption (#3540): a boundary MOUNTED over a held
414
+ // value (its first pass; a first pass under it that read a hold was
415
+ // collected, `joinPass` — the tree's own, or a render effect's that
416
+ // binds the content) shows its fallback now and the content at the
417
+ // commit — entering the transaction would make the output itself
418
+ // born held, and nothing would show until the commit. The seam
419
+ // keeps a held reader until it is committed, then re-derives this
420
+ // pass. A boundary with a committed value reads a held tree and
421
+ // enters: the outside sees its committed value until the landing,
422
+ // which reveals the content — a fallback staged earlier is replaced
423
+ // ahead of the commit and never shown.
424
+ const e = getOwner();
425
+ if (o.V.size !== 0 && e.S & STATUS_UNINITIALIZED && !(e.i & CONFIG_HELD)) return fallback(o);
426
+ // Readers under it still unready: the fallback, the tree untouched.
427
+ // The seam re-derives this pass when they settle.
428
+ if (prune(o, true) !== 0) return fallback(o);
429
+ }
430
+ let r;
529
431
  try {
530
- read(o);
531
- } catch (e) {
532
- if (e instanceof NotReadyError) t = true; else throw e;
432
+ r = read(i);
433
+ } catch (n) {
434
+ if (n instanceof NotReadyError ? e === STATUS_PENDING : e === STATUS_ERROR) {
435
+ if (isCollecting(o)) {
436
+ caught(o, i, n);
437
+ return fallback(o);
438
+ }
439
+ // Forwarded: a loading boundary showing content keeps it and
440
+ // holds the frame like any reader (recorded for its `on`).
441
+ o.V.add(i);
442
+ collecting.add(o);
443
+ }
444
+ // A pending passes an error boundary by (A6).
445
+ throw n;
533
446
  }
534
- s.v = t || !!(o.h & e) || o.o?._ instanceof NotReadyError;
535
- });
536
- const l = _revealUsed && e === STATUS_PENDING ? getContext(RevealControllerContext) : null;
537
- if (l) {
538
- s.W = l;
539
- l.$(s);
540
- cleanup(() => l.ee(s));
541
- }
542
- return accessor(s.ne = computed(() => {
543
- // `_disabled` selects fallback or content: set by a collecting
544
- // notification or a re-arm (`_rearm`), cleared by the sweep when the
545
- // collected sources settle — each re-runs this pass.
546
- if (!read(s.P)) {
547
- const e = read(o);
548
- if (!untrack(() => read(s.P))) return s.q = true, e;
447
+ // Readers under it still unready (caught before, or while the read
448
+ // ran the content at a mount): the fallback.
449
+ if (isCollecting(o) && o.V.size !== 0) return fallback(o);
450
+ o.h = true;
451
+ o.v = false;
452
+ // The fallback dies when the content that replaces it is on screen —
453
+ // with this flush's commits when the pass is the frame's, at the
454
+ // landing when the frame parks (the run is held with it, as the old
455
+ // children of a held pass are) — not now: a held swap keeps the
456
+ // fallback on screen, live, until it commits. A pass back to the
457
+ // fallback before then finds it still built and keeps it.
458
+ const t = o.le;
459
+ if (t !== null) globalQueue.enqueue(EFFECT_RENDER, () => {
460
+ if (!o.v && o.le === t) {
461
+ dispose(t);
462
+ o.le = null;
463
+ }
464
+ });
465
+ release(o);
466
+ // Shown: the slot leaves its reveal order (a later re-arm gates nobody
467
+ // else; the frontier moves on).
468
+ if (o.H !== null) {
469
+ const e = o.H;
470
+ o.H = null;
471
+ revealHooks.P(e);
549
472
  }
550
- // Collapsed reveal slots suppress their own output entirely; the
551
- // renderer treats the hole as empty, so the cast never leaks to users
552
- // outside a `createRevealOrder` scope.
553
- if (_revealUsed && read(s.L)) return undefined;
554
- return r(s);
473
+ return r;
555
474
  },
556
- // Boundary structure, not a user source: its value is fallback-or-content and
557
- // legitimately swaps mid-hydration (reveal/resume), so it must never be frozen
558
- // by snapshot capture. The tree no longer carries foreign status flags, so
559
- // capture can't rely on PENDING to skip this node the way it used to.
475
+ // Boundary structure, not a user source: its value is fallback-or-content
476
+ // and legitimately swaps mid-hydration (resume), so it must never be
477
+ // frozen by snapshot capture.
560
478
  {
561
- H: true
562
- }));
479
+ ae: true
480
+ });
481
+ f.i |= CONFIG_REDERIVE;
482
+ o.Z = f;
483
+ return accessor(f);
484
+ }
485
+
486
+ function fallback(e) {
487
+ // The swap a display-ahead re-arm asked for: this pass is the lane's — a
488
+ // lane read by construction (REACTIVE_LANE_READ: `recompute`'s tail keeps
489
+ // the pass in the lane).
490
+ if (e.B !== null) {
491
+ setPassLane(e.B);
492
+ e.Z.R |= REACTIVE_LANE_READ;
493
+ e.B = null;
494
+ }
495
+ e.v = true;
496
+ // Built in its own scope, like `Show`'s function child, once per show
497
+ // (D-39: the fallback is the display until a new answer replaces the
498
+ // error): a re-derive of the output while it shows — a second reader
499
+ // caught, the same error propagated again through a sibling's re-run, a
500
+ // retry pending after `reset()`, a later error — keeps the built
501
+ // fallback, its DOM and focus with it (#3991). A later error reaches it
502
+ // through the error accessor; only a fallback reading the accessor at its
503
+ // top level is rebuilt by it. Created beside the output — in its owner,
504
+ // outside the boundary, as the output is: its errors and runs are the
505
+ // enclosing boundary's — with the output's id, so what it builds gets the
506
+ // ids a build under the output would; disposed once content is on screen.
507
+ if (e.le === null) {
508
+ const n = getOwner();
509
+ e.le = runWithOwner(n._parent, () => computed(() => e.te(e), {
510
+ id: n.id
511
+ }));
512
+ }
513
+ return read(e.le);
563
514
  }
564
515
 
516
+ const ERROR_SIGNAL = {
517
+ ownedWrite: true,
518
+ ae: true
519
+ };
520
+
565
521
  /**
566
522
  * Lower-level primitive that backs the `<Loading>` flow control. Catches
567
523
  * pending async reads inside `fn` and renders `fallback` until they settle.
@@ -603,8 +559,8 @@ function createCollectionBoundary(e, t, r, n) {
603
559
  * ) as unknown as JSX.Element;
604
560
  * }
605
561
  * ```
606
- */ function createLoadingBoundary(e, t, r) {
607
- return createCollectionBoundary(STATUS_PENDING, e, () => t(), r?.on);
562
+ */ function createLoadingBoundary(e, n, r) {
563
+ return createBoundary(STATUS_PENDING, e, () => n(), r?.on);
608
564
  }
609
565
 
610
566
  /**
@@ -629,154 +585,20 @@ function createCollectionBoundary(e, t, r, n) {
629
585
  * ) as unknown as JSX.Element;
630
586
  * }
631
587
  * ```
632
- */ function createErrorBoundary(e, t) {
633
- return createCollectionBoundary(STATUS_ERROR, e, e => t(accessor(e._), () => e.Se()));
588
+ */ function createErrorBoundary(e, n) {
589
+ return createBoundary(STATUS_ERROR, e, e => n(accessor(e.F), () => reset(e)));
634
590
  }
635
591
 
636
- /**
637
- * Coordinate the reveal timing of sibling loading boundaries.
638
- *
639
- * Accepts reactive accessors:
640
- * - `order`: `"sequential"` (default) | `"together"` | `"natural"`.
641
- * - `"sequential"` — classic frontier reveal: siblings reveal in registration order
642
- * as each resolves; later siblings stay hidden until earlier ones complete.
643
- * - `"together"` — every direct slot stays on its fallback until the whole group
644
- * is "minimally ready" (each direct slot has produced its own first visible
645
- * content under its own order), then the whole group releases at once.
646
- * - `"natural"` — children reveal independently (as each resolves). At the top
647
- * level this is a no-op compared to not using `createRevealOrder`; the mode
648
- * exists for nesting, where the group registers as a single composite slot to
649
- * any enclosing `createRevealOrder`.
650
- * - `collapsed`: only meaningful when `order === "sequential"`. When set, tail siblings
651
- * past the frontier suppress their own fallback output. Ignored under `"together"`
652
- * and `"natural"` — those orders have no frontier.
653
- *
654
- * Nested `createRevealOrder` groups compose: the inner controller registers as a
655
- * single slot in the outer controller and is held on its fallbacks until the outer
656
- * releases that slot. Once released, the inner controller runs its own order locally
657
- * over anything still pending. There is no opt-out from an outer hold.
658
- *
659
- * "Minimally ready" is what an order considers its first visible content:
660
- * - `sequential` — frontier-0 is minimally ready (leaf: on resolve; nested: via its
661
- * own minimal signal).
662
- * - `together` — every direct slot is minimally ready.
663
- * - `natural` — any direct slot has visible content (leaves on resolve; nested
664
- * composites via their own minimal signal).
665
- *
666
- * @example
667
- * ```ts
668
- * // Primitive form of `<Reveal>` — coordinate sibling loading boundaries
669
- * // programmatically. App code uses the JSX `<Reveal>` component instead.
670
- * // Both options are accessors so they can react to state changes.
671
- * createRevealOrder(
672
- * () => renderSiblings(),
673
- * { order: () => mode(), collapsed: () => true }
674
- * );
675
- * ```
676
- */ function createRevealOrder(e, t) {
677
- _revealUsed = true;
678
- const r = createOwner();
679
- const n = getContext(RevealControllerContext);
680
- const i = t?.order || SEQUENTIAL_ACCESSOR, s = t?.collapsed || FALSE_ACCESSOR;
681
- const o = new RevealController(i, s);
682
- setContext(RevealControllerContext, o, r);
683
- return runWithOwner(r, () => {
684
- const t = e();
685
- const r = computed(() => {
686
- i();
687
- s();
688
- o.B();
689
- });
690
- // Post-construction rather than an options argument, so the prod call
691
- // keeps its shape; the observe node literal already carries the slot.
692
- if (false) ;
693
- if (n) {
694
- o.j = n;
695
- n.$(o);
696
- cleanup(() => n.ee(o));
697
- }
698
- return t;
699
- });
700
- }
592
+ // Installed at module evaluation — present exactly when something imports a
593
+ // boundary. An app without one pays the three null checks and nothing else.
594
+ GlobalQueue.ie = catchStatus;
701
595
 
702
- /**
703
- * Resolves a children value to its renderable form: unwraps zero-arg functions
704
- * (accessors), recursively flattens arrays, and optionally skips
705
- * non-rendering values (`null`, `undefined`, `true`, `false`, `""`).
706
- *
707
- * Used internally by flow components and by the renderer to walk a children
708
- * tree. App code rarely needs this directly — see `children()` in `solid-js`
709
- * for the user-facing helper that memoizes the result.
710
- *
711
- * @param children value or array of values to flatten
712
- * @param options
713
- * - `skipNonRendered` — drop values that won't render
714
- * - `doNotUnwrap` — leave function children as-is (caller will resolve)
715
- *
716
- * @example
717
- * ```ts
718
- * // Custom renderer walking a children tree manually. Most authors should
719
- * // use `children()` from solid-js, which memoizes the resolved value.
720
- * function renderChildren(value: unknown): unknown {
721
- * return flatten(value, { skipNonRendered: true });
722
- * }
723
- * ```
724
- */ function flatten(e, t) {
725
- if (typeof e === "function" && !e.length) {
726
- if (t?.doNotUnwrap) return e;
727
- do {
728
- e = e();
729
- } while (typeof e === "function" && !e.length);
730
- }
731
- if (t?.skipNonRendered && (e == null || e === true || e === false || e === "")) return;
732
- if (Array.isArray(e)) {
733
- let r = [];
734
- if (flattenArray(e, r, t)) {
735
- return () => {
736
- let e = [];
737
- flattenArray(r, e, {
738
- ...t,
739
- doNotUnwrap: false
740
- });
741
- return e;
742
- };
743
- }
744
- return r;
745
- }
746
- return e;
747
- }
596
+ GlobalQueue.fe = e => e.S & STATUS_UNINITIALIZED && catchStatus(e, STATUS_PENDING);
748
597
 
749
- function flattenArray(e, t = [], r) {
750
- let n = null;
751
- let i = false;
752
- for (let s = 0; s < e.length; s++) {
753
- try {
754
- let n = e[s];
755
- if (typeof n === "function" && !n.length) {
756
- if (r?.doNotUnwrap) {
757
- t.push(n);
758
- i = true;
759
- continue;
760
- }
761
- do {
762
- n = n();
763
- } while (typeof n === "function" && !n.length);
764
- }
765
- if (Array.isArray(n)) {
766
- // OR, don't overwrite: an accessor already pushed under doNotUnwrap
767
- // still needs the resolving wrapper even when a later sibling
768
- // fragment contains no functions (#3133).
769
- i = flattenArray(n, t, r) || i;
770
- } else if (r?.skipNonRendered && (n == null || n === true || n === false || n === "")) {
771
- // skip
772
- } else t.push(n);
773
- } catch (e) {
774
- if (!(e instanceof NotReadyError)) throw e;
775
- n = e;
776
- }
777
- }
778
- if (n) throw n;
779
- return i;
780
- }
598
+ GlobalQueue.ce = hidden;
599
+
600
+ GlobalQueue.se = boundarySeam;
601
+
602
+ GlobalQueue.Ee = heldRun;
781
603
 
782
- export { CollectionQueue, RevealController, createErrorBoundary, createLoadingBoundary, createRevealOrder, flatten };
604
+ export { REVEAL, createErrorBoundary, createLoadingBoundary, prune, ready, redraw, revealHooks, setRevealHooks };