@solidjs/signals 2.0.0-rc.2 → 2.0.0-rc.20

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 (198) hide show
  1. package/dist/dev-shared.js +6505 -0
  2. package/dist/dev.attribution.js +3484 -0
  3. package/dist/dev.js +10227 -9508
  4. package/dist/observe/affects-marks.js +86 -0
  5. package/dist/observe/affects.js +112 -0
  6. package/dist/observe/attribution.js +7 -0
  7. package/dist/observe/boundaries.js +696 -0
  8. package/dist/observe/core/action.js +182 -0
  9. package/dist/observe/core/async.js +824 -0
  10. package/dist/observe/core/attribution-costs.js +66 -0
  11. package/dist/observe/core/attribution-feedback.js +280 -0
  12. package/dist/observe/core/attribution-hooks.js +83 -0
  13. package/dist/observe/core/attribution-queries.js +43 -0
  14. package/dist/observe/core/attribution.js +3079 -0
  15. package/dist/observe/core/constants.js +269 -0
  16. package/dist/observe/core/context.js +63 -0
  17. package/dist/observe/core/copies.js +10 -0
  18. package/dist/observe/core/core.js +1707 -0
  19. package/dist/observe/core/dev.js +394 -0
  20. package/dist/observe/core/effect.js +193 -0
  21. package/dist/observe/core/error-hooks.js +71 -0
  22. package/dist/observe/core/error.js +82 -0
  23. package/dist/observe/core/external.js +97 -0
  24. package/dist/observe/core/graph.js +162 -0
  25. package/dist/observe/core/heap.js +124 -0
  26. package/dist/observe/core/identities.js +62 -0
  27. package/dist/observe/core/invariants.js +12 -0
  28. package/dist/observe/core/lanes.js +972 -0
  29. package/dist/observe/core/owner.js +371 -0
  30. package/dist/observe/core/scheduler.js +1157 -0
  31. package/dist/observe/core/truth.js +47 -0
  32. package/dist/observe/core/verdict.js +470 -0
  33. package/dist/observe/deferred.js +186 -0
  34. package/dist/observe/flatten.js +79 -0
  35. package/dist/observe/from.js +248 -0
  36. package/dist/observe/index.js +71 -0
  37. package/dist/observe/map.js +593 -0
  38. package/dist/observe/reveal.js +212 -0
  39. package/dist/observe/signals.js +717 -0
  40. package/dist/observe/store/affects.js +142 -0
  41. package/dist/observe/store/edges.js +39 -0
  42. package/dist/observe/store/hooks.js +16 -0
  43. package/dist/observe/store/index.js +47 -0
  44. package/dist/observe/store/optimistic.js +713 -0
  45. package/dist/observe/store/projection.js +345 -0
  46. package/dist/observe/store/reconcile.js +281 -0
  47. package/dist/observe/store/store.js +2127 -0
  48. package/dist/observe/store/storePath.js +103 -0
  49. package/dist/observe/store/target.js +46 -0
  50. package/dist/observe/store/types.js +83 -0
  51. package/dist/observe/store/utils.js +1063 -0
  52. package/dist/prod/affects-marks.js +86 -0
  53. package/dist/prod/affects.js +98 -112
  54. package/dist/prod/attribution.js +45 -0
  55. package/dist/prod/boundaries.js +578 -468
  56. package/dist/prod/core/action.js +92 -59
  57. package/dist/prod/core/async.js +522 -336
  58. package/dist/prod/core/attribution-hooks.js +3 -0
  59. package/dist/prod/core/constants.js +168 -44
  60. package/dist/prod/core/context.js +12 -16
  61. package/dist/prod/core/copies.js +10 -0
  62. package/dist/prod/core/core.js +1345 -627
  63. package/dist/prod/core/dev.js +123 -1
  64. package/dist/prod/core/effect.js +113 -80
  65. package/dist/prod/core/error-hooks.js +71 -0
  66. package/dist/prod/core/error.js +18 -4
  67. package/dist/prod/core/external.js +6 -7
  68. package/dist/prod/core/graph.js +119 -72
  69. package/dist/prod/core/heap.js +85 -109
  70. package/dist/prod/core/identities.js +62 -0
  71. package/dist/prod/core/invariants.js +1 -32
  72. package/dist/prod/core/lanes.js +931 -132
  73. package/dist/prod/core/owner.js +192 -129
  74. package/dist/prod/core/scheduler.js +948 -642
  75. package/dist/prod/core/truth.js +47 -0
  76. package/dist/prod/core/verdict.js +409 -420
  77. package/dist/prod/deferred.js +184 -0
  78. package/dist/prod/flatten.js +77 -0
  79. package/dist/prod/from.js +248 -0
  80. package/dist/prod/index.js +40 -16
  81. package/dist/prod/map.js +481 -211
  82. package/dist/prod/reveal.js +212 -0
  83. package/dist/prod/signals.js +401 -84
  84. package/dist/prod/store/affects.js +142 -0
  85. package/dist/prod/store/edges.js +39 -0
  86. package/dist/prod/store/hooks.js +16 -0
  87. package/dist/prod/store/index.js +27 -18
  88. package/dist/prod/store/optimistic.js +707 -0
  89. package/dist/prod/store/projection.js +331 -0
  90. package/dist/prod/store/reconcile.js +281 -0
  91. package/dist/prod/store/store.js +1952 -267
  92. package/dist/prod/store/storePath.js +1 -1
  93. package/dist/prod/store/target.js +43 -0
  94. package/dist/prod/store/types.js +83 -0
  95. package/dist/prod/store/utils.js +997 -138
  96. package/dist/types/affects-marks.d.ts +14 -0
  97. package/dist/types/affects.d.ts +12 -30
  98. package/dist/types/attribution.d.ts +19 -0
  99. package/dist/types/attribution.prod.d.ts +22 -0
  100. package/dist/types/boundaries.d.ts +99 -124
  101. package/dist/types/carved.d.ts +1 -0
  102. package/dist/types/core/action.d.ts +23 -9
  103. package/dist/types/core/async.d.ts +34 -6
  104. package/dist/types/core/attribution-costs.d.ts +35 -0
  105. package/dist/types/core/attribution-feedback.d.ts +133 -0
  106. package/dist/types/core/attribution-hooks.d.ts +307 -8
  107. package/dist/types/core/attribution-queries.d.ts +24 -0
  108. package/dist/types/core/attribution.d.ts +802 -72
  109. package/dist/types/core/constants.d.ts +195 -38
  110. package/dist/types/core/copies.d.ts +54 -0
  111. package/dist/types/core/core.d.ts +295 -65
  112. package/dist/types/core/dev.d.ts +509 -22
  113. package/dist/types/core/effect.d.ts +1 -1
  114. package/dist/types/core/error-hooks.d.ts +71 -0
  115. package/dist/types/core/error.d.ts +18 -1
  116. package/dist/types/core/graph.d.ts +40 -1
  117. package/dist/types/core/heap.d.ts +5 -5
  118. package/dist/types/core/identities.d.ts +17 -0
  119. package/dist/types/core/index.d.ts +11 -7
  120. package/dist/types/core/invariants.d.ts +1 -26
  121. package/dist/types/core/lanes.d.ts +74 -43
  122. package/dist/types/core/owner.d.ts +27 -9
  123. package/dist/types/core/scheduler.d.ts +353 -102
  124. package/dist/types/core/truth.d.ts +18 -0
  125. package/dist/types/core/types.d.ts +97 -71
  126. package/dist/types/core/verdict.d.ts +35 -0
  127. package/dist/types/deferred.d.ts +28 -0
  128. package/dist/types/flatten.d.ts +8 -0
  129. package/dist/types/from.d.ts +47 -0
  130. package/dist/types/index.d.ts +33 -7
  131. package/dist/types/map.d.ts +4 -2
  132. package/dist/types/reveal.d.ts +19 -0
  133. package/dist/types/signals.d.ts +196 -45
  134. package/dist/types/store/affects.d.ts +12 -0
  135. package/dist/types/store/edges.d.ts +9 -0
  136. package/dist/types/store/hooks.d.ts +62 -0
  137. package/dist/types/store/index.d.ts +42 -15
  138. package/dist/types/store/optimistic.d.ts +24 -45
  139. package/dist/types/store/projection.d.ts +17 -68
  140. package/dist/types/store/reconcile.d.ts +3 -46
  141. package/dist/types/store/store.d.ts +133 -136
  142. package/dist/types/store/storePath.d.ts +1 -1
  143. package/dist/types/store/target.d.ts +166 -0
  144. package/dist/types/store/types.d.ts +105 -0
  145. package/dist/types/store/utils.d.ts +188 -0
  146. package/package.json +31 -26
  147. package/dist/node.cjs +0 -8745
  148. package/dist/prod/core/optimistic.js +0 -265
  149. package/dist/prod/store/next/optimistic.js +0 -385
  150. package/dist/prod/store/next/projection.js +0 -172
  151. package/dist/prod/store/next/reconcile.js +0 -331
  152. package/dist/prod/store/next/store.js +0 -1499
  153. package/dist/prod/store/next/target.js +0 -20
  154. package/dist/types/core/optimistic.d.ts +0 -6
  155. package/dist/types/store/next/optimistic.d.ts +0 -20
  156. package/dist/types/store/next/projection.d.ts +0 -8
  157. package/dist/types/store/next/reconcile.d.ts +0 -3
  158. package/dist/types/store/next/store.d.ts +0 -77
  159. package/dist/types/store/next/target.d.ts +0 -117
  160. package/dist/types-cjs/affects.d.cts +0 -47
  161. package/dist/types-cjs/boundaries.d.cts +0 -175
  162. package/dist/types-cjs/core/action.d.cts +0 -64
  163. package/dist/types-cjs/core/async.d.cts +0 -21
  164. package/dist/types-cjs/core/attribution-hooks.d.cts +0 -52
  165. package/dist/types-cjs/core/attribution.d.cts +0 -186
  166. package/dist/types-cjs/core/constants.d.cts +0 -97
  167. package/dist/types-cjs/core/context.d.cts +0 -36
  168. package/dist/types-cjs/core/core.d.cts +0 -163
  169. package/dist/types-cjs/core/dev.d.cts +0 -81
  170. package/dist/types-cjs/core/effect.d.cts +0 -30
  171. package/dist/types-cjs/core/error.d.cts +0 -47
  172. package/dist/types-cjs/core/external.d.cts +0 -15
  173. package/dist/types-cjs/core/graph.d.cts +0 -6
  174. package/dist/types-cjs/core/heap.d.cts +0 -22
  175. package/dist/types-cjs/core/index.d.cts +0 -13
  176. package/dist/types-cjs/core/invariants.d.cts +0 -59
  177. package/dist/types-cjs/core/lanes.d.cts +0 -44
  178. package/dist/types-cjs/core/optimistic.d.cts +0 -6
  179. package/dist/types-cjs/core/owner.d.cts +0 -124
  180. package/dist/types-cjs/core/scheduler.d.cts +0 -180
  181. package/dist/types-cjs/core/types.d.cts +0 -192
  182. package/dist/types-cjs/core/verdict.d.cts +0 -2
  183. package/dist/types-cjs/index.d.cts +0 -10
  184. package/dist/types-cjs/map.d.cts +0 -69
  185. package/dist/types-cjs/package.json +0 -3
  186. package/dist/types-cjs/signals.d.cts +0 -624
  187. package/dist/types-cjs/store/index.d.cts +0 -19
  188. package/dist/types-cjs/store/next/optimistic.d.cts +0 -20
  189. package/dist/types-cjs/store/next/projection.d.cts +0 -8
  190. package/dist/types-cjs/store/next/reconcile.d.cts +0 -3
  191. package/dist/types-cjs/store/next/store.d.cts +0 -77
  192. package/dist/types-cjs/store/next/target.d.cts +0 -117
  193. package/dist/types-cjs/store/optimistic.d.cts +0 -45
  194. package/dist/types-cjs/store/projection.d.cts +0 -70
  195. package/dist/types-cjs/store/reconcile.d.cts +0 -46
  196. package/dist/types-cjs/store/store.d.cts +0 -138
  197. package/dist/types-cjs/store/storePath.d.cts +0 -58
  198. package/dist/types-cjs/store/utils.d.cts +0 -74
