@solidjs/signals 2.0.0-rc.11 → 2.0.0-rc.14

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