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

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 (89) hide show
  1. package/dist/dev-shared.js +785 -367
  2. package/dist/dev.attribution.js +75 -52
  3. package/dist/dev.js +1120 -567
  4. package/dist/observe/affects-marks.js +86 -0
  5. package/dist/observe/affects.js +22 -73
  6. package/dist/observe/boundaries.js +191 -189
  7. package/dist/observe/core/async.js +175 -131
  8. package/dist/observe/core/attribution-queries.js +1 -1
  9. package/dist/observe/core/attribution.js +176 -159
  10. package/dist/observe/core/constants.js +17 -3
  11. package/dist/observe/core/context.js +3 -3
  12. package/dist/observe/core/core.js +503 -363
  13. package/dist/observe/core/dev.js +66 -38
  14. package/dist/observe/core/effect.js +48 -48
  15. package/dist/observe/core/error.js +5 -3
  16. package/dist/observe/core/external.js +5 -6
  17. package/dist/observe/core/graph.js +57 -43
  18. package/dist/observe/core/heap.js +33 -33
  19. package/dist/observe/core/identities.js +62 -0
  20. package/dist/observe/core/lanes.js +226 -259
  21. package/dist/observe/core/owner.js +62 -50
  22. package/dist/observe/core/scheduler.js +319 -171
  23. package/dist/observe/core/truth.js +45 -0
  24. package/dist/observe/core/verdict.js +88 -50
  25. package/dist/observe/deferred.js +184 -0
  26. package/dist/observe/index.js +7 -1
  27. package/dist/observe/map.js +345 -260
  28. package/dist/observe/reveal.js +76 -76
  29. package/dist/observe/signals.js +41 -30
  30. package/dist/observe/store/affects.js +6 -6
  31. package/dist/observe/store/edges.js +39 -0
  32. package/dist/observe/store/optimistic.js +41 -36
  33. package/dist/observe/store/projection.js +68 -55
  34. package/dist/observe/store/store.js +157 -104
  35. package/dist/prod/affects-marks.js +86 -0
  36. package/dist/prod/affects.js +23 -72
  37. package/dist/prod/boundaries.js +155 -153
  38. package/dist/prod/core/async.js +171 -127
  39. package/dist/prod/core/constants.js +17 -3
  40. package/dist/prod/core/context.js +3 -3
  41. package/dist/prod/core/core.js +478 -342
  42. package/dist/prod/core/effect.js +68 -68
  43. package/dist/prod/core/error.js +5 -3
  44. package/dist/prod/core/external.js +5 -6
  45. package/dist/prod/core/graph.js +57 -43
  46. package/dist/prod/core/heap.js +38 -38
  47. package/dist/prod/core/identities.js +62 -0
  48. package/dist/prod/core/lanes.js +217 -250
  49. package/dist/prod/core/owner.js +62 -51
  50. package/dist/prod/core/scheduler.js +310 -162
  51. package/dist/prod/core/truth.js +45 -0
  52. package/dist/prod/core/verdict.js +87 -49
  53. package/dist/prod/deferred.js +182 -0
  54. package/dist/prod/index.js +7 -1
  55. package/dist/prod/map.js +360 -275
  56. package/dist/prod/reveal.js +76 -76
  57. package/dist/prod/signals.js +41 -30
  58. package/dist/prod/store/affects.js +6 -6
  59. package/dist/prod/store/edges.js +39 -0
  60. package/dist/prod/store/optimistic.js +37 -33
  61. package/dist/prod/store/projection.js +25 -19
  62. package/dist/prod/store/store.js +142 -96
  63. package/dist/types/affects-marks.d.ts +14 -0
  64. package/dist/types/boundaries.d.ts +1 -1
  65. package/dist/types/core/async.d.ts +10 -0
  66. package/dist/types/core/constants.d.ts +13 -1
  67. package/dist/types/core/core.d.ts +44 -2
  68. package/dist/types/core/dev.d.ts +43 -13
  69. package/dist/types/core/graph.d.ts +11 -0
  70. package/dist/types/core/identities.d.ts +17 -0
  71. package/dist/types/core/index.d.ts +2 -1
  72. package/dist/types/core/lanes.d.ts +2 -14
  73. package/dist/types/core/owner.d.ts +5 -3
  74. package/dist/types/core/scheduler.d.ts +47 -28
  75. package/dist/types/core/truth.d.ts +16 -0
  76. package/dist/types/core/types.d.ts +21 -0
  77. package/dist/types/core/verdict.d.ts +32 -0
  78. package/dist/types/deferred.d.ts +28 -0
  79. package/dist/types/index.d.ts +6 -0
  80. package/dist/types/map.d.ts +2 -2
  81. package/dist/types/signals.d.ts +28 -7
  82. package/dist/types/store/edges.d.ts +9 -0
  83. package/dist/types/store/index.d.ts +10 -3
  84. package/dist/types/store/optimistic.d.ts +3 -2
  85. package/dist/types/store/projection.d.ts +4 -3
  86. package/dist/types/store/store.d.ts +1 -1
  87. package/dist/types/store/target.d.ts +16 -8
  88. package/dist/types/store/types.d.ts +14 -0
  89. package/package.json +1 -1
package/dist/dev.js CHANGED
@@ -1,167 +1,228 @@
1
1
  import {
2
2
  C as CONFIG_GUESS,
3
- s as schedule,
4
3
  N as NOT_PENDING,
5
- a as CONFIG_OVERRIDE,
4
+ a as NotReadyError,
5
+ b as CONFIG_AUTHORITATIVE,
6
+ e as enqueueSub,
7
+ c as CONFIG_OVERRIDE,
8
+ s as schedule,
6
9
  q as question,
7
10
  n as nextQuestion,
11
+ i as installLaneHooks,
8
12
  G as GlobalQueue,
9
- R as REACTIVE_DISPOSED,
10
- b as CONFIG_VERDICT,
11
- c as REACTIVE_PROBE_UNANSWERED,
12
- d as blocked,
13
- e as REACTIVE_LANE_DIRTY,
14
- f as enqueueSub,
15
13
  r as resolveTx,
16
- g as CONFIG_AUTHORITATIVE,
17
- t as txOf,
18
- h as tracking,
19
- i as CONFIG_CHILDREN_FORBIDDEN,
20
- p as passLane,
21
- S as STATUS_PENDING,
22
- E as EFFECT_RENDER,
23
- j as sameLane,
14
+ d as newTransaction,
15
+ o as ownFlights,
16
+ R as REACTIVE_DISPOSED,
17
+ f as CONFIG_VERDICT,
18
+ g as REACTIVE_PROBE_UNANSWERED,
19
+ h as blocked,
20
+ j as REACTIVE_LANE_DIRTY,
24
21
  k as CONFIG_HELD,
25
- l as flushTransaction,
26
- m as STATUS_UNINITIALIZED,
27
- o as NotReadyError,
28
- u as REACTIVE_SCREEN_READ,
29
- v as REACTIVE_FRAME_READ,
30
- w as REACTIVE_LANE_READ,
31
- x as ext,
32
- y as REACTIVE_RECOMPUTING_DEPS,
33
- z as queuePendingNode,
34
- A as list,
35
- B as clock,
36
- D as insertSubs,
37
- F as laneDirty,
38
- H as merge$1,
39
- I as newTransaction,
40
- J as reruns,
41
- K as CONFIG_STAGED,
42
- L as releaseQueues,
43
- M as ownFlights,
44
- O as setPassLane,
45
- P as REACTIVE_JOINED,
46
- Q as REACTIVE_STAGED_READ,
47
- T as stagedRead,
48
- U as CONFIG_SLOT_NODE,
49
- V as attrHooks,
22
+ t as txOf,
23
+ l as clock,
24
+ m as insertSubs,
25
+ p as laneDirty,
26
+ u as REACTIVE_LANE_READ,
27
+ v as ext,
28
+ S as STATUS_UNINITIALIZED,
29
+ w as REACTIVE_RECOMPUTING_DEPS,
30
+ x as queuePendingNode,
31
+ y as list,
32
+ z as STATUS_ERROR,
33
+ A as tracking,
34
+ B as CONFIG_CHILDREN_FORBIDDEN,
35
+ D as passLane,
36
+ E as STATUS_PENDING,
37
+ F as EFFECT_RENDER,
38
+ H as sameLane,
39
+ I as flushTransaction,
40
+ J as REACTIVE_SCREEN_READ,
41
+ K as REACTIVE_FRAME_READ,
42
+ L as attrHooks,
43
+ M as commitPendingNode,
44
+ O as REACTIVE_IN_HEAP,
45
+ P as REACTIVE_DIRTY,
46
+ Q as REACTIVE_CHECK,
47
+ T as CONFIG_INPUTS_PUBLISHED,
48
+ U as releaseQueues,
49
+ V as CONFIG_SLOT_NODE,
50
50
  W as holdNode,
51
51
  X as joinFuture,
52
- Y as commitPendingNode,
53
- Z as REACTIVE_IN_HEAP,
54
- _ as REACTIVE_DIRTY,
55
- $ as REACTIVE_CHECK,
56
- a0 as CONFIG_INPUTS_PUBLISHED,
57
- a1 as setVerdict,
58
- a2 as context,
59
- a3 as REACTIVE_PROBED,
60
- a4 as bornInto,
61
- a5 as globalQueue,
62
- a6 as staleReader,
52
+ Y as reruns,
53
+ Z as CONFIG_STAGED,
54
+ _ as merge$1,
55
+ $ as setPassLane,
56
+ a0 as REACTIVE_JOINED,
57
+ a1 as REACTIVE_STAGED_READ,
58
+ a2 as stagedRead,
59
+ a3 as setVerdict,
60
+ a4 as context,
61
+ a5 as installVerdictHooks,
62
+ a6 as REACTIVE_PROBED,
63
63
  a7 as verdict,
64
64
  a8 as link,
65
65
  a9 as pullComputed,
66
66
  aa as strictRead,
67
67
  ab as warnStrictReadUntracked,
68
- ac as joinPass,
69
- ad as markLateLinker,
70
- ae as spectating,
71
- af as STATUS_ERROR,
72
- ag as unflushedValue,
73
- ah as stagedReaders,
74
- ai as enterAction,
75
- aj as setEffectCallback,
76
- ak as exitAction,
77
- al as isThenable,
78
- am as actionDepth,
79
- an as flush,
80
- ao as setQuestion,
81
- ap as emitDiagnostic,
82
- aq as enterCallback,
83
- ar as exitCallback,
84
- as as getOwner,
85
- at as inEffectCallback,
86
- au as signal,
87
- av as setSignal,
88
- aw as cleanup,
89
- ax as read,
90
- ay as NoOwnerError,
91
- az as ContextNotFoundError,
92
- aA as createEffectNode,
93
- aB as recompute,
94
- aC as EFFECT_USER,
95
- aD as reportDiagnostic,
96
- aE as computed,
97
- aF as staleValues,
98
- aG as CONFIG_AUTO_DISPOSE,
99
- aH as EFFECT_TRACKED,
100
- aI as unwrapStatusError,
101
- aJ as haltReactivity,
102
- aK as setStrictRead,
103
- aL as StatusError,
104
- aM as trimStaleDeps,
105
- aN as setTrackedQueueCallback,
106
- aO as setEffectStatusNotify,
107
- aP as _hitUnhandledAsync,
108
- aQ as resetUnhandledAsync,
109
- aR as ROOT_ERROR_HOOK,
110
- aS as registerGraph,
111
- aT as runWithOwner,
112
- aU as setMemo,
113
- aV as $REFRESH,
114
- aW as markRefresh,
115
- aX as createRoot,
116
- aY as getObserver,
117
- aZ as dispose,
118
- a_ as TimeoutError,
119
- a$ as untrack,
120
- b0 as dirtyQueue,
121
- b1 as CONFIG_REDERIVE,
122
- b2 as passTx,
123
- b3 as CONFIG_FRESH_READ,
124
- b4 as createOwner,
125
- b5 as REACTIVE_WRITE_IN_OWNED_SCOPE_SIGNAL_MESSAGE,
126
- b6 as ownedScopeWriteMessage,
127
- b7 as DEV$1,
128
- b8 as isEqual,
129
- b9 as CONFIG_MANUAL_WRITE,
130
- ba as snapshotCaptureActive,
131
- bb as captureWriteSnapshot,
132
- bc as notePromotedWrite,
133
- bd as slotSignal,
134
- be as stagedScreen,
135
- bf as CONFIG_PROMOTED,
136
- bg as setSlotUnobserved,
137
- bh as joinPassTx,
138
- bi as asyncTailFlights,
139
- bj as untrackDepth,
140
- bk as checkPostAwaitRead,
141
- bl as insertIntoHeap,
142
- bm as isDisposed,
143
- bn as handleAsync,
144
- bo as addPendingSource,
145
- bp as propagateStatus,
146
- bq as forEachDependent,
147
- br as settlePendingSource,
148
- bs as notifyEpoch,
149
- bt as SUPPORTS_PROXY,
150
- bu as REACTIVE_ZOMBIE,
151
- bv as OBSERVE$1
68
+ ac as staleReader,
69
+ ad as globalQueue,
70
+ ae as joinPass,
71
+ af as markLateLinker,
72
+ ag as spectating,
73
+ ah as unflushedValue,
74
+ ai as CONFIG_STAGED_ERROR,
75
+ aj as stagedReaders,
76
+ ak as bornInto,
77
+ al as enterAction,
78
+ am as setEffectCallback,
79
+ an as exitAction,
80
+ ao as isThenable,
81
+ ap as actionDepth,
82
+ aq as flush,
83
+ ar as setQuestion,
84
+ as as emitDiagnostic,
85
+ at as enterCallback,
86
+ au as exitCallback,
87
+ av as getOwner,
88
+ aw as inEffectCallback,
89
+ ax as setExternalHooks,
90
+ ay as signal,
91
+ az as setSignal,
92
+ aA as cleanup,
93
+ aB as read,
94
+ aC as DEV$1,
95
+ aD as CONFIG_TRANSPARENT,
96
+ aE as NoOwnerError,
97
+ aF as ContextNotFoundError,
98
+ aG as createEffectNode,
99
+ aH as recompute,
100
+ aI as EFFECT_USER,
101
+ aJ as reportDiagnostic,
102
+ aK as computed,
103
+ aL as staleValues,
104
+ aM as CONFIG_AUTO_DISPOSE,
105
+ aN as EFFECT_TRACKED,
106
+ aO as heldRunHook,
107
+ aP as unwrapStatusError,
108
+ aQ as haltReactivity,
109
+ aR as setStrictRead,
110
+ aS as StatusError,
111
+ aT as trimStaleDeps,
112
+ aU as setTrackedQueueCallback,
113
+ aV as setEffectStatusNotify,
114
+ aW as _hitUnhandledAsync,
115
+ aX as resetUnhandledAsync,
116
+ aY as ROOT_ERROR_HOOK,
117
+ aZ as registerGraph,
118
+ a_ as runWithOwner,
119
+ a$ as setMemo,
120
+ b0 as $REFRESH,
121
+ b1 as markRefresh,
122
+ b2 as createRoot,
123
+ b3 as getObserver,
124
+ b4 as dispose,
125
+ b5 as TimeoutError,
126
+ b6 as untrack,
127
+ b7 as dirtyQueue,
128
+ b8 as CONFIG_REDERIVE,
129
+ b9 as passTx,
130
+ ba as CONFIG_FRESH_READ,
131
+ bb as createOwner,
132
+ bc as writeHook,
133
+ bd as REACTIVE_WRITE_IN_OWNED_SCOPE_SIGNAL_MESSAGE,
134
+ be as ownedScopeWriteMessage,
135
+ bf as isEqual,
136
+ bg as CONFIG_MANUAL_WRITE,
137
+ bh as snapshotCaptureActive,
138
+ bi as captureWriteSnapshot,
139
+ bj as notePromotedWrite,
140
+ bk as slotSignal,
141
+ bl as stagedScreen,
142
+ bm as CONFIG_PROMOTED,
143
+ bn as setSlotUnobserved,
144
+ bo as installStoreHooks,
145
+ bp as joinPassTx,
146
+ bq as asyncTailFlights,
147
+ br as untrackDepth,
148
+ bs as checkPostAwaitRead,
149
+ bt as disposalDepth,
150
+ bu as insertIntoHeap,
151
+ bv as isDisposed,
152
+ bw as handleAsync,
153
+ bx as addPendingSource,
154
+ by as propagateStatus,
155
+ bz as forEachDependent,
156
+ bA as settlePendingSource,
157
+ bB as notifyEpoch,
158
+ bC as SUPPORTS_PROXY,
159
+ bD as releaseMarksHook,
160
+ bE as installMarkHooks,
161
+ bF as CONFIG_LAGS,
162
+ bG as REACTIVE_REASK,
163
+ bH as heldUnder,
164
+ bI as unhold,
165
+ bJ as laneStageHook,
166
+ bK as REACTIVE_ZOMBIE,
167
+ bL as installBoundaryHooks,
168
+ bM as CAUGHT_REARMED,
169
+ bN as OBSERVE$1
152
170
  } from "./dev-shared.js";