@@ -1,379 +1,609 @@
1
- import { runWithOwner, signal, untrack, read, computed, setSignal, ext, recompute } from "./core/core.js";
1
+ import { STATUS_PENDING, STATUS_ERROR, CONFIG_REDERIVE, CONFIG_HELD, CONFIG_FRAME, STATUS_UNINITIALIZED, EFFECT_RENDER, CONFIG_VERDICT, REACTIVE_DISPOSED, REACTIVE_LANE_READ, REACTIVE_PROBED, CONFIG_STAGED_ERROR, CONFIG_OVERRIDE, EFFECT_USER, EFFECT_TRACKED, REACTIVE_ZOMBIE, CAUGHT_REARMED } from "./core/constants.js";
2
2
 
3
- import { NotReadyError, unwrapStatusError } from "./core/error.js";
3
+ import { signal, runWithOwner, computed, read, setSignal, recompute } from "./core/core.js";
4
+
5
+ import { reportDiagnostic, emitDiagnostic } from "./core/dev.js";
4
6
 
5
- import { createOwner, cleanup } from "./core/owner.js";
7
+ import { NotReadyError, unwrapStatusError } from "./core/error.js";
6
8
 
7
- import { Queue, haltReactivity, schedule } from "./core/scheduler.js";
9
+ import { reportClientError } from "./core/error-hooks.js";
8
10
 
9
- import { getContext, setContext, createContext } from "./core/context.js";
11
+ import { link } from "./core/graph.js";
10
12
 
11
- import { STATUS_PENDING, STATUS_ERROR, REACTIVE_DISPOSED, CONFIG_AUTO_DISPOSE } from "./core/constants.js";
13
+ import { enqueueSub } from "./core/heap.js";
12
14
 
13
- import "./core/invariants.js";
15
+ import { createOwner, getOwner, cleanup, dispose } from "./core/owner.js";
14
16
 
15
- import "./core/verdict.js";
17
+ import { globalQueue, haltReactivityAt, haltReactivity, schedule, passLane, joinFuture, GlobalQueue, flushTransaction, heldUnder, setPassLane, txOf, unhold, laneStageHook, installBoundaryHooks } from "./core/scheduler.js";
16
18
 
17
- import "./core/effect.js";
19
+ import { flatten } from "./flatten.js";
18
20
 
19
21
  import { accessor } from "./signals.js";
20
22
 
21
- function boundaryComputed(e, t) {
22
- const r = computed(e, {
23
- lazy: true
24
- });
25
- ext(r).A = (e, t) => {
26
- // Use passed values if provided, otherwise read from node
27
- const n = e !== undefined ? e : r.S;
28
- const s = t !== undefined ? t : r.o?._;
29
- // Notify both status dimensions like a render effect does; the queue chain
30
- // consumes this boundary's own type and forwards the remainder upward until
31
- // a boundary that handles it is found.
32
- r.S &= ~r.R;
33
- const i = r.C.notify(r, STATUS_PENDING | STATUS_ERROR, n, s);
34
- // The queue is the only propagation channel: a foreign status must not stay
35
- // reader-visible on the tree, or reads re-throw it across the boundary and
36
- // link unrelated ambient contexts (the #2809 nested-boundary loop). Deps are
37
- // untouched, so the tree still recomputes when the foreign source settles.
38
- const o = n & ~r.R & (STATUS_PENDING | STATUS_ERROR);
39
- if (o) {
40
- r.S &= ~o;
41
- if (r.o?._ === s && !(r.S & (STATUS_PENDING | STATUS_ERROR))) if (r.o !== null) r.o._ = undefined;
42
- }
43
- // An ERROR the chain could not deliver to any boundary is uncaught. The
44
- // scrub above already removed it from reader-visible state, so without
45
- // escalation here it would vanish entirely (#2884) — halt-and-throw,
46
- // exactly like an unhandled effect error.
47
- if (!i && n & STATUS_ERROR) {
48
- haltReactivity(unwrapStatusError(s));
49
- throw s;
50
- }
51
- };
52
- r.R = t;
53
- r.T &= ~CONFIG_AUTO_DISPOSE;
54
- recompute(r, true);
55
- return r;
56
- }
23
+ /**
24
+ * Boundaries — the display consumers between a frame reader and the root:
25
+ * `createLoadingBoundary` (`<Loading>`) and `createErrorBoundary`
26
+ * (`<Errored>`).
27
+ *
28
+ * A boundary is an owner whose context names it (`BOUNDARY`), so every node
29
+ * created under it finds it in O(1) and the enclosing boundaries through
30
+ * `_parent`; a `tree` (the content, flattened) and an `output` that shows
31
+ * the tree or the fallback. The output is a frame reader (CONFIG_REDERIVE):
32
+ * status reaching it from its content re-derives it instead of marking it,
33
+ * and its pass decides.
34
+ *
35
+ * Status protocol (B5). A frame reader under a boundary that goes pending or
36
+ * errors tells `GlobalQueue.notify`, which asks `catchStatus` before the
37
+ * root: the nearest boundary of that status that is collecting — an error
38
+ * boundary always; a loading boundary that has not shown content yet, or
39
+ * whose `on` just changed — records the reader and shows its fallback, and
40
+ * the root never hears of it (no transaction is opened: A33, a fallback-
41
+ * caught flight holds nothing). A loading boundary showing content forwards:
42
+ * the root holds the frame as for any reader. The tree's own status takes
43
+ * the same route from the output's pass (`read(tree)` throws it).
44
+ *
45
+ * The hold (`blocked`, scheduler.ts) asks `hiddenBy`: a reader behind a
46
+ * fallback — any boundary on its chain whose last pass chose the fallback —
47
+ * is not on screen and holds nothing. An error boundary flipping to its
48
+ * fallback is the same as a loading one resetting: every reader under it
49
+ * stops counting, and a flight whose only visible reader that was loses its
50
+ * hold (maintainer, 2026-10-01).
51
+ *
52
+ * Reveal. The seam (`boundarySeam`) drops readers that settled, died, or
53
+ * landed and committed; a fallback with none left re-derives its output,
54
+ * which reads the tree again. A reader that landed but is held (CONFIG_HELD,
55
+ * its value staged in a transaction) is not settled: the content reveals at
56
+ * the commit, with the rest of the frame (#3540).
57
+ *
58
+ * `on` (loading only): a tracked function whose reads re-arm the boundary.
59
+ * A pass of it after the mount arms the boundary for the flush: anything
60
+ * pending under it — already (its forwarded readers), or going pending later
61
+ * in the same heap — is collected and the fallback shows now, with the
62
+ * change's frame; nothing pending, and the arm is a no-op at the seam.
63
+ */
64
+ /** Context key: the nearest boundary of a node, inherited at creation. */ const BOUNDARY = Symbol("");
57
65
 
58
- function createBoundChildren(e, t, r, n) {
59
- const s = e.C;
60
- s.addChild(e.C = r);
61
- cleanup(() => s.removeChild(e.C));
62
- return runWithOwner(e, () => {
63
- const e = computed(t);
64
- return boundaryComputed(() => flatten(read(e)), n);
65
- });
66
- }
66
+ /** Context key: the reveal controller a Loading boundary created here is a
67
+ * slot of (reveal.ts); a boundary clears it for its content — only direct
68
+ * children are slots. */ const REVEAL = Symbol("");
67
69
 
68
- const ON_INIT = Symbol();
70
+ // Every call site checks the hooks themselves first (not only the slot or
71
+ // controller, which are never set without them): a build that does not
72
+ // import `createRevealOrder` folds the reveal-order arms away.
73
+ let revealHooks = null;
69
74
 
70
- const RevealControllerContext = /* @__PURE__ */ createContext(null);
75
+ function setRevealHooks(e) {
76
+ revealHooks = e;
77
+ }
71
78
 
72
- let _revealUsed = false;
79
+ /** Boundaries with readers to sweep (or an arm to resolve) at the seam. */ const collecting = new Set;
73
80
 
74
- const FALSE_ACCESSOR = () => false;
81
+ const boundaryOf = e => e.A[BOUNDARY];
75
82
 
76
- const SEQUENTIAL_ACCESSOR = () => "sequential";
83
+ /** Catches what reaches it: an error boundary always; a loading one until it
84
+ * has shown content, or while re-armed. */ const isCollecting = e => e.i === STATUS_ERROR || !e.D || e.h;
77
85
 