153
171
  export {
154
- bw as clearSnapshots,
155
- bx as enforceLoadingBoundary,
156
- by as getNextChildId,
157
- bz as markSnapshotScope,
158
- bA as peekNextChildId,
159
- bB as releaseSnapshotScope,
160
- bC as resetErrorHalt,
161
- bD as setConsoleFooter,
162
- bE as setSnapshotCapture
172
+ bO as clearSnapshots,
173
+ bP as enforceLoadingBoundary,
174
+ bQ as getNextChildId,
175
+ bR as markSnapshotScope,
176
+ bS as peekNextChildId,
177
+ bT as releaseSnapshotScope,
178
+ bU as resetErrorHalt,
179
+ bV as setConsoleFooter,
180
+ bW as setSnapshotCapture,
181
+ bX as setWriteHook
163
182
  } from "./dev-shared.js";
164
183
 
184
+ // Truth reads (`until`, `refresh`; CONFIG_AUTHORITATIVE) over lane nodes.
185
+ // Side-effect free and apart from lanes.ts so a truth reader alone does not
186
+ // retain the lane engine: its reads reach these only through `laneRead`,
187
+ // which exists once something installs lanes (`createOptimistic`, verdicts).
188
+ /** A lane node's current value: its guess, or the latest it derived — the
189
+ * staging a shown lane holds for its next reveal, else its slot; the
190
+ * committed value while it has none (its first lane pass in flight). */
191
+ function laneValueOf(el) {
192
+ if (!(el._config & CONFIG_GUESS) && el._pendingValue !== NOT_PENDING) return el._pendingValue;
193
+ const v = el._x._lane;
194
+ return v !== NOT_PENDING ? v : el._value;
195
+ }
196
+ /** Truth readers (`until`, `refresh`; CONFIG_AUTHORITATIVE) — pay-for-use:
197
+ * the first one installs these on GlobalQueue (signals.ts `watch`).
198
+ *
199
+ * A truth reader's read of a lane node: the lane's value when it is the
200
+ * truth (#3943), else the truth staged beneath a guess, else the committed
201
+ * value. */
202
+ function truthRead(el) {
203
+ if (truth(el)) return laneValueOf(el);
204
+ if (!(el._config & CONFIG_GUESS)) throw new NotReadyError(null);
205
+ return el._pendingValue !== NOT_PENDING ? el._pendingValue : el._value;
206
+ }
207
+ /** A guess confirmed: its lane's truth readers re-read — of the lane's
208
+ * derivations too, which may be the truth now (#3943). */
209
+ function truthConfirmed(l) {
210
+ for (let i = 0; i < l._nodes.length; i++)
211
+ for (let s = l._nodes[i]._subs; s !== null; s = s._nextSub)
212
+ if (s._sub._config & CONFIG_AUTHORITATIVE) enqueueSub(s._sub);
213
+ }
214
+ /** A lane node's value is the truth when every guess beneath it is
215
+ * confirmed — its source staged a value equal to the guess — and nothing
216
+ * beneath it is held in the parent: same inputs, same value. */
217
+ function truth(n) {
218
+ if (n._config & CONFIG_GUESS)
219
+ return n._pendingValue !== NOT_PENDING && n._equals && n._equals(n._x._lane, n._pendingValue);
220
+ if (n._pendingValue !== NOT_PENDING) return;
221
+ if (n._config & CONFIG_OVERRIDE)
222
+ for (let d = n._deps; d; d = d._nextDep) if (!truth(d._dep)) return;
223
+ return 1;
224
+ }
225
+
165
226
  // Lanes (plan sec. 28 replay, 2026-10-02; maintainer rulings 2026-10-01/02). A lane
166
227
  // is "a new base of a transition": the sub-frame an optimistic write opens
167
228
  // in the transition of the frame it is made in. It sees the screen plus its
@@ -201,14 +262,6 @@ function newLane(parent) {
201
262
  lanes.push(l);
202
263
  return l;
203
264
  }
204
- /** A lane node's current value: its guess, or the latest it derived — the
205
- * staging a shown lane holds for its next reveal, else its slot; the
206
- * committed value while it has none (its first lane pass in flight). */
207
- function laneValueOf(el) {
208
- if (!(el._config & CONFIG_GUESS) && el._pendingValue !== NOT_PENDING) return el._pendingValue;
209
- const v = el._x._lane;
210
- return v !== NOT_PENDING ? v : el._value;
211
- }
212
265
  /** What the screen shows for a lane's node: the lane's value once the lane
213
266
  * has revealed, the committed truth before. */