78
- function isRevealController(e) {
79
- return e instanceof RevealController;
86
+ /** A reader `b` waits on: recorded for its `on`, and `b` visited at the seam. */ function record(e, n, r = n) {
87
+ e.F.set(n, r);
88
+ collecting.add(e);
80
89
  }
81
90
 
82
- function isSlotReady(e) {
83
- return isRevealController(e) ? e.O() : e.v.size === 0 && !e.N;
91
+ /** The reader goes to the fallback: recorded, and the output re-derives
92
+ * (a no-op while the output's own pass is running — its post-check
93
+ * decides). */ function caught(e, n, r, o = n) {
94
+ if (e.i === STATUS_ERROR) {
95
+ // An error boundary waits on what THREW (`StatusError.source`), not on
96
+ // the reader that saw it, while the error is settled only when its
97
+ // source is neither errored nor in flight and the reader is not pending
98
+ // on the answer that replaces it (`prune`) — or when the
99
+ // reader is disposed: a source no live reader holds holds nothing
100
+ // (#4373). A source that died (a memo its reader owned, kept past the
101
+ // pass that disposed it) settles nothing: the reader is waited on.
102
+ // `reset` re-runs the source.
103
+ r ??= n.T?.U;
104
+ o = r?.source ?? n;
105
+ if (o.R & REACTIVE_DISPOSED) o = n;
106
+ const t = unwrapStatusError(r);
107
+ setSignal(e.U, t);
108
+ reportClientError(t, e.H, o);
109
+ }
110
+ record(e, n, o);
111
+ // A loading fallback behind an error fallback is hidden: the error stays
112
+ // the display until what the fallback waits on answers (D-39) — the
113
+ // error boundary waits on it too.
114
+ // (`_type`: STATUS_PENDING 1 < STATUS_ERROR 2 — an error boundary above a
115
+ // loading one.)
116
+ for (let r = e; r = r._parent; ) if (r.L && r.i > e.i) record(r, n);
117
+ if (e.h) flip(e);
118
+ // Already showing the fallback: one more reader to wait on changes
119
+ // nothing on screen. The fallback is instantiated once per show — a later
120
+ // error reaches it through the `error` accessor, which is why it is one.
121
+ if (!e.L) redraw(e);
122
+ if (revealHooks && e.v !== null) revealHooks.V(e.v);
84
123
  }
85
124
 
86
- function isSlotMinimallyReady(e) {
87
- return isRevealController(e) ? e.U() : isSlotReady(e);
125
+ /** A re-armed boundary found something pending: fresh again, and the swap
126
+ * the arming pass asked for is that pass's lane's (if any): the output is
127
+ * the lane's work from its next pass's head — not a re-pass of the
128
+ * transaction holding its pending content, which would entangle that
129
+ * transaction with the arming frame's (#3540 `on: () => latest(dep)`). */ function flip(e) {
130
+ e.D = false;
131
+ e.P = e.B;
132
+ const n = e.j;
133
+ if (!e.P && n.l & CONFIG_HELD && txOf(n) !== flushTransaction) unhold(n);
134
+ if (laneStageHook && e.P !== null && e.j !== null) laneStageHook(e.j, e.P, true, false);
88
135
  }
89
136
 
90
- function setSlotState(e, t, r, n) {
91
- setSignal(e.I, r);
92
- setSignal(e.D, n);
93
- if (isRevealController(e)) {
94
- if (!r && e.P === t) e.P = undefined;
95
- return e.B(r, n);
137
+ function redraw(e) {
138
+ if (e.j !== null && !(e.j.R & (REACTIVE_DISPOSED | REACTIVE_ZOMBIE))) {
139
+ enqueueSub(e.j);
140
+ schedule();
96
141
  }
97
- if (!r && e.W === t && e.L) e.W = undefined;
98
142
  }
99
143
 
100
- class RevealController {
101
- F;
102
- q;
103
- V=[];
104
- P;
105
- I=signal(false, {
106
- ownedWrite: true,
107
- H: true
108
- });
109
- D=signal(false, {
110
- ownedWrite: true,
111
- H: true
112
- });
113
- J=true;
114
- K=true;
115
- X=false;
116
- constructor(e, t) {
117
- this.F = e;
118
- this.q = t;
144
+ /** `catchHook` (scheduler.ts): status from a frame reader, nearest boundary first.
145
+ * A loading boundary on the way records a pending reader whether or not it
146
+ * catches it (its `on` may collect it later). */ function catchStatus(e, n, r) {
147
+ if (n === 0) {
148
+ // A status cleared. Judged by the node's status now, against each
149
+ // boundary's own rule (`prune`'s mask) — the error path clears pending
150
+ // first and the node is still errored then. The last reader a fallback
151
+ // waited on settling re-derives the output now, in the same heap (a
152
+ // held reader's landing has joined its transaction, so the content
153
+ // lands with its run, not a round after). Others still waiting: nothing
154
+ // on screen changes, the fallback stays as it is. `node` here is an
155
+ // effect reporting its own status. An error boundary records an effect
156
+ // only while it is errored, and a staged error keeps STATUS_ERROR on a
157
+ // node that had it (`stageError`), so `prune`'s CONFIG_STAGED_ERROR case
158
+ // cannot reach this path (#4364).
159
+ if (collecting.size !== 0) for (let n = boundaryOf(e); n; n = n._parent) if (!(n.F.get(e)?.m & (n.i | STATUS_PENDING)) && n.F.delete(e) && n.F.size === 0) {
160
+ if (n.L) redraw(n);
161
+ if (revealHooks && n.v !== null) revealHooks.V(n.v);
162
+ }
163
+ return false;
119
164
  }
120
- Y(e) {
121
- for (let t = 0; t < this.V.length; t++) {
122
- const r = this.V[t];
123
- if ((isRevealController(r) ? r.P : r.W) !== this) continue;
124
- if (e(r) === false) return false;
165
+ for (let o = boundaryOf(e); o; o = o._parent) {
166
+ if (!(o.i & n)) continue;
167
+ if (isCollecting(o)) {
168
+ caught(o, e, r);
169
+ return o.h ? CAUGHT_REARMED : true;
125
170
  }
126
- return true;
171
+ record(o, e);
172
+ }
173
+ return false;
174
+ }
175
+
176
+ /** The nearest loading boundary that would catch `node` was re-armed this
177
+ * flush. */ function armedOver(e) {
178
+ for (let n = boundaryOf(e); n; n = n._parent) if (n.i & STATUS_PENDING && isCollecting(n)) return n.h;
179
+ }
180
+
181
+ /** The nearest boundary on `r`'s chain showing its fallback, if any
182
+ * (`hiddenHook`: `r` is behind a fallback). */ function hiddenBy(e) {
183
+ for (let n = boundaryOf(e); n; n = n._parent) if (n.L) return n;
184
+ return undefined;
185
+ }
186
+
187
+ /** `heldRunHook` (scheduler.ts): a queued run behind a fallback waits for the reveal
188
+ * (maintainer, 2026-10-02: the queue is held, the synchronous first render
189
+ * goes through). A user effect's, which would read a DOM that is not
190
+ * attached ("many depend on reading the DOM, something that can't happen
191
+ * off screen"); a render effect's update too — a Portal or a head-tag
192
+ * library writes outside the hidden subtree, and a queued render run is
193
+ * what mounts them. A user effect's first run is scheduled like every other,
194
+ * so a mount under a fallback defers it too; a render effect's first run is
195
+ * synchronous on creation and never comes here. The run's `_modified` stays
196
+ * set; the reveal re-queues it by type (`release`), and a boundary above
197
+ * still showing its fallback holds it again then. */ function heldRun(e) {
198
+ const n = hiddenBy(e);
199
+ if (n === undefined) return false;
200
+ (n.M ??= new Set).add(e);
201
+ return true;
202
+ }
203
+
204
+ /** The reveal: the runs held behind the fallback are this flush's, each in
205
+ * its own phase — render updates with the frame that attaches the content,
206
+ * user effects after it. */ function release(e) {
207
+ if (e.M === null) return;
208
+ for (const n of e.M) {
209
+ const e = n.i;
210
+ globalQueue.enqueue(e === EFFECT_TRACKED ? EFFECT_USER : e, e === EFFECT_TRACKED ? n.Z : n.W ??= GlobalQueue.q.bind(null, n));
127
211
  }
128
- O() {
129
- return this.Y(isSlotReady);
212
+ e.M = null;
213
+ }
214
+
215
+ /** Drop the readers that settled or died. Settled: a loading reader no
216
+ * longer pending; an error's source neither errored nor in flight. A reader
217
+ * that landed but is held (its value staged in a transaction) is not: the
218
+ * content reveals at the commit, with its run. The tree is: the output reads
219
+ * it and enters the transaction itself (its committed value shows until the
220
+ * landing). The statuses that keep a reader: pending — and its error, for an
221
+ * error boundary (`_type` is the status it catches) — on the reader or its
222
+ * source. A reader pending again after the source that threw answered (it
223
+ * re-asks through a memo that carried the error) has no new answer yet: the
224
+ * error stays the display (D-39: a pending is not a value; async-gen 91501
225
+ * #1831). */ function prune(e, n) {
226
+ const r = e.i | STATUS_PENDING;
227
+ for (const [o, t] of e.F) {
228
+ if (!((o.R | t.R) & REACTIVE_DISPOSED)) {
229
+ // An error staged on its source (D-41, `stageError`) is not settled:
230
+ // it publishes with the commit. A reader that threw it now (a lane
231
+ // pass, a reader of a node born staged) reached the boundary first
232
+ // (#4364).
233
+ if ((o.m | t.m) & r || r & STATUS_ERROR && t.l & CONFIG_STAGED_ERROR) continue;
234
+ if ((t !== e.K || e.h) && (t.l & (CONFIG_HELD | CONFIG_OVERRIDE)) === CONFIG_HELD) {
235
+ // Landed, held: the content is that transaction's — the output's
236
+ // pass enters it and the reveal lands with the reader's run. The
237
+ // seam, past its park decision, waits for the commit instead. (A
238
+ // lane's reader reveals with the lane, this round.) A boundary that
239
+ // has not shown content catches it instead: the fallback stays until
240
+ // the reader is committed (#3540: what a fresh boundary catches holds
241
+ // nothing outside it).
242
+ if (!n || !e.D) continue;
243
+ joinFuture(txOf(t));
244
+ }
245
+ }
246
+ e.F.delete(o);
130
247
  }
131
- /**
132
- * "Minimally ready" = this group has something visible to show under its own policy.
133
- * Used by an enclosing `together` group to decide when it can release.
134
- * - `together`: every direct slot is minimally ready.
135
- * - `sequential`: the first owned slot is minimally ready (frontier can advance).
136
- * - `natural`: any owned slot is minimally ready.
137
- */ U() {
138
- const e = untrack(this.F);
139
- if (e === "together") return this.Y(isSlotMinimallyReady);
140
- if (e === "natural") {
141
- let e = false;
142
- let t = false;
143
- this.Y(r => {
144
- e = true;
145
- if (isSlotMinimallyReady(r)) {
146
- t = true;
147
- return false;
148
- }
149
- });
150
- return !e || t;
248
+ return e.F.size;
249
+ }
250
+
251
+ /** Reveal order: a slot is ready when its content can show — it has shown
252
+ * content, or nothing under it is unready (the tree neither pending nor
253
+ * born held, no reader waited on). */ function ready(e) {
254
+ return e.D || !(e.K.m & STATUS_PENDING) && !(e.K.l & CONFIG_HELD && (e.j === null || e.j.m & STATUS_UNINITIALIZED)) && prune(e, false) === 0;
255
+ }
256
+
257
+ /** `boundarySeamHook` (scheduler.ts), end of the seam: readers that settled, died,
258
+ * or landed and committed are dropped; a fallback with none left reveals
259
+ * next round. An arm resolves: nothing collected, nothing happened. */ function boundarySeam() {
260
+ for (const e of collecting) {
261
+ const n = e.j.R;
262
+ if (n & REACTIVE_DISPOSED) {
263
+ collecting.delete(e);
264
+ continue;
265
+ }
266
+ // A parked frame's boundary (zombie) keeps its state for its revival.
267
+ if (n & REACTIVE_ZOMBIE) continue;
268
+ prune(e, false);
269
+ const r = e.j;
270
+ // A fallback staged into the very hold its readers landed in folds back
271
+ // (D-16, U-9): the content is ready when that hold commits (D-15). Not
272
+ // only at the re-arm's own seam: readers pending then land, held, a
273
+ // flush later (#4379 — the staged fallback otherwise commits with the
274
+ // hold, and a quiet refresh then caught keeps it up, F-9).
275
+ if (!e.D && r.l & CONFIG_HELD && [ ...e.F ].every(([, e]) => !(e.m & STATUS_PENDING) && e.l & CONFIG_HELD && txOf(e) === txOf(r))) {
276
+ e.F.clear();
277
+ e.D = true;
278
+ }
279
+ e.h = false;
280
+ if (e.F.size === 0) {
281
+ collecting.delete(e);
282
+ if (e.L) redraw(e);
283
+ if (revealHooks && e.v !== null) revealHooks.V(e.v);
151
284
  }
152
- // sequential: only the first owned slot matters.
153
- let t = true;
154
- this.Y(e => {
155
- t = isSlotMinimallyReady(e);
156
- return false;
157
- });
158
- return t;
159
285
  }
160
- Z(e) {
161
- if (this.V.includes(e)) return;
162
- this.V.push(e);
163
- const t = untrack(this.F);
164
- setSignal(e.I, true), setSignal(e.D, t === "sequential" ? !!untrack(this.q) : false);
165
- untrack(() => this.B());
286
+ }
287
+
288
+ /** `on` changed after the mount: fresh again if anything under it is
289
+ * pending now; collecting for the rest of the flush either way. */ function arm(e) {
290
+ if (!e.D) {
291
+ // Already re-armed and on its fallback, as the frame's work — and the
292
+ // `on` pass arms again as lane work: its verdict was provisional (a
293
+ // memo of `isPending(m2)` is a watcher until the flush parks, then the
294
+ // holder's verdict lane's — verdict.ts), and the swap it asked for is
295
+ // display-ahead after all (#3528). Re-home the swap: the output re-runs
296
+ // as the lane's, shown now beside the held frame.
297
+ if (e.L && e.P === null && e.B === null && passLane !== null) {
298
+ e.B = passLane;
299
+ // The swap the frame's pass staged (held with the frame) is void: the
300
+ // lane's pass stages it anew (`flip` lists the output in the lane,
301
+ // which voids its staging), as a watcher's provisional value is
302
+ // voided at the seam (verdict.ts `verdictSeam`).
303
+ flip(e);
304
+ redraw(e);
305
+ }
306
+ return;
166
307
  }
167
- $(e) {
168
- const t = this.V.indexOf(e);
169
- if (t >= 0) this.V.splice(t, 1);
170
- untrack(() => this.B());
308
+ e.h = true;
309
+ // The swap is the change's work: the output joins it (D-13, #3540).
310
+ e.j.l &= ~CONFIG_FRAME;
311
+ // A verdict read in `on` (`isPending`, or a memo of one — #3528's
312
+ // `createMemo(() => isPending(m2))`: the mark carries, core.ts `read`)
313
+ // whose frame has no transaction yet: the swap is still display-ahead — the frame gets one now (it lands
314
+ // at this very seam if nothing holds it), and the swap is its verdict
315
+ // lane's work, shown ahead of the frame's own runs (#3528).
316
+ let n = passLane;
317
+ if (n === null && globalQueue.Y && getOwner().l & CONFIG_VERDICT) {
318
+ joinFuture(null);
319
+ n = GlobalQueue.J(flushTransaction);
171
320
  }
172
- B(e, t) {
173
- if (this.X) return;
174
- this.X = true;
175
- const r = this.J;
176
- const n = this.K;
177
- try {
178
- const r = e ?? read(this.I), n = untrack(this.F), s = n === "sequential" && !!untrack(this.q), i = t ?? s;
179
- if (r) {
180
- // Held by an outer group. Propagate the hold (and whatever collapsed policy
181
- // the outer asked for) down the whole subtree. Inner order is ignored while
182
- // held; it resumes once the outer releases us.
183
- this.Y(e => setSlotState(e, this, true, i));
184
- } else if (n === "natural") {
185
- // Each child reveals based on its own readiness. A nested controller slot
186
- // is released to run its own order locally — we bypass setSlotState for it
187
- // so the parent backpointer survives for upward readiness notifications.
188
- this.Y(e => {
189
- if (isRevealController(e)) {
190
- setSignal(e.D, false);
191
- setSignal(e.I, false);
192
- e.B(false, false);
193
- } else {
194
- setSlotState(e, this, !isSlotReady(e), false);
195
- }
196
- });
197
- } else if (n === "together") {
198
- // Release when every direct slot is minimally ready (has something to show
199
- // under its own order). A fully-ready inner together is minimally ready;
200
- // sequential's first slot being ready is minimally ready; natural having any
201
- // ready child is minimally ready. This lets `together` guarantee a single
202
- // cohesive reveal without waiting for every grandchild.
203
- const e = this.Y(isSlotMinimallyReady);
204
- this.Y(t => setSlotState(t, this, !e, false));
205
- } else {
206
- let e = false;
207
- this.Y(t => {
208
- if (e) return setSlotState(t, this, true, s);
209
- if (isSlotReady(t)) return setSlotState(t, this, false, false);
210
- e = true;
211
- // Frontier slot. For a leaf, holding `_disabled=true` is what keeps its
212
- // fallback visible. For a composite, we instead release it so it runs
213
- // its own order locally — its leaves will each show their own fallback
214
- // until their data lands. Outer still waits on full readiness before
215
- // advancing past this slot, and we bypass setSlotState so the parent
216
- // backpointer survives for upward readiness notifications.
217
- if (isRevealController(t)) {
218
- setSignal(t.D, false);
219
- setSignal(t.I, false);
220
- t.B(false, false);
221
- } else {
222
- setSlotState(t, this, true, false);
223
- }
224
- });
225
- }
226
- } finally {
227
- this.J = this.O();
228
- this.K = this.U();
229
- this.X = false;
321
+ e.B = n;
322
+ collecting.add(e);
323
+ const r = e.F.size;
324
+ heldUnder(e.H, flushTransaction, n => record(e, n));
325
+ for (const [n] of e.F) {
326
+ if (e.F.size !== r || n.m & STATUS_PENDING && !(n.R & REACTIVE_DISPOSED)) {
327
+ // What it collects was forwarded through it to the boundaries above
328
+ // too (`catchStatus` records a reader in every boundary on the way).
329
+ // It is this boundary's alone now (B5, the nearest catcher): an outer
330
+ // re-armed by the same tick does not wait for it (D-14, U-4).
331
+ for (let n = e._parent; n !== null; n = n._parent)
332
+ // An outer error boundary keys its readers too, but waits on their
333
+ // sources: a re-armed Loading taking the pending does not settle it.
334
+ if (n.i & STATUS_PENDING) for (const [r] of e.F) n.F.delete(r);
335
+ flip(e);
336
+ redraw(e);
337
+ return;
230
338
  }
231
- if (this.P && (r !== this.J || n !== this.K)) this.P.B();
232
339
  }
233
340
  }
234
341
 
235
- class CollectionQueue extends Queue {
236
- ee;
237
- v=new Set;
238
- te;
239
- N=true;
240
- I=signal(false, {
241
- ownedWrite: true,
242
- H: true
243
- });
244
- _;
245
- D=signal(false, {
246
- ownedWrite: true,
247
- H: true
342
+ /** The error boundary's `reset`: re-run what threw (a comparator throw
343
+ * names a signal — nothing to re-run). */ function reset(e) {
344
+ // A snapshot: a source failing again is caught — and re-added — mid-walk.
345
+ for (const n of new Set(e.F.values())) if (n.X !== undefined && !(n.R & REACTIVE_DISPOSED)) recompute(n);
346
+ schedule();
347
+ }
348
+
349
+ /**
350
+ * Dev CHECK: the `on` dependencies evaluated to a function — `<Loading
351
+ * on={route}>` or `on={() => route()}` passes the accessor itself, a stable
352
+ * value that is never called, so nothing is read and the boundary never
353
+ * re-arms. Checked on the first run only: the shape is static.
354
+ */ function reportOnAccessor(e) {
355
+ reportDiagnostic(emitDiagnostic({
356
+ code: "LOADING_ON_ACCESSOR",
357
+ kind: "async",
358
+ severity: "warn",
359
+ message: "[LOADING_ON_ACCESSOR] A Loading boundary's `on` received a function. `on` takes the " + "dependency's value — `on={route()}`, or `on={[query(), page()]}` for several — and an " + "accessor passed instead is never called, so nothing is read and the boundary never re-arms."
360
+ }, e.H));
361
+ }
362
+
363
+ function createBoundary(e, n, r, o) {
364
+ const t = createOwner();
365
+ const l = {
366
+ i: e,
367
+ _parent: boundaryOf(t) ?? null,
368
+ H: t,
369
+ K: null,
370
+ j: null,
371
+ F: new Map,
372
+ D: false,
373
+ h: false,
374
+ B: null,
375
+ P: null,
376
+ L: false,
377
+ M: null,
378
+ v: null,
379
+ $: false,
380
+ ee: false,
381
+ U: e === STATUS_ERROR ? signal(undefined, ERROR_SIGNAL) : null,
382
+ ne: r,
383
+ re: null
384
+ };
385
+ const a = e === STATUS_PENDING ? t.A[REVEAL] : null;
386
+ if (getOwner()) cleanup(() => {
387
+ collecting.delete(l);
388
+ if (revealHooks && a) revealHooks.oe(a, l);
248
389
  });
249
- W;
250
- L=false;
251
- re;
252
- ne=ON_INIT;
253
- constructor(e) {
254
- super();
255
- this.ee = e;
256
- }
257
- run(e) {
258
- if (!e || read(this.I) && (!_revealUsed || read(this.D))) return;
259
- return super.run(e);
390
+ // The `on` dependencies live OUTSIDE the boundary, as the condition of a
391
+ // `<Show>` wrapping it would: created before the owner's context names it.
392
+ // A source of it going pending re-runs it (CONFIG_REDERIVE) rather than
393
+ // marking it: the pass catches the NotReady — it is a notification, it
394
+ // does not suspend the parent — and re-arms.
395
+ if (o) {
396
+ let e = false;
397
+ runWithOwner(t, () => computed(() => {
398
+ try {
399
+ const n = o();
400
+ if (false && !e && typeof n === "function") ;
401
+ } catch (e) {
402
+ if (!(e instanceof NotReadyError)) {
403
+ if (!globalQueue.notify(getOwner(), STATUS_ERROR, STATUS_ERROR, e)) {
404
+ if (false) ; else haltReactivity(e);
405
+ throw e;
406
+ }
407
+ return;
408
+ }
409
+ }
410
+ if (e) arm(l); else e = true;
411
+ })).l |= CONFIG_REDERIVE;
260
412
  }
261
- notify(e, t, r, n) {
262
- if (!(t & this.ee)) return super.notify(e, t, r, n);
263
- if (this.L && this.re) {
264
- const e = untrack(() => {
265
- try {
266
- return this.re();
267
- } catch {
268
- return ON_INIT;
413
+ const u = {
414
+ ...t.A,
415
+ [BOUNDARY]: l
416
+ };
417
+ if (revealHooks !== null) u[REVEAL] = null;
418
+ t.A = u;
419
+ const i = runWithOwner(t, () => {
420
+ const e = false ? computed(n, {
421
+ name: "children"
422
+ }) : computed(n);
423
+ return false ? computed(() => flatten(read(e)), {
424
+ name: "boundary"
425
+ }) : computed(() => flatten(read(e)));
426
+ });
427
+ l.K = i;
428
+ // A slot of the reveal order in its context (reveal.ts): gated until the
429
+ // controller releases it — before the output's first pass.
430
+ if (revealHooks && a) revealHooks.te(a, l);
431
+ const s = computed(() => {
432
+ // Structure, not a derivation of its content (CONFIG_FRAME, #4283): it
433
+ // reads a hold it is not part of as the screen, as the render effect
434
+ // flattening the same children without a boundary would. Not while a
435
+ // re-arm's swap is the change's work (`arm`), until that swap commits
436
+ // (D-13, #3540).
437
+ const n = getOwner();
438
+ if (!l.h && !(n.l & CONFIG_HELD)) n.l |= CONFIG_FRAME;
439
+ // Reveal order (reveal.ts): a gated slot shows its fallback — or, in a
440
+ // collapsed tail, nothing — whatever its content's state; the
441
+ // controller is told the pass ran (its readiness may have changed) and
442
+ // releases it in turn.
443
+ if (l.$) {
444
+ // Linked without being read: the tree settling (or changing)
445
+ // re-derives this pass, which is how the controller hears of it.
446
+ // It may release this very slot (the frontier whose content just
447
+ // settled): the pass goes on to show it — the redraw it asks for is
448
+ // refused mid-pass and not needed.
449
+ link(i, n);
450
+ if (revealHooks && l.v !== null) revealHooks.V(l.v);
451
+ if (l.$) {
452
+ if (l.ee) {
453
+ l.L = true;
454
+ return undefined;
269
455
  }
270
- });
271
- if (e !== this.ne) {
272
- this.ne = e;
273
- this.L = false;
274
- this.v.clear();
456
+ return fallback(l);
275
457
  }
276
458
  }
277
- // Routing is dimension-independent: each boundary consumes only its own
278
- // status dimension from the mask (`type &= ~collectionType` below) and
279
- // forwards the remainder up the queue chain. An error inside a `Loading`
280
- // needs no special rule — the ERROR dimension survives consumption here and
281
- // reaches the `Errored` that catches it natively, and `flags & collectionType`
282
- // keeps this boundary from collecting a node that isn't actually pending.
283
- // Symmetrically, a pending inside an `Errored` forwards on the PENDING
284
- // dimension, while a status already caught by an inner boundary arrives with
285
- // its dimension consumed from the mask and is correctly not re-routed
286
- // (the Loading > Errored > content composition escape, #2856).
287
- if (this.ee & STATUS_PENDING && this.L) return super.notify(e, t, r, n);
288
- if (r & this.ee) {
289
- this.N = true;
290
- const t = n?.source || e.o?._?.source;
291
- if (t) {
292
- const e = this.v.size === 0;
293
- this.v.add(t);
294
- if (e) setSignal(this.I, true);
295
- if (this.ee & STATUS_ERROR) {
296
- setSignal(this._, unwrapStatusError(t.o?._));
459
+ // A tree carrying a status is not read: an errored memo's reactive
460
+ // re-read retries it (`read`), and neither a fallback re-rendering nor
461
+ // passing the error by may re-run the content; a pending one read as
462
+ // lane work would serve its committed value (A31) — the swap a
463
+ // display-ahead `on` causes is the lane's, and must still be the
464
+ // fallback. Linked, so its recovery re-derives this pass; read again
465
+ // once clean.
466
+ const r = i.m & (STATUS_PENDING | STATUS_ERROR);
467
+ if (r !== 0) {
468
+ link(i, n);
469
+ if (r & e) {
470
+ if (isCollecting(l)) {
471
+ caught(l, i, undefined);
472
+ return fallback(l);
297
473
  }
474
+ // Forwarded: a loading boundary showing content keeps it and
475
+ // holds the frame like any reader (recorded for its `on`).
476
+ record(l, i);
298
477
  }
478
+ // A pending passes an error boundary by (A6); an error, a loading
479
+ // one; a loading boundary showing content forwards its pending.
480
+ throw i.T.U;
299
481
  }
300
- t &= ~this.ee;
301
- return t ? super.notify(e, t, r, n) : true;
302
- }
303
- se() {
304
- for (const e of this.v) {
305
- // A source with a live affects() mark holds display state for the
306
- // mark's lifetime (the visual channel): the marked node carries no
307
- // status of its own, so the count is the liveness test. The release
308
- // sweep (finalizePureQueue after mark release) re-runs this check.
309
- if (e.ie & REACTIVE_DISPOSED || !e.o?.t && !(e.S & this.ee) && !(this.ee & STATUS_ERROR && e.S & STATUS_PENDING)) this.v.delete(e);
482
+ if (isCollecting(l)) {
483
+ // A29's boundary exemption (#3540): a boundary MOUNTED over a held
484
+ // value (its first pass; a first pass under it that read a hold was
485
+ // collected, `joinPass` — the tree's own, or a render effect's that
486
+ // binds the content) shows its fallback now and the content at the
487
+ // commit — entering the transaction would make the output itself
488
+ // born held, and nothing would show until the commit. The seam
489
+ // keeps a held reader until it is committed, then re-derives this
490
+ // pass. A boundary with a committed value reads a held tree and
491
+ // enters: the outside sees its committed value until the landing,
492
+ // which reveals the content — a fallback staged earlier is replaced
493
+ // ahead of the commit and never shown.
494
+ if (l.F.size !== 0 && n.m & STATUS_UNINITIALIZED && !(n.l & CONFIG_HELD)) return fallback(l);
495
+ // Readers under it still unready: the fallback, the tree untouched.
496
+ // The seam re-derives this pass when they settle.
497
+ if (prune(l, true) !== 0) return fallback(l);
310
498
  }
311
- if (!this.v.size) {
312
- if (this.ee & STATUS_PENDING && this.N && !this.L && this.te) {
313
- this.N = !!(this.te.S & this.ee);
314
- } else {
315
- this.N = false;
316
- }
317
- if (!this.N) {
318
- setSignal(this.I, false);
319
- if (this.re) {
320
- try {
321
- this.ne = untrack(() => this.re());
322
- } catch {
323
- /* value not yet committed — _prevOn stays stale, next notify will reset */}
499
+ let o;
500
+ try {
501
+ o = read(i);
502
+ } catch (n) {
503
+ if (n instanceof NotReadyError ? e === STATUS_PENDING : e === STATUS_ERROR) {
504
+ if (isCollecting(l)) {
505
+ caught(l, i, n);
506
+ return fallback(l);
324
507
  }
508
+ // Forwarded: a loading boundary showing content keeps it and
509
+ // holds the frame like any reader (recorded for its `on`).
510
+ record(l, i);
325
511
  }
512
+ // A pending passes an error boundary by (A6).
513
+ throw n;
326
514
  }
327
- if (_revealUsed) this.W?.B();
328
- }
329
- }
330
-
331
- function createCollectionBoundary(e, t, r, n) {
332
- const s = createOwner();
333
- if (_revealUsed) setContext(RevealControllerContext, null, s);
334
- const i = new CollectionQueue(e);
335
- if (e === STATUS_ERROR) i._ = signal(undefined, {
336
- ownedWrite: true,
337
- H: true
338
- });
339
- if (n) i.re = n;
340
- const o = i.te = createBoundChildren(s, t, i, e);
341
- // Prime source tracking so reveal registration sees pending sources.
342
- untrack(() => {
343
- let t = false;
344
- try {
345
- read(o);
346
- } catch (e) {
347
- if (e instanceof NotReadyError) t = true; else throw e;
348
- }
349
- i.N = t || !!(o.S & e) || o.o?._ instanceof NotReadyError;
350
- });
351
- const l = _revealUsed && e === STATUS_PENDING ? getContext(RevealControllerContext) : null;
352
- if (l) {
353
- i.W = l;
354
- l.Z(i);
355
- cleanup(() => l.$(i));
356
- }
357
- return accessor(computed(() => {
358
- if (!read(i.I)) {
359
- const e = read(o);
360
- if (!untrack(() => read(i.I))) return i.L = true, e;
515
+ // Readers under it still unready (caught before, or while the read
516
+ // ran the content at a mount): the fallback.
517
+ if (isCollecting(l) && l.F.size !== 0) return fallback(l);
518
+ l.D = true;
519
+ l.L = false;
520
+ // The fallback dies when the content that replaces it is on screen —
521
+ // with this flush's commits when the pass is the frame's, at the
522
+ // landing when the frame parks (the run is held with it, as the old
523
+ // children of a held pass are) — not now: a held swap keeps the
524
+ // fallback on screen, live, until it commits. A pass back to the
525
+ // fallback before then finds it still built and keeps it. A fallback
526
+ // the screen does not show (a swap staged with a held frame, replaced
527
+ // before it commits) dies now: what it reads holds nothing (S-22 row
528
+ // 3′, #4268).
529
+ const t = l.re;
530
+ const drop = () => {
531
+ if (!l.L && l.re === t) {
532
+ // A fallback that threw (a rethrow the boundary does not
533
+ // handle) is what an enclosing boundary waits on: it died, and
534
+ // the seam of another round drops it there (#4270).
535
+ if (t.m & STATUS_ERROR) schedule();
536
+ dispose(t);
537
+ l.re = null;
538
+ }
539
+ };
540
+ if (t !== null) l.j.le !== t.le ? drop() : globalQueue.enqueue(EFFECT_RENDER, drop);
541
+ release(l);
542
+ // Shown: the slot leaves its reveal order (a later re-arm gates nobody
543
+ // else; the frontier moves on).
544
+ if (revealHooks && l.v !== null) {
545
+ const e = l.v;
546
+ l.v = null;
547
+ revealHooks.V(e);
361
548
  }
362
- // Collapsed reveal slots suppress their own output entirely; the
363
- // renderer treats the hole as empty, so the cast never leaks to users
364
- // outside a `createRevealOrder` scope.
365
- if (_revealUsed && read(i.D)) return undefined;
366
- return r(i);
549
+ return o;
367
550
  },
368
- // Boundary structure, not a user source: its value is fallback-or-content and
369
- // legitimately swaps mid-hydration (reveal/resume), so it must never be frozen
370
- // by snapshot capture. The tree no longer carries foreign status flags, so
371
- // capture can't rely on PENDING to skip this node the way it used to.
551
+ // Boundary structure, not a user source: its value is fallback-or-content
552
+ // and legitimately swaps mid-hydration (resume), so it must never be
553
+ // frozen by snapshot capture.
372
554
  {
373
- H: true
555
+ ae: true
556
+ });
557
+ s.l |= CONFIG_REDERIVE;
558
+ // The tree is structure too (the output's pass marks the output).
559
+ i.l |= CONFIG_FRAME;
560
+ l.j = s;
561
+ return accessor(s);
562
+ }
563
+
564
+ function fallback(e) {
565
+ // (The output's pass.)
566
+ const n = getOwner();
567
+ // The swap a display-ahead re-arm asked for: this pass is the lane's — a
568
+ // lane read by construction (REACTIVE_LANE_READ: `recompute`'s tail keeps
569
+ // the pass in the lane).
570
+ if (e.P !== null) {
571
+ setPassLane(e.P);
572
+ n.R |= REACTIVE_LANE_READ;
573
+ e.P = null;
574
+ }
575
+ e.L = true;
576
+ // Built in its own scope, like `Show`'s function child, once per show
577
+ // (D-39: the fallback is the display until a new answer replaces the
578
+ // error): a re-derive of the output while it shows — a second reader
579
+ // caught, the same error propagated again through a sibling's re-run, a
580
+ // retry pending after `reset()`, a later error — keeps the built
581
+ // fallback, its DOM and focus with it (#3991). A later error reaches it
582
+ // through the error accessor; only a fallback reading the accessor at its
583
+ // top level is rebuilt by it. Created beside the output — in its owner,
584
+ // outside the boundary, as the output is: its errors and runs are the
585
+ // enclosing boundary's — with the output's id, so what it builds gets the
586
+ // ids a build under the output would; disposed once content is on screen.
587
+ if (e.re === null) e.re = runWithOwner(n._parent, () => computed(() => e.ne(e), {
588
+ id: n.id
374
589
  }));
590
+ // The swap is structure (#3540): what the fallback reads ahead — a
591
+ // verdict (`latest()`), a lane — is the fallback's display-ahead, not the
592
+ // swap's: the output's pass stays where it was (F-6 stays inside the
593
+ // fallback, #4337).
594
+ const r = passLane;
595
+ const o = n.R;
596
+ const t = read(e.re);
597
+ setPassLane(r);
598
+ n.R ^= (n.R ^ o) & (REACTIVE_PROBED | REACTIVE_LANE_READ);
599
+ return t;
375
600
  }
376
601
 
602
+ const ERROR_SIGNAL = {
603
+ ownedWrite: true,
604
+ ae: true
605
+ };
606
+
377
607
  /**
378
608
  * Lower-level primitive that backs the `<Loading>` flow control. Catches
379
609
  * pending async reads inside `fn` and renders `fallback` until they settle.
@@ -383,8 +613,27 @@ function createCollectionBoundary(e, t, r, n) {
383
613
  *
384
614
  * @param fn the tracked subtree
385
615
  * @param fallback the fallback shown while async reads in `fn` are unresolved
386
- * @param options `on` — accessor whose value scopes the boundary; when set,
387
- * transitions caused by writes to other reactive sources are *not* caught
616
+ * @param options `on` — a dependency list: a tracked function whose reads
617
+ * re-arm the boundary. Its return value is irrelevant (never compared);
618
+ * what matters is what it reads. Without `on`, a boundary that has shown
619
+ * content keeps it through a refetch (the pending holds with the
620
+ * transaction). With `on`, a write to anything it reads makes the boundary
621
+ * fresh again: it stops waiting on its current content, and if something
622
+ * under it is pending it shows `fallback` until the new content is ready;
623
+ * if nothing is pending, the notification is a no-op. The fallback lands
624
+ * with the same frame as the change that caused it — now, when nothing
625
+ * else holds that frame; together with the rest of the new page during a
626
+ * held navigation, not before it. If the same data is also read outside
627
+ * the boundary, the frame waits on it and the fallback can never be seen
628
+ * (DEV warns `LOADING_ON_OUTSIDE_HOLD`); the fix is structural — move the
629
+ * outside read under the boundary so one hold owns the data. A frame held
630
+ * past the content's landing by something else (the write's action, other
631
+ * pending data) also shows no fallback; that is a race the fallback may
632
+ * lose, a legitimate outcome, and not reported. A display-ahead read in
633
+ * `on` (`latest()`) shows the fallback now, beside the held frame; that
634
+ * is a capability, not the recommended shape. Optimistic writes and a
635
+ * source going pending notify like any other. The children are not
636
+ * re-created — they stay alive behind the fallback.
388
637
  *
389
638
  * @example
390
639
  * ```tsx
@@ -396,8 +645,8 @@ function createCollectionBoundary(e, t, r, n) {
396
645
  * ) as unknown as JSX.Element;
397
646
  * }
398
647
  * ```
399
- */ function createLoadingBoundary(e, t, r) {
400
- return createCollectionBoundary(STATUS_PENDING, e, () => t(), r?.on);
648
+ */ function createLoadingBoundary(e, n, r) {
649
+ return createBoundary(STATUS_PENDING, e, () => n(), r?.on);
401
650
  }
402
651
 
403
652
  /**
@@ -422,151 +671,12 @@ function createCollectionBoundary(e, t, r, n) {
422
671
  * ) as unknown as JSX.Element;
423
672
  * }
424
673
  * ```
425
- */ function createErrorBoundary(e, t) {
426
- return createCollectionBoundary(STATUS_ERROR, e, e => t(accessor(e._), () => {
427
- for (const t of e.v) recompute(t);
428
- schedule();
429
- }));
430
- }
431
-
432
- /**
433
- * Coordinate the reveal timing of sibling loading boundaries.
434
- *
435
- * Accepts reactive accessors:
436
- * - `order`: `"sequential"` (default) | `"together"` | `"natural"`.
437
- * - `"sequential"` — classic frontier reveal: siblings reveal in registration order
438
- * as each resolves; later siblings stay hidden until earlier ones complete.
439
- * - `"together"` — every direct slot stays on its fallback until the whole group
440
- * is "minimally ready" (each direct slot has produced its own first visible
441
- * content under its own order), then the whole group releases at once.
442
- * - `"natural"` — children reveal independently (as each resolves). At the top
443
- * level this is a no-op compared to not using `createRevealOrder`; the mode
444
- * exists for nesting, where the group registers as a single composite slot to
445
- * any enclosing `createRevealOrder`.
446
- * - `collapsed`: only meaningful when `order === "sequential"`. When set, tail siblings
447
- * past the frontier suppress their own fallback output. Ignored under `"together"`
448
- * and `"natural"` — those orders have no frontier.
449
- *
450
- * Nested `createRevealOrder` groups compose: the inner controller registers as a
451
- * single slot in the outer controller and is held on its fallbacks until the outer
452
- * releases that slot. Once released, the inner controller runs its own order locally
453
- * over anything still pending. There is no opt-out from an outer hold.
454
- *
455
- * "Minimally ready" is what an order considers its first visible content:
456
- * - `sequential` — frontier-0 is minimally ready (leaf: on resolve; nested: via its
457
- * own minimal signal).
458
- * - `together` — every direct slot is minimally ready.
459
- * - `natural` — any direct slot has visible content (leaves on resolve; nested
460
- * composites via their own minimal signal).
461
- *
462
- * @example
463
- * ```ts
464
- * // Primitive form of `<Reveal>` — coordinate sibling loading boundaries
465
- * // programmatically. App code uses the JSX `<Reveal>` component instead.
466
- * // Both options are accessors so they can react to state changes.
467
- * createRevealOrder(
468
- * () => renderSiblings(),
469
- * { order: () => mode(), collapsed: () => true }
470
- * );
471
- * ```
472
- */ function createRevealOrder(e, t) {
473
- _revealUsed = true;
474
- const r = createOwner();
475
- const n = getContext(RevealControllerContext);
476
- const s = t?.order || SEQUENTIAL_ACCESSOR, i = t?.collapsed || FALSE_ACCESSOR;
477
- const o = new RevealController(s, i);
478
- setContext(RevealControllerContext, o, r);
479
- return runWithOwner(r, () => {
480
- const t = e();
481
- computed(() => {
482
- s();
483
- i();
484
- o.B();
485
- });
486
- if (n) {
487
- o.P = n;
488
- n.Z(o);
489
- cleanup(() => n.$(o));
490
- }
491
- return t;
492
- });
493
- }
494
-
495
- /**
496
- * Resolves a children value to its renderable form: unwraps zero-arg functions
497
- * (accessors), recursively flattens arrays, and optionally skips
498
- * non-rendering values (`null`, `undefined`, `true`, `false`, `""`).
499
- *
500
- * Used internally by flow components and by the renderer to walk a children
501
- * tree. App code rarely needs this directly — see `children()` in `solid-js`
502
- * for the user-facing helper that memoizes the result.
503
- *
504
- * @param children value or array of values to flatten
505
- * @param options
506
- * - `skipNonRendered` — drop values that won't render
507
- * - `doNotUnwrap` — leave function children as-is (caller will resolve)
508
- *
509
- * @example
510
- * ```ts
511
- * // Custom renderer walking a children tree manually. Most authors should
512
- * // use `children()` from solid-js, which memoizes the resolved value.
513
- * function renderChildren(value: unknown): unknown {
514
- * return flatten(value, { skipNonRendered: true });
515
- * }
516
- * ```
517
- */ function flatten(e, t) {
518
- if (typeof e === "function" && !e.length) {
519
- if (t?.doNotUnwrap) return e;
520
- do {
521
- e = e();
522
- } while (typeof e === "function" && !e.length);
523
- }
524
- if (t?.skipNonRendered && (e == null || e === true || e === false || e === "")) return;
525
- if (Array.isArray(e)) {
526
- let r = [];
527
- if (flattenArray(e, r, t)) {
528
- return () => {
529
- let e = [];
530
- flattenArray(r, e, {
531
- ...t,
532
- doNotUnwrap: false
533
- });
534
- return e;
535
- };
536
- }
537
- return r;
538
- }
539
- return e;
674
+ */ function createErrorBoundary(e, n) {
675
+ return createBoundary(STATUS_ERROR, e, e => n(accessor(e.U), () => reset(e)));
540
676
  }
541
677
 
542
- function flattenArray(e, t = [], r) {
543
- let n = null;
544
- let s = false;
545
- for (let i = 0; i < e.length; i++) {
546
- try {
547
- let n = e[i];
548
- if (typeof n === "function" && !n.length) {
549
- if (r?.doNotUnwrap) {
550
- t.push(n);
551
- s = true;
552
- continue;
553
- }
554
- do {
555
- n = n();
556
- } while (typeof n === "function" && !n.length);
557
- }
558
- if (Array.isArray(n)) {
559
- s = flattenArray(n, t, r);
560
- } else if (r?.skipNonRendered && (n == null || n === true || n === false || n === "")) {
561
- // skip
562
- } else t.push(n);
563
- } catch (e) {
564
- if (!(e instanceof NotReadyError)) throw e;
565
- n = e;
566
- }
567
- }
568
- if (n) throw n;
569
- return s;
570
- }
678
+ // Installed at module evaluation — present exactly when something imports a
679
+ // boundary. An app without one pays the three null checks and nothing else.
680
+ installBoundaryHooks(catchStatus, (e, n) => !n && (e.m & STATUS_UNINITIALIZED ? catchStatus(e, STATUS_PENDING) : armedOver(e)), hiddenBy, boundarySeam, heldRun);
571
681
 
572
- export { CollectionQueue, RevealController, createErrorBoundary, createLoadingBoundary, createRevealOrder, flatten };
682
+ export { REVEAL, createErrorBoundary, createLoadingBoundary, prune, ready, redraw, revealHooks, setRevealHooks };