214
267
  function display(el) {
@@ -273,23 +326,28 @@ function applyGuesses(parent) {
273
326
  const n = pendingGuesses[i];
274
327
  const v = pendingGuesses[i + 1];
275
328
  let changed = true;
329
+ // The frame already holding the slot: a guess over it is a second
330
+ // suggestion for the slot, and two cannot finish apart (A34 (1)) — the
331
+ // frames entangle.
332
+ let holder = null;
276
333
  if (n._config & CONFIG_OVERRIDE) {
277
334
  // A lane's node already: a guess re-asked, or a derivation guessed
278
- // over (its staging is void — the guess is the value now). Two
279
- // suggestions for one slot cannot finish apart (A34 (1)): the frames
280
- // entangle. The same value re-asked renews the question; nothing to
281
- // notify.
335
+ // over (its staging is void — the guess is the value now). The same
336
+ // value re-asked renews the question; nothing to notify.
282
337
  changed = !n._equals || !n._equals(laneValueOf(n), v);
283
338
  if (!(n._config & CONFIG_GUESS)) {
284
339
  n._pendingValue = NOT_PENDING;
285
340
  n._config |= CONFIG_GUESS;
286
341
  }
287
342
  n._x._lane = v;
288
- const holder = txOf(n)._parent;
289
- if (parent !== null && holder !== null) merge$1(parent, holder);
343
+ holder = txOf(n)._parent;
290
344
  } else {
291
345
  let l;
292
- if (n._config & CONFIG_HELD) l = newLane(txOf(n));
346
+ // A held node: a truth staged there, or what a corrected guess showed.
347
+ // The guess composes on it, so it ends with that hold — and with its
348
+ // own frame's (F-11): it lands only when both have (the board's rapid
349
+ // adds — a later add's row reverted with an earlier add's landing).
350
+ if (n._config & CONFIG_HELD) l = newLane((holder = txOf(n)));
293
351
  else {
294
352
  if (parent === null && inFlight$1(n)) parent = newTransaction(false);
295
353
  if (parent === null) continue;
@@ -305,6 +363,7 @@ function applyGuesses(parent) {
305
363
  n._config |= CONFIG_OVERRIDE | CONFIG_GUESS;
306
364
  list(n, l);
307
365
  }
366
+ if (parent !== null && holder !== null) merge$1(parent, holder);
308
367
  ext(n)._q = pendingGuesses[i + 2];
309
368
  if (changed) {
310
369
  // The lane's members re-derive as its work (REACTIVE_LANE_DIRTY): a
@@ -504,37 +563,6 @@ function supersede(n, value, changed) {
504
563
  n._pendingValue = value;
505
564
  joinFuture(parent);
506
565
  }
507
- /** Truth readers (`until`, `refresh`; CONFIG_AUTHORITATIVE) — pay-for-use:
508
- * the first one installs these on GlobalQueue (signals.ts `watch`).
509
- *
510
- * A truth reader's read of a lane node: the lane's value when it is the
511
- * truth (#3943), else the truth staged beneath a guess, else the committed
512
- * value. */
513
- function truthRead(el) {
514
- return truth(el)
515
- ? laneValueOf(el)
516
- : el._config & CONFIG_GUESS && el._pendingValue !== NOT_PENDING
517
- ? el._pendingValue
518
- : el._value;
519
- }
520
- /** A guess confirmed: its lane's truth readers re-read — of the lane's
521
- * derivations too, which may be the truth now (#3943). */
522
- function truthConfirmed(l) {
523
- for (let i = 0; i < l._nodes.length; i++)
524
- for (let s = l._nodes[i]._subs; s !== null; s = s._nextSub)
525
- if (s._sub._config & CONFIG_AUTHORITATIVE) enqueueSub(s._sub);
526
- }
527
- /** A lane node's value is the truth when every guess beneath it is
528
- * confirmed — its source staged a value equal to the guess — and nothing
529
- * beneath it is held in the parent: same inputs, same value. */
530
- function truth(n) {
531
- if (n._config & CONFIG_GUESS)
532
- return n._pendingValue !== NOT_PENDING && n._equals && n._equals(n._x._lane, n._pendingValue);
533
- if (n._pendingValue !== NOT_PENDING) return;
534
- if (n._config & CONFIG_OVERRIDE)
535
- for (let d = n._deps; d; d = d._nextDep) if (!truth(d._dep)) return;
536
- return 1;
537
- }
538
566
  /** A lane's end, two ways. `into === null`: the parent landed — the lane's
539
567
  * values are the truth now: a guess lands the truth staged beneath it or
540
568
  * reverts to the one it covered (`_value`, untouched by the guess); a
@@ -669,9 +697,11 @@ function dissolveLane(l, into, except) {
669
697
  function laneRead(c, el) {
670
698
  const guess = el._config & CONFIG_GUESS;
671
699
  if (c !== null && c._config & CONFIG_AUTHORITATIVE) return GlobalQueue._truth(el);
700
+ const status = el._statusFlags;
701
+ // An errored member's lane value predates its error; the error is the answer (#4106).
702
+ if (!guess && status & STATUS_ERROR) return NOT_PENDING;
672
703
  if (c === null) return laneValueOf(el);
673
704
  const l = txOf(el);
674
- const status = el._statusFlags;
675
705
  if (!tracking || c._config & CONFIG_CHILDREN_FORBIDDEN) {
676
706
  // An untracked read inside lane work derives from the lane like a
677
707
  // tracked one, minus the subscription — `untrack` is about
@@ -898,20 +928,16 @@ function answered(el) {
898
928
  }
899
929
  return q;
900
930
  }
901
- GlobalQueue._laneRead = laneRead;
902
- GlobalQueue._laneStage = laneStage;
903
- GlobalQueue._laneOutcome = laneOutcome;
904
- GlobalQueue._laneWrite = laneWrite;
905
931
  /** The lanes alive before this seam's guesses opened theirs (a prefix of
906
932
  * `lanes`; nothing dissolves one between here and the seam loop): a lane
907
933
  * born at the seam has no work to judge yet (its passes run next round) —
908
934
  * it is neither shown nor blocked until it has. */
909
935
  let judged = 0;
910
- GlobalQueue._applyGuesses = parent => {
936
+ const applyGuessesAt = parent => {
911
937
  judged = lanes.length;
912
938
  if (pendingGuesses.length !== 0) applyGuesses(parent);
913
939
  };
914
- GlobalQueue._laneSeams = leaks => {
940
+ const laneSeams = leaks => {
915
941
  // Passes that read a write the parked frame holds as the screen
916
942
  // (`stagedReaders`: lane work, a verdict reader before the verdict): they
917
943
  // re-derive on the committed world next round (and register as stale
@@ -949,8 +975,17 @@ GlobalQueue._laneSeams = leaks => {
949
975
  for (let k = Math.min(judged, lanes.length) - 1; k >= 0; k--)
950
976
  laneSeam(lanes[k], leaked.indexOf(lanes[k]) !== -1);
951
977
  };
952
- GlobalQueue._laneCorrections = () => lanes.length !== 0 && laneCorrections();
953
- GlobalQueue._endLanes = endLanes;
978
+ installLaneHooks(
979
+ laneRead,
980
+ laneStage,
981
+ laneOutcome,
982
+ laneWrite,
983
+ applyGuessesAt,
984
+ laneSeams,
985
+ () => lanes.length !== 0 && laneCorrections(),
986
+ endLanes,
987
+ lanesBlocked
988
+ );
954
989
  GlobalQueue._laneGuesses = t => {
955
990
  const out = [];
956
991
  for (let i = 0; i < lanes.length; i++) {
@@ -962,7 +997,6 @@ GlobalQueue._laneGuesses = t => {
962
997
  }
963
998
  return out;
964
999
  };
965
- GlobalQueue._lanesBlocked = lanesBlocked;
966
1000
  GlobalQueue._verdictLane = verdictLane;
967
1001
 
968
1002
  // Verdicts (plan sec. 28 replay, 2026-10-02; maintainer, 2026-10-01: verdicts are
@@ -1116,7 +1150,7 @@ function heldNotFinal(owner) {
1116
1150
  !latestActive &&
1117
1151
  !owner._x._reask &&
1118
1152
  !(owner._statusFlags & STATUS_UNINITIALIZED) &&
1119
- (!owner._equals || !owner._equals(owner._value, owner._pendingValue))
1153
+ (!!owner._x._shownError || !owner._equals || !owner._equals(owner._value, owner._pendingValue))
1120
1154
  );
1121
1155
  }
1122
1156
  /** `isPending`'s answer for an unheld node whose flight is up (cause ii). */
@@ -1160,6 +1194,38 @@ function latest(fn) {
1160
1194
  setVerdict(prevVerdict);
1161
1195
  }
1162
1196
  }
1197
+ /**
1198
+ * Reports whether what `fn` reads is still waiting on an update. Read the
1199
+ * values in question inside `fn`: the probe answers `true` while any of
1200
+ * them depends on a change that is in flight — a write or an async source
1201
+ * that has started producing a new value whose result has not landed yet —
1202
+ * and `false` once the latest value is the one on screen. The answer is
1203
+ * tracked like any read, so a memo or effect that calls `isPending` re-runs
1204
+ * when it flips.
1205
+ *
1206
+ * A read that has no value yet is loading, not pending: inside `fn` it
1207
+ * follows the surrounding `Loading` path like any other read, and the probe
1208
+ * reports only updates to a value that already exists. A bare `refresh()`
1209
+ * does not flip `isPending`; pair it with `affects()` to make the re-ask
1210
+ * visible.
1211
+ *
1212
+ * @param fn expression to probe; read the values in question inside it
1213
+ *
1214
+ * @returns `true` while a read inside `fn` depends on a change that has not committed yet
1215
+ *
1216
+ * @example
1217
+ * ```ts
1218
+ * const [query, setQuery] = createSignal("");
1219
+ * const results = createMemo(() => searchProducts(query()));
1220
+ * const stale = createMemo(() => isPending(results));
1221
+ *
1222
+ * setQuery("shoes");
1223
+ * // stale() is true until the new results land, while results() keeps
1224
+ * // serving the previous list — dim it instead of showing a spinner.
1225
+ * ```
1226
+ *
1227
+ * @description https://v2.solidjs.com/reference/solid-js/reactivity/is-pending
1228
+ */
1163
1229
  function isPending(fn) {
1164
1230
  markVerdictReader(1);
1165
1231
  const prevProbing = probing;
@@ -1186,12 +1252,17 @@ function isPending(fn) {
1186
1252
  setVerdict(prevVerdict);
1187
1253
  }
1188
1254
  }
1255
+ /** The staging, or the staged error thrown (D-41). */
1256
+ function staged(el) {
1257
+ if (el._config & CONFIG_STAGED_ERROR) throw el._pendingValue;
1258
+ return el._pendingValue;
1259
+ }
1189
1260
  /** `latest()` of a held node: the flushed staging — the proposal — and, for
1190
1261
  * a tracked reader of a rewrite not yet flushed, the late-linker re-run in
1191
1262
  * the flush that carries it. */
1192
1263
  function heldLatest(el, c) {
1193
1264
  const u = unflushedValue(el);
1194
- if (u === NOT_PENDING) return el._pendingValue;
1265
+ if (u === NOT_PENDING) return staged(el);
1195
1266
  if (c !== null) markLateLinker(c);
1196
1267
  return u;
1197
1268
  }
@@ -1239,7 +1310,10 @@ function verdictValue(el, c) {
1239
1310
  probeFound = true;
1240
1311
  if (tracked && flushTransaction !== null) verdictRead(c, flushTransaction);
1241
1312
  }
1242
- if (el._config & CONFIG_OVERRIDE) {
1313
+ if (
1314
+ el._config & CONFIG_OVERRIDE &&
1315
+ !(owner._statusFlags & STATUS_ERROR && !(el._config & CONFIG_GUESS))
1316
+ ) {
1243
1317
  const l = txOf(el);
1244
1318
  if (tracked) verdictRead(c, l._parent ?? l);
1245
1319
  const flying = (owner._statusFlags & STATUS_PENDING) !== 0 && !quietPending(owner);
@@ -1304,7 +1378,7 @@ function verdictValue(el, c) {
1304
1378
  // has not shown content) and answers for the commit, where it is final.
1305
1379
  if (el._pendingValue !== NOT_PENDING && !(owner._statusFlags & STATUS_PENDING)) {
1306
1380
  if (probing && owned) joinPass(c, el);
1307
- return el._pendingValue;
1381
+ return staged(el);
1308
1382
  }
1309
1383
  } else if (el._pendingValue !== NOT_PENDING) {
1310
1384
  if (owner._statusFlags & STATUS_PENDING && !uninitialized)
@@ -1321,7 +1395,7 @@ function verdictValue(el, c) {
1321
1395
  // A28: an unflushed write is not what `latest` serves; a tracked
1322
1396
  // reader derived from the flushed world runs again in the carrying
1323
1397
  // flush.
1324
- if (globalQueue._running) return el._pendingValue;
1398
+ if (globalQueue._running) return staged(el);
1325
1399
  if (c !== null) markLateLinker(c);
1326
1400
  }
1327
1401
  return el._value;
@@ -1343,7 +1417,7 @@ function verdictValue(el, c) {
1343
1417
  // Installed at module evaluation — present exactly when something imports
1344
1418
  // `latest` or `isPending`. (`read` dispatches to `verdictValue` through
1345
1419
  // `verdict`, set while a window is open.)
1346
- GlobalQueue._observeFlight = observeFlight;
1420
+ installVerdictHooks(observeFlight);
1347
1421
  /** A store read inside an untracked `isPending` probe found a live mark on
1348
1422
  * a record with no node to carry it (store/affects.ts): not final. */
1349
1423
  GlobalQueue._witnessMark = () => {
@@ -1589,7 +1663,7 @@ let externalSourceConfig = null;
1589
1663
  * ```
1590
1664
  */
1591
1665
  // Wires a freshly created computed through the active external-source bridge.
1592
- // Lives here (installed on GlobalQueue while a config is active) rather than
1666
+ // Lives here (installed as a scheduler hook while a config is active) rather than
1593
1667
  // inline in core: esbuild cannot literal-track the mutable config binding the
1594
1668
  // way rollup does, so an inline `if (externalSourceConfig)` block ships in
1595
1669
  // every bundle even though only enableExternalSource() can make it reachable.
@@ -1611,8 +1685,8 @@ function externalUntrack(fn) {
1611
1685
  // removed on reset) so core's presence checks stay equivalent to the old
1612
1686
  // `externalSourceConfig` truthiness checks.
1613
1687
  function syncExternalHooks() {
1614
- GlobalQueue._wireExternalSource = externalSourceConfig ? wireExternalSource : undefined;
1615
- GlobalQueue._externalUntrack = externalSourceConfig ? externalUntrack : undefined;
1688
+ if (externalSourceConfig) setExternalHooks(wireExternalSource, externalUntrack);
1689
+ else setExternalHooks(undefined, undefined);
1616
1690
  }
1617
1691
  function enableExternalSource(config) {
1618
1692
  const { factory, untrack: untrackFn = fn => fn() } = config;
@@ -1638,6 +1712,61 @@ function enableExternalSource(config) {
1638
1712
  syncExternalHooks();
1639
1713
  }
1640
1714
 
1715
+ // Deferred hydration, dependency edges: plain-signal identities.
1716
+ // While on (`setSignalIdentities`; the server-components client turns it on
1717
+ // before hydrating), a plain signal created under an id-bearing owner is
1718
+ // named `<owner id>~<n>`, n counting the plain signals (and plain store
1719
+ // roots: `createStore` over an object, store/edges.ts) created under that
1720
+ // owner in its current pass (transparent owners defer to the nearest
1721
+ // id-bearing one, as child ids do). The server names the same signal the
1722
+ // same way and restarts the count on every re-run, so an edge it shipped (a
1723
+ // slot fill read it) matches the client node; the write hook receives the
1724
+ // node and looks its name up (`signalName`). The count restarts when the owner's pass
1725
+ // generation (`_depGen`, bumped by every recompute) moves on. Only the
1726
+ // frames client imports this module, so nothing here ships without it.
1727
+ // Names live in a side table, not on the node: frames reads them across the
1728
+ // package boundary, where a mangled field would not survive.
1729
+ let counts = null;
1730
+ const names = new WeakMap();
1731
+ /** @internal The identity of a plain signal named while identities were on. */
1732
+ function signalName(node) {
1733
+ return node === undefined ? undefined : names.get(node);
1734
+ }
1735
+ function identify(node) {
1736
+ let o = getOwner();
1737
+ if (!o) return;
1738
+ while (o._config & CONFIG_TRANSPARENT && o._parent) o = o._parent;
1739
+ if (o.id == null) return;
1740
+ const gen = o._depGen;
1741
+ let c = counts.get(o);
1742
+ if (c === undefined || c[0] !== gen) counts.set(o, (c = [gen, 0]));
1743
+ const sid = o.id + "~" + c[1]++;
1744
+ // A store root named twice (one object made into two stores, or a store
1745
+ // handed to `createStore`) is one target under two server names: its
1746
+ // writes cannot be attributed (an empty name).
1747
+ names.set(node, names.has(node) ? "" : sid);
1748
+ }
1749
+ /** @internal Name plain signals from now on (`on`), or stop. */
1750
+ function setSignalIdentities(on) {
1751
+ counts = on ? counts || new WeakMap() : null;
1752
+ signalHook = on ? identify : null;
1753
+ }
1754
+ /**
1755
+ * @internal Called with every plain signal `createSignal` makes, and every
1756
+ * plain store root `createStore` makes, while set (the namer above). Never
1757
+ * set unless `setSignalIdentities` is imported, so a bundle without it keeps
1758
+ * only a null check.
1759
+ */
1760
+ let signalHook = null;
1761
+ /**
1762
+ * @internal Dev only: `watch` is called with every node read while set (the
1763
+ * dev hook `onRead`) — the frames client's identity-drift check watches the
1764
+ * named signals a deferred occurrence reads as it mounts. A no-op in prod.
1765
+ */
1766
+ function setReadWatch(watch) {
1767
+ DEV$1.hooks.onRead = watch || undefined;
1768
+ }
1769
+
1641
1770
  /**
1642
1771
  * Context provides a form of dependency injection. It is used to save from needing to pass
1643
1772
  * data as props through intermediate components. This function creates a new context object
@@ -1798,7 +1927,7 @@ function runEffect(node, type) {
1798
1927
  // hidden subtree. The synchronous first render on creation is not a queued
1799
1928
  // run and goes through. `_modified` stays set; the boundary re-queues the
1800
1929
  // run (`release`).
1801
- if (GlobalQueue._heldRun && GlobalQueue._heldRun(node)) return;
1930
+ if (heldRunHook?.(node)) return;
1802
1931
  // Error arm (#2840), user effects only: a compute-phase error that is still
1803
1932
  // the node's settled state at effect time runs the bundle's error handler in
1804
1933
  // this same imperative, writable scope. Unwrap the StatusError used for
@@ -1893,7 +2022,7 @@ function trackedEffect(fn, options) {
1893
2022
  // pass, but several passes in one flush each enqueue `_run` into the same
1894
2023
  // user queue, and this gate is what collapses them into one run.
1895
2024
  if (!node._modified || node._flags & REACTIVE_DISPOSED) return;
1896
- if (GlobalQueue._heldRun && GlobalQueue._heldRun(node)) return;
2025
+ if (heldRunHook?.(node)) return;
1897
2026
  setTrackedQueueCallback(true);
1898
2027
  try {
1899
2028
  node._modified = false;
@@ -2024,6 +2153,155 @@ function reportClientError(error, owner, thrower) {
2024
2153
  }
2025
2154
  }
2026
2155
 
2156
+ /**
2157
+ * Ownership stamp (#3360): every backing the store ALLOCATES (CoW clones,
2158
+ * privatized committed backings) carries its owning target under this
2159
+ * enumerable symbol. One property write replaces the two weak-collection
2160
+ * registrations (ownership set + raw→target map) a fresh object used to pay
2161
+ * per draft — V8's identity-hash + ephemeron cost dominated the one-key
2162
+ * write floor. Enumerable so a spread copy (the plain-data clone path) stays
2163
+ * on the fast path and carries the stamp along.
2164
+ *
2165
+ * Owned backings are never user-reachable (`snapshot` copies them, the traps
2166
+ * hide the key), so every raw key walk in the store must skip `$OWNER`, and
2167
+ * ownership is answered by `isOwned` — a user object never carries it.
2168
+ * Overlay drafts (`Object.create(v)` over an owned `v`) inherit the stamp.
2169
+ */
2170
+ const $OWNER = Symbol("STORE_OWNER");
2171
+ /** raw → target for plain (family-less) stores. Family stores use their own
2172
+ * map so one raw can wrap once per family (§7b). */
2173
+ const storeLookup = new WeakMap();
2174
+ function setLookup(raw, t) {
2175
+ (t.fam?.map ?? storeLookup).set(raw, t);
2176
+ }
2177
+ function isOwned(raw) {
2178
+ return raw[$OWNER] !== undefined;
2179
+ }
2180
+ /** The target wrapping `raw` in `fam` (or the plain lookup). An owned raw
2181
+ * answers from its stamp; a user raw from the map. */
2182
+ function lookupTarget(raw, fam) {
2183
+ const owner = raw[$OWNER];
2184
+ return owner !== undefined && owner.fam === fam ? owner : (fam?.map ?? storeLookup).get(raw);
2185
+ }
2186
+ function devAssertNeverUserMutation(target) {
2187
+ return;
2188
+ }
2189
+ /** Sticky descendants flag up the parent chain (§6d): adoption descends
2190
+ * into a changed child pair only where a target with nodes exists below. */
2191
+ function markDescendants(target) {
2192
+ let t = target;
2193
+ while (t && !t.d) {
2194
+ t.d = true;
2195
+ t = t.u;
2196
+ }
2197
+ }
2198
+ /** Observe tier: the owner in scope at `createStore` (attribution names and
2199
+ * owner paths); the `_owner` write never reaches the proxy. */
2200
+ const storeOwners = new WeakMap();
2201
+ /** Brand keys the proxy answers directly (never user data). */
2202
+ const $TRACK = Symbol("STORE_TRACK"),
2203
+ $TARGET = Symbol("STORE_TARGET"),
2204
+ $PROXY = Symbol("STORE_PROXY"),
2205
+ $RECORD = Symbol("VIEW_RECORD");
2206
+ // ---------------------------------------------------------------------------
2207
+ // raw marking (R42 / RUL-12: a value is never both wrapped and raw)
2208
+ const rawValues = new WeakSet();
2209
+ let rawValuesUsed = false;
2210
+ function isRawValue(value) {
2211
+ return rawValuesUsed && rawValues.has(value);
2212
+ }
2213
+ /** Mark a value so no store ever wraps it (sticky; it reads as a leaf). */
2214
+ function markRaw(value) {
2215
+ if (isWrappable(value)) {
2216
+ rawValuesUsed = true;
2217
+ rawValues.add(value);
2218
+ }
2219
+ return value;
2220
+ }
2221
+ /** A shallow store's ingested record: raw by contract (values served
2222
+ * verbatim, slots replaced by reference). */
2223
+ function markRawOne(v) {
2224
+ if (isWrappable(v)) {
2225
+ if (v[$TARGET] !== undefined) return;
2226
+ if (lookupTarget(v, null) !== undefined)
2227
+ throw new Error(
2228
+ "shallow store: an ingested record is already tracked as a deep store — one value cannot present both wrapped and raw"
2229
+ );
2230
+ rawValuesUsed = true;
2231
+ rawValues.add(v);
2232
+ }
2233
+ }
2234
+ function markRawIngest(container) {
2235
+ if (Array.isArray(container)) {
2236
+ for (let i = 0, len = container.length; i < len; i++) markRawOne(container[i]);
2237
+ } else {
2238
+ for (const k in container) markRawOne(container[k]);
2239
+ }
2240
+ }
2241
+ // ---------------------------------------------------------------------------
2242
+ // wrappability
2243
+ const OBJECT_PROTO = Object.prototype;
2244
+ const wrappableProtos = new WeakMap();
2245
+ function isWrappable(obj) {
2246
+ if (obj == null || typeof obj !== "object" || Object.isFrozen(obj)) return false;
2247
+ const proto = Object.getPrototypeOf(obj);
2248
+ if (proto === OBJECT_PROTO || proto === null) return true;
2249
+ if (Array.isArray(obj)) return true;
2250
+ let wrappable = wrappableProtos.get(proto);
2251
+ if (wrappable === undefined) {
2252
+ wrappable =
2253
+ Object.prototype.toString.call(obj) === "[object Object]" &&
2254
+ (typeof Node === "undefined" || !(obj instanceof Node));
2255
+ wrappableProtos.set(proto, wrappable);
2256
+ }
2257
+ return wrappable;
2258
+ }
2259
+ function ownEnumerableKeys(o) {
2260
+ return Reflect.ownKeys(o).filter(k => Object.prototype.propertyIsEnumerable.call(o, k));
2261
+ }
2262
+ // ---------------------------------------------------------------------------
2263
+ // write override: a projection's draft ops carry it (the derive is the
2264
+ // author — its writes must land outside the sync setter scope too, in a
2265
+ // continuation after an `await`/`yield`). Projections return in S3; the
2266
+ // traps already honour it.
2267
+ let writeOverride = false;
2268
+ function setWriteOverride(value) {
2269
+ writeOverride = value;
2270
+ }
2271
+
2272
+ /**
2273
+ * Deferred hydration, dependency edges for stores. Only the frames client
2274
+ * imports this module, so nothing here ships without it.
2275
+ *
2276
+ * A plain store root (`createStore` over an object) is named like a plain
2277
+ * signal (core/identities.ts: `<owner id>~<n>`, on the same count), and the
2278
+ * whole store goes by that one name: the server records it for any read of
2279
+ * the store, at any depth, and a setter call on the store — on its root or
2280
+ * on a proxy nested in it — is a write to it. Its readers mount at the
2281
+ * setter's entry, before the draft runs, as a plain signal's do.
2282
+ */
2283
+ /**
2284
+ * @internal The name of the store `x` belongs to — `x` a store proxy (a
2285
+ * setter's entry) or one of its property nodes (the setter notifying it) —
2286
+ * or undefined when it has none or it cannot be placed: a projection or
2287
+ * optimistic family, a store chained over another store's proxy, a root
2288
+ * named twice, or a proxy reached through a store other than the one it
2289
+ * was named as the root of.
2290
+ */
2291
+ function storeName(x) {
2292
+ if (x === undefined) return undefined;
2293
+ let t = x._config & CONFIG_SLOT_NODE ? x._host : x[$TARGET];
2294
+ if (t === undefined) return undefined;
2295
+ // Up to the root; a target on the way named as a root of its own is one
2296
+ // object in two stores.
2297
+ while (t.u !== null) {
2298
+ if (t.fam !== null || t.ch || signalName(t) !== undefined) return undefined;
2299
+ t = t.u;
2300
+ }
2301
+ if (t.fam !== null || t.ch) return undefined;
2302
+ return signalName(t) || undefined;
2303
+ }
2304
+
2027
2305
  /**
2028
2306
  * Low-level reactive-cleanup primitive. Registers a callback that runs when
2029
2307
  * the surrounding owner is disposed.
@@ -2112,6 +2390,7 @@ function createSignal(first, second) {
2112
2390
  }
2113
2391
  const node = signal(first, second);
2114
2392
  registerGraph(node, getOwner());
2393
+ if (signalHook !== null) signalHook(node);
2115
2394
  return [accessor(node), setSignal.bind(null, node)];
2116
2395
  }
2117
2396
  function createOptimistic(first, second) {
@@ -2193,7 +2472,7 @@ function createMemo(compute, options) {
2193
2472
  * );
2194
2473
  * ```
2195
2474
  *
2196
- * @description https://docs.solidjs.com/reference/basic-reactivity/create-effect
2475
+ * @description https://v2.solidjs.com/reference/solid-js/reactivity/create-effect
2197
2476
  */
2198
2477
  function createEffect(compute, effectFn, options) {
2199
2478
  if (effectFn === undefined) {
@@ -2242,7 +2521,7 @@ function createEffect(compute, effectFn, options) {
2242
2521
  * }
2243
2522
  * ```
2244
2523
  *
2245
- * @description https://docs.solidjs.com/reference/secondary-primitives/create-render-effect
2524
+ * @description https://v2.solidjs.com/reference/solid-js/advanced/specialized-reactivity/create-render-effect
2246
2525
  */
2247
2526
  function createRenderEffect(compute, effectFn, options) {
2248
2527
  effect(compute, effectFn, undefined, options);
@@ -2290,7 +2569,7 @@ function createRenderEffect(compute, effectFn, options) {
2290
2569
  * });
2291
2570
  * ```
2292
2571
  *
2293
- * @description https://docs.solidjs.com/reference/secondary-primitives/create-tracked-effect
2572
+ * @description https://v2.solidjs.com/reference/solid-js/advanced/specialized-reactivity/create-tracked-effect
2294
2573
  */
2295
2574
  function createTrackedEffect(compute, options) {
2296
2575
  trackedEffect(compute, options);
@@ -2319,7 +2598,7 @@ function createTrackedEffect(compute, options) {
2319
2598
  * setCount(1); // logs once, reaction re-armed for next change
2320
2599
  * ```
2321
2600
  *
2322
- * @description https://docs.solidjs.com/reference/secondary-primitives/create-reaction
2601
+ * @description https://v2.solidjs.com/reference/solid-js/advanced/specialized-reactivity/create-reaction
2323
2602
  */
2324
2603
  function createReaction(effectFn, options) {
2325
2604
  let cl = undefined;
@@ -2389,15 +2668,21 @@ function watch(fn, onValue, onError, config = 0, inPass, own = flushTransaction)
2389
2668
  // after: the first pass's own error is the promise's rejection.)
2390
2669
  computed(
2391
2670
  () => {
2392
- let v;
2671
+ let v,
2672
+ failed = false;
2393
2673
  try {
2394
2674
  v = fn();
2395
2675
  } catch (e) {
2396
2676
  if (e instanceof NotReadyError) throw e;
2397
- queueMicrotask(() => onError(unwrapStatusError(e)));
2398
- return;
2677
+ // An error is an answer (D-41): delivered by the same frame rule.
2678
+ v = unwrapStatusError(e);
2679
+ failed = true;
2399
2680
  }
2400
2681
  const c = getOwner();
2682
+ // A truth reader waits out a `createDeferred` flight its value derives
2683
+ // from, which serves everyone else the stale answer (D-40).
2684
+ const lag = config & CONFIG_AUTHORITATIVE && GlobalQueue._lagging?.(c);
2685
+ if (lag) throw new NotReadyError(lag);
2401
2686
  // (Outside a flush a mainline pass joins through `passTx`.)
2402
2687
  const joined = flushTransaction ?? passTx;
2403
2688
  if (
@@ -2408,6 +2693,7 @@ function watch(fn, onValue, onError, config = 0, inPass, own = flushTransaction)
2408
2693
  staleReader(c, resolveTx(joined));
2409
2694
  return;
2410
2695
  }
2696
+ if (failed) return void queueMicrotask(() => onError(v));
2411
2697
  inPass?.(v);
2412
2698
  queueMicrotask(() => onValue(v));
2413
2699
  },
@@ -2769,122 +3055,6 @@ function onSettled(callback) {
2769
3055
  });
2770
3056
  }
2771
3057
 
2772
- /**
2773
- * Ownership stamp (#3360): every backing the store ALLOCATES (CoW clones,
2774
- * privatized committed backings) carries its owning target under this
2775
- * enumerable symbol. One property write replaces the two weak-collection
2776
- * registrations (ownership set + raw→target map) a fresh object used to pay
2777
- * per draft — V8's identity-hash + ephemeron cost dominated the one-key
2778
- * write floor. Enumerable so a spread copy (the plain-data clone path) stays
2779
- * on the fast path and carries the stamp along.
2780
- *
2781
- * Owned backings are never user-reachable (`snapshot` copies them, the traps
2782
- * hide the key), so every raw key walk in the store must skip `$OWNER`, and
2783
- * ownership is answered by `isOwned` — a user object never carries it.
2784
- * Overlay drafts (`Object.create(v)` over an owned `v`) inherit the stamp.
2785
- */
2786
- const $OWNER = Symbol("STORE_OWNER");
2787
- /** raw → target for plain (family-less) stores. Family stores use their own
2788
- * map so one raw can wrap once per family (§7b). */
2789
- const storeLookup = new WeakMap();
2790
- function setLookup(raw, t) {
2791
- (t.fam?.map ?? storeLookup).set(raw, t);
2792
- }
2793
- function isOwned(raw) {
2794
- return raw[$OWNER] !== undefined;
2795
- }
2796
- /** The target wrapping `raw` in `fam` (or the plain lookup). An owned raw
2797
- * answers from its stamp; a user raw from the map. */
2798
- function lookupTarget(raw, fam) {
2799
- const owner = raw[$OWNER];
2800
- return owner !== undefined && owner.fam === fam ? owner : (fam?.map ?? storeLookup).get(raw);
2801
- }
2802
- function devAssertNeverUserMutation(target) {
2803
- return;
2804
- }
2805
- /** Sticky descendants flag up the parent chain (§6d): adoption descends
2806
- * into a changed child pair only where a target with nodes exists below. */
2807
- function markDescendants(target) {
2808
- let t = target;
2809
- while (t && !t.d) {
2810
- t.d = true;
2811
- t = t.u;
2812
- }
2813
- }
2814
- /** Observe tier: the owner in scope at `createStore` (attribution names and
2815
- * owner paths); the `_owner` write never reaches the proxy. */
2816
- const storeOwners = new WeakMap();
2817
- /** Brand keys the proxy answers directly (never user data). */
2818
- const $TRACK = Symbol("STORE_TRACK"),
2819
- $TARGET = Symbol("STORE_TARGET"),
2820
- $PROXY = Symbol("STORE_PROXY"),
2821
- $RECORD = Symbol("VIEW_RECORD");
2822
- // ---------------------------------------------------------------------------
2823
- // raw marking (R42 / RUL-12: a value is never both wrapped and raw)
2824
- const rawValues = new WeakSet();
2825
- let rawValuesUsed = false;
2826
- function isRawValue(value) {
2827
- return rawValuesUsed && rawValues.has(value);
2828
- }
2829
- /** Mark a value so no store ever wraps it (sticky; it reads as a leaf). */
2830
- function markRaw(value) {
2831
- if (isWrappable(value)) {
2832
- rawValuesUsed = true;
2833
- rawValues.add(value);
2834
- }
2835
- return value;
2836
- }
2837
- /** A shallow store's ingested record: raw by contract (values served
2838
- * verbatim, slots replaced by reference). */
2839
- function markRawOne(v) {
2840
- if (isWrappable(v)) {
2841
- if (v[$TARGET] !== undefined) return;
2842
- if (lookupTarget(v, null) !== undefined)
2843
- throw new Error(
2844
- "shallow store: an ingested record is already tracked as a deep store — one value cannot present both wrapped and raw"
2845
- );
2846
- rawValuesUsed = true;
2847
- rawValues.add(v);
2848
- }
2849
- }
2850
- function markRawIngest(container) {
2851
- if (Array.isArray(container)) {
2852
- for (let i = 0, len = container.length; i < len; i++) markRawOne(container[i]);
2853
- } else {
2854
- for (const k in container) markRawOne(container[k]);
2855
- }
2856
- }
2857
- // ---------------------------------------------------------------------------
2858
- // wrappability
2859
- const OBJECT_PROTO = Object.prototype;
2860
- const wrappableProtos = new WeakMap();
2861
- function isWrappable(obj) {
2862
- if (obj == null || typeof obj !== "object" || Object.isFrozen(obj)) return false;
2863
- const proto = Object.getPrototypeOf(obj);
2864
- if (proto === OBJECT_PROTO || proto === null) return true;
2865
- if (Array.isArray(obj)) return true;
2866
- let wrappable = wrappableProtos.get(proto);
2867
- if (wrappable === undefined) {
2868
- wrappable =
2869
- Object.prototype.toString.call(obj) === "[object Object]" &&
2870
- (typeof Node === "undefined" || !(obj instanceof Node));
2871
- wrappableProtos.set(proto, wrappable);
2872
- }
2873
- return wrappable;
2874
- }
2875
- function ownEnumerableKeys(o) {
2876
- return Reflect.ownKeys(o).filter(k => Object.prototype.propertyIsEnumerable.call(o, k));
2877
- }
2878
- // ---------------------------------------------------------------------------
2879
- // write override: a projection's draft ops carry it (the derive is the
2880
- // author — its writes must land outside the sync setter scope too, in a
2881
- // continuation after an `await`/`yield`). Projections return in S3; the
2882
- // traps already honour it.
2883
- let writeOverride = false;
2884
- function setWriteOverride(value) {
2885
- writeOverride = value;
2886
- }
2887
-
2888
3058
  function mapArray(list, map, options) {
2889
3059
  const keyFn = typeof options?.keyed === "function" ? options.keyed : undefined;
2890
3060
  const indexes = map.length > 1;
@@ -3058,6 +3228,79 @@ function commitSmallMove(data, newItems, newLen, srcPos, dstPos, runs) {
3058
3228
  }
3059
3229
  return true;
3060
3230
  }
3231
+ /** Stage a PURE REMOVAL without the key map: when the new window is the old
3232
+ * window with items dropped, in order (one-item removals, every-nth, the
3233
+ * table filters), a two-pointer walk lands every survivor in the window
3234
+ * stage with one key call per item and returns the leavers; a shape that is
3235
+ * not an ordered subsequence returns null with the node stage cleared, and
3236
+ * the map path below starts from scratch (it rewrites every window slot of
3237
+ * `temp` / `rows` / `indexes`, so only `tempNodes`' `!== undefined` probe
3238
+ * needs the reset). The stage arrays are scratch — nothing live is touched. */
3239
+ function stageRemoval(data, newItems, start, end, newEnd, temp, tempNodes, rows, indexes) {
3240
+ const key = data._key;
3241
+ const oldItems = data._items;
3242
+ const nodes = data._nodes;
3243
+ const removed = [];
3244
+ let i = start;
3245
+ for (let j = start; j <= newEnd; i++, j++) {
3246
+ const nk = key ? key(newItems[j]) : newItems[j];
3247
+ while (i <= end && (key ? key(oldItems[i]) : oldItems[i]) !== nk) removed.push(nodes[i++]);
3248
+ if (i > end) {
3249
+ tempNodes.fill(undefined);
3250
+ return null;
3251
+ }
3252
+ temp[j] = data._mappings[i];
3253
+ tempNodes[j - start] = nodes[i];
3254
+ if (rows) rows[j - start] = data._rows[i];
3255
+ if (indexes) indexes[j - start] = data._indexes[i];
3256
+ }
3257
+ for (; i <= end; i++) removed.push(nodes[i]);
3258
+ return removed;
3259
+ }
3260
+ /** Land a staged window into the live bookkeeping arrays in place: shift the
3261
+ * suffix by `dif` (a loop walked from the side that never overwrites an
3262
+ * unread slot), take the window from the stage, write survivors' row and
3263
+ * index signals. Its own function so the general path's body stays under
3264
+ * the JIT's optimization budget — the inlined commit measured ~10% slower on
3265
+ * full-window reorders (the small-move note has the same finding). */
3266
+ function commitWindow(data, newItems, newLen, start, end, newEnd, tempNodes, rows, indexes) {
3267
+ const nodes = data._nodes;
3268
+ const liveRows = data._rows;
3269
+ const liveIndexes = data._indexes;
3270
+ const oldLen = data._len;
3271
+ const dif = newLen - oldLen;
3272
+ let i, j;
3273
+ if (dif > 0) {
3274
+ // Grow first: a store past the end from the top is a third slower than
3275
+ // filling a pre-sized tail (measured on 1k refs, 2026-10-15).
3276
+ nodes.length = newLen;
3277
+ if (liveRows) liveRows.length = newLen;
3278
+ if (liveIndexes) liveIndexes.length = newLen;
3279
+ for (i = oldLen - 1, j = newLen - 1; i > end; i--, j--) {
3280
+ nodes[j] = nodes[i];
3281
+ if (liveRows) liveRows[j] = liveRows[i];
3282
+ if (liveIndexes) liveIndexes[j] = liveIndexes[i];
3283
+ }
3284
+ } else if (dif < 0) {
3285
+ for (i = end + 1, j = newEnd + 1; i < oldLen; i++, j++) {
3286
+ nodes[j] = nodes[i];
3287
+ if (liveRows) liveRows[j] = liveRows[i];
3288
+ if (liveIndexes) liveIndexes[j] = liveIndexes[i];
3289
+ }
3290
+ nodes.length = newLen;
3291
+ if (liveRows) liveRows.length = newLen;
3292
+ if (liveIndexes) liveIndexes.length = newLen;
3293
+ }
3294
+ for (j = start; j <= newEnd; j++) {
3295
+ nodes[j] = tempNodes[j - start];
3296
+ if (liveRows) setSignal((liveRows[j] = rows[j - start]), newItems[j]);
3297
+ if (liveIndexes) setSignal((liveIndexes[j] = indexes[j - start]), j);
3298
+ }
3299
+ for (j = newEnd + 1; j < newLen; j++) {
3300
+ if (liveRows) setSignal(liveRows[j], newItems[j]);
3301
+ if (liveIndexes && dif !== 0) setSignal(liveIndexes[j], j);
3302
+ }
3303
+ }
3061
3304
  function updateKeyedMap() {
3062
3305
  // Untracked reads inside the internal owner resolve via _parentComputed:
3063
3306
  // routed through the node, a store-proxy lookup sees pending writes (not a
@@ -3072,6 +3315,10 @@ function updateKeyedMap() {
3072
3315
  runWithOwner(this._owner, () => {
3073
3316
  let i,
3074
3317
  j,
3318
+ // The stage slot a mapper writes its row/index signal into: `j` on the
3319
+ // create-all path, `j - start` on the general path, whose stage arrays
3320
+ // are sized to the changed WINDOW, not the list.
3321
+ slot,
3075
3322
  rows,
3076
3323
  indexes,
3077
3324
  // Mappers write freshly-created row/index signals into the STAGE
@@ -3079,19 +3326,19 @@ function updateKeyedMap() {
3079
3326
  mapper = this._rows
3080
3327
  ? this._byIndex
3081
3328
  ? () => {
3082
- rows[j] = signal(newItems[j], pureOptions);
3083
- return this._map(accessor(rows[j]), j);
3329
+ rows[slot] = signal(newItems[j], pureOptions);
3330
+ return this._map(accessor(rows[slot]), j);
3084
3331
  }
3085
3332
  : () => {
3086
- rows[j] = signal(newItems[j], pureOptions);
3087
- indexes && (indexes[j] = signal(j, pureOptions));
3088
- return this._map(accessor(rows[j]), indexes ? accessor(indexes[j]) : undefined);
3333
+ rows[slot] = signal(newItems[j], pureOptions);
3334
+ indexes && (indexes[slot] = signal(j, pureOptions));
3335
+ return this._map(accessor(rows[slot]), indexes ? accessor(indexes[slot]) : undefined);
3089
3336
  }
3090
3337
  : this._indexes
3091
3338
  ? () => {
3092
3339
  const item = newItems[j];
3093
- indexes[j] = signal(j, pureOptions);
3094
- return this._map(item, accessor(indexes[j]));
3340
+ indexes[slot] = signal(j, pureOptions);
3341
+ return this._map(item, accessor(indexes[slot]));
3095
3342
  }
3096
3343
  : () => {
3097
3344
  const item = newItems[j];
@@ -3122,7 +3369,10 @@ function updateKeyedMap() {
3122
3369
  rows = this._rows && new Array(newLen);
3123
3370
  indexes = this._indexes && new Array(newLen);
3124
3371
  try {
3125
- for (j = 0; j < newLen; j++) mappings[j] = runWithOwner((nodes[j] = createOwner()), mapper);
3372
+ for (j = 0; j < newLen; j++) {
3373
+ slot = j;
3374
+ mappings[j] = runWithOwner((nodes[j] = createOwner()), mapper);
3375
+ }
3126
3376
  } catch (err) {
3127
3377
  for (i = 0; i <= j; i++) nodes[i]?.dispose();
3128
3378
  throw err;
@@ -3224,81 +3474,89 @@ function updateKeyedMap() {
3224
3474
  if (k <= hi && trySmallMove(this, newItems, newLen, start)) return;
3225
3475
  }
3226
3476
  const dif = newLen - this._len;
3477
+ // WINDOW-SIZED STAGING, IN-PLACE COMMIT (2026-10-15). The strong-abort
3478
+ // contract (#2903) only needs the pass to touch nothing live before
3479
+ // every mapper has succeeded; it never needed four fresh newLen-sized
3480
+ // arrays. Only the changed window [start, newEnd] is staged — the
3481
+ // prefix and suffix are, by construction, the rows that did not move
3482
+ // relative to each other — and at commit the bookkeeping arrays
3483
+ // (`_nodes`, `_rows`, `_indexes`) take the window in place with the
3484
+ // suffix shifted by a plain loop (`copyWithin` is a generic
3485
+ // per-element builtin in V8 — 30× slower than the loop on 1k refs,
3486
+ // measured 2026-10-15). `_mappings` alone is
3487
+ // rebuilt: its fresh identity is the change signal downstream relies
3488
+ // on. A one-row edit on a 500-row list used to allocate ~20 KB of
3489
+ // staging plus a position-sized chain array per commit (uibench's
3490
+ // one-item tree ops at 4–6× Octane, jfb-reorder `removefirst` at
3491
+ // 2.5×); now it stages one slot.
3492
+ const wLen = newEnd - start + 1;
3227
3493
  const temp = new Array(newLen);
3228
- const tempNodes = new Array(newLen);
3229
- rows = this._rows ? new Array(newLen) : undefined;
3230
- indexes = this._indexes ? new Array(newLen) : undefined;
3231
- // 0) prepare a map of all indices in the changed window of newItems,
3232
- // scanning backwards so we encounter them in natural order
3233
- newIndices = new Map();
3234
- newIndicesNext = new Array(newEnd + 1);
3235
- for (j = newEnd; j >= start; j--) {
3236
- item = newItems[j];
3237
- key = this._key ? this._key(item) : item;
3238
- i = newIndices.get(key);
3239
- newIndicesNext[j] = i === undefined ? -1 : i;
3240
- newIndices.set(key, j);
3241
- }
3242
- // 1) step through the old changed window and see if items can be found
3243
- // in the new set; if so, stage them at their new positions; if not,
3244
- // queue them for disposal at commit
3245
- for (i = start; i <= end; i++) {
3246
- item = this._items[i];
3247
- key = this._key ? this._key(item) : item;
3248
- j = newIndices.get(key);
3249
- if (j !== undefined && j !== -1) {
3250
- temp[j] = this._mappings[i];
3251
- tempNodes[j] = this._nodes[i];
3252
- rows && (rows[j] = this._rows[i]);
3253
- indexes && (indexes[j] = this._indexes[i]);
3254
- j = newIndicesNext[j];
3494
+ const tempNodes = new Array(wLen);
3495
+ rows = this._rows ? new Array(wLen) : undefined;
3496
+ indexes = this._indexes ? new Array(wLen) : undefined;
3497
+ // A shrinking pass is tried as a pure removal first (stageRemoval: no
3498
+ // key map, one key call per item); anything else takes the map path.
3499
+ const pure =
3500
+ dif < 0
3501
+ ? stageRemoval(this, newItems, start, end, newEnd, temp, tempNodes, rows, indexes)
3502
+ : null;
3503
+ if (pure !== null) {
3504
+ // (The list-churn census below wants removals AND creations in one
3505
+ // pass; a pure removal has none, so no item ledger is kept here.)
3506
+ if (pure.length !== 0) removed = pure;
3507
+ } else {
3508
+ // 0) prepare a map of all indices in the changed window of newItems,
3509
+ // scanning backwards so we encounter them in natural order
3510
+ newIndices = new Map();
3511
+ newIndicesNext = new Array(wLen);
3512
+ for (j = newEnd; j >= start; j--) {
3513
+ item = newItems[j];
3514
+ key = this._key ? this._key(item) : item;
3515
+ i = newIndices.get(key);
3516
+ newIndicesNext[j - start] = i === undefined ? -1 : i;
3255
3517
  newIndices.set(key, j);
3256
- } else {
3257
- (removed ??= []).push(this._nodes[i]);
3258
- if (true && attrHooks !== null) (removedItems ??= []).push(item);
3259
- }
3260
- }
3261
- // 2) create new rows into the temp arrays; an abort disposes only these
3262
- try {
3263
- for (j = start; j <= newEnd; j++) {
3264
- if (tempNodes[j] !== undefined) continue;
3265
- (created ??= []).push((tempNodes[j] = createOwner()));
3266
- if (true && attrHooks !== null) (createdItems ??= []).push(newItems[j]);
3267
- temp[j] = runWithOwner(tempNodes[j], mapper);
3268
3518
  }
3269
- } catch (err) {
3270
- if (created) for (i = 0; i < created.length; i++) created[i].dispose();
3271
- throw err;
3272
- }
3273
- // 3) commit: land the retained prefix and suffix plus the staged window
3274
- // into the fresh arrays, swap them in (new identity for downstream
3275
- // change propagation), then dispose exited rows
3276
- for (i = 0; i < start; i++) {
3277
- temp[i] = this._mappings[i];
3278
- tempNodes[i] = this._nodes[i];
3279
- rows && (rows[i] = this._rows[i]);
3280
- indexes && (indexes[i] = this._indexes[i]);
3281
- }
3282
- for (j = start; j <= newEnd; j++) {
3283
- if (rows) write(rows[j], newItems[j]);
3284
- if (indexes) write(indexes[j], j);
3285
- }
3286
- for (j = newEnd + 1; j < newLen; j++) {
3287
- temp[j] = this._mappings[j - dif];
3288
- tempNodes[j] = this._nodes[j - dif];
3289
- if (rows) {
3290
- rows[j] = this._rows[j - dif];
3291
- write(rows[j], newItems[j]);
3519
+ // 1) step through the old changed window and see if items can be found
3520
+ // in the new set; if so, stage them at their new positions; if not,
3521
+ // queue them for disposal at commit
3522
+ for (i = start; i <= end; i++) {
3523
+ item = this._items[i];
3524
+ key = this._key ? this._key(item) : item;
3525
+ j = newIndices.get(key);
3526
+ if (j !== undefined && j !== -1) {
3527
+ temp[j] = this._mappings[i];
3528
+ tempNodes[j - start] = this._nodes[i];
3529
+ rows && (rows[j - start] = this._rows[i]);
3530
+ indexes && (indexes[j - start] = this._indexes[i]);
3531
+ j = newIndicesNext[j - start];
3532
+ newIndices.set(key, j);
3533
+ } else {
3534
+ (removed ??= []).push(this._nodes[i]);
3535
+ if (true && attrHooks !== null) (removedItems ??= []).push(item);
3536
+ }
3292
3537
  }
3293
- if (indexes) {
3294
- indexes[j] = this._indexes[j - dif];
3295
- if (dif !== 0) write(indexes[j], j);
3538
+ // 2) create new rows into the window stage; an abort disposes only these
3539
+ try {
3540
+ for (j = start; j <= newEnd; j++) {
3541
+ if (tempNodes[j - start] !== undefined) continue;
3542
+ (created ??= []).push((tempNodes[j - start] = createOwner()));
3543
+ if (true && attrHooks !== null) (createdItems ??= []).push(newItems[j]);
3544
+ slot = j - start;
3545
+ temp[j] = runWithOwner(tempNodes[j - start], mapper);
3546
+ }
3547
+ } catch (err) {
3548
+ if (created) for (i = 0; i < created.length; i++) created[i].dispose();
3549
+ throw err;
3296
3550
  }
3297
3551
  }
3552
+ // 3) commit: the retained prefix and suffix land in the fresh mapped
3553
+ // array; the bookkeeping arrays take the staged window IN PLACE (own
3554
+ // function: kept out of this body on purpose, see commitWindow); then
3555
+ // the exited rows dispose
3556
+ for (i = 0; i < start; i++) temp[i] = this._mappings[i];
3557
+ for (j = newEnd + 1; j < newLen; j++) temp[j] = this._mappings[j - dif];
3558
+ commitWindow(this, newItems, newLen, start, end, newEnd, tempNodes, rows, indexes);
3298
3559
  this._mappings = temp;
3299
- this._nodes = tempNodes;
3300
- rows && (this._rows = rows);
3301
- indexes && (this._indexes = indexes);
3302
3560
  this._len = newLen;
3303
3561
  // save a copy of the mapped items for the next update
3304
3562
  this._items = newItems.slice(0);
@@ -3328,7 +3586,7 @@ function updateKeyedMap() {
3328
3586
  * const view = repeat(count, i => `Item ${i}`, { fallback: () => "empty" });
3329
3587
  * ```
3330
3588
  *
3331
- * @description https://docs.solidjs.com/reference/reactive-utilities/repeat
3589
+ * @description https://v2.solidjs.com/reference/solid-js/advanced/jsx-component-primitives/repeat
3332
3590
  */
3333
3591
  function repeat(count, map, options) {
3334
3592
  const wrappedMap = options?.name
@@ -3658,11 +3916,16 @@ function noteNode(target, node, key) {
3658
3916
  // dispatches CONFIG_SLOT_NODE nodes here (graph.ts) instead of holding a
3659
3917
  // per-node closure in a per-node NodeExtension. A dropped node is
3660
3918
  // unreachable through the store — a fresh read makes a fresh node.
3919
+ // A staged write — or a lane's value (a guess, S4) — is state only the node
3920
+ // holds: its release waits for the commit that resolves it. So does a leaf of
3921
+ // a held container (#4146): a mainline write it carried is in the held
3922
+ // staging too, where it would read as the hold's until the landing.
3923
+ const slotKept = node =>
3924
+ node._pendingValue !== NOT_PENDING ||
3925
+ node._config & CONFIG_OVERRIDE ||
3926
+ node._host.k?._config & CONFIG_HELD;
3661
3927
  setSlotUnobserved(node => {
3662
- // A staged write — or a lane's value (a guess, S4) — is state only the
3663
- // node holds: defer the release to the commit that resolves it.
3664
- if (node._pendingValue !== NOT_PENDING || node._config & CONFIG_OVERRIDE)
3665
- deferredReleases.add(node);
3928
+ if (slotKept(node)) deferredReleases.add(node);
3666
3929
  else releaseSlot(node);
3667
3930
  });
3668
3931
  /** A node born while its container has a staging is born from the two
@@ -3722,7 +3985,9 @@ function getNode(
3722
3985
  // the hottest store path, and the disabled cost must stay one null
3723
3986
  // check (nodes created before enable() stay generically named).
3724
3987
  if (attrHooks !== null) {
3725
- created._name = storeLabel(target) + "." + String(key);
3988
+ const root = storeRoot(target);
3989
+ created._name = (storeNames.get(root) ?? "store") + "." + String(key);
3990
+ created._loc = storeLocs.get(root);
3726
3991
  stampNodeOwner(created, target);
3727
3992
  }
3728
3993
  nodes[key] = node;
@@ -3775,7 +4040,7 @@ function getContainerNode(target) {
3775
4040
  * staging: nothing allocates, nothing is swept at the commit (dbmon's tick
3776
4041
  * re-created, queued, swept and dropped ~7000 container nodes). If the
3777
4042
  * flush parks, the seam materializes and holds the node then
3778
- * (`_storePark`) — the one case the staging home exists for. */
4043
+ * (`storeParkHook`) — the one case the staging home exists for. */
3779
4044
  function stageOn(target, pb) {
3780
4045
  if (
3781
4046
  target.k === null &&
@@ -3798,7 +4063,7 @@ function deriveHolds(target) {
3798
4063
  const fw = target.fam?.node;
3799
4064
  return fw != null && !userWrite && (fw._config & CONFIG_HELD) !== 0;
3800
4065
  }
3801
- GlobalQueue._storePark = t => {
4066
+ const storePark = t => {
3802
4067
  for (let i = 0; i < foldList.length; i++) {
3803
4068
  const target = foldList[i];
3804
4069
  if (target.k === null && target.pb !== null) holdNode(getContainerNode(target), t);
@@ -3846,11 +4111,16 @@ function stampNodeOwner(created, target) {
3846
4111
  created._owner = storeRootOwner(target) ?? null;
3847
4112
  }
3848
4113
  const storeNames = new WeakMap();
3849
- function nameStore(proxy, name) {
3850
- if (attrHooks !== null && name) storeNames.set(proxy[$TARGET], name);
3851
- }
3852
- function storeLabel(target) {
3853
- return storeNames.get(storeRoot(target)) ?? "store";
4114
+ // The store's `loc` option, carried onto every property node beside its
4115
+ // label (the same gate: nodes created before the engine is installed carry
4116
+ // neither).
4117
+ const storeLocs = new WeakMap();
4118
+ function nameStore(proxy, name, loc) {
4119
+ if (attrHooks !== null) {
4120
+ const target = proxy[$TARGET];
4121
+ if (name) storeNames.set(target, name);
4122
+ if (loc) storeLocs.set(target, loc);
4123
+ }
3854
4124
  }
3855
4125
  // ---------------------------------------------------------------------------
3856
4126
  // clones (CoW privatization)
@@ -3934,7 +4204,8 @@ function overlayRebuilds(t, pb) {
3934
4204
  if (t.kc > OVERLAY_REBUILD_MAX_KEYS) return false;
3935
4205
  if (t.del !== null && t.del.size !== 0) return true;
3936
4206
  const wk = t.wk;
3937
- if (wk !== null && wk !== WK_ALL && wk.size <= OVERLAY_REBUILD_MIN_ADDS) return false;
4207
+ if (wk !== null && wk !== WK_ALL && wk.u === undefined && wk.size <= OVERLAY_REBUILD_MIN_ADDS)
4208
+ return false;
3938
4209
  const v = t.v;
3939
4210
  let adds = 0;
3940
4211
  for (const key of Reflect.ownKeys(pb))
@@ -4164,11 +4435,7 @@ function drainFolds() {
4164
4435
  }
4165
4436
  if (deferredReleases.size !== 0) {
4166
4437
  for (const node of deferredReleases) {
4167
- if (
4168
- node._pendingValue === NOT_PENDING &&
4169
- !(node._config & CONFIG_OVERRIDE) &&
4170
- node._subs === null
4171
- ) {
4438
+ if (node._subs === null && !slotKept(node)) {
4172
4439
  deferredReleases.delete(node);
4173
4440
  releaseSlot(node);
4174
4441
  }
@@ -4197,10 +4464,10 @@ GlobalQueue._slotCovered = n => {
4197
4464
  }
4198
4465
  return n._value;
4199
4466
  };
4200
- GlobalQueue._storeCommit = () => {
4467
+ installStoreHooks(storePark, () => {
4201
4468
  drainFolds();
4202
4469
  optHooks?.sweep();
4203
- };
4470
+ });
4204
4471
  /** The store half of `affects()` (store/affects.ts) makes its marks' nodes
4205
4472
  * and walks records through these: it never imports the engine. */
4206
4473
  GlobalQueue._storeNode = getNode;
@@ -4248,10 +4515,11 @@ function foldTarget(t, old) {
4248
4515
  const v = t.v;
4249
4516
  const wk = t.wk;
4250
4517
  if (wk !== null && wk !== WK_ALL) {
4251
- for (const key of wk) {
4252
- if (hasOwn$1.call(pb, key)) copyOwn(v, pb, key);
4253
- else delete v[key];
4254
- }
4518
+ for (let w = wk; w !== undefined; w = w.u)
4519
+ for (const key of w) {
4520
+ if (hasOwn$1.call(pb, key)) copyOwn(v, pb, key);
4521
+ else delete v[key];
4522
+ }
4255
4523
  } else {
4256
4524
  for (const key of Reflect.ownKeys(pb)) {
4257
4525
  const d = Object.getOwnPropertyDescriptor(pb, key);
@@ -4424,7 +4692,10 @@ function notifyWrites(t) {
4424
4692
  // value can change when ANY key is written), or a non-plain prototype
4425
4693
  // (class instances: prototype getters derive from arbitrary fields).
4426
4694
  // Overlay pbs chain to the COMMITTED object (#3044) — plainness is the
4427
- // committed container's prototype, not the overlay's.
4695
+ // committed container's prototype, not the overlay's. THIS setter's keys
4696
+ // only (D-8, #3743): a key an earlier setter of the batch wrote and this
4697
+ // one did not is no second proposal — its node was told then, and telling
4698
+ // a held node its unchanged value again would join this tick to the hold.
4428
4699
  const wk0 = t.wk;
4429
4700
  const writtenKeys = wk0 === WK_ALL || t.a === true || !plainProto(t.ovl ? t.v : pb) ? null : wk0;
4430
4701
  if (attrHooks !== null) reportReplacedContainers(t, old, pb, writtenKeys);
@@ -5084,6 +5355,22 @@ function optRead(target) {
5084
5355
  const UNSAFE_KEYS = new Set(["__proto__", "prototype", "constructor"]);
5085
5356
  // ---------------------------------------------------------------------------
5086
5357
  // traps
5358
+ /** A trap write: the target is told at the setter's exit, the key recorded
5359
+ * in this setter's `wk`. A setter's first write to a target still staged by
5360
+ * an earlier setter of the batch opens a set of its own (the notify visits
5361
+ * it, D-8) and chains the earlier one behind for the fold's union. */
5362
+ function touch(target, key) {
5363
+ let wk = target.wk;
5364
+ if (!pendingNotify.has(target)) {
5365
+ pendingNotify.add(target);
5366
+ if (wk !== null && wk !== WK_ALL) {
5367
+ wk = new Set();
5368
+ wk.u = target.wk;
5369
+ target.wk = wk;
5370
+ }
5371
+ }
5372
+ if (wk !== WK_ALL) (target.wk ??= new Set()).add(key);
5373
+ }
5087
5374
  const traps = {
5088
5375
  get(target, key, receiver) {
5089
5376
  // One typeof gates every brand-symbol compare off the hot string path.
@@ -5331,16 +5618,23 @@ const traps = {
5331
5618
  if (key === "__proto__") return true; // pollution guard (core R30)
5332
5619
  const uv = target.s ? value : unwrapValue(value);
5333
5620
  const pb = ensurePB(target);
5334
- pendingNotify.add(target);
5621
+ // #4146 (R1: node existence is unobservable; #3706: the hold's unit is
5622
+ // the key): a user setter writing a key a HELD container's staging left
5623
+ // unchanged would write it into the held staging — read as the hold's
5624
+ // until its landing, while the same key with a leaf (a live reader)
5625
+ // publishes at the flush. The key gets its leaf first, born from the two
5626
+ // frames (`bornStaged`: a key the hold left unchanged — or added — is
5627
+ // unstaged and held by no one; a key the hold changed is born held, A34
5628
+ // (1)), and the write stages on it (A28; a write by the holder joins it,
5629
+ // A34) — as it would with a reader's leaf. Kept while the container is
5630
+ // held (`deferredReleases`).
5631
+ if (userWrite && target.k?._config & CONFIG_HELD) deferredReleases.add(getNode(target, key));
5335
5632
  if (Array.isArray(pb)) {
5633
+ touch(target, key);
5336
5634
  if (key === "length") target.wk = WK_ALL;
5337
- else if (target.wk !== WK_ALL) {
5338
- const wk = (target.wk ??= new Set());
5339
- wk.add(key);
5340
- wk.add("length");
5341
- }
5635
+ else touch(target, "length");
5342
5636
  } else {
5343
- if (target.wk !== WK_ALL) (target.wk ??= new Set()).add(key);
5637
+ touch(target, key);
5344
5638
  if (!(key in pb)) target.kc++;
5345
5639
  }
5346
5640
  if (UNSAFE_KEYS.has(key)) {
@@ -5375,8 +5669,7 @@ const traps = {
5375
5669
  if ("value" in desc) desc = { ...desc, value: unwrapValue(desc.value) };
5376
5670
  const pb = ensurePB(target);
5377
5671
  if (target.a || !(desc.enumerable && desc.writable && desc.configurable)) target.sc = 1;
5378
- pendingNotify.add(target);
5379
- if (target.wk !== WK_ALL) (target.wk ??= new Set()).add(key);
5672
+ touch(target, key);
5380
5673
  Object.defineProperty(pb, key, desc);
5381
5674
  if (target.del !== null) target.del.delete(key);
5382
5675
  if (override) notifyWrites(target);
@@ -5387,10 +5680,22 @@ const traps = {
5387
5680
  const override = !draft && writeOverride;
5388
5681
  if (!draft && !override) return true;
5389
5682
  const pb = ensurePB(target);
5390
- pendingNotify.add(target);
5391
- if (target.wk !== WK_ALL) (target.wk ??= new Set()).add(key);
5392
- delete pb[key];
5393
- if (target.ovl && hasOwn$1.call(target.v, key)) (target.del ??= new Set()).add(key);
5683
+ touch(target, key);
5684
+ // The key count (`kc`, the overlay gate and the rebuild cap) follows a
5685
+ // delete the way it follows an add: an own key of the draft leaving — a
5686
+ // clone's key, or an overlay's own add — is one fewer now. An overlay's
5687
+ // override of a committed key is the exception: the committed key's
5688
+ // deletion is recorded in `del` and counted at the rebuild, as before.
5689
+ // Without the decrement the natural "delete the old key, set the new
5690
+ // one" shape drifted the count up by one per write until a three-key
5691
+ // object crossed the overlay threshold and paid a rebuild on every
5692
+ // commit after.
5693
+ const committed = target.ovl && hasOwn$1.call(target.v, key);
5694
+ if (hasOwn$1.call(pb, key)) {
5695
+ delete pb[key];
5696
+ committed || target.kc--;
5697
+ }
5698
+ if (committed) (target.del ??= new Set()).add(key);
5394
5699
  if (override) notifyWrites(target);
5395
5700
  return true;
5396
5701
  }
@@ -5418,7 +5723,7 @@ const ASYNC_STORE_SETTER_MESSAGE =
5418
5723
  * write violation — the user guard (`mapArray`'s pattern: the node-level
5419
5724
  * writes carry `ownedWrite`; the setter asks). */
5420
5725
  function devGuardStoreSetterWrite() {
5421
- if (context && !(context._config & CONFIG_CHILDREN_FORBIDDEN)) {
5726
+ if (context && !(context._config & CONFIG_CHILDREN_FORBIDDEN) && !disposalDepth) {
5422
5727
  emitDiagnostic({
5423
5728
  code: "REACTIVE_WRITE_IN_OWNED_SCOPE",
5424
5729
  kind: "write",
@@ -5454,6 +5759,7 @@ function devGuardStoreSetterResult(result) {
5454
5759
  }
5455
5760
  }
5456
5761
  function storeSetter(proxy, fn, guard = true) {
5762
+ if (writeHook !== null) writeHook(proxy);
5457
5763
  if (guard) devGuardStoreSetterWrite();
5458
5764
  const target = proxy[$TARGET];
5459
5765
  const prevScopes = writeScopes;
@@ -5521,6 +5827,8 @@ function createStore$1(initialValue, options) {
5521
5827
  throw new Error("createStore({ shallow }): value is already a store proxy");
5522
5828
  }
5523
5829
  const proxy = wrap(initialValue);
5830
+ // Dependency edges (store/edges.ts): the root is named like a plain signal.
5831
+ if (signalHook !== null) signalHook(proxy[$TARGET]);
5524
5832
  if (shallow) {
5525
5833
  proxy[$TARGET].s = true;
5526
5834
  markRawIngest(initialValue);
@@ -5535,7 +5843,7 @@ function createStore$1(initialValue, options) {
5535
5843
  // disabled, buys nothing.
5536
5844
  if (attrHooks !== null) {
5537
5845
  storeOwners.set(proxy[$TARGET], owner);
5538
- nameStore(proxy, options?.name);
5846
+ nameStore(proxy, options?.name, options?.loc);
5539
5847
  }
5540
5848
  }
5541
5849
  const setter = fn => storeSetter(proxy, fn);
@@ -6299,6 +6607,11 @@ function runProjectionComputed(wrappedStore, fn, key, wrapCommit, aroundDraftWri
6299
6607
  // A landing (not the sync pass's own commit): the readers held
6300
6608
  // pending on the derive settle.
6301
6609
  if (!first || !(owner._statusFlags & STATUS_UNINITIALIZED)) settleFamily(fam);
6610
+ // A commit is the derive's change for the readers that observed its
6611
+ // flight (linked by `pullFamily`; a settled pull does not link), as
6612
+ // a memo's landing is for its readers: one that caught the NotReady
6613
+ // (a fallback value, not pending) re-runs too (#4070).
6614
+ insertSubs(owner);
6302
6615
  };
6303
6616
  const sync = handleAsync(owner, result, commit, (error, pending) => {
6304
6617
  // The flight rejected: the leaves' readers learn it from here —
@@ -6340,6 +6653,9 @@ function runProjectionComputed(wrappedStore, fn, key, wrapCommit, aroundDraftWri
6340
6653
  return owner;
6341
6654
  }
6342
6655
  function createProjectionInternal(fn, seed, options) {
6656
+ // Seedless (`fn` takes no draft): the root starts as an empty record. It is
6657
+ // a draft like any seed, never observable before the first landing (A25).
6658
+ seed ||= {};
6343
6659
  const keyOption = options?.key === undefined ? "id" : options.key;
6344
6660
  const fam = {
6345
6661
  map: new WeakMap(),
@@ -6362,9 +6678,13 @@ function createProjectionInternal(fn, seed, options) {
6362
6678
  }
6363
6679
  let nodeOptions;
6364
6680
  if (options?.seedLoadingValue) nodeOptions = { loadingValue: undefined };
6365
- if (options?.name) {
6366
- nodeOptions = { ...nodeOptions, name: options.name };
6367
- nameStore(store, options.name);
6681
+ if (attrHooks !== null)
6682
+ // As createStore: the owner in scope stamps the projection's property
6683
+ // nodes, so attribution places them under it (#4035).
6684
+ storeOwners.set(store[$TARGET], getOwner());
6685
+ if (options?.name || options?.loc) {
6686
+ nodeOptions = { ...nodeOptions, name: options.name, loc: options.loc };
6687
+ nameStore(store, options.name, options.loc);
6368
6688
  }
6369
6689
  const node = computed(() => {
6370
6690
  if (!fam.node) fam.node = getOwner();
@@ -6372,14 +6692,11 @@ function createProjectionInternal(fn, seed, options) {
6372
6692
  }, nodeOptions);
6373
6693
  node._config &= ~CONFIG_AUTO_DISPOSE;
6374
6694
  fam.node = node;
6695
+ // The derive's leaves are not its dependents: a mark's repoll
6696
+ // (affects-marks.ts) reaches their readers through this.
6697
+ node.fam = fam;
6375
6698
  return { store, node };
6376
6699
  }
6377
- /**
6378
- * A derived store: `fn` runs in a computed and writes its draft; readers of
6379
- * the store's keys re-run only when the keys they read change (the
6380
- * firewall). `fn` may be async or an async iterator; the seed is a draft,
6381
- * never an observable value, until the first landing (A25).
6382
- */
6383
6700
  function createProjection(fn, seed, options) {
6384
6701
  return createProjectionInternal(fn, seed, options).store;
6385
6702
  }
@@ -6906,7 +7223,9 @@ function optimisticView(
6906
7223
  function createOptimisticStore(first, second, third) {
6907
7224
  const derived = typeof first === "function";
6908
7225
  const options = derived ? third : second;
6909
- const initialValue = derived ? second : first;
7226
+ // Seedless derived form: the root starts as an empty record (see
7227
+ // createProjectionInternal).
7228
+ const initialValue = derived ? second || {} : first;
6910
7229
  const keyOption = options?.key === undefined ? "id" : options.key;
6911
7230
  const fam = {
6912
7231
  map: new WeakMap(),
@@ -6922,7 +7241,7 @@ function createOptimisticStore(first, second, third) {
6922
7241
  : row => (isWrappable(row) ? row[keyOption] : undefined)
6923
7242
  };
6924
7243
  const store = wrap(initialValue, null, null, fam);
6925
- nameStore(store, options?.name);
7244
+ nameStore(store, options?.name, options?.loc);
6926
7245
  if (fam.shallow) {
6927
7246
  store[$TARGET].s = true;
6928
7247
  markRawIngest(initialValue);
@@ -6931,7 +7250,8 @@ function createOptimisticStore(first, second, third) {
6931
7250
  const fn = first;
6932
7251
  let nodeOptions;
6933
7252
  if (options?.seedLoadingValue) nodeOptions = { loadingValue: undefined };
6934
- if (options?.name) nodeOptions = { ...nodeOptions, name: options.name };
7253
+ if (options?.name || options?.loc)
7254
+ nodeOptions = { ...nodeOptions, name: options.name, loc: options.loc };
6935
7255
  const node = computed(() => {
6936
7256
  if (!fam.node) fam.node = getOwner();
6937
7257
  runProjectionComputed(store, fn, keyOption);
@@ -8000,6 +8320,83 @@ function deep(value) {
8000
8320
  return deep$1(value);
8001
8321
  }
8002
8322
 
8323
+ // The mark core: a declared not-final state on a node that reads pending
8324
+ // (`isPending` → true) on the node and everything derived from it, without
8325
+ // holding anything or entangling a reader. Two declarers share it:
8326
+ // `affects()` (affects.ts — a mark scoped to a transaction, with a store
8327
+ // half) and `createDeferred` (deferred.ts — a mark whose lifetime is the
8328
+ // node's own flight). Split out so the second does not ship the first.
8329
+ //
8330
+ // Representation: a count on the marked node (`_x._marks`). Coverage of the
8331
+ // derivations is pull-derived at probe time — `isPending`'s read walks the
8332
+ // probed node's dependencies for a marked one (verdict.ts → `_marked`) — so
8333
+ // nothing is stored downstream and a mid-window recompute strands nothing.
8334
+ // The push half is the verdict readers' re-derivation when a node's marks
8335
+ // start and end (`repoll`): the only notification a mark makes.
8336
+ /** Live marks, every declarer — the gate on the probe's walk. */
8337
+ let active = 0;
8338
+ /** The verdict readers downstream of `node` — through derivations, stopping
8339
+ * at effects — re-derive: the mark's one notification (a probe's answer
8340
+ * changed with no value notifying anyone). Truth readers (`until`, the
8341
+ * `refresh` waiter) too, which a lagging flight parked (deferred.ts) — but
8342
+ * not through a derived store, whose leaves change only once its derive
8343
+ * re-runs: they re-derive at that write. */
8344
+ function repoll(node, seen = new Set(), readers = CONFIG_VERDICT | CONFIG_AUTHORITATIVE) {
8345
+ for (let s = node._subs; s !== null; s = s._nextSub) {
8346
+ const sub = s._sub;
8347
+ if (seen.has(sub)) continue;
8348
+ seen.add(sub);
8349
+ if (sub._config & readers) enqueueSub(sub);
8350
+ if (!sub._type) repoll(sub, seen, readers);
8351
+ // A derived store's derive: its leaves' readers derive from it too
8352
+ // (store/projection.ts; a live target's property, has and keys nodes).
8353
+ const fam = sub.fam;
8354
+ if (fam)
8355
+ for (const t of fam.live)
8356
+ for (const o of [t.n, t.h, { k: t.k, d: t.dk }])
8357
+ if (o) for (const k of Reflect.ownKeys(o)) o[k] && repoll(o[k], seen, CONFIG_VERDICT);
8358
+ }
8359
+ }
8360
+ /** A node is covered by a live mark iff it carries one, or derives — through
8361
+ * its current dependencies — from one that does. A store slot derives from
8362
+ * its family's derive (a derived store's leaves are not the derive's
8363
+ * dependents, store/projection.ts), so coverage hops there. A real error
8364
+ * outranks an inherited mark (A24 (c)): coverage does not flow through an
8365
+ * errored node (a direct mark on one still reads pending). Mid-recompute,
8366
+ * only the validated prefix of the dependency list is this pass's. */
8367
+ function marked(el, seen) {
8368
+ if (el._x !== null && el._x._marks !== 0) return true;
8369
+ const c = el;
8370
+ if (c._statusFlags & STATUS_ERROR || seen.has(el)) return false;
8371
+ seen.add(el);
8372
+ if (c._config & CONFIG_SLOT_NODE) {
8373
+ const derive = c._host?.fam?.node;
8374
+ return derive != null && marked(derive, seen);
8375
+ }
8376
+ const tail = c._flags & REACTIVE_RECOMPUTING_DEPS ? c._depsTail : undefined;
8377
+ if (tail !== null)
8378
+ for (let d = c._deps ?? null; d !== null; d = d._nextDep) {
8379
+ if (marked(d._dep, seen)) return true;
8380
+ if (d === tail) break;
8381
+ }
8382
+ return false;
8383
+ }
8384
+ /** One mark on `node`. The declarer repolls once its bookkeeping is done. */
8385
+ function mark(node) {
8386
+ ext(node)._marks++;
8387
+ active++;
8388
+ }
8389
+ /** Drops one mark from `node`: true when it was the last — the node's
8390
+ * verdict readers have re-derived. */
8391
+ function unmark(node) {
8392
+ active--;
8393
+ if (--node._x._marks !== 0) return false;
8394
+ repoll(node);
8395
+ return true;
8396
+ }
8397
+ GlobalQueue._marked = el => active !== 0 && marked(el, new Set());
8398
+ GlobalQueue._mark = mark;
8399
+
8003
8400
  /**
8004
8401
  * Store — the store half of `affects()` (plan sec. 29, plan sec. 37; A24 (4), #2882,
8005
8402
  * #2893, #2904).
@@ -8128,7 +8525,7 @@ function releaseMarkScope(carrier) {
8128
8525
  const entry = scopes.get(carrier);
8129
8526
  if (entry === undefined) return;
8130
8527
  scopes.delete(carrier);
8131
- GlobalQueue._releaseMarks(entry._inherited);
8528
+ releaseMarksHook(entry._inherited);
8132
8529
  }
8133
8530
 
8134
8531
  // `affects()` — the declaration verb of the pending model (A24 (4), ruled
@@ -8144,62 +8541,23 @@ function releaseMarkScope(carrier) {
8144
8541
  // data joins nothing through it. A mark is never a re-ask, so a declared
8145
8542
  // reload's own `refresh()` cannot silence it; a real error outranks it.
8146
8543
  //
8147
- // Representation: a count on the marked node (`_x._marks`), listed with its
8148
- // scope (the transaction's `_marks`, or the ambient list). Coverage of the
8149
- // derivations is pull-derived at probe time — `isPending`'s read walks the
8150
- // probed node's dependencies for a marked one (verdict.ts → `_marked`) — so
8151
- // nothing is stored downstream and a mid-window recompute strands nothing.
8152
- // The push half is the verdict readers' re-derivation at registration and at
8153
- // release (`repoll`): the only notification a mark makes.
8544
+ // Representation: the mark core (affects-marks.ts) — a count on the marked
8545
+ // node, coverage pull-derived at probe time, readers repolled at registration
8546
+ // and release. Here: each mark's scope (the transaction's `_marks`, or the
8547
+ // ambient list) and the store half.
8154
8548
  //
8155
8549
  // Installed on `GlobalQueue` when imported: a program that never declares a
8156
8550
  // mark carries none of this.
8157
- /** Live marks, every scope — the gate on the probe's walk. */
8158
- let active = 0;
8159
8551
  /** Marks declared outside a transaction: released at the next seam. */
8160
8552
  const ambient = [];
8161
- /** The verdict readers downstream of `node` — through derivations, stopping
8162
- * at effects — re-derive: the mark's one notification (a probe's answer
8163
- * changed with no value notifying anyone). */
8164
- function repoll(node, seen = new Set()) {
8165
- for (let s = node._subs; s !== null; s = s._nextSub) {
8166
- const sub = s._sub;
8167
- if (seen.has(sub)) continue;
8168
- seen.add(sub);
8169
- if (sub._config & CONFIG_VERDICT) enqueueSub(sub);
8170
- if (!sub._type) repoll(sub, seen);
8171
- }
8172
- }
8173
- /** A node is covered by a live mark iff it carries one, or derives — through
8174
- * its current dependencies — from one that does. A real error outranks an
8175
- * inherited mark (A24 (c)): coverage does not flow through an errored node
8176
- * (a direct mark on one still reads pending). Mid-recompute, only the
8177
- * validated prefix of the dependency list is this pass's. */
8178
- function marked(el, seen) {
8179
- if (el._x !== null && el._x._marks !== 0) return true;
8180
- const c = el;
8181
- if (c._statusFlags & STATUS_ERROR || seen.has(el)) return false;
8182
- seen.add(el);
8183
- const tail = c._flags & REACTIVE_RECOMPUTING_DEPS ? c._depsTail : undefined;
8184
- if (tail !== null)
8185
- for (let d = c._deps ?? null; d !== null; d = d._nextDep) {
8186
- if (marked(d._dep, seen)) return true;
8187
- if (d === tail) break;
8188
- }
8189
- return false;
8190
- }
8191
8553
  /** Releases one scope's marks (a landed transaction's, the ambient list at
8192
8554
  * the seam): a node whose last mark drops re-derives its verdict readers. */
8193
8555
  function release$1(nodes) {
8194
8556
  for (let i = 0; i < nodes.length; i++) {
8195
8557
  const n = nodes[i];
8196
- active--;
8197
- if (--n._x._marks === 0) {
8198
- repoll(n);
8199
- // A store mark's carrier: its scope (and the marks the nodes born in
8200
- // its window inherited) goes with it.
8201
- releaseMarkScope(n);
8202
- }
8558
+ // A store mark's carrier: its scope (and the marks the nodes born in
8559
+ // its window inherited) goes with the last mark.
8560
+ if (unmark(n)) releaseMarkScope(n);
8203
8561
  }
8204
8562
  nodes.length = 0;
8205
8563
  }
@@ -8215,10 +8573,6 @@ function register(node) {
8215
8573
  }
8216
8574
  repoll(node);
8217
8575
  }
8218
- function mark(node) {
8219
- ext(node)._marks++;
8220
- active++;
8221
- }
8222
8576
  function affects(target, key) {
8223
8577
  if (arguments.length > 2)
8224
8578
  invalid(
@@ -8256,10 +8610,7 @@ function invalid(detail) {
8256
8610
  emitDiagnostic({ code: "INVALID_AFFECTS_TARGET", kind: "write", severity: "error", message });
8257
8611
  throw new Error(message);
8258
8612
  }
8259
- GlobalQueue._marked = el => active !== 0 && marked(el, new Set());
8260
- GlobalQueue._mark = mark;
8261
- GlobalQueue._releaseMarks = release$1;
8262
- GlobalQueue._releaseAmbientMarks = parked => {
8613
+ installMarkHooks(release$1, parked => {
8263
8614
  if (ambient.length === 0) return;
8264
8615
  // The flush parked: the declaration's window is the frame's — the marks
8265
8616
  // are its transaction's ("nothing async below ⇒ no window").
@@ -8282,7 +8633,7 @@ GlobalQueue._releaseAmbientMarks = parked => {
8282
8633
  }
8283
8634
  ambient.length = kept;
8284
8635
  if (done.length !== 0) release$1(done);
8285
- };
8636
+ });
8286
8637
  function inFlight(n) {
8287
8638
  return (
8288
8639
  (n._statusFlags & STATUS_PENDING) !== 0 ||
@@ -8290,6 +8641,174 @@ function inFlight(n) {
8290
8641
  );
8291
8642
  }
8292
8643
 
8644
+ // `createDeferred` — an async memo that may lag, but never leads (D-40,
8645
+ // docs/create-deferred.md).
8646
+ //
8647
+ // Once the node has a committed value, its own non-final work is invisible
8648
+ // to the graph: its readers keep the committed value and hold nothing, its
8649
+ // landings are changes of their own, and only the verdict (`isPending`) and
8650
+ // the truth readers (`until`, `refresh`'s waiter) see the flight.
8651
+ //
8652
+ // Built from parts the engine already has, so core pays nothing for it:
8653
+ //
8654
+ // - **Read side: the loading window** (`_loading`, async.ts). Every pass of
8655
+ // a node with a committed value opens the window before it asks, so a
8656
+ // flight serves the committed value instead of throwing, and an unready
8657
+ // source parks the node instead of pending it. `CONFIG_REDERIVE` makes a
8658
+ // source going pending a question for the node's pass (which parks) rather
8659
+ // than a status pushed through it to its readers.
8660
+ // - **Verdict: a mark** (affects-marks.ts) whose lifetime is the flight —
8661
+ // started when a pass asks a new question (a `refresh()` re-ask is quiet, F-9),
8662
+ // dropped at the landing, the rejection, a sync answer or the node's death.
8663
+ // - **Never leads:** a flight that lands while an input it read is held
8664
+ // derived from that hold's world, so it lands into the hold (`holdNode`;
8665
+ // `setSignal` stages it there). An input that changed after the pass
8666
+ // would have superseded the flight, so a held input at the landing is one
8667
+ // the pass read held.
8668
+ // - **Truth readers** wait the flight out (`GlobalQueue._lagging`, signals.ts
8669
+ // `watch`): `await refresh(d)` delivers the landing, `until` sees only
8670
+ // settled values.
8671
+ /** Deferred nodes with a flight up. */
8672
+ const lagging = new Set();
8673
+ /** The deferred node with a flight up that `c`'s value derives from, if
8674
+ * any: `c` itself, or one reached through its current dependencies. */
8675
+ function lagOf(c, seen) {
8676
+ if (lagging.has(c)) return c;
8677
+ if (seen.has(c)) return;
8678
+ seen.add(c);
8679
+ if (c._config & CONFIG_SLOT_NODE) return lagOf(c._host?.fam?.node ?? c, seen);
8680
+ const tail = c._flags & REACTIVE_RECOMPUTING_DEPS ? c._depsTail : undefined;
8681
+ if (tail !== null)
8682
+ for (let d = c._deps ?? null; d !== null; d = d._nextDep) {
8683
+ const l = lagOf(d._dep, seen);
8684
+ if (l) return l;
8685
+ if (d === tail) break;
8686
+ }
8687
+ }
8688
+ GlobalQueue._lagging = c => (lagging.size !== 0 ? lagOf(c, new Set()) : undefined);
8689
+ /**
8690
+ * Creates an async memo that may lag, but never leads. Before its first
8691
+ * answer it is an ordinary async memo: readers suspend to the nearest
8692
+ * `<Loading>`. After it, a refetch holds nothing: readers keep the last
8693
+ * committed value until the new one lands, and the rest of the screen moves
8694
+ * on without it. `isPending` reports the refetch, on the node and on what
8695
+ * derives from it. Use it for independent widgets (dashboards, feeds,
8696
+ * search results) where the previous answer with a "refreshing" indicator is
8697
+ * better than waiting with everything else.
8698
+ *
8699
+ * - It never shows a value ahead of a held change: a refetch that read a
8700
+ * held value lands with that hold.
8701
+ * - A rejected refetch errors like any memo; the stale value never masks it.
8702
+ * - `refresh(d)` is a quiet re-ask, and `await refresh(d)` and `until`
8703
+ * wait for the landing rather than the stale value.
8704
+ * - Combine with `loadingValue` for a node that never suspends at all.
8705
+ *
8706
+ * @example
8707
+ * ```tsx
8708
+ * const rows = createDeferred(() => fetchRows(store.period));
8709
+ *
8710
+ * <section class={{ stale: isPending(() => rows()) }}>
8711
+ * <For each={rows()}>{row => <Row row={row} />}</For>
8712
+ * </section>
8713
+ * ```
8714
+ */
8715
+ function createDeferred(fn, options) {
8716
+ let node;
8717
+ /** The flight in force: a promise, a stream, or the error an unready
8718
+ * source parked the node with. */
8719
+ let flight = null;
8720
+ let marked = false;
8721
+ let answered = false;
8722
+ /** The flight `r` is over. An async answer (`held`) arriving while an
8723
+ * input is held lands into that hold first. */
8724
+ const land = (r, held) => {
8725
+ if (r === null || flight !== r) return;
8726
+ flight = null;
8727
+ lagging.delete(node);
8728
+ if (held)
8729
+ for (let d = node._deps; d !== null; d = d._nextDep)
8730
+ if (d._dep._config & CONFIG_HELD) {
8731
+ holdNode(node, txOf(d._dep));
8732
+ break;
8733
+ }
8734
+ if (marked) {
8735
+ marked = false;
8736
+ unmark(node);
8737
+ schedule();
8738
+ }
8739
+ };
8740
+ const ask = r => {
8741
+ flight = r;
8742
+ lagging.add(node);
8743
+ if (!marked && !(node._flags & REACTIVE_REASK)) {
8744
+ marked = true;
8745
+ mark(node);
8746
+ repoll(node);
8747
+ }
8748
+ };
8749
+ /** Registers the flight and serves the pass's value: the committed one,
8750
+ * or over a held landing the landing itself, which a lagging question
8751
+ * does not replace (D-40) — restaged as the committed value, the hold's
8752
+ * readers would re-derive against the old input. */
8753
+ const serve = r => {
8754
+ const v = handleAsync(node, r);
8755
+ return node._config & CONFIG_HELD && node._pendingValue !== NOT_PENDING
8756
+ ? node._pendingValue
8757
+ : v;
8758
+ };
8759
+ const end = () => node._flags & REACTIVE_DISPOSED && land(flight);
8760
+ return accessor(
8761
+ computed(prev => {
8762
+ node = getOwner();
8763
+ node._config |= CONFIG_REDERIVE;
8764
+ cleanup(end);
8765
+ // Answered: the node has a real committed answer — not a first load,
8766
+ // not a `loadingValue` window still open (both are loading, F-9).
8767
+ // Sticky, and read before this wrapper ever opens the window itself.
8768
+ const open = (answered ||= !(node._statusFlags & STATUS_UNINITIALIZED) && !node._loading);
8769
+ if (open) ((node._loading = true), (node._config |= CONFIG_LAGS));
8770
+ let r;
8771
+ try {
8772
+ r = fn(prev);
8773
+ } catch (e) {
8774
+ // The wrapping form: an unready source parks the node in the window,
8775
+ // and the wait is its flight until the source's settle re-runs it.
8776
+ if (open && e instanceof NotReadyError) ask(e);
8777
+ throw e;
8778
+ }
8779
+ if (open && typeof r === "object" && r !== null) {
8780
+ if (isThenable(r)) {
8781
+ ask(r);
8782
+ const done = () => land(r, true);
8783
+ r.then(done, done);
8784
+ return serve(r);
8785
+ }
8786
+ const iterate = r[Symbol.asyncIterator];
8787
+ if (iterate) {
8788
+ // A stream's flight ends at its first yield (or its end).
8789
+ const it = iterate.call(r);
8790
+ let first = true;
8791
+ const done = () => land(s, true);
8792
+ const s = {
8793
+ // Registered ahead of the engine's own reaction to the same
8794
+ // promise, as for a plain flight: verdict and value in one flush.
8795
+ next: v => {
8796
+ const p = it.next(v);
8797
+ if (first) ((first = false), p.then(done, done));
8798
+ return p;
8799
+ },
8800
+ return: v => it.return?.(v) ?? Promise.resolve({ done: true, value: v })
8801
+ };
8802
+ ask(s);
8803
+ return serve({ [Symbol.asyncIterator]: () => s });
8804
+ }
8805
+ }
8806
+ land(flight);
8807
+ return r;
8808
+ }, options)
8809
+ );
8810
+ }
8811
+
8293
8812
  // CARVE 4: `flatten` lived in boundaries.ts but is the general children
8294
8813
  // resolver the renderers (`web`, `universal`, `solid`'s `children()`) walk
8295
8814
  // through — not a boundary feature. Moved verbatim so the boundary carve
@@ -8381,7 +8900,7 @@ function flattenArray(children, results = [], options) {
8381
8900
  * the root holds the frame as for any reader. The tree's own status takes
8382
8901
  * the same route from the output's pass (`read(tree)` throws it).
8383
8902
  *
8384
- * The hold (`blocked`, scheduler.ts) asks `hidden`: a reader behind a
8903
+ * The hold (`blocked`, scheduler.ts) asks `hiddenBy`: a reader behind a
8385
8904
  * fallback — any boundary on its chain whose last pass chose the fallback —
8386
8905
  * is not on screen and holds nothing. An error boundary flipping to its
8387
8906
  * fallback is the same as a loading one resetting: every reader under it
@@ -8416,6 +8935,11 @@ const boundaryOf = node => node._context[BOUNDARY];
8416
8935
  /** Catches what reaches it: an error boundary always; a loading one until it
8417
8936
  * has shown content, or while re-armed. */
8418
8937
  const isCollecting = b => b._type === STATUS_ERROR || !b._initialized || b._armed;
8938
+ /** A reader `b` waits on: recorded for its `on`, and `b` visited at the seam. */
8939
+ function record(b, node) {
8940
+ b._readers.add(node);
8941
+ collecting.add(b);
8942
+ }
8419
8943
  /** The reader goes to the fallback: recorded, and the output re-derives
8420
8944
  * (a no-op while the output's own pass is running — its post-check
8421
8945
  * decides). */
@@ -8432,8 +8956,7 @@ function caught(b, node, error) {
8432
8956
  setSignal(b._error, caught);
8433
8957
  reportClientError(caught, b._owner, node);
8434
8958
  }
8435
- b._readers.add(node);
8436
- collecting.add(b);
8959
+ record(b, node);
8437
8960
  if (b._armed) flip(b);
8438
8961
  // Already showing the fallback: one more reader to wait on changes
8439
8962
  // nothing on screen. The fallback is instantiated once per show — a later
@@ -8449,8 +8972,9 @@ function caught(b, node, error) {
8449
8972
  function flip(b) {
8450
8973
  b._initialized = false;
8451
8974
  b._lane = b._armLane;
8452
- if (b._lane !== null && b._output !== null)
8453
- GlobalQueue._laneStage(b._output, b._lane, true, false);
8975
+ const o = b._output;
8976
+ if (!b._lane && o._config & CONFIG_HELD && txOf(o) !== flushTransaction) unhold(o);
8977
+ if (b._lane !== null && b._output !== null) laneStageHook(b._output, b._lane, true, false);
8454
8978
  }
8455
8979
  function redraw(b) {
8456
8980
  if (b._output !== null && !(b._output._flags & (REACTIVE_DISPOSED | REACTIVE_ZOMBIE))) {
@@ -8458,7 +8982,7 @@ function redraw(b) {
8458
8982
  schedule();
8459
8983
  }
8460
8984
  }
8461
- /** GlobalQueue._catch: status from a frame reader, nearest boundary first.
8985
+ /** `catchHook` (scheduler.ts): status from a frame reader, nearest boundary first.
8462
8986
  * A loading boundary on the way records a pending reader whether or not it
8463
8987
  * catches it (its `on` may collect it later). */
8464
8988
  function catchStatus(node, flags, error) {
@@ -8471,7 +8995,7 @@ function catchStatus(node, flags, error) {
8471
8995
  // lands with its run, not a round after). Others still waiting: nothing
8472
8996
  // on screen changes, the fallback stays as it is.
8473
8997
  if (collecting.size !== 0)
8474
- for (let b = boundaryOf(node); b !== undefined; b = b._parent ?? undefined)
8998
+ for (let b = boundaryOf(node); b; b = b._parent)
8475
8999
  if (
8476
9000
  !(node._statusFlags & unsettled(b)) &&
8477
9001
  b._readers.delete(node) &&
@@ -8482,28 +9006,29 @@ function catchStatus(node, flags, error) {
8482
9006
  }
8483
9007
  return false;
8484
9008
  }
8485
- for (let b = boundaryOf(node); b !== undefined; b = b._parent ?? undefined) {
9009
+ for (let b = boundaryOf(node); b; b = b._parent) {
8486
9010
  if (!(b._type & flags)) continue;
8487
9011
  if (isCollecting(b)) {
8488
9012
  caught(b, node, error);
8489
- return true;
9013
+ return b._armed ? CAUGHT_REARMED : true;
8490
9014
  }
8491
- b._readers.add(node);
8492
- collecting.add(b);
9015
+ record(b, node);
8493
9016
  }
8494
9017
  return false;
8495
9018
  }
8496
- /** The nearest boundary on `r`'s chain showing its fallback, if any. */
9019
+ /** The nearest loading boundary that would catch `node` was re-armed this
9020
+ * flush. */
9021
+ function armedOver(node) {
9022
+ for (let b = boundaryOf(node); b; b = b._parent)
9023
+ if (b._type & STATUS_PENDING && isCollecting(b)) return b._armed;
9024
+ }
9025
+ /** The nearest boundary on `r`'s chain showing its fallback, if any
9026
+ * (`hiddenHook`: `r` is behind a fallback). */
8497
9027
  function hiddenBy(r) {
8498
- for (let b = boundaryOf(r); b !== undefined; b = b._parent ?? undefined)
8499
- if (b._fallback) return b;
9028
+ for (let b = boundaryOf(r); b; b = b._parent) if (b._fallback) return b;
8500
9029
  return undefined;
8501
9030
  }
8502
- /** GlobalQueue._hidden: behind a fallback. */
8503
- function hidden(r) {
8504
- return hiddenBy(r) !== undefined;
8505
- }
8506
- /** GlobalQueue._heldRun: a queued run behind a fallback waits for the reveal
9031
+ /** `heldRunHook` (scheduler.ts): a queued run behind a fallback waits for the reveal
8507
9032
  * (maintainer, 2026-10-02: the queue is held, the synchronous first render
8508
9033
  * goes through). A user effect's, which would read a DOM that is not
8509
9034
  * attached ("many depend on reading the DOM, something that can't happen
@@ -8548,7 +9073,10 @@ function prune(b, pass) {
8548
9073
  for (const r of b._readers) {
8549
9074
  if (r._flags & REACTIVE_DISPOSED) b._readers.delete(r);
8550
9075
  else if (!(r._statusFlags & mask)) {
8551
- if (r !== b._tree && (r._config & (CONFIG_HELD | CONFIG_OVERRIDE)) === CONFIG_HELD) {
9076
+ if (
9077
+ (r !== b._tree || b._armed) &&
9078
+ (r._config & (CONFIG_HELD | CONFIG_OVERRIDE)) === CONFIG_HELD
9079
+ ) {
8552
9080
  // Landed, held: the content is that transaction's — the output's
8553
9081
  // pass enters it and the reveal lands with the reader's run. The
8554
9082
  // seam, past its park decision, waits for the commit instead. (A
@@ -8578,7 +9106,7 @@ function ready(b) {
8578
9106
  prune(b, false) === 0)
8579
9107
  );
8580
9108
  }
8581
- /** GlobalQueue._boundarySeam, end of the seam: readers that settled, died,
9109
+ /** `boundarySeamHook` (scheduler.ts), end of the seam: readers that settled, died,
8582
9110
  * or landed and committed are dropped; a fallback with none left reveals
8583
9111
  * next round. An arm resolves: nothing collected, nothing happened. */
8584
9112
  function boundarySeam() {
@@ -8591,6 +9119,18 @@ function boundarySeam() {
8591
9119
  // A parked frame's boundary (zombie) keeps its state for its revival.
8592
9120
  if (flags & REACTIVE_ZOMBIE) continue;
8593
9121
  prune(b, false);
9122
+ const o = b._output;
9123
+ if (
9124
+ b._armed &&
9125
+ !b._initialized &&
9126
+ o._config & CONFIG_HELD &&
9127
+ [...b._readers].every(
9128
+ r => !(r._statusFlags & STATUS_PENDING) && r._config & CONFIG_HELD && txOf(r) === txOf(o)
9129
+ )
9130
+ ) {
9131
+ b._readers.clear();
9132
+ b._initialized = true;
9133
+ }
8594
9134
  if (b._armed && !b._initialized && !b._ahead) reportOutsideHold(b);
8595
9135
  b._armed = false;
8596
9136
  if (b._readers.size === 0) {
@@ -8617,15 +9157,17 @@ function reportOutsideHold(b) {
8617
9157
  if (outside(s._sub, false)) return true;
8618
9158
  return false;
8619
9159
  }
8620
- if (hidden(reader)) return false;
8621
- for (let o = boundaryOf(reader); o !== undefined; o = o._parent ?? undefined)
8622
- if (o === b) return false;
9160
+ if (hiddenBy(reader)) return false;
9161
+ for (let o = boundaryOf(reader); o; o = o._parent) if (o === b) return false;
8623
9162
  return true;
8624
9163
  };
8625
9164
  for (const r of b._readers) {
8626
9165
  const sources = r._x?._pendingSources;
8627
9166
  if (sources === undefined) continue;
8628
9167
  for (const source of sources) {
9168
+ // Only the flight the change itself started (D-16): an outside reader
9169
+ // of an earlier write's flight holds nothing the change waits on.
9170
+ if (source._time !== clock) continue;
8629
9171
  for (let s = source._subs; s !== null; s = s._nextSub) {
8630
9172
  if (!outside(s._sub, true)) continue;
8631
9173
  const name = source._name;
@@ -8650,15 +9192,6 @@ function reportOutsideHold(b) {
8650
9192
  }
8651
9193
  }
8652
9194
  }
8653
- /** The `on` pass is a verdict's: it probed itself (CONFIG_VERDICT), or it
8654
- * read a memo that did (#3528's `createMemo(() => isPending(m2))`) — its
8655
- * swap is display-ahead either way. */
8656
- function verdictDerived(c) {
8657
- if (c._config & CONFIG_VERDICT) return true;
8658
- for (let d = c._deps; d !== null; d = d._nextDep)
8659
- if (d._dep._config & CONFIG_VERDICT) return true;
8660
- return false;
8661
- }
8662
9195
  /** `on` changed after the mount: fresh again if anything under it is
8663
9196
  * pending now; collecting for the rest of the flush either way. */
8664
9197
  function arm(b) {
@@ -8682,20 +9215,32 @@ function arm(b) {
8682
9215
  return;
8683
9216
  }
8684
9217
  b._armed = true;
8685
- // A verdict read in `on` (`isPending`) whose frame has no transaction
8686
- // yet: the swap is still display-ahead — the frame gets one now (it lands
9218
+ // A verdict read in `on` (`isPending`, or a memo of one — #3528's
9219
+ // `createMemo(() => isPending(m2))`: the mark carries, core.ts `read`)
9220
+ // whose frame has no transaction yet: the swap is still display-ahead — the frame gets one now (it lands
8687
9221
  // at this very seam if nothing holds it), and the swap is its verdict
8688
9222
  // lane's work, shown ahead of the frame's own runs (#3528).
8689
9223
  let lane = passLane;
8690
- if (lane === null && globalQueue._running && verdictDerived(getOwner())) {
9224
+ if (lane === null && globalQueue._running && getOwner()._config & CONFIG_VERDICT) {
8691
9225
  joinFuture(null);
8692
9226
  lane = GlobalQueue._verdictLane(flushTransaction);
8693
9227
  }
8694
9228
  b._armLane = lane;
8695
9229
  b._ahead = lane !== null;
8696
9230
  collecting.add(b);
9231
+ const n = b._readers.size;
9232
+ heldUnder(b._owner, flushTransaction, r => record(b, r));
8697
9233
  for (const r of b._readers) {
8698
- if (r._statusFlags & STATUS_PENDING && !(r._flags & REACTIVE_DISPOSED)) {
9234
+ if (
9235
+ b._readers.size !== n ||
9236
+ (r._statusFlags & STATUS_PENDING && !(r._flags & REACTIVE_DISPOSED))
9237
+ ) {
9238
+ // What it collects was forwarded through it to the boundaries above
9239
+ // too (`catchStatus` records a reader in every boundary on the way).
9240
+ // It is this boundary's alone now (B5, the nearest catcher): an outer
9241
+ // re-armed by the same tick does not wait for it (D-14, U-4).
9242
+ for (let a = b._parent; a !== null; a = a._parent)
9243
+ for (const o of b._readers) a._readers.delete(o);
8699
9244
  flip(b);
8700
9245
  redraw(b);
8701
9246
  return;
@@ -8828,8 +9373,7 @@ function createBoundary(type, fn, show, onFn) {
8828
9373
  }
8829
9374
  // Forwarded: a loading boundary showing content keeps it and
8830
9375
  // holds the frame like any reader (recorded for its `on`).
8831
- b._readers.add(tree);
8832
- collecting.add(b);
9376
+ record(b, tree);
8833
9377
  }
8834
9378
  // A pending passes an error boundary by (A6); an error, a loading
8835
9379
  // one; a loading boundary showing content forwards its pending.
@@ -8869,8 +9413,7 @@ function createBoundary(type, fn, show, onFn) {
8869
9413
  }
8870
9414
  // Forwarded: a loading boundary showing content keeps it and
8871
9415
  // holds the frame like any reader (recorded for its `on`).
8872
- b._readers.add(tree);
8873
- collecting.add(b);
9416
+ record(b, tree);
8874
9417
  }
8875
9418
  // A pending passes an error boundary by (A6).
8876
9419
  throw e;
@@ -9031,12 +9574,17 @@ function createErrorBoundary(fn, fallback) {
9031
9574
  }
9032
9575
  // Installed at module evaluation — present exactly when something imports a
9033
9576
  // boundary. An app without one pays the three null checks and nothing else.
9034
- GlobalQueue._catch = catchStatus;
9035
- GlobalQueue._fresh = node =>
9036
- node._statusFlags & STATUS_UNINITIALIZED && catchStatus(node, STATUS_PENDING);
9037
- GlobalQueue._hidden = hidden;
9038
- GlobalQueue._boundarySeam = boundarySeam;
9039
- GlobalQueue._heldRun = heldRun;
9577
+ installBoundaryHooks(
9578
+ catchStatus,
9579
+ (node, held) =>
9580
+ !held &&
9581
+ (node._statusFlags & STATUS_UNINITIALIZED
9582
+ ? catchStatus(node, STATUS_PENDING)
9583
+ : armedOver(node)),
9584
+ hiddenBy,
9585
+ boundarySeam,
9586
+ heldRun
9587
+ );
9040
9588
 
9041
9589
  /**
9042
9590
  * Reveal order — `createRevealOrder` (`<Reveal>`): the order in which a group
@@ -9279,6 +9827,7 @@ export {
9279
9827
  affects,
9280
9828
  configureClientErrors,
9281
9829
  createContext,
9830
+ createDeferred,
9282
9831
  createEffect,
9283
9832
  createErrorBoundary,
9284
9833
  createLoadingBoundary,
@@ -9324,6 +9873,9 @@ export {
9324
9873
  resolvedTable,
9325
9874
  runWithOwner,
9326
9875
  setContext,
9876
+ setReadWatch,
9877
+ setSignalIdentities,
9878
+ signalName,
9327
9879
  snapshot,
9328
9880
  sourceGet,
9329
9881
  sourceHas,
@@ -9332,6 +9884,7 @@ export {
9332
9884
  storeHasFamily,
9333
9885
  storeHasOptimisticFamily,
9334
9886
  storeIsShallow,
9887
+ storeName,
9335
9888
  storePath,
9336
9889
  until,
9337
9890
  untrack,