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

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 (90) hide show
  1. package/dist/dev-shared.js +2268 -1734
  2. package/dist/dev.attribution.js +75 -52
  3. package/dist/dev.js +1348 -658
  4. package/dist/observe/affects-marks.js +86 -0
  5. package/dist/observe/affects.js +22 -73
  6. package/dist/observe/boundaries.js +192 -190
  7. package/dist/observe/core/async.js +177 -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 +5 -3
  12. package/dist/observe/core/core.js +520 -364
  13. package/dist/observe/core/dev.js +66 -38
  14. package/dist/observe/core/effect.js +50 -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 +60 -44
  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 +238 -260
  21. package/dist/observe/core/owner.js +117 -79
  22. package/dist/observe/core/scheduler.js +381 -217
  23. package/dist/observe/core/truth.js +45 -0
  24. package/dist/observe/core/verdict.js +91 -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 +47 -31
  30. package/dist/observe/store/affects.js +18 -18
  31. package/dist/observe/store/edges.js +39 -0
  32. package/dist/observe/store/optimistic.js +146 -100
  33. package/dist/observe/store/projection.js +68 -55
  34. package/dist/observe/store/store.js +180 -118
  35. package/dist/prod/affects-marks.js +86 -0
  36. package/dist/prod/affects.js +23 -72
  37. package/dist/prod/boundaries.js +156 -154
  38. package/dist/prod/core/async.js +173 -127
  39. package/dist/prod/core/constants.js +17 -3
  40. package/dist/prod/core/context.js +5 -3
  41. package/dist/prod/core/core.js +495 -343
  42. package/dist/prod/core/effect.js +70 -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 +60 -44
  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 +230 -252
  49. package/dist/prod/core/owner.js +114 -77
  50. package/dist/prod/core/scheduler.js +327 -163
  51. package/dist/prod/core/truth.js +45 -0
  52. package/dist/prod/core/verdict.js +90 -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 +42 -26
  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 +143 -98
  61. package/dist/prod/store/projection.js +25 -19
  62. package/dist/prod/store/store.js +157 -100
  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 +54 -5
  68. package/dist/types/core/dev.d.ts +51 -13
  69. package/dist/types/core/error.d.ts +9 -1
  70. package/dist/types/core/graph.d.ts +11 -0
  71. package/dist/types/core/identities.d.ts +17 -0
  72. package/dist/types/core/index.d.ts +2 -1
  73. package/dist/types/core/lanes.d.ts +2 -14
  74. package/dist/types/core/owner.d.ts +9 -3
  75. package/dist/types/core/scheduler.d.ts +70 -32
  76. package/dist/types/core/truth.d.ts +16 -0
  77. package/dist/types/core/types.d.ts +21 -0
  78. package/dist/types/core/verdict.d.ts +32 -0
  79. package/dist/types/deferred.d.ts +28 -0
  80. package/dist/types/index.d.ts +6 -0
  81. package/dist/types/map.d.ts +2 -2
  82. package/dist/types/signals.d.ts +28 -7
  83. package/dist/types/store/edges.d.ts +9 -0
  84. package/dist/types/store/index.d.ts +10 -3
  85. package/dist/types/store/optimistic.d.ts +3 -2
  86. package/dist/types/store/projection.d.ts +4 -3
  87. package/dist/types/store/store.d.ts +1 -1
  88. package/dist/types/store/target.d.ts +16 -8
  89. package/dist/types/store/types.d.ts +14 -0
  90. package/package.json +1 -1
package/dist/dev.js CHANGED
@@ -1,167 +1,230 @@
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 haltReactivityAt,
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 joinPassAt,
158
+ bC as notifyEpoch,
159
+ bD as SUPPORTS_PROXY,
160
+ bE as releaseMarksHook,
161
+ bF as installMarkHooks,
162
+ bG as CONFIG_LAGS,
163
+ bH as REACTIVE_REASK,
164
+ bI as haltReactivity,
165
+ bJ as heldUnder,
166
+ bK as unhold,
167
+ bL as laneStageHook,
168
+ bM as REACTIVE_ZOMBIE,
169
+ bN as installBoundaryHooks,
170
+ bO as CAUGHT_REARMED,
171
+ bP as OBSERVE$1
152
172
  } from "./dev-shared.js";
153
173
  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
174
+ bQ as clearSnapshots,
175
+ bR as enforceLoadingBoundary,
176
+ bS as getNextChildId,
177
+ bT as markSnapshotScope,
178
+ bU as peekNextChildId,
179
+ bV as releaseSnapshotScope,
180
+ bW as resetErrorHalt,
181
+ bX as setConsoleFooter,
182
+ bY as setSnapshotCapture,
183
+ bZ as setWriteHook
163
184
  } from "./dev-shared.js";
164
185
 
186
+ // Truth reads (`until`, `refresh`; CONFIG_AUTHORITATIVE) over lane nodes.
187
+ // Side-effect free and apart from lanes.ts so a truth reader alone does not
188
+ // retain the lane engine: its reads reach these only through `laneRead`,
189
+ // which exists once something installs lanes (`createOptimistic`, verdicts).
190
+ /** A lane node's current value: its guess, or the latest it derived — the
191
+ * staging a shown lane holds for its next reveal, else its slot; the
192
+ * committed value while it has none (its first lane pass in flight). */
193
+ function laneValueOf(el) {
194
+ if (!(el._config & CONFIG_GUESS) && el._pendingValue !== NOT_PENDING) return el._pendingValue;
195
+ const v = el._x._lane;
196
+ return v !== NOT_PENDING ? v : el._value;
197
+ }
198
+ /** Truth readers (`until`, `refresh`; CONFIG_AUTHORITATIVE) — pay-for-use:
199
+ * the first one installs these on GlobalQueue (signals.ts `watch`).
200
+ *
201
+ * A truth reader's read of a lane node: the lane's value when it is the
202
+ * truth (#3943), else the truth staged beneath a guess, else the committed
203
+ * value. */
204
+ function truthRead(el) {
205
+ if (truth(el)) return laneValueOf(el);
206
+ if (!(el._config & CONFIG_GUESS)) throw new NotReadyError(null);
207
+ return el._pendingValue !== NOT_PENDING ? el._pendingValue : el._value;
208
+ }
209
+ /** A guess confirmed: its lane's truth readers re-read — of the lane's
210
+ * derivations too, which may be the truth now (#3943). */
211
+ function truthConfirmed(l) {
212
+ for (let i = 0; i < l._nodes.length; i++)
213
+ for (let s = l._nodes[i]._subs; s !== null; s = s._nextSub)
214
+ if (s._sub._config & CONFIG_AUTHORITATIVE) enqueueSub(s._sub);
215
+ }
216
+ /** A lane node's value is the truth when every guess beneath it is
217
+ * confirmed — its source staged a value equal to the guess — and nothing
218
+ * beneath it is held in the parent: same inputs, same value. */
219
+ function truth(n) {
220
+ if (n._config & CONFIG_GUESS)
221
+ return n._pendingValue !== NOT_PENDING && n._equals && n._equals(n._x._lane, n._pendingValue);
222
+ if (n._pendingValue !== NOT_PENDING) return;
223
+ if (n._config & CONFIG_OVERRIDE)
224
+ for (let d = n._deps; d; d = d._nextDep) if (!truth(d._dep)) return;
225
+ return 1;
226
+ }
227
+
165
228
  // Lanes (plan sec. 28 replay, 2026-10-02; maintainer rulings 2026-10-01/02). A lane
166
229
  // is "a new base of a transition": the sub-frame an optimistic write opens
167
230
  // in the transition of the frame it is made in. It sees the screen plus its
@@ -201,14 +264,6 @@ function newLane(parent) {
201
264
  lanes.push(l);
202
265
  return l;
203
266
  }
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
267
  /** What the screen shows for a lane's node: the lane's value once the lane
213
268
  * has revealed, the committed truth before. */
214
269
  function display(el) {
@@ -273,23 +328,28 @@ function applyGuesses(parent) {
273
328
  const n = pendingGuesses[i];
274
329
  const v = pendingGuesses[i + 1];
275
330
  let changed = true;
331
+ // The frame already holding the slot: a guess over it is a second
332
+ // suggestion for the slot, and two cannot finish apart (A34 (1)) — the
333
+ // frames entangle.
334
+ let holder = null;
276
335
  if (n._config & CONFIG_OVERRIDE) {
277
336
  // 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.
337
+ // over (its staging is void — the guess is the value now). The same
338
+ // value re-asked renews the question; nothing to notify.
282
339
  changed = !n._equals || !n._equals(laneValueOf(n), v);
283
340
  if (!(n._config & CONFIG_GUESS)) {
284
341
  n._pendingValue = NOT_PENDING;
285
342
  n._config |= CONFIG_GUESS;
286
343
  }
287
344
  n._x._lane = v;
288
- const holder = txOf(n)._parent;
289
- if (parent !== null && holder !== null) merge$1(parent, holder);
345
+ holder = txOf(n)._parent;
290
346
  } else {
291
347
  let l;
292
- if (n._config & CONFIG_HELD) l = newLane(txOf(n));
348
+ // A held node: a truth staged there, or what a corrected guess showed.
349
+ // The guess composes on it, so it ends with that hold — and with its
350
+ // own frame's (F-11): it lands only when both have (the board's rapid
351
+ // adds — a later add's row reverted with an earlier add's landing).
352
+ if (n._config & CONFIG_HELD) l = newLane((holder = txOf(n)));
293
353
  else {
294
354
  if (parent === null && inFlight$1(n)) parent = newTransaction(false);
295
355
  if (parent === null) continue;
@@ -305,6 +365,7 @@ function applyGuesses(parent) {
305
365
  n._config |= CONFIG_OVERRIDE | CONFIG_GUESS;
306
366
  list(n, l);
307
367
  }
368
+ if (parent !== null && holder !== null) merge$1(parent, holder);
308
369
  ext(n)._q = pendingGuesses[i + 2];
309
370
  if (changed) {
310
371
  // The lane's members re-derive as its work (REACTIVE_LANE_DIRTY): a
@@ -504,37 +565,6 @@ function supersede(n, value, changed) {
504
565
  n._pendingValue = value;
505
566
  joinFuture(parent);
506
567
  }
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
568
  /** A lane's end, two ways. `into === null`: the parent landed — the lane's
539
569
  * values are the truth now: a guess lands the truth staged beneath it or
540
570
  * reverts to the one it covered (`_value`, untouched by the guess); a
@@ -668,10 +698,21 @@ function dissolveLane(l, into, except) {
668
698
  * member throws like any. */
669
699
  function laneRead(c, el) {
670
700
  const guess = el._config & CONFIG_GUESS;
671
- if (c !== null && c._config & CONFIG_AUTHORITATIVE) return GlobalQueue._truth(el);
672
- if (c === null) return laneValueOf(el);
673
701
  const l = txOf(el);
702
+ // A lane a correction folded under (D-34): store/optimistic.ts reads it.
703
+ // Before the authoritative arm: an `until` outside the lane reads the
704
+ // same truth there, and joins the hold it folded into — its posture over
705
+ // a foreign hold (D-31), which the truth read alone would not take.
706
+ const folded = c !== null && l.folded;
707
+ if (folded) {
708
+ const v = folded(c, el);
709
+ if (v !== NOT_PENDING) return v;
710
+ }
711
+ if (c !== null && c._config & CONFIG_AUTHORITATIVE) return GlobalQueue._truth(el);
674
712
  const status = el._statusFlags;
713
+ // An errored member's lane value predates its error; the error is the answer (#4106).
714
+ if (!guess && status & STATUS_ERROR) return NOT_PENDING;
715
+ if (c === null) return laneValueOf(el);
675
716
  if (!tracking || c._config & CONFIG_CHILDREN_FORBIDDEN) {
676
717
  // An untracked read inside lane work derives from the lane like a
677
718
  // tracked one, minus the subscription — `untrack` is about
@@ -839,10 +880,12 @@ function laneWrite(el, v) {
839
880
  // question is the one being asked (`question`: a slice's, a derive's
840
881
  // continuation's — the flight's); unstamped writes and mainline are
841
882
  // current.
842
- if (stale(el, question)) {
883
+ // A store re-bases a keyed arrangement guess over an older truth, and
884
+ // folds a current one beneath it (D-34): true.
885
+ const older = stale(el, question);
886
+ if (GlobalQueue._laneRebase?.(el, v, older) || older) {
843
887
  el._pendingValue = v;
844
888
  el._config |= CONFIG_HELD;
845
- GlobalQueue._laneRebase?.(el, v);
846
889
  schedule();
847
890
  return v;
848
891
  }
@@ -898,20 +941,16 @@ function answered(el) {
898
941
  }
899
942
  return q;
900
943
  }
901
- GlobalQueue._laneRead = laneRead;
902
- GlobalQueue._laneStage = laneStage;
903
- GlobalQueue._laneOutcome = laneOutcome;
904
- GlobalQueue._laneWrite = laneWrite;
905
944
  /** The lanes alive before this seam's guesses opened theirs (a prefix of
906
945
  * `lanes`; nothing dissolves one between here and the seam loop): a lane
907
946
  * born at the seam has no work to judge yet (its passes run next round) —
908
947
  * it is neither shown nor blocked until it has. */
909
948
  let judged = 0;
910
- GlobalQueue._applyGuesses = parent => {
949
+ const applyGuessesAt = parent => {
911
950
  judged = lanes.length;
912
951
  if (pendingGuesses.length !== 0) applyGuesses(parent);
913
952
  };
914
- GlobalQueue._laneSeams = leaks => {
953
+ const laneSeams = leaks => {
915
954
  // Passes that read a write the parked frame holds as the screen
916
955
  // (`stagedReaders`: lane work, a verdict reader before the verdict): they
917
956
  // re-derive on the committed world next round (and register as stale
@@ -949,8 +988,17 @@ GlobalQueue._laneSeams = leaks => {
949
988
  for (let k = Math.min(judged, lanes.length) - 1; k >= 0; k--)
950
989
  laneSeam(lanes[k], leaked.indexOf(lanes[k]) !== -1);
951
990
  };
952
- GlobalQueue._laneCorrections = () => lanes.length !== 0 && laneCorrections();
953
- GlobalQueue._endLanes = endLanes;
991
+ installLaneHooks(
992
+ laneRead,
993
+ laneStage,
994
+ laneOutcome,
995
+ laneWrite,
996
+ applyGuessesAt,
997
+ laneSeams,
998
+ () => lanes.length !== 0 && laneCorrections(),
999
+ endLanes,
1000
+ lanesBlocked
1001
+ );
954
1002
  GlobalQueue._laneGuesses = t => {
955
1003
  const out = [];
956
1004
  for (let i = 0; i < lanes.length; i++) {
@@ -962,7 +1010,6 @@ GlobalQueue._laneGuesses = t => {
962
1010
  }
963
1011
  return out;
964
1012
  };
965
- GlobalQueue._lanesBlocked = lanesBlocked;
966
1013
  GlobalQueue._verdictLane = verdictLane;
967
1014
 
968
1015
  // Verdicts (plan sec. 28 replay, 2026-10-02; maintainer, 2026-10-01: verdicts are
@@ -1116,7 +1163,7 @@ function heldNotFinal(owner) {
1116
1163
  !latestActive &&
1117
1164
  !owner._x._reask &&
1118
1165
  !(owner._statusFlags & STATUS_UNINITIALIZED) &&
1119
- (!owner._equals || !owner._equals(owner._value, owner._pendingValue))
1166
+ (!!owner._x._shownError || !owner._equals || !owner._equals(owner._value, owner._pendingValue))
1120
1167
  );
1121
1168
  }
1122
1169
  /** `isPending`'s answer for an unheld node whose flight is up (cause ii). */
@@ -1160,6 +1207,38 @@ function latest(fn) {
1160
1207
  setVerdict(prevVerdict);
1161
1208
  }
1162
1209
  }
1210
+ /**
1211
+ * Reports whether what `fn` reads is still waiting on an update. Read the
1212
+ * values in question inside `fn`: the probe answers `true` while any of
1213
+ * them depends on a change that is in flight — a write or an async source
1214
+ * that has started producing a new value whose result has not landed yet —
1215
+ * and `false` once the latest value is the one on screen. The answer is
1216
+ * tracked like any read, so a memo or effect that calls `isPending` re-runs
1217
+ * when it flips.
1218
+ *
1219
+ * A read that has no value yet is loading, not pending: inside `fn` it
1220
+ * follows the surrounding `Loading` path like any other read, and the probe
1221
+ * reports only updates to a value that already exists. A bare `refresh()`
1222
+ * does not flip `isPending`; pair it with `affects()` to make the re-ask
1223
+ * visible.
1224
+ *
1225
+ * @param fn expression to probe; read the values in question inside it
1226
+ *
1227
+ * @returns `true` while a read inside `fn` depends on a change that has not committed yet
1228
+ *
1229
+ * @example
1230
+ * ```ts
1231
+ * const [query, setQuery] = createSignal("");
1232
+ * const results = createMemo(() => searchProducts(query()));
1233
+ * const stale = createMemo(() => isPending(results));
1234
+ *
1235
+ * setQuery("shoes");
1236
+ * // stale() is true until the new results land, while results() keeps
1237
+ * // serving the previous list — dim it instead of showing a spinner.
1238
+ * ```
1239
+ *
1240
+ * @description https://v2.solidjs.com/reference/solid-js/reactivity/is-pending
1241
+ */
1163
1242
  function isPending(fn) {
1164
1243
  markVerdictReader(1);
1165
1244
  const prevProbing = probing;
@@ -1186,12 +1265,17 @@ function isPending(fn) {
1186
1265
  setVerdict(prevVerdict);
1187
1266
  }
1188
1267
  }
1268
+ /** The staging, or the staged error thrown (D-41). */
1269
+ function staged(el) {
1270
+ if (el._config & CONFIG_STAGED_ERROR) throw el._pendingValue;
1271
+ return el._pendingValue;
1272
+ }
1189
1273
  /** `latest()` of a held node: the flushed staging — the proposal — and, for
1190
1274
  * a tracked reader of a rewrite not yet flushed, the late-linker re-run in
1191
1275
  * the flush that carries it. */
1192
1276
  function heldLatest(el, c) {
1193
1277
  const u = unflushedValue(el);
1194
- if (u === NOT_PENDING) return el._pendingValue;
1278
+ if (u === NOT_PENDING) return staged(el);
1195
1279
  if (c !== null) markLateLinker(c);
1196
1280
  return u;
1197
1281
  }
@@ -1239,7 +1323,10 @@ function verdictValue(el, c) {
1239
1323
  probeFound = true;
1240
1324
  if (tracked && flushTransaction !== null) verdictRead(c, flushTransaction);
1241
1325
  }
1242
- if (el._config & CONFIG_OVERRIDE) {
1326
+ if (
1327
+ el._config & CONFIG_OVERRIDE &&
1328
+ !(owner._statusFlags & STATUS_ERROR && !(el._config & CONFIG_GUESS))
1329
+ ) {
1243
1330
  const l = txOf(el);
1244
1331
  if (tracked) verdictRead(c, l._parent ?? l);
1245
1332
  const flying = (owner._statusFlags & STATUS_PENDING) !== 0 && !quietPending(owner);
@@ -1251,6 +1338,9 @@ function verdictValue(el, c) {
1251
1338
  (!el._equals || !el._equals(el._x._lane, el._pendingValue))))
1252
1339
  )
1253
1340
  probeFound = true;
1341
+ // A lane a correction folded under (D-34): `latest()` shows the hold's
1342
+ // world whole — the truth beneath each guess, else what it covered.
1343
+ if (latestActive && l.folded) return l.folded(null, el);
1254
1344
  return el._x._lane;
1255
1345
  }
1256
1346
  if (probing && flying) probeFound = true;
@@ -1304,7 +1394,7 @@ function verdictValue(el, c) {
1304
1394
  // has not shown content) and answers for the commit, where it is final.
1305
1395
  if (el._pendingValue !== NOT_PENDING && !(owner._statusFlags & STATUS_PENDING)) {
1306
1396
  if (probing && owned) joinPass(c, el);
1307
- return el._pendingValue;
1397
+ return staged(el);
1308
1398
  }
1309
1399
  } else if (el._pendingValue !== NOT_PENDING) {
1310
1400
  if (owner._statusFlags & STATUS_PENDING && !uninitialized)
@@ -1321,7 +1411,7 @@ function verdictValue(el, c) {
1321
1411
  // A28: an unflushed write is not what `latest` serves; a tracked
1322
1412
  // reader derived from the flushed world runs again in the carrying
1323
1413
  // flush.
1324
- if (globalQueue._running) return el._pendingValue;
1414
+ if (globalQueue._running) return staged(el);
1325
1415
  if (c !== null) markLateLinker(c);
1326
1416
  }
1327
1417
  return el._value;
@@ -1343,7 +1433,7 @@ function verdictValue(el, c) {
1343
1433
  // Installed at module evaluation — present exactly when something imports
1344
1434
  // `latest` or `isPending`. (`read` dispatches to `verdictValue` through
1345
1435
  // `verdict`, set while a window is open.)
1346
- GlobalQueue._observeFlight = observeFlight;
1436
+ installVerdictHooks(observeFlight);
1347
1437
  /** A store read inside an untracked `isPending` probe found a live mark on
1348
1438
  * a record with no node to carry it (store/affects.ts): not final. */
1349
1439
  GlobalQueue._witnessMark = () => {
@@ -1589,7 +1679,7 @@ let externalSourceConfig = null;
1589
1679
  * ```
1590
1680
  */
1591
1681
  // Wires a freshly created computed through the active external-source bridge.
1592
- // Lives here (installed on GlobalQueue while a config is active) rather than
1682
+ // Lives here (installed as a scheduler hook while a config is active) rather than
1593
1683
  // inline in core: esbuild cannot literal-track the mutable config binding the
1594
1684
  // way rollup does, so an inline `if (externalSourceConfig)` block ships in
1595
1685
  // every bundle even though only enableExternalSource() can make it reachable.
@@ -1611,8 +1701,8 @@ function externalUntrack(fn) {
1611
1701
  // removed on reset) so core's presence checks stay equivalent to the old
1612
1702
  // `externalSourceConfig` truthiness checks.
1613
1703
  function syncExternalHooks() {
1614
- GlobalQueue._wireExternalSource = externalSourceConfig ? wireExternalSource : undefined;
1615
- GlobalQueue._externalUntrack = externalSourceConfig ? externalUntrack : undefined;
1704
+ if (externalSourceConfig) setExternalHooks(wireExternalSource, externalUntrack);
1705
+ else setExternalHooks(undefined, undefined);
1616
1706
  }
1617
1707
  function enableExternalSource(config) {
1618
1708
  const { factory, untrack: untrackFn = fn => fn() } = config;
@@ -1638,6 +1728,61 @@ function enableExternalSource(config) {
1638
1728
  syncExternalHooks();
1639
1729
  }
1640
1730
 
1731
+ // Deferred hydration, dependency edges: plain-signal identities.
1732
+ // While on (`setSignalIdentities`; the server-components client turns it on
1733
+ // before hydrating), a plain signal created under an id-bearing owner is
1734
+ // named `<owner id>~<n>`, n counting the plain signals (and plain store
1735
+ // roots: `createStore` over an object, store/edges.ts) created under that
1736
+ // owner in its current pass (transparent owners defer to the nearest
1737
+ // id-bearing one, as child ids do). The server names the same signal the
1738
+ // same way and restarts the count on every re-run, so an edge it shipped (a
1739
+ // slot fill read it) matches the client node; the write hook receives the
1740
+ // node and looks its name up (`signalName`). The count restarts when the owner's pass
1741
+ // generation (`_depGen`, bumped by every recompute) moves on. Only the
1742
+ // frames client imports this module, so nothing here ships without it.
1743
+ // Names live in a side table, not on the node: frames reads them across the
1744
+ // package boundary, where a mangled field would not survive.
1745
+ let counts = null;
1746
+ const names = new WeakMap();
1747
+ /** @internal The identity of a plain signal named while identities were on. */
1748
+ function signalName(node) {
1749
+ return node === undefined ? undefined : names.get(node);
1750
+ }
1751
+ function identify(node) {
1752
+ let o = getOwner();
1753
+ if (!o) return;
1754
+ while (o._config & CONFIG_TRANSPARENT && o._parent) o = o._parent;
1755
+ if (o.id == null) return;
1756
+ const gen = o._depGen;
1757
+ let c = counts.get(o);
1758
+ if (c === undefined || c[0] !== gen) counts.set(o, (c = [gen, 0]));
1759
+ const sid = o.id + "~" + c[1]++;
1760
+ // A store root named twice (one object made into two stores, or a store
1761
+ // handed to `createStore`) is one target under two server names: its
1762
+ // writes cannot be attributed (an empty name).
1763
+ names.set(node, names.has(node) ? "" : sid);
1764
+ }
1765
+ /** @internal Name plain signals from now on (`on`), or stop. */
1766
+ function setSignalIdentities(on) {
1767
+ counts = on ? counts || new WeakMap() : null;
1768
+ signalHook = on ? identify : null;
1769
+ }
1770
+ /**
1771
+ * @internal Called with every plain signal `createSignal` makes, and every
1772
+ * plain store root `createStore` makes, while set (the namer above). Never
1773
+ * set unless `setSignalIdentities` is imported, so a bundle without it keeps
1774
+ * only a null check.
1775
+ */
1776
+ let signalHook = null;
1777
+ /**
1778
+ * @internal Dev only: `watch` is called with every node read while set (the
1779
+ * dev hook `onRead`) — the frames client's identity-drift check watches the
1780
+ * named signals a deferred occurrence reads as it mounts. A no-op in prod.
1781
+ */
1782
+ function setReadWatch(watch) {
1783
+ DEV$1.hooks.onRead = watch || undefined;
1784
+ }
1785
+
1641
1786
  /**
1642
1787
  * Context provides a form of dependency injection. It is used to save from needing to pass
1643
1788
  * data as props through intermediate components. This function creates a new context object
@@ -1647,6 +1792,10 @@ function enableExternalSource(config) {
1647
1792
  * via a `setContext` call.
1648
1793
  */
1649
1794
  function createContext(defaultValue, description) {
1795
+ // Dev: an explicit `undefined` default is still no default (`undefined`
1796
+ // means "not provided"); remembered so a failed read can say so (#4181).
1797
+ if (arguments.length && defaultValue === undefined)
1798
+ return { id: Symbol(description), defaultValue, _undefinedDefault: true };
1650
1799
  return { id: Symbol(description), defaultValue };
1651
1800
  }
1652
1801
  /**
@@ -1667,7 +1816,9 @@ function getContext(context, owner = getOwner()) {
1667
1816
  let value = owner._context[context.id];
1668
1817
  if (value === undefined) value = context.defaultValue;
1669
1818
  if (value === undefined) {
1670
- throw new ContextNotFoundError();
1819
+ // Dev names the context and why it has no value: no provider, or a
1820
+ // provider that passed `undefined` (the key is set, to `undefined`).
1821
+ throw new ContextNotFoundError(context, context.id in owner._context);
1671
1822
  }
1672
1823
  return value;
1673
1824
  }
@@ -1757,7 +1908,12 @@ function notifyEffectStatus(status, error) {
1757
1908
  return;
1758
1909
  }
1759
1910
  if (!globalQueue.notify(this, STATUS_ERROR, STATUS_ERROR)) {
1760
- haltReactivity(unwrapStatusError(actualError));
1911
+ // Dev locates the halt at the node that threw: the StatusError's
1912
+ // source, upstream of this effect when a computed failed.
1913
+ haltReactivityAt(
1914
+ unwrapStatusError(actualError),
1915
+ actualError instanceof StatusError ? actualError.source : this
1916
+ );
1761
1917
  throw actualError;
1762
1918
  }
1763
1919
  } else if (this._type === EFFECT_RENDER) {
@@ -1798,7 +1954,7 @@ function runEffect(node, type) {
1798
1954
  // hidden subtree. The synchronous first render on creation is not a queued
1799
1955
  // run and goes through. `_modified` stays set; the boundary re-queues the
1800
1956
  // run (`release`).
1801
- if (GlobalQueue._heldRun && GlobalQueue._heldRun(node)) return;
1957
+ if (heldRunHook?.(node)) return;
1802
1958
  // Error arm (#2840), user effects only: a compute-phase error that is still
1803
1959
  // the node's settled state at effect time runs the bundle's error handler in
1804
1960
  // this same imperative, writable scope. Unwrap the StatusError used for
@@ -1823,7 +1979,7 @@ function runEffect(node, type) {
1823
1979
  : console.error(err);
1824
1980
  } catch (error) {
1825
1981
  if (!globalQueue.notify(node, STATUS_ERROR, STATUS_ERROR)) {
1826
- haltReactivity(error);
1982
+ haltReactivityAt(error, node);
1827
1983
  throw error;
1828
1984
  }
1829
1985
  }
@@ -1859,7 +2015,7 @@ function runEffect(node, type) {
1859
2015
  ext(node)._error = new StatusError(node, error);
1860
2016
  node._statusFlags |= STATUS_ERROR;
1861
2017
  if (!globalQueue.notify(node, STATUS_ERROR, STATUS_ERROR)) {
1862
- haltReactivity(error);
2018
+ haltReactivityAt(error, node);
1863
2019
  throw error;
1864
2020
  }
1865
2021
  } finally {
@@ -1893,7 +2049,7 @@ function trackedEffect(fn, options) {
1893
2049
  // pass, but several passes in one flush each enqueue `_run` into the same
1894
2050
  // user queue, and this gate is what collapses them into one run.
1895
2051
  if (!node._modified || node._flags & REACTIVE_DISPOSED) return;
1896
- if (GlobalQueue._heldRun && GlobalQueue._heldRun(node)) return;
2052
+ if (heldRunHook?.(node)) return;
1897
2053
  setTrackedQueueCallback(true);
1898
2054
  try {
1899
2055
  node._modified = false;
@@ -2024,6 +2180,155 @@ function reportClientError(error, owner, thrower) {
2024
2180
  }
2025
2181
  }
2026
2182
 
2183
+ /**
2184
+ * Ownership stamp (#3360): every backing the store ALLOCATES (CoW clones,
2185
+ * privatized committed backings) carries its owning target under this
2186
+ * enumerable symbol. One property write replaces the two weak-collection
2187
+ * registrations (ownership set + raw→target map) a fresh object used to pay
2188
+ * per draft — V8's identity-hash + ephemeron cost dominated the one-key
2189
+ * write floor. Enumerable so a spread copy (the plain-data clone path) stays
2190
+ * on the fast path and carries the stamp along.
2191
+ *
2192
+ * Owned backings are never user-reachable (`snapshot` copies them, the traps
2193
+ * hide the key), so every raw key walk in the store must skip `$OWNER`, and
2194
+ * ownership is answered by `isOwned` — a user object never carries it.
2195
+ * Overlay drafts (`Object.create(v)` over an owned `v`) inherit the stamp.
2196
+ */
2197
+ const $OWNER = Symbol("STORE_OWNER");
2198
+ /** raw → target for plain (family-less) stores. Family stores use their own
2199
+ * map so one raw can wrap once per family (§7b). */
2200
+ const storeLookup = new WeakMap();
2201
+ function setLookup(raw, t) {
2202
+ (t.fam?.map ?? storeLookup).set(raw, t);
2203
+ }
2204
+ function isOwned(raw) {
2205
+ return raw[$OWNER] !== undefined;
2206
+ }
2207
+ /** The target wrapping `raw` in `fam` (or the plain lookup). An owned raw
2208
+ * answers from its stamp; a user raw from the map. */
2209
+ function lookupTarget(raw, fam) {
2210
+ const owner = raw[$OWNER];
2211
+ return owner !== undefined && owner.fam === fam ? owner : (fam?.map ?? storeLookup).get(raw);
2212
+ }
2213
+ function devAssertNeverUserMutation(target) {
2214
+ return;
2215
+ }
2216
+ /** Sticky descendants flag up the parent chain (§6d): adoption descends
2217
+ * into a changed child pair only where a target with nodes exists below. */
2218
+ function markDescendants(target) {
2219
+ let t = target;
2220
+ while (t && !t.d) {
2221
+ t.d = true;
2222
+ t = t.u;
2223
+ }
2224
+ }
2225
+ /** Observe tier: the owner in scope at `createStore` (attribution names and
2226
+ * owner paths); the `_owner` write never reaches the proxy. */
2227
+ const storeOwners = new WeakMap();
2228
+ /** Brand keys the proxy answers directly (never user data). */
2229
+ const $TRACK = Symbol("STORE_TRACK"),
2230
+ $TARGET = Symbol("STORE_TARGET"),
2231
+ $PROXY = Symbol("STORE_PROXY"),
2232
+ $RECORD = Symbol("VIEW_RECORD");
2233
+ // ---------------------------------------------------------------------------
2234
+ // raw marking (R42 / RUL-12: a value is never both wrapped and raw)
2235
+ const rawValues = new WeakSet();
2236
+ let rawValuesUsed = false;
2237
+ function isRawValue(value) {
2238
+ return rawValuesUsed && rawValues.has(value);
2239
+ }
2240
+ /** Mark a value so no store ever wraps it (sticky; it reads as a leaf). */
2241
+ function markRaw(value) {
2242
+ if (isWrappable(value)) {
2243
+ rawValuesUsed = true;
2244
+ rawValues.add(value);
2245
+ }
2246
+ return value;
2247
+ }
2248
+ /** A shallow store's ingested record: raw by contract (values served
2249
+ * verbatim, slots replaced by reference). */
2250
+ function markRawOne(v) {
2251
+ if (isWrappable(v)) {
2252
+ if (v[$TARGET] !== undefined) return;
2253
+ if (lookupTarget(v, null) !== undefined)
2254
+ throw new Error(
2255
+ "shallow store: an ingested record is already tracked as a deep store — one value cannot present both wrapped and raw"
2256
+ );
2257
+ rawValuesUsed = true;
2258
+ rawValues.add(v);
2259
+ }
2260
+ }
2261
+ function markRawIngest(container) {
2262
+ if (Array.isArray(container)) {
2263
+ for (let i = 0, len = container.length; i < len; i++) markRawOne(container[i]);
2264
+ } else {
2265
+ for (const k in container) markRawOne(container[k]);
2266
+ }
2267
+ }
2268
+ // ---------------------------------------------------------------------------
2269
+ // wrappability
2270
+ const OBJECT_PROTO = Object.prototype;
2271
+ const wrappableProtos = new WeakMap();
2272
+ function isWrappable(obj) {
2273
+ if (obj == null || typeof obj !== "object" || Object.isFrozen(obj)) return false;
2274
+ const proto = Object.getPrototypeOf(obj);
2275
+ if (proto === OBJECT_PROTO || proto === null) return true;
2276
+ if (Array.isArray(obj)) return true;
2277
+ let wrappable = wrappableProtos.get(proto);
2278
+ if (wrappable === undefined) {
2279
+ wrappable =
2280
+ Object.prototype.toString.call(obj) === "[object Object]" &&
2281
+ (typeof Node === "undefined" || !(obj instanceof Node));
2282
+ wrappableProtos.set(proto, wrappable);
2283
+ }
2284
+ return wrappable;
2285
+ }
2286
+ function ownEnumerableKeys(o) {
2287
+ return Reflect.ownKeys(o).filter(k => Object.prototype.propertyIsEnumerable.call(o, k));
2288
+ }
2289
+ // ---------------------------------------------------------------------------
2290
+ // write override: a projection's draft ops carry it (the derive is the
2291
+ // author — its writes must land outside the sync setter scope too, in a
2292
+ // continuation after an `await`/`yield`). Projections return in S3; the
2293
+ // traps already honour it.
2294
+ let writeOverride = false;
2295
+ function setWriteOverride(value) {
2296
+ writeOverride = value;
2297
+ }
2298
+
2299
+ /**
2300
+ * Deferred hydration, dependency edges for stores. Only the frames client
2301
+ * imports this module, so nothing here ships without it.
2302
+ *
2303
+ * A plain store root (`createStore` over an object) is named like a plain
2304
+ * signal (core/identities.ts: `<owner id>~<n>`, on the same count), and the
2305
+ * whole store goes by that one name: the server records it for any read of
2306
+ * the store, at any depth, and a setter call on the store — on its root or
2307
+ * on a proxy nested in it — is a write to it. Its readers mount at the
2308
+ * setter's entry, before the draft runs, as a plain signal's do.
2309
+ */
2310
+ /**
2311
+ * @internal The name of the store `x` belongs to — `x` a store proxy (a
2312
+ * setter's entry) or one of its property nodes (the setter notifying it) —
2313
+ * or undefined when it has none or it cannot be placed: a projection or
2314
+ * optimistic family, a store chained over another store's proxy, a root
2315
+ * named twice, or a proxy reached through a store other than the one it
2316
+ * was named as the root of.
2317
+ */
2318
+ function storeName(x) {
2319
+ if (x === undefined) return undefined;
2320
+ let t = x._config & CONFIG_SLOT_NODE ? x._host : x[$TARGET];
2321
+ if (t === undefined) return undefined;
2322
+ // Up to the root; a target on the way named as a root of its own is one
2323
+ // object in two stores.
2324
+ while (t.u !== null) {
2325
+ if (t.fam !== null || t.ch || signalName(t) !== undefined) return undefined;
2326
+ t = t.u;
2327
+ }
2328
+ if (t.fam !== null || t.ch) return undefined;
2329
+ return signalName(t) || undefined;
2330
+ }
2331
+
2027
2332
  /**
2028
2333
  * Low-level reactive-cleanup primitive. Registers a callback that runs when
2029
2334
  * the surrounding owner is disposed.
@@ -2112,6 +2417,7 @@ function createSignal(first, second) {
2112
2417
  }
2113
2418
  const node = signal(first, second);
2114
2419
  registerGraph(node, getOwner());
2420
+ if (signalHook !== null) signalHook(node);
2115
2421
  return [accessor(node), setSignal.bind(null, node)];
2116
2422
  }
2117
2423
  function createOptimistic(first, second) {
@@ -2193,7 +2499,7 @@ function createMemo(compute, options) {
2193
2499
  * );
2194
2500
  * ```
2195
2501
  *
2196
- * @description https://docs.solidjs.com/reference/basic-reactivity/create-effect
2502
+ * @description https://v2.solidjs.com/reference/solid-js/reactivity/create-effect
2197
2503
  */
2198
2504
  function createEffect(compute, effectFn, options) {
2199
2505
  if (effectFn === undefined) {
@@ -2242,7 +2548,7 @@ function createEffect(compute, effectFn, options) {
2242
2548
  * }
2243
2549
  * ```
2244
2550
  *
2245
- * @description https://docs.solidjs.com/reference/secondary-primitives/create-render-effect
2551
+ * @description https://v2.solidjs.com/reference/solid-js/advanced/specialized-reactivity/create-render-effect
2246
2552
  */
2247
2553
  function createRenderEffect(compute, effectFn, options) {
2248
2554
  effect(compute, effectFn, undefined, options);
@@ -2290,7 +2596,7 @@ function createRenderEffect(compute, effectFn, options) {
2290
2596
  * });
2291
2597
  * ```
2292
2598
  *
2293
- * @description https://docs.solidjs.com/reference/secondary-primitives/create-tracked-effect
2599
+ * @description https://v2.solidjs.com/reference/solid-js/advanced/specialized-reactivity/create-tracked-effect
2294
2600
  */
2295
2601
  function createTrackedEffect(compute, options) {
2296
2602
  trackedEffect(compute, options);
@@ -2319,7 +2625,7 @@ function createTrackedEffect(compute, options) {
2319
2625
  * setCount(1); // logs once, reaction re-armed for next change
2320
2626
  * ```
2321
2627
  *
2322
- * @description https://docs.solidjs.com/reference/secondary-primitives/create-reaction
2628
+ * @description https://v2.solidjs.com/reference/solid-js/advanced/specialized-reactivity/create-reaction
2323
2629
  */
2324
2630
  function createReaction(effectFn, options) {
2325
2631
  let cl = undefined;
@@ -2338,7 +2644,12 @@ function createReaction(effectFn, options) {
2338
2644
  }
2339
2645
  runWithOwner(owner, () => {
2340
2646
  effect(
2341
- () => (tracking(), (arm = getOwner())),
2647
+ () => {
2648
+ const node = getOwner();
2649
+ arm = node;
2650
+ tracking();
2651
+ return node;
2652
+ },
2342
2653
  node => {
2343
2654
  arm = undefined;
2344
2655
  cl?.();
@@ -2389,15 +2700,21 @@ function watch(fn, onValue, onError, config = 0, inPass, own = flushTransaction)
2389
2700
  // after: the first pass's own error is the promise's rejection.)
2390
2701
  computed(
2391
2702
  () => {
2392
- let v;
2703
+ let v,
2704
+ failed = false;
2393
2705
  try {
2394
2706
  v = fn();
2395
2707
  } catch (e) {
2396
2708
  if (e instanceof NotReadyError) throw e;
2397
- queueMicrotask(() => onError(unwrapStatusError(e)));
2398
- return;
2709
+ // An error is an answer (D-41): delivered by the same frame rule.
2710
+ v = unwrapStatusError(e);
2711
+ failed = true;
2399
2712
  }
2400
2713
  const c = getOwner();
2714
+ // A truth reader waits out a `createDeferred` flight its value derives
2715
+ // from, which serves everyone else the stale answer (D-40).
2716
+ const lag = config & CONFIG_AUTHORITATIVE && GlobalQueue._lagging?.(c);
2717
+ if (lag) throw new NotReadyError(lag);
2401
2718
  // (Outside a flush a mainline pass joins through `passTx`.)
2402
2719
  const joined = flushTransaction ?? passTx;
2403
2720
  if (
@@ -2408,6 +2725,7 @@ function watch(fn, onValue, onError, config = 0, inPass, own = flushTransaction)
2408
2725
  staleReader(c, resolveTx(joined));
2409
2726
  return;
2410
2727
  }
2728
+ if (failed) return void queueMicrotask(() => onError(v));
2411
2729
  inPass?.(v);
2412
2730
  queueMicrotask(() => onValue(v));
2413
2731
  },
@@ -2697,192 +3015,76 @@ function until(fn, options) {
2697
3015
  * onSettled(() => {
2698
3016
  * const onResize = () => setWidth(window.innerWidth);
2699
3017
  * window.addEventListener("resize", onResize);
2700
- * return () => window.removeEventListener("resize", onResize);
2701
- * });
2702
- * return width;
2703
- * }
2704
- * ```
2705
- *
2706
- * @example
2707
- * ```tsx
2708
- * // Post-settle "ready" hook — no cleanup needed.
2709
- * function Dashboard() {
2710
- * const data = createMemo(async () => fetchData());
2711
- *
2712
- * onSettled(() => {
2713
- * analytics.track("dashboard.ready");
2714
- * });
2715
- *
2716
- * return <Loading fallback={<Spinner />}><pre>{data()}</pre></Loading>;
2717
- * }
2718
- * ```
2719
- *
2720
- * @example
2721
- * ```tsx
2722
- * // Event-handler — runs after the action settles.
2723
- * function SaveButton() {
2724
- * const save = action(function* () {
2725
- * yield api.save();
2726
- * });
2727
- *
2728
- * const handleClick = () => {
2729
- * save();
2730
- * onSettled(() => toast("Saved!"));
2731
- * };
2732
- *
2733
- * return <button onClick={handleClick}>Save</button>;
2734
- * }
2735
- * ```
2736
- *
2737
- * @param callback Function to run; may return a cleanup function that fires
2738
- * on owner disposal
2739
- */
2740
- function onSettled(callback) {
2741
- const owner = getOwner();
2742
- owner && !(owner._config & CONFIG_CHILDREN_FORBIDDEN)
2743
- ? trackedEffect(() => untrack(callback), { name: "onSettled" })
2744
- : globalQueue.enqueue(EFFECT_USER, function fire() {
2745
- // Settled means derived. A settle that reverts optimism (or replays
2746
- // gated reads) only enqueues the affected subscribers; the pass after
2747
- // the commit re-derives them. Fired in the commit pass, the callback
2748
- // read the optimistic source already reverted beside a sync memo of it
2749
- // still holding the optimistic value — reads do not pull (#3411). Fall
2750
- // to the next pass while the heap has work; `run` swapped the queue,
2751
- // so this lands there, and `enqueue` keeps the drain alive.
2752
- if (dirtyQueue._max >= dirtyQueue._min) return globalQueue.enqueue(EFFECT_USER, fire);
2753
- // Unowned, out-of-band fire (no owner, or a children-forbidden one this
2754
- // one-shot must not bind to): a returned cleanup has no lifecycle to
2755
- // attach to. Reject it in dev; in production the return is simply
2756
- // dropped — never bound to an unrelated owner or run eagerly.
2757
- const cleanup = callback();
2758
- if (cleanup !== undefined) {
2759
- const message =
2760
- "[SETTLED_CLEANUP_UNOWNED] onSettled returned a cleanup in an unowned scope; a cleanup can only be honored under an owner. Call your setup helper from an owned scope (e.g. the component body) instead of from inside an event handler, tracked effect, or another onSettled.";
2761
- emitDiagnostic({
2762
- code: "SETTLED_CLEANUP_UNOWNED",
2763
- kind: "lifecycle",
2764
- severity: "error",
2765
- message
2766
- });
2767
- throw new Error(message);
2768
- }
2769
- });
2770
- }
2771
-
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;
3018
+ * return () => window.removeEventListener("resize", onResize);
3019
+ * });
3020
+ * return width;
3021
+ * }
3022
+ * ```
3023
+ *
3024
+ * @example
3025
+ * ```tsx
3026
+ * // Post-settle "ready" hook — no cleanup needed.
3027
+ * function Dashboard() {
3028
+ * const data = createMemo(async () => fetchData());
3029
+ *
3030
+ * onSettled(() => {
3031
+ * analytics.track("dashboard.ready");
3032
+ * });
3033
+ *
3034
+ * return <Loading fallback={<Spinner />}><pre>{data()}</pre></Loading>;
3035
+ * }
3036
+ * ```
3037
+ *
3038
+ * @example
3039
+ * ```tsx
3040
+ * // Event-handler — runs after the action settles.
3041
+ * function SaveButton() {
3042
+ * const save = action(function* () {
3043
+ * yield api.save();
3044
+ * });
3045
+ *
3046
+ * const handleClick = () => {
3047
+ * save();
3048
+ * onSettled(() => toast("Saved!"));
3049
+ * };
3050
+ *
3051
+ * return <button onClick={handleClick}>Save</button>;
3052
+ * }
3053
+ * ```
3054
+ *
3055
+ * @param callback Function to run; may return a cleanup function that fires
3056
+ * on owner disposal
3057
+ */
3058
+ function onSettled(callback) {
3059
+ const owner = getOwner();
3060
+ owner && !(owner._config & CONFIG_CHILDREN_FORBIDDEN)
3061
+ ? trackedEffect(() => untrack(callback), { name: "onSettled" })
3062
+ : globalQueue.enqueue(EFFECT_USER, function fire() {
3063
+ // Settled means derived. A settle that reverts optimism (or replays
3064
+ // gated reads) only enqueues the affected subscribers; the pass after
3065
+ // the commit re-derives them. Fired in the commit pass, the callback
3066
+ // read the optimistic source already reverted beside a sync memo of it
3067
+ // still holding the optimistic value — reads do not pull (#3411). Fall
3068
+ // to the next pass while the heap has work; `run` swapped the queue,
3069
+ // so this lands there, and `enqueue` keeps the drain alive.
3070
+ if (dirtyQueue._max >= dirtyQueue._min) return globalQueue.enqueue(EFFECT_USER, fire);
3071
+ // Unowned, out-of-band fire (no owner, or a children-forbidden one this
3072
+ // one-shot must not bind to): a returned cleanup has no lifecycle to
3073
+ // attach to. Reject it in dev; in production the return is simply
3074
+ // dropped — never bound to an unrelated owner or run eagerly.
3075
+ const cleanup = callback();
3076
+ if (cleanup !== undefined) {
3077
+ const message =
3078
+ "[SETTLED_CLEANUP_UNOWNED] onSettled returned a cleanup in an unowned scope; a cleanup can only be honored under an owner. Call your setup helper from an owned scope (e.g. the component body) instead of from inside an event handler, tracked effect, or another onSettled.";
3079
+ emitDiagnostic({
3080
+ code: "SETTLED_CLEANUP_UNOWNED",
3081
+ kind: "lifecycle",
3082
+ severity: "error",
3083
+ message
3084
+ });
3085
+ throw new Error(message);
3086
+ }
3087
+ });
2886
3088
  }
2887
3089
 
2888
3090
  function mapArray(list, map, options) {
@@ -3058,6 +3260,79 @@ function commitSmallMove(data, newItems, newLen, srcPos, dstPos, runs) {
3058
3260
  }
3059
3261
  return true;
3060
3262
  }
3263
+ /** Stage a PURE REMOVAL without the key map: when the new window is the old
3264
+ * window with items dropped, in order (one-item removals, every-nth, the
3265
+ * table filters), a two-pointer walk lands every survivor in the window
3266
+ * stage with one key call per item and returns the leavers; a shape that is
3267
+ * not an ordered subsequence returns null with the node stage cleared, and
3268
+ * the map path below starts from scratch (it rewrites every window slot of
3269
+ * `temp` / `rows` / `indexes`, so only `tempNodes`' `!== undefined` probe
3270
+ * needs the reset). The stage arrays are scratch — nothing live is touched. */
3271
+ function stageRemoval(data, newItems, start, end, newEnd, temp, tempNodes, rows, indexes) {
3272
+ const key = data._key;
3273
+ const oldItems = data._items;
3274
+ const nodes = data._nodes;
3275
+ const removed = [];
3276
+ let i = start;
3277
+ for (let j = start; j <= newEnd; i++, j++) {
3278
+ const nk = key ? key(newItems[j]) : newItems[j];
3279
+ while (i <= end && (key ? key(oldItems[i]) : oldItems[i]) !== nk) removed.push(nodes[i++]);
3280
+ if (i > end) {
3281
+ tempNodes.fill(undefined);
3282
+ return null;
3283
+ }
3284
+ temp[j] = data._mappings[i];
3285
+ tempNodes[j - start] = nodes[i];
3286
+ if (rows) rows[j - start] = data._rows[i];
3287
+ if (indexes) indexes[j - start] = data._indexes[i];
3288
+ }
3289
+ for (; i <= end; i++) removed.push(nodes[i]);
3290
+ return removed;
3291
+ }
3292
+ /** Land a staged window into the live bookkeeping arrays in place: shift the
3293
+ * suffix by `dif` (a loop walked from the side that never overwrites an
3294
+ * unread slot), take the window from the stage, write survivors' row and
3295
+ * index signals. Its own function so the general path's body stays under
3296
+ * the JIT's optimization budget — the inlined commit measured ~10% slower on
3297
+ * full-window reorders (the small-move note has the same finding). */
3298
+ function commitWindow(data, newItems, newLen, start, end, newEnd, tempNodes, rows, indexes) {
3299
+ const nodes = data._nodes;
3300
+ const liveRows = data._rows;
3301
+ const liveIndexes = data._indexes;
3302
+ const oldLen = data._len;
3303
+ const dif = newLen - oldLen;
3304
+ let i, j;
3305
+ if (dif > 0) {
3306
+ // Grow first: a store past the end from the top is a third slower than
3307
+ // filling a pre-sized tail (measured on 1k refs, 2026-10-15).
3308
+ nodes.length = newLen;
3309
+ if (liveRows) liveRows.length = newLen;
3310
+ if (liveIndexes) liveIndexes.length = newLen;
3311
+ for (i = oldLen - 1, j = newLen - 1; i > end; i--, j--) {
3312
+ nodes[j] = nodes[i];
3313
+ if (liveRows) liveRows[j] = liveRows[i];
3314
+ if (liveIndexes) liveIndexes[j] = liveIndexes[i];
3315
+ }
3316
+ } else if (dif < 0) {
3317
+ for (i = end + 1, j = newEnd + 1; i < oldLen; i++, j++) {
3318
+ nodes[j] = nodes[i];
3319
+ if (liveRows) liveRows[j] = liveRows[i];
3320
+ if (liveIndexes) liveIndexes[j] = liveIndexes[i];
3321
+ }
3322
+ nodes.length = newLen;
3323
+ if (liveRows) liveRows.length = newLen;
3324
+ if (liveIndexes) liveIndexes.length = newLen;
3325
+ }
3326
+ for (j = start; j <= newEnd; j++) {
3327
+ nodes[j] = tempNodes[j - start];
3328
+ if (liveRows) setSignal((liveRows[j] = rows[j - start]), newItems[j]);
3329
+ if (liveIndexes) setSignal((liveIndexes[j] = indexes[j - start]), j);
3330
+ }
3331
+ for (j = newEnd + 1; j < newLen; j++) {
3332
+ if (liveRows) setSignal(liveRows[j], newItems[j]);
3333
+ if (liveIndexes && dif !== 0) setSignal(liveIndexes[j], j);
3334
+ }
3335
+ }
3061
3336
  function updateKeyedMap() {
3062
3337
  // Untracked reads inside the internal owner resolve via _parentComputed:
3063
3338
  // routed through the node, a store-proxy lookup sees pending writes (not a
@@ -3072,6 +3347,10 @@ function updateKeyedMap() {
3072
3347
  runWithOwner(this._owner, () => {
3073
3348
  let i,
3074
3349
  j,
3350
+ // The stage slot a mapper writes its row/index signal into: `j` on the
3351
+ // create-all path, `j - start` on the general path, whose stage arrays
3352
+ // are sized to the changed WINDOW, not the list.
3353
+ slot,
3075
3354
  rows,
3076
3355
  indexes,
3077
3356
  // Mappers write freshly-created row/index signals into the STAGE
@@ -3079,19 +3358,19 @@ function updateKeyedMap() {
3079
3358
  mapper = this._rows
3080
3359
  ? this._byIndex
3081
3360
  ? () => {
3082
- rows[j] = signal(newItems[j], pureOptions);
3083
- return this._map(accessor(rows[j]), j);
3361
+ rows[slot] = signal(newItems[j], pureOptions);
3362
+ return this._map(accessor(rows[slot]), j);
3084
3363
  }
3085
3364
  : () => {
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);
3365
+ rows[slot] = signal(newItems[j], pureOptions);
3366
+ indexes && (indexes[slot] = signal(j, pureOptions));
3367
+ return this._map(accessor(rows[slot]), indexes ? accessor(indexes[slot]) : undefined);
3089
3368
  }
3090
3369
  : this._indexes
3091
3370
  ? () => {
3092
3371
  const item = newItems[j];
3093
- indexes[j] = signal(j, pureOptions);
3094
- return this._map(item, accessor(indexes[j]));
3372
+ indexes[slot] = signal(j, pureOptions);
3373
+ return this._map(item, accessor(indexes[slot]));
3095
3374
  }
3096
3375
  : () => {
3097
3376
  const item = newItems[j];
@@ -3108,7 +3387,7 @@ function updateKeyedMap() {
3108
3387
  this._rows && (this._rows = []);
3109
3388
  this._indexes && (this._indexes = []);
3110
3389
  }
3111
- if (this._fallback && !this._mappings[0]) {
3390
+ if (this._fallback && !this._mappings.length) {
3112
3391
  // an aborted fallback attempt leaves an owner without a mapping;
3113
3392
  // dispose it before re-creating
3114
3393
  this._nodes[0]?.dispose();
@@ -3122,7 +3401,10 @@ function updateKeyedMap() {
3122
3401
  rows = this._rows && new Array(newLen);
3123
3402
  indexes = this._indexes && new Array(newLen);
3124
3403
  try {
3125
- for (j = 0; j < newLen; j++) mappings[j] = runWithOwner((nodes[j] = createOwner()), mapper);
3404
+ for (j = 0; j < newLen; j++) {
3405
+ slot = j;
3406
+ mappings[j] = runWithOwner((nodes[j] = createOwner()), mapper);
3407
+ }
3126
3408
  } catch (err) {
3127
3409
  for (i = 0; i <= j; i++) nodes[i]?.dispose();
3128
3410
  throw err;
@@ -3224,81 +3506,89 @@ function updateKeyedMap() {
3224
3506
  if (k <= hi && trySmallMove(this, newItems, newLen, start)) return;
3225
3507
  }
3226
3508
  const dif = newLen - this._len;
3509
+ // WINDOW-SIZED STAGING, IN-PLACE COMMIT (2026-10-15). The strong-abort
3510
+ // contract (#2903) only needs the pass to touch nothing live before
3511
+ // every mapper has succeeded; it never needed four fresh newLen-sized
3512
+ // arrays. Only the changed window [start, newEnd] is staged — the
3513
+ // prefix and suffix are, by construction, the rows that did not move
3514
+ // relative to each other — and at commit the bookkeeping arrays
3515
+ // (`_nodes`, `_rows`, `_indexes`) take the window in place with the
3516
+ // suffix shifted by a plain loop (`copyWithin` is a generic
3517
+ // per-element builtin in V8 — 30× slower than the loop on 1k refs,
3518
+ // measured 2026-10-15). `_mappings` alone is
3519
+ // rebuilt: its fresh identity is the change signal downstream relies
3520
+ // on. A one-row edit on a 500-row list used to allocate ~20 KB of
3521
+ // staging plus a position-sized chain array per commit (uibench's
3522
+ // one-item tree ops at 4–6× Octane, jfb-reorder `removefirst` at
3523
+ // 2.5×); now it stages one slot.
3524
+ const wLen = newEnd - start + 1;
3227
3525
  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];
3526
+ const tempNodes = new Array(wLen);
3527
+ rows = this._rows ? new Array(wLen) : undefined;
3528
+ indexes = this._indexes ? new Array(wLen) : undefined;
3529
+ // A shrinking pass is tried as a pure removal first (stageRemoval: no
3530
+ // key map, one key call per item); anything else takes the map path.
3531
+ const pure =
3532
+ dif < 0
3533
+ ? stageRemoval(this, newItems, start, end, newEnd, temp, tempNodes, rows, indexes)
3534
+ : null;
3535
+ if (pure !== null) {
3536
+ // (The list-churn census below wants removals AND creations in one
3537
+ // pass; a pure removal has none, so no item ledger is kept here.)
3538
+ if (pure.length !== 0) removed = pure;
3539
+ } else {
3540
+ // 0) prepare a map of all indices in the changed window of newItems,
3541
+ // scanning backwards so we encounter them in natural order
3542
+ newIndices = new Map();
3543
+ newIndicesNext = new Array(wLen);
3544
+ for (j = newEnd; j >= start; j--) {
3545
+ item = newItems[j];
3546
+ key = this._key ? this._key(item) : item;
3547
+ i = newIndices.get(key);
3548
+ newIndicesNext[j - start] = i === undefined ? -1 : i;
3255
3549
  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
3550
  }
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]);
3551
+ // 1) step through the old changed window and see if items can be found
3552
+ // in the new set; if so, stage them at their new positions; if not,
3553
+ // queue them for disposal at commit
3554
+ for (i = start; i <= end; i++) {
3555
+ item = this._items[i];
3556
+ key = this._key ? this._key(item) : item;
3557
+ j = newIndices.get(key);
3558
+ if (j !== undefined && j !== -1) {
3559
+ temp[j] = this._mappings[i];
3560
+ tempNodes[j - start] = this._nodes[i];
3561
+ rows && (rows[j - start] = this._rows[i]);
3562
+ indexes && (indexes[j - start] = this._indexes[i]);
3563
+ j = newIndicesNext[j - start];
3564
+ newIndices.set(key, j);
3565
+ } else {
3566
+ (removed ??= []).push(this._nodes[i]);
3567
+ if (true && attrHooks !== null) (removedItems ??= []).push(item);
3568
+ }
3292
3569
  }
3293
- if (indexes) {
3294
- indexes[j] = this._indexes[j - dif];
3295
- if (dif !== 0) write(indexes[j], j);
3570
+ // 2) create new rows into the window stage; an abort disposes only these
3571
+ try {
3572
+ for (j = start; j <= newEnd; j++) {
3573
+ if (tempNodes[j - start] !== undefined) continue;
3574
+ (created ??= []).push((tempNodes[j - start] = createOwner()));
3575
+ if (true && attrHooks !== null) (createdItems ??= []).push(newItems[j]);
3576
+ slot = j - start;
3577
+ temp[j] = runWithOwner(tempNodes[j - start], mapper);
3578
+ }
3579
+ } catch (err) {
3580
+ if (created) for (i = 0; i < created.length; i++) created[i].dispose();
3581
+ throw err;
3296
3582
  }
3297
3583
  }
3584
+ // 3) commit: the retained prefix and suffix land in the fresh mapped
3585
+ // array; the bookkeeping arrays take the staged window IN PLACE (own
3586
+ // function: kept out of this body on purpose, see commitWindow); then
3587
+ // the exited rows dispose
3588
+ for (i = 0; i < start; i++) temp[i] = this._mappings[i];
3589
+ for (j = newEnd + 1; j < newLen; j++) temp[j] = this._mappings[j - dif];
3590
+ commitWindow(this, newItems, newLen, start, end, newEnd, tempNodes, rows, indexes);
3298
3591
  this._mappings = temp;
3299
- this._nodes = tempNodes;
3300
- rows && (this._rows = rows);
3301
- indexes && (this._indexes = indexes);
3302
3592
  this._len = newLen;
3303
3593
  // save a copy of the mapped items for the next update
3304
3594
  this._items = newItems.slice(0);
@@ -3328,7 +3618,7 @@ function updateKeyedMap() {
3328
3618
  * const view = repeat(count, i => `Item ${i}`, { fallback: () => "empty" });
3329
3619
  * ```
3330
3620
  *
3331
- * @description https://docs.solidjs.com/reference/reactive-utilities/repeat
3621
+ * @description https://v2.solidjs.com/reference/solid-js/advanced/jsx-component-primitives/repeat
3332
3622
  */
3333
3623
  function repeat(count, map, options) {
3334
3624
  const wrappedMap = options?.name
@@ -3378,7 +3668,7 @@ function updateRepeat() {
3378
3668
  // Reset offset to match the cleared data (#2767, repro 2).
3379
3669
  this._offset = 0;
3380
3670
  }
3381
- if (this._fallback && !this._mappings[0]) {
3671
+ if (this._fallback && !this._mappings.length) {
3382
3672
  // an aborted fallback attempt leaves an owner without a mapping;
3383
3673
  // dispose it before re-creating
3384
3674
  this._nodes[0]?.dispose();
@@ -3658,11 +3948,16 @@ function noteNode(target, node, key) {
3658
3948
  // dispatches CONFIG_SLOT_NODE nodes here (graph.ts) instead of holding a
3659
3949
  // per-node closure in a per-node NodeExtension. A dropped node is
3660
3950
  // unreachable through the store — a fresh read makes a fresh node.
3951
+ // A staged write — or a lane's value (a guess, S4) — is state only the node
3952
+ // holds: its release waits for the commit that resolves it. So does a leaf of
3953
+ // a held container (#4146): a mainline write it carried is in the held
3954
+ // staging too, where it would read as the hold's until the landing.
3955
+ const slotKept = node =>
3956
+ node._pendingValue !== NOT_PENDING ||
3957
+ node._config & CONFIG_OVERRIDE ||
3958
+ node._host.k?._config & CONFIG_HELD;
3661
3959
  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);
3960
+ if (slotKept(node)) deferredReleases.add(node);
3666
3961
  else releaseSlot(node);
3667
3962
  });
3668
3963
  /** A node born while its container has a staging is born from the two
@@ -3678,9 +3973,13 @@ function bornStaged(target, node, staged, key) {
3678
3973
  if (k === null) return; // a node-less staging holds nothing (`stageOn`)
3679
3974
  // Held by the container's transaction only for a key the HELD staging
3680
3975
  // changed — a mainline layer's change above it stages mainline. (A
3681
- // container carrying a guess is a lane's: the flush's park decides.)
3682
- if ((k._config & (CONFIG_HELD | CONFIG_OVERRIDE)) === CONFIG_HELD && heldKeyChanged(target, key))
3683
- holdNode(node, txOf(k));
3976
+ // container carrying a guess is a lane's: the flush's park decides —
3977
+ // unless a correction folded under the guess, D-34: the key is that
3978
+ // correction's hold's.)
3979
+ if (k._config & CONFIG_HELD && heldKeyChanged(target, key)) {
3980
+ const l = txOf(k);
3981
+ if (!(k._config & CONFIG_OVERRIDE) || l.folded) holdNode(node, resolveTx(l._parent ?? l));
3982
+ }
3684
3983
  // A28 (4): a batch written inside a creation-time pass is promoted — its
3685
3984
  // late-materialized leaf with it.
3686
3985
  if (k._config & CONFIG_PROMOTED) notePromotedWrite(node);
@@ -3722,7 +4021,9 @@ function getNode(
3722
4021
  // the hottest store path, and the disabled cost must stay one null
3723
4022
  // check (nodes created before enable() stay generically named).
3724
4023
  if (attrHooks !== null) {
3725
- created._name = storeLabel(target) + "." + String(key);
4024
+ const root = storeRoot(target);
4025
+ created._name = (storeNames.get(root) ?? "store") + "." + String(key);
4026
+ created._loc = storeLocs.get(root);
3726
4027
  stampNodeOwner(created, target);
3727
4028
  }
3728
4029
  nodes[key] = node;
@@ -3775,7 +4076,7 @@ function getContainerNode(target) {
3775
4076
  * staging: nothing allocates, nothing is swept at the commit (dbmon's tick
3776
4077
  * re-created, queued, swept and dropped ~7000 container nodes). If the
3777
4078
  * flush parks, the seam materializes and holds the node then
3778
- * (`_storePark`) — the one case the staging home exists for. */
4079
+ * (`storeParkHook`) — the one case the staging home exists for. */
3779
4080
  function stageOn(target, pb) {
3780
4081
  if (
3781
4082
  target.k === null &&
@@ -3798,7 +4099,7 @@ function deriveHolds(target) {
3798
4099
  const fw = target.fam?.node;
3799
4100
  return fw != null && !userWrite && (fw._config & CONFIG_HELD) !== 0;
3800
4101
  }
3801
- GlobalQueue._storePark = t => {
4102
+ const storePark = t => {
3802
4103
  for (let i = 0; i < foldList.length; i++) {
3803
4104
  const target = foldList[i];
3804
4105
  if (target.k === null && target.pb !== null) holdNode(getContainerNode(target), t);
@@ -3846,11 +4147,16 @@ function stampNodeOwner(created, target) {
3846
4147
  created._owner = storeRootOwner(target) ?? null;
3847
4148
  }
3848
4149
  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";
4150
+ // The store's `loc` option, carried onto every property node beside its
4151
+ // label (the same gate: nodes created before the engine is installed carry
4152
+ // neither).
4153
+ const storeLocs = new WeakMap();
4154
+ function nameStore(proxy, name, loc) {
4155
+ if (attrHooks !== null) {
4156
+ const target = proxy[$TARGET];
4157
+ if (name) storeNames.set(target, name);
4158
+ if (loc) storeLocs.set(target, loc);
4159
+ }
3854
4160
  }
3855
4161
  // ---------------------------------------------------------------------------
3856
4162
  // clones (CoW privatization)
@@ -3934,7 +4240,8 @@ function overlayRebuilds(t, pb) {
3934
4240
  if (t.kc > OVERLAY_REBUILD_MAX_KEYS) return false;
3935
4241
  if (t.del !== null && t.del.size !== 0) return true;
3936
4242
  const wk = t.wk;
3937
- if (wk !== null && wk !== WK_ALL && wk.size <= OVERLAY_REBUILD_MIN_ADDS) return false;
4243
+ if (wk !== null && wk !== WK_ALL && wk.u === undefined && wk.size <= OVERLAY_REBUILD_MIN_ADDS)
4244
+ return false;
3938
4245
  const v = t.v;
3939
4246
  let adds = 0;
3940
4247
  for (const key of Reflect.ownKeys(pb))
@@ -4164,11 +4471,7 @@ function drainFolds() {
4164
4471
  }
4165
4472
  if (deferredReleases.size !== 0) {
4166
4473
  for (const node of deferredReleases) {
4167
- if (
4168
- node._pendingValue === NOT_PENDING &&
4169
- !(node._config & CONFIG_OVERRIDE) &&
4170
- node._subs === null
4171
- ) {
4474
+ if (node._subs === null && !slotKept(node)) {
4172
4475
  deferredReleases.delete(node);
4173
4476
  releaseSlot(node);
4174
4477
  }
@@ -4197,10 +4500,10 @@ GlobalQueue._slotCovered = n => {
4197
4500
  }
4198
4501
  return n._value;
4199
4502
  };
4200
- GlobalQueue._storeCommit = () => {
4503
+ installStoreHooks(storePark, () => {
4201
4504
  drainFolds();
4202
4505
  optHooks?.sweep();
4203
- };
4506
+ });
4204
4507
  /** The store half of `affects()` (store/affects.ts) makes its marks' nodes
4205
4508
  * and walks records through these: it never imports the engine. */
4206
4509
  GlobalQueue._storeNode = getNode;
@@ -4248,10 +4551,11 @@ function foldTarget(t, old) {
4248
4551
  const v = t.v;
4249
4552
  const wk = t.wk;
4250
4553
  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
- }
4554
+ for (let w = wk; w !== undefined; w = w.u)
4555
+ for (const key of w) {
4556
+ if (hasOwn$1.call(pb, key)) copyOwn(v, pb, key);
4557
+ else delete v[key];
4558
+ }
4255
4559
  } else {
4256
4560
  for (const key of Reflect.ownKeys(pb)) {
4257
4561
  const d = Object.getOwnPropertyDescriptor(pb, key);
@@ -4424,7 +4728,10 @@ function notifyWrites(t) {
4424
4728
  // value can change when ANY key is written), or a non-plain prototype
4425
4729
  // (class instances: prototype getters derive from arbitrary fields).
4426
4730
  // Overlay pbs chain to the COMMITTED object (#3044) — plainness is the
4427
- // committed container's prototype, not the overlay's.
4731
+ // committed container's prototype, not the overlay's. THIS setter's keys
4732
+ // only (D-8, #3743): a key an earlier setter of the batch wrote and this
4733
+ // one did not is no second proposal — its node was told then, and telling
4734
+ // a held node its unchanged value again would join this tick to the hold.
4428
4735
  const wk0 = t.wk;
4429
4736
  const writtenKeys = wk0 === WK_ALL || t.a === true || !plainProto(t.ovl ? t.v : pb) ? null : wk0;
4430
4737
  if (attrHooks !== null) reportReplacedContainers(t, old, pb, writtenKeys);
@@ -5084,6 +5391,63 @@ function optRead(target) {
5084
5391
  const UNSAFE_KEYS = new Set(["__proto__", "prototype", "constructor"]);
5085
5392
  // ---------------------------------------------------------------------------
5086
5393
  // traps
5394
+ /** A trap write: the target is told at the setter's exit, the key recorded
5395
+ * in this setter's `wk`. A setter's first write to a target still staged by
5396
+ * an earlier setter of the batch opens a set of its own (the notify visits
5397
+ * it, D-8) and chains the earlier one behind for the fold's union. */
5398
+ function touch(target, key) {
5399
+ let wk = target.wk;
5400
+ if (!pendingNotify.has(target)) {
5401
+ pendingNotify.add(target);
5402
+ if (wk !== null && wk !== WK_ALL) {
5403
+ wk = new Set();
5404
+ wk.u = target.wk;
5405
+ target.wk = wk;
5406
+ }
5407
+ }
5408
+ if (wk !== WK_ALL) (target.wk ??= new Set()).add(key);
5409
+ }
5410
+ /** Dev: targets (and their keys) already warned about for a write outside
5411
+ * the setter — #4179's once-per-target+key memory. */
5412
+ let warnedOutsideWrites;
5413
+ /** Dev: set while the runtime itself writes through a proxy it knows the
5414
+ * trap ignores (`registerGraph`'s `_owner` stamp). */
5415
+ let internalOutsideWrite = false;
5416
+ /** Dev: a write, defineProperty or delete on a store proxy outside its
5417
+ * setter's draft is ignored (the trap returns `true` so strict-mode code
5418
+ * does not throw) — say so once per target+key instead of silently dropping
5419
+ * it (#4179). */
5420
+ function warnWriteOutsideSetter(target, key, operation) {
5421
+ if (internalOutsideWrite) return;
5422
+ warnedOutsideWrites ??= new WeakMap();
5423
+ let keys = warnedOutsideWrites.get(target);
5424
+ if (keys === undefined) warnedOutsideWrites.set(target, (keys = new Set()));
5425
+ if (keys.has(key)) return;
5426
+ keys.add(key);
5427
+ const name = String(key);
5428
+ const access =
5429
+ typeof key === "string" && /^[A-Za-z_$][\w$]*$/.test(key)
5430
+ ? `.${key}`
5431
+ : typeof key === "string" && /^\d+$/.test(key)
5432
+ ? `[${key}]`
5433
+ : `[${typeof key === "string" ? JSON.stringify(key) : name}]`;
5434
+ const hint =
5435
+ operation === "deleteProperty"
5436
+ ? `setStore(s => { delete s${access} })`
5437
+ : `setStore(s => { s${access} = value })`;
5438
+ reportDiagnostic(
5439
+ emitDiagnostic({
5440
+ code: "STORE_WRITE_OUTSIDE_SETTER",
5441
+ kind: "write",
5442
+ severity: "warn",
5443
+ message:
5444
+ `[STORE_WRITE_OUTSIDE_SETTER] ${operation === "deleteProperty" ? "Deleting" : "Writing"} store property "${name}" outside its setter was ignored. ` +
5445
+ `Stores are read-only outside the setter's draft: use \`${hint}\`.`,
5446
+ nodeName: name,
5447
+ data: { operation, property: name }
5448
+ })
5449
+ );
5450
+ }
5087
5451
  const traps = {
5088
5452
  get(target, key, receiver) {
5089
5453
  // One typeof gates every brand-symbol compare off the hot string path.
@@ -5327,20 +5691,30 @@ const traps = {
5327
5691
  set(target, key, value) {
5328
5692
  const draft = inDraft(target);
5329
5693
  const override = !draft && writeOverride;
5330
- if (!draft && !override) return true;
5694
+ if (!draft && !override) {
5695
+ warnWriteOutsideSetter(target, key, "set");
5696
+ return true;
5697
+ }
5331
5698
  if (key === "__proto__") return true; // pollution guard (core R30)
5332
5699
  const uv = target.s ? value : unwrapValue(value);
5333
5700
  const pb = ensurePB(target);
5334
- pendingNotify.add(target);
5701
+ // #4146 (R1: node existence is unobservable; #3706: the hold's unit is
5702
+ // the key): a user setter writing a key a HELD container's staging left
5703
+ // unchanged would write it into the held staging — read as the hold's
5704
+ // until its landing, while the same key with a leaf (a live reader)
5705
+ // publishes at the flush. The key gets its leaf first, born from the two
5706
+ // frames (`bornStaged`: a key the hold left unchanged — or added — is
5707
+ // unstaged and held by no one; a key the hold changed is born held, A34
5708
+ // (1)), and the write stages on it (A28; a write by the holder joins it,
5709
+ // A34) — as it would with a reader's leaf. Kept while the container is
5710
+ // held (`deferredReleases`).
5711
+ if (userWrite && target.k?._config & CONFIG_HELD) deferredReleases.add(getNode(target, key));
5335
5712
  if (Array.isArray(pb)) {
5713
+ touch(target, key);
5336
5714
  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
- }
5715
+ else touch(target, "length");
5342
5716
  } else {
5343
- if (target.wk !== WK_ALL) (target.wk ??= new Set()).add(key);
5717
+ touch(target, key);
5344
5718
  if (!(key in pb)) target.kc++;
5345
5719
  }
5346
5720
  if (UNSAFE_KEYS.has(key)) {
@@ -5369,14 +5743,16 @@ const traps = {
5369
5743
  defineProperty(target, key, desc) {
5370
5744
  const draft = inDraft(target);
5371
5745
  const override = !draft && writeOverride;
5372
- if (!draft && !override) return true;
5746
+ if (!draft && !override) {
5747
+ warnWriteOutsideSetter(target, key, "defineProperty");
5748
+ return true;
5749
+ }
5373
5750
  if (key === "__proto__") return true;
5374
5751
  if (desc.get || desc.set) target.a = true;
5375
5752
  if ("value" in desc) desc = { ...desc, value: unwrapValue(desc.value) };
5376
5753
  const pb = ensurePB(target);
5377
5754
  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);
5755
+ touch(target, key);
5380
5756
  Object.defineProperty(pb, key, desc);
5381
5757
  if (target.del !== null) target.del.delete(key);
5382
5758
  if (override) notifyWrites(target);
@@ -5385,12 +5761,27 @@ const traps = {
5385
5761
  deleteProperty(target, key) {
5386
5762
  const draft = inDraft(target);
5387
5763
  const override = !draft && writeOverride;
5388
- if (!draft && !override) return true;
5764
+ if (!draft && !override) {
5765
+ warnWriteOutsideSetter(target, key, "deleteProperty");
5766
+ return true;
5767
+ }
5389
5768
  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);
5769
+ touch(target, key);
5770
+ // The key count (`kc`, the overlay gate and the rebuild cap) follows a
5771
+ // delete the way it follows an add: an own key of the draft leaving — a
5772
+ // clone's key, or an overlay's own add — is one fewer now. An overlay's
5773
+ // override of a committed key is the exception: the committed key's
5774
+ // deletion is recorded in `del` and counted at the rebuild, as before.
5775
+ // Without the decrement the natural "delete the old key, set the new
5776
+ // one" shape drifted the count up by one per write until a three-key
5777
+ // object crossed the overlay threshold and paid a rebuild on every
5778
+ // commit after.
5779
+ const committed = target.ovl && hasOwn$1.call(target.v, key);
5780
+ if (hasOwn$1.call(pb, key)) {
5781
+ delete pb[key];
5782
+ committed || target.kc--;
5783
+ }
5784
+ if (committed) (target.del ??= new Set()).add(key);
5394
5785
  if (override) notifyWrites(target);
5395
5786
  return true;
5396
5787
  }
@@ -5418,7 +5809,7 @@ const ASYNC_STORE_SETTER_MESSAGE =
5418
5809
  * write violation — the user guard (`mapArray`'s pattern: the node-level
5419
5810
  * writes carry `ownedWrite`; the setter asks). */
5420
5811
  function devGuardStoreSetterWrite() {
5421
- if (context && !(context._config & CONFIG_CHILDREN_FORBIDDEN)) {
5812
+ if (context && !(context._config & CONFIG_CHILDREN_FORBIDDEN) && !disposalDepth) {
5422
5813
  emitDiagnostic({
5423
5814
  code: "REACTIVE_WRITE_IN_OWNED_SCOPE",
5424
5815
  kind: "write",
@@ -5454,6 +5845,7 @@ function devGuardStoreSetterResult(result) {
5454
5845
  }
5455
5846
  }
5456
5847
  function storeSetter(proxy, fn, guard = true) {
5848
+ if (writeHook !== null) writeHook(proxy);
5457
5849
  if (guard) devGuardStoreSetterWrite();
5458
5850
  const target = proxy[$TARGET];
5459
5851
  const prevScopes = writeScopes;
@@ -5521,6 +5913,8 @@ function createStore$1(initialValue, options) {
5521
5913
  throw new Error("createStore({ shallow }): value is already a store proxy");
5522
5914
  }
5523
5915
  const proxy = wrap(initialValue);
5916
+ // Dependency edges (store/edges.ts): the root is named like a plain signal.
5917
+ if (signalHook !== null) signalHook(proxy[$TARGET]);
5524
5918
  if (shallow) {
5525
5919
  proxy[$TARGET].s = true;
5526
5920
  markRawIngest(initialValue);
@@ -5528,14 +5922,17 @@ function createStore$1(initialValue, options) {
5528
5922
  {
5529
5923
  const owner = getOwner();
5530
5924
  // Dev-tier graph registration (owner signal lists, onGraph); the
5531
- // `_owner` write itself never reaches the proxy, see storeOwners.
5925
+ // `_owner` write itself never reaches the proxy, see storeOwners — an
5926
+ // internal ignored write, not the user's (#4179's warning skips it).
5927
+ internalOutsideWrite = true;
5532
5928
  registerGraph(proxy, owner);
5929
+ internalOutsideWrite = false;
5533
5930
  // Only once the engine is installed, like the node stamping it feeds: a
5534
5931
  // WeakMap.set per fresh store is a growing ephemeron table and,
5535
5932
  // disabled, buys nothing.
5536
5933
  if (attrHooks !== null) {
5537
5934
  storeOwners.set(proxy[$TARGET], owner);
5538
- nameStore(proxy, options?.name);
5935
+ nameStore(proxy, options?.name, options?.loc);
5539
5936
  }
5540
5937
  }
5541
5938
  const setter = fn => storeSetter(proxy, fn);
@@ -6299,6 +6696,11 @@ function runProjectionComputed(wrappedStore, fn, key, wrapCommit, aroundDraftWri
6299
6696
  // A landing (not the sync pass's own commit): the readers held
6300
6697
  // pending on the derive settle.
6301
6698
  if (!first || !(owner._statusFlags & STATUS_UNINITIALIZED)) settleFamily(fam);
6699
+ // A commit is the derive's change for the readers that observed its
6700
+ // flight (linked by `pullFamily`; a settled pull does not link), as
6701
+ // a memo's landing is for its readers: one that caught the NotReady
6702
+ // (a fallback value, not pending) re-runs too (#4070).
6703
+ insertSubs(owner);
6302
6704
  };
6303
6705
  const sync = handleAsync(owner, result, commit, (error, pending) => {
6304
6706
  // The flight rejected: the leaves' readers learn it from here —
@@ -6340,6 +6742,9 @@ function runProjectionComputed(wrappedStore, fn, key, wrapCommit, aroundDraftWri
6340
6742
  return owner;
6341
6743
  }
6342
6744
  function createProjectionInternal(fn, seed, options) {
6745
+ // Seedless (`fn` takes no draft): the root starts as an empty record. It is
6746
+ // a draft like any seed, never observable before the first landing (A25).
6747
+ seed ||= {};
6343
6748
  const keyOption = options?.key === undefined ? "id" : options.key;
6344
6749
  const fam = {
6345
6750
  map: new WeakMap(),
@@ -6362,9 +6767,13 @@ function createProjectionInternal(fn, seed, options) {
6362
6767
  }
6363
6768
  let nodeOptions;
6364
6769
  if (options?.seedLoadingValue) nodeOptions = { loadingValue: undefined };
6365
- if (options?.name) {
6366
- nodeOptions = { ...nodeOptions, name: options.name };
6367
- nameStore(store, options.name);
6770
+ if (attrHooks !== null)
6771
+ // As createStore: the owner in scope stamps the projection's property
6772
+ // nodes, so attribution places them under it (#4035).
6773
+ storeOwners.set(store[$TARGET], getOwner());
6774
+ if (options?.name || options?.loc) {
6775
+ nodeOptions = { ...nodeOptions, name: options.name, loc: options.loc };
6776
+ nameStore(store, options.name, options.loc);
6368
6777
  }
6369
6778
  const node = computed(() => {
6370
6779
  if (!fam.node) fam.node = getOwner();
@@ -6372,14 +6781,11 @@ function createProjectionInternal(fn, seed, options) {
6372
6781
  }, nodeOptions);
6373
6782
  node._config &= ~CONFIG_AUTO_DISPOSE;
6374
6783
  fam.node = node;
6784
+ // The derive's leaves are not its dependents: a mark's repoll
6785
+ // (affects-marks.ts) reaches their readers through this.
6786
+ node.fam = fam;
6375
6787
  return { store, node };
6376
6788
  }
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
6789
  function createProjection(fn, seed, options) {
6384
6790
  return createProjectionInternal(fn, seed, options).store;
6385
6791
  }
@@ -6492,10 +6898,12 @@ function notifyOptimisticWrites(t, pb) {
6492
6898
  if (structural) {
6493
6899
  const rows = isArr ? pb.map(unwrapValue) : shallowKeys(pb);
6494
6900
  // The rows the setter removed from the view it saw, by key: a later
6495
- // re-base over another action's truth must not bring them back.
6901
+ // re-base over another action's truth must not bring them back. A set
6902
+ // (empty or not) marks a keyed arrangement guess (`fold`).
6496
6903
  let removed = null;
6497
6904
  const keyFn = t.fam.key;
6498
6905
  if (isArr && keyFn !== null && keyFn !== undefined) {
6906
+ removed = new Set();
6499
6907
  const kept = new Set();
6500
6908
  for (let i = 0; i < rows.length; i++) {
6501
6909
  const k = isWrappable(rows[i]) ? keyFn(rows[i]) : undefined;
@@ -6504,7 +6912,7 @@ function notifyOptimisticWrites(t, pb) {
6504
6912
  for (let i = 0; i < old.length; i++) {
6505
6913
  const r = unwrapValue(old[i]);
6506
6914
  const k = isWrappable(r) ? keyFn(r) : undefined;
6507
- if (k !== undefined && !kept.has(k)) (removed ??= new Set()).add(k);
6915
+ if (k !== undefined && !kept.has(k)) removed.add(k);
6508
6916
  }
6509
6917
  }
6510
6918
  optimisticWrite(getContainerNode(t), new LaneView(rows, base, removed));
@@ -6640,7 +7048,8 @@ function reconcileDraft(draft, incoming, keyFn) {
6640
7048
  * committed length differs, and as the `LaneView`'s base for structural
6641
7049
  * reads. Unkeyed families compose
6642
7050
  * positionally — the guess stands as written (the documented residue). */
6643
- GlobalQueue._laneRebase = (el, truth) => {
7051
+ GlobalQueue._laneRebase = (el, truth, older) => {
7052
+ if (!older) return fold(el, truth);
6644
7053
  const t = el._host;
6645
7054
  if (t === undefined || el._key !== $TRACK) return;
6646
7055
  const view = el._x._lane;
@@ -6701,6 +7110,51 @@ GlobalQueue._laneRebase = (el, truth) => {
6701
7110
  laneSet(len, rows.length, lane);
6702
7111
  }
6703
7112
  };
7113
+ /** D-34 for a keyed arrangement guess: rows by key, not positions. A
7114
+ * current truth that differs folds into the hold beneath it instead of
7115
+ * voiding the lane: the guess stays the lane's value and the screen's until
7116
+ * the hold commits, where rows match by key, and the graph outside the lane
7117
+ * re-derives from the truth now (`foldedRead`). A confirmation is silent
7118
+ * (A17): only truth readers are told. True: the lane stages the truth
7119
+ * beneath (`laneWrite`). */
7120
+ function fold(el, truth) {
7121
+ const k = el._host?.k;
7122
+ const l = txOf(el);
7123
+ // A guess the screen never showed is void at the correction (D-34); a
7124
+ // keyed arrangement guess's view carries its removed-keys set.
7125
+ if (!l._shown || !(k?._config & CONFIG_OVERRIDE) || !k._x._lane?.removed) return false;
7126
+ const guess = el._x._lane;
7127
+ if ((!el._equals || !el._equals(guess, truth)) && !GlobalQueue._echoes(guess, truth)) {
7128
+ l.folded = foldedRead;
7129
+ insertSubs(el);
7130
+ } else GlobalQueue._confirmed?.(l);
7131
+ joinFuture(resolveTx(l._parent ?? l));
7132
+ return true;
7133
+ }
7134
+ /** A folded lane's guess read by a deriving pass outside the lane, tracked
7135
+ * or not (`untrack` is about dependencies, not about which world a pass
7136
+ * derives from), or by `latest()` (`c` null): the hold's world — the truth
7137
+ * staged beneath the guess, else the value it covered — and the pass joins
7138
+ * the hold. Lane passes and the screen keep the guess: held truth is masked
7139
+ * from lane passes only (#3164). The store's own derive reconciles its
7140
+ * landings against the arrangement the guesses made: it is the author, not
7141
+ * a reader. */
7142
+ function foldedRead(c, el) {
7143
+ if (c !== null) {
7144
+ if (
7145
+ !(el._config & CONFIG_GUESS) ||
7146
+ passLane !== null ||
7147
+ c._type === EFFECT_RENDER ||
7148
+ c._config & CONFIG_CHILDREN_FORBIDDEN ||
7149
+ el._host?.fam?.node === c
7150
+ )
7151
+ return NOT_PENDING;
7152
+ const l = txOf(el);
7153
+ joinPassAt(c, resolveTx(l._parent ?? l));
7154
+ }
7155
+ if (el._pendingValue !== NOT_PENDING) return el._pendingValue;
7156
+ return el._config & CONFIG_SLOT_NODE ? GlobalQueue._slotCovered(el) : el._value;
7157
+ }
6704
7158
  function sameRow(a, b, keyFn) {
6705
7159
  if (a === b) return true;
6706
7160
  if (!isWrappable(a) || !isWrappable(b)) return false;
@@ -6906,7 +7360,9 @@ function optimisticView(
6906
7360
  function createOptimisticStore(first, second, third) {
6907
7361
  const derived = typeof first === "function";
6908
7362
  const options = derived ? third : second;
6909
- const initialValue = derived ? second : first;
7363
+ // Seedless derived form: the root starts as an empty record (see
7364
+ // createProjectionInternal).
7365
+ const initialValue = derived ? second || {} : first;
6910
7366
  const keyOption = options?.key === undefined ? "id" : options.key;
6911
7367
  const fam = {
6912
7368
  map: new WeakMap(),
@@ -6922,7 +7378,7 @@ function createOptimisticStore(first, second, third) {
6922
7378
  : row => (isWrappable(row) ? row[keyOption] : undefined)
6923
7379
  };
6924
7380
  const store = wrap(initialValue, null, null, fam);
6925
- nameStore(store, options?.name);
7381
+ nameStore(store, options?.name, options?.loc);
6926
7382
  if (fam.shallow) {
6927
7383
  store[$TARGET].s = true;
6928
7384
  markRawIngest(initialValue);
@@ -6931,7 +7387,8 @@ function createOptimisticStore(first, second, third) {
6931
7387
  const fn = first;
6932
7388
  let nodeOptions;
6933
7389
  if (options?.seedLoadingValue) nodeOptions = { loadingValue: undefined };
6934
- if (options?.name) nodeOptions = { ...nodeOptions, name: options.name };
7390
+ if (options?.name || options?.loc)
7391
+ nodeOptions = { ...nodeOptions, name: options.name, loc: options.loc };
6935
7392
  const node = computed(() => {
6936
7393
  if (!fam.node) fam.node = getOwner();
6937
7394
  runProjectionComputed(store, fn, keyOption);
@@ -8000,6 +8457,83 @@ function deep(value) {
8000
8457
  return deep$1(value);
8001
8458
  }
8002
8459
 
8460
+ // The mark core: a declared not-final state on a node that reads pending
8461
+ // (`isPending` → true) on the node and everything derived from it, without
8462
+ // holding anything or entangling a reader. Two declarers share it:
8463
+ // `affects()` (affects.ts — a mark scoped to a transaction, with a store
8464
+ // half) and `createDeferred` (deferred.ts — a mark whose lifetime is the
8465
+ // node's own flight). Split out so the second does not ship the first.
8466
+ //
8467
+ // Representation: a count on the marked node (`_x._marks`). Coverage of the
8468
+ // derivations is pull-derived at probe time — `isPending`'s read walks the
8469
+ // probed node's dependencies for a marked one (verdict.ts → `_marked`) — so
8470
+ // nothing is stored downstream and a mid-window recompute strands nothing.
8471
+ // The push half is the verdict readers' re-derivation when a node's marks
8472
+ // start and end (`repoll`): the only notification a mark makes.
8473
+ /** Live marks, every declarer — the gate on the probe's walk. */
8474
+ let active = 0;
8475
+ /** The verdict readers downstream of `node` — through derivations, stopping
8476
+ * at effects — re-derive: the mark's one notification (a probe's answer
8477
+ * changed with no value notifying anyone). Truth readers (`until`, the
8478
+ * `refresh` waiter) too, which a lagging flight parked (deferred.ts) — but
8479
+ * not through a derived store, whose leaves change only once its derive
8480
+ * re-runs: they re-derive at that write. */
8481
+ function repoll(node, seen = new Set(), readers = CONFIG_VERDICT | CONFIG_AUTHORITATIVE) {
8482
+ for (let s = node._subs; s !== null; s = s._nextSub) {
8483
+ const sub = s._sub;
8484
+ if (seen.has(sub)) continue;
8485
+ seen.add(sub);
8486
+ if (sub._config & readers) enqueueSub(sub);
8487
+ if (!sub._type) repoll(sub, seen, readers);
8488
+ // A derived store's derive: its leaves' readers derive from it too
8489
+ // (store/projection.ts; a live target's property, has and keys nodes).
8490
+ const fam = sub.fam;
8491
+ if (fam)
8492
+ for (const t of fam.live)
8493
+ for (const o of [t.n, t.h, { k: t.k, d: t.dk }])
8494
+ if (o) for (const k of Reflect.ownKeys(o)) o[k] && repoll(o[k], seen, CONFIG_VERDICT);
8495
+ }
8496
+ }
8497
+ /** A node is covered by a live mark iff it carries one, or derives — through
8498
+ * its current dependencies — from one that does. A store slot derives from
8499
+ * its family's derive (a derived store's leaves are not the derive's
8500
+ * dependents, store/projection.ts), so coverage hops there. A real error
8501
+ * outranks an inherited mark (A24 (c)): coverage does not flow through an
8502
+ * errored node (a direct mark on one still reads pending). Mid-recompute,
8503
+ * only the validated prefix of the dependency list is this pass's. */
8504
+ function marked(el, seen) {
8505
+ if (el._x !== null && el._x._marks !== 0) return true;
8506
+ const c = el;
8507
+ if (c._statusFlags & STATUS_ERROR || seen.has(el)) return false;
8508
+ seen.add(el);
8509
+ if (c._config & CONFIG_SLOT_NODE) {
8510
+ const derive = c._host?.fam?.node;
8511
+ return derive != null && marked(derive, seen);
8512
+ }
8513
+ const tail = c._flags & REACTIVE_RECOMPUTING_DEPS ? c._depsTail : undefined;
8514
+ if (tail !== null)
8515
+ for (let d = c._deps ?? null; d !== null; d = d._nextDep) {
8516
+ if (marked(d._dep, seen)) return true;
8517
+ if (d === tail) break;
8518
+ }
8519
+ return false;
8520
+ }
8521
+ /** One mark on `node`. The declarer repolls once its bookkeeping is done. */
8522
+ function mark(node) {
8523
+ ext(node)._marks++;
8524
+ active++;
8525
+ }
8526
+ /** Drops one mark from `node`: true when it was the last — the node's
8527
+ * verdict readers have re-derived. */
8528
+ function unmark(node) {
8529
+ active--;
8530
+ if (--node._x._marks !== 0) return false;
8531
+ repoll(node);
8532
+ return true;
8533
+ }
8534
+ GlobalQueue._marked = el => active !== 0 && marked(el, new Set());
8535
+ GlobalQueue._mark = mark;
8536
+
8003
8537
  /**
8004
8538
  * Store — the store half of `affects()` (plan sec. 29, plan sec. 37; A24 (4), #2882,
8005
8539
  * #2893, #2904).
@@ -8128,7 +8662,7 @@ function releaseMarkScope(carrier) {
8128
8662
  const entry = scopes.get(carrier);
8129
8663
  if (entry === undefined) return;
8130
8664
  scopes.delete(carrier);
8131
- GlobalQueue._releaseMarks(entry._inherited);
8665
+ releaseMarksHook(entry._inherited);
8132
8666
  }
8133
8667
 
8134
8668
  // `affects()` — the declaration verb of the pending model (A24 (4), ruled
@@ -8144,62 +8678,23 @@ function releaseMarkScope(carrier) {
8144
8678
  // data joins nothing through it. A mark is never a re-ask, so a declared
8145
8679
  // reload's own `refresh()` cannot silence it; a real error outranks it.
8146
8680
  //
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.
8681
+ // Representation: the mark core (affects-marks.ts) — a count on the marked
8682
+ // node, coverage pull-derived at probe time, readers repolled at registration
8683
+ // and release. Here: each mark's scope (the transaction's `_marks`, or the
8684
+ // ambient list) and the store half.
8154
8685
  //
8155
8686
  // Installed on `GlobalQueue` when imported: a program that never declares a
8156
8687
  // mark carries none of this.
8157
- /** Live marks, every scope — the gate on the probe's walk. */
8158
- let active = 0;
8159
8688
  /** Marks declared outside a transaction: released at the next seam. */
8160
8689
  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
8690
  /** Releases one scope's marks (a landed transaction's, the ambient list at
8192
8691
  * the seam): a node whose last mark drops re-derives its verdict readers. */
8193
8692
  function release$1(nodes) {
8194
8693
  for (let i = 0; i < nodes.length; i++) {
8195
8694
  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
- }
8695
+ // A store mark's carrier: its scope (and the marks the nodes born in
8696
+ // its window inherited) goes with the last mark.
8697
+ if (unmark(n)) releaseMarkScope(n);
8203
8698
  }
8204
8699
  nodes.length = 0;
8205
8700
  }
@@ -8215,10 +8710,6 @@ function register(node) {
8215
8710
  }
8216
8711
  repoll(node);
8217
8712
  }
8218
- function mark(node) {
8219
- ext(node)._marks++;
8220
- active++;
8221
- }
8222
8713
  function affects(target, key) {
8223
8714
  if (arguments.length > 2)
8224
8715
  invalid(
@@ -8256,10 +8747,7 @@ function invalid(detail) {
8256
8747
  emitDiagnostic({ code: "INVALID_AFFECTS_TARGET", kind: "write", severity: "error", message });
8257
8748
  throw new Error(message);
8258
8749
  }
8259
- GlobalQueue._marked = el => active !== 0 && marked(el, new Set());
8260
- GlobalQueue._mark = mark;
8261
- GlobalQueue._releaseMarks = release$1;
8262
- GlobalQueue._releaseAmbientMarks = parked => {
8750
+ installMarkHooks(release$1, parked => {
8263
8751
  if (ambient.length === 0) return;
8264
8752
  // The flush parked: the declaration's window is the frame's — the marks
8265
8753
  // are its transaction's ("nothing async below ⇒ no window").
@@ -8282,7 +8770,7 @@ GlobalQueue._releaseAmbientMarks = parked => {
8282
8770
  }
8283
8771
  ambient.length = kept;
8284
8772
  if (done.length !== 0) release$1(done);
8285
- };
8773
+ });
8286
8774
  function inFlight(n) {
8287
8775
  return (
8288
8776
  (n._statusFlags & STATUS_PENDING) !== 0 ||
@@ -8290,6 +8778,174 @@ function inFlight(n) {
8290
8778
  );
8291
8779
  }
8292
8780
 
8781
+ // `createDeferred` — an async memo that may lag, but never leads (D-40,
8782
+ // docs/create-deferred.md).
8783
+ //
8784
+ // Once the node has a committed value, its own non-final work is invisible
8785
+ // to the graph: its readers keep the committed value and hold nothing, its
8786
+ // landings are changes of their own, and only the verdict (`isPending`) and
8787
+ // the truth readers (`until`, `refresh`'s waiter) see the flight.
8788
+ //
8789
+ // Built from parts the engine already has, so core pays nothing for it:
8790
+ //
8791
+ // - **Read side: the loading window** (`_loading`, async.ts). Every pass of
8792
+ // a node with a committed value opens the window before it asks, so a
8793
+ // flight serves the committed value instead of throwing, and an unready
8794
+ // source parks the node instead of pending it. `CONFIG_REDERIVE` makes a
8795
+ // source going pending a question for the node's pass (which parks) rather
8796
+ // than a status pushed through it to its readers.
8797
+ // - **Verdict: a mark** (affects-marks.ts) whose lifetime is the flight —
8798
+ // started when a pass asks a new question (a `refresh()` re-ask is quiet, F-9),
8799
+ // dropped at the landing, the rejection, a sync answer or the node's death.
8800
+ // - **Never leads:** a flight that lands while an input it read is held
8801
+ // derived from that hold's world, so it lands into the hold (`holdNode`;
8802
+ // `setSignal` stages it there). An input that changed after the pass
8803
+ // would have superseded the flight, so a held input at the landing is one
8804
+ // the pass read held.
8805
+ // - **Truth readers** wait the flight out (`GlobalQueue._lagging`, signals.ts
8806
+ // `watch`): `await refresh(d)` delivers the landing, `until` sees only
8807
+ // settled values.
8808
+ /** Deferred nodes with a flight up. */
8809
+ const lagging = new Set();
8810
+ /** The deferred node with a flight up that `c`'s value derives from, if
8811
+ * any: `c` itself, or one reached through its current dependencies. */
8812
+ function lagOf(c, seen) {
8813
+ if (lagging.has(c)) return c;
8814
+ if (seen.has(c)) return;
8815
+ seen.add(c);
8816
+ if (c._config & CONFIG_SLOT_NODE) return lagOf(c._host?.fam?.node ?? c, seen);
8817
+ const tail = c._flags & REACTIVE_RECOMPUTING_DEPS ? c._depsTail : undefined;
8818
+ if (tail !== null)
8819
+ for (let d = c._deps ?? null; d !== null; d = d._nextDep) {
8820
+ const l = lagOf(d._dep, seen);
8821
+ if (l) return l;
8822
+ if (d === tail) break;
8823
+ }
8824
+ }
8825
+ GlobalQueue._lagging = c => (lagging.size !== 0 ? lagOf(c, new Set()) : undefined);
8826
+ /**
8827
+ * Creates an async memo that may lag, but never leads. Before its first
8828
+ * answer it is an ordinary async memo: readers suspend to the nearest
8829
+ * `<Loading>`. After it, a refetch holds nothing: readers keep the last
8830
+ * committed value until the new one lands, and the rest of the screen moves
8831
+ * on without it. `isPending` reports the refetch, on the node and on what
8832
+ * derives from it. Use it for independent widgets (dashboards, feeds,
8833
+ * search results) where the previous answer with a "refreshing" indicator is
8834
+ * better than waiting with everything else.
8835
+ *
8836
+ * - It never shows a value ahead of a held change: a refetch that read a
8837
+ * held value lands with that hold.
8838
+ * - A rejected refetch errors like any memo; the stale value never masks it.
8839
+ * - `refresh(d)` is a quiet re-ask, and `await refresh(d)` and `until`
8840
+ * wait for the landing rather than the stale value.
8841
+ * - Combine with `loadingValue` for a node that never suspends at all.
8842
+ *
8843
+ * @example
8844
+ * ```tsx
8845
+ * const rows = createDeferred(() => fetchRows(store.period));
8846
+ *
8847
+ * <section class={{ stale: isPending(() => rows()) }}>
8848
+ * <For each={rows()}>{row => <Row row={row} />}</For>
8849
+ * </section>
8850
+ * ```
8851
+ */
8852
+ function createDeferred(fn, options) {
8853
+ let node;
8854
+ /** The flight in force: a promise, a stream, or the error an unready
8855
+ * source parked the node with. */
8856
+ let flight = null;
8857
+ let marked = false;
8858
+ let answered = false;
8859
+ /** The flight `r` is over. An async answer (`held`) arriving while an
8860
+ * input is held lands into that hold first. */
8861
+ const land = (r, held) => {
8862
+ if (r === null || flight !== r) return;
8863
+ flight = null;
8864
+ lagging.delete(node);
8865
+ if (held)
8866
+ for (let d = node._deps; d !== null; d = d._nextDep)
8867
+ if (d._dep._config & CONFIG_HELD) {
8868
+ holdNode(node, txOf(d._dep));
8869
+ break;
8870
+ }
8871
+ if (marked) {
8872
+ marked = false;
8873
+ unmark(node);
8874
+ schedule();
8875
+ }
8876
+ };
8877
+ const ask = r => {
8878
+ flight = r;
8879
+ lagging.add(node);
8880
+ if (!marked && !(node._flags & REACTIVE_REASK)) {
8881
+ marked = true;
8882
+ mark(node);
8883
+ repoll(node);
8884
+ }
8885
+ };
8886
+ /** Registers the flight and serves the pass's value: the committed one,
8887
+ * or over a held landing the landing itself, which a lagging question
8888
+ * does not replace (D-40) — restaged as the committed value, the hold's
8889
+ * readers would re-derive against the old input. */
8890
+ const serve = r => {
8891
+ const v = handleAsync(node, r);
8892
+ return node._config & CONFIG_HELD && node._pendingValue !== NOT_PENDING
8893
+ ? node._pendingValue
8894
+ : v;
8895
+ };
8896
+ const end = () => node._flags & REACTIVE_DISPOSED && land(flight);
8897
+ return accessor(
8898
+ computed(prev => {
8899
+ node = getOwner();
8900
+ node._config |= CONFIG_REDERIVE;
8901
+ cleanup(end);
8902
+ // Answered: the node has a real committed answer — not a first load,
8903
+ // not a `loadingValue` window still open (both are loading, F-9).
8904
+ // Sticky, and read before this wrapper ever opens the window itself.
8905
+ const open = (answered ||= !(node._statusFlags & STATUS_UNINITIALIZED) && !node._loading);
8906
+ if (open) ((node._loading = true), (node._config |= CONFIG_LAGS));
8907
+ let r;
8908
+ try {
8909
+ r = fn(prev);
8910
+ } catch (e) {
8911
+ // The wrapping form: an unready source parks the node in the window,
8912
+ // and the wait is its flight until the source's settle re-runs it.
8913
+ if (open && e instanceof NotReadyError) ask(e);
8914
+ throw e;
8915
+ }
8916
+ if (open && typeof r === "object" && r !== null) {
8917
+ if (isThenable(r)) {
8918
+ ask(r);
8919
+ const done = () => land(r, true);
8920
+ r.then(done, done);
8921
+ return serve(r);
8922
+ }
8923
+ const iterate = r[Symbol.asyncIterator];
8924
+ if (iterate) {
8925
+ // A stream's flight ends at its first yield (or its end).
8926
+ const it = iterate.call(r);
8927
+ let first = true;
8928
+ const done = () => land(s, true);
8929
+ const s = {
8930
+ // Registered ahead of the engine's own reaction to the same
8931
+ // promise, as for a plain flight: verdict and value in one flush.
8932
+ next: v => {
8933
+ const p = it.next(v);
8934
+ if (first) ((first = false), p.then(done, done));
8935
+ return p;
8936
+ },
8937
+ return: v => it.return?.(v) ?? Promise.resolve({ done: true, value: v })
8938
+ };
8939
+ ask(s);
8940
+ return serve({ [Symbol.asyncIterator]: () => s });
8941
+ }
8942
+ }
8943
+ land(flight);
8944
+ return r;
8945
+ }, options)
8946
+ );
8947
+ }
8948
+
8293
8949
  // CARVE 4: `flatten` lived in boundaries.ts but is the general children
8294
8950
  // resolver the renderers (`web`, `universal`, `solid`'s `children()`) walk
8295
8951
  // through — not a boundary feature. Moved verbatim so the boundary carve
@@ -8381,7 +9037,7 @@ function flattenArray(children, results = [], options) {
8381
9037
  * the root holds the frame as for any reader. The tree's own status takes
8382
9038
  * the same route from the output's pass (`read(tree)` throws it).
8383
9039
  *
8384
- * The hold (`blocked`, scheduler.ts) asks `hidden`: a reader behind a
9040
+ * The hold (`blocked`, scheduler.ts) asks `hiddenBy`: a reader behind a
8385
9041
  * fallback — any boundary on its chain whose last pass chose the fallback —
8386
9042
  * is not on screen and holds nothing. An error boundary flipping to its
8387
9043
  * fallback is the same as a loading one resetting: every reader under it
@@ -8416,6 +9072,11 @@ const boundaryOf = node => node._context[BOUNDARY];
8416
9072
  /** Catches what reaches it: an error boundary always; a loading one until it
8417
9073
  * has shown content, or while re-armed. */
8418
9074
  const isCollecting = b => b._type === STATUS_ERROR || !b._initialized || b._armed;
9075
+ /** A reader `b` waits on: recorded for its `on`, and `b` visited at the seam. */
9076
+ function record(b, node) {
9077
+ b._readers.add(node);
9078
+ collecting.add(b);
9079
+ }
8419
9080
  /** The reader goes to the fallback: recorded, and the output re-derives
8420
9081
  * (a no-op while the output's own pass is running — its post-check
8421
9082
  * decides). */
@@ -8432,8 +9093,7 @@ function caught(b, node, error) {
8432
9093
  setSignal(b._error, caught);
8433
9094
  reportClientError(caught, b._owner, node);
8434
9095
  }
8435
- b._readers.add(node);
8436
- collecting.add(b);
9096
+ record(b, node);
8437
9097
  if (b._armed) flip(b);
8438
9098
  // Already showing the fallback: one more reader to wait on changes
8439
9099
  // nothing on screen. The fallback is instantiated once per show — a later
@@ -8449,8 +9109,9 @@ function caught(b, node, error) {
8449
9109
  function flip(b) {
8450
9110
  b._initialized = false;
8451
9111
  b._lane = b._armLane;
8452
- if (b._lane !== null && b._output !== null)
8453
- GlobalQueue._laneStage(b._output, b._lane, true, false);
9112
+ const o = b._output;
9113
+ if (!b._lane && o._config & CONFIG_HELD && txOf(o) !== flushTransaction) unhold(o);
9114
+ if (b._lane !== null && b._output !== null) laneStageHook(b._output, b._lane, true, false);
8454
9115
  }
8455
9116
  function redraw(b) {
8456
9117
  if (b._output !== null && !(b._output._flags & (REACTIVE_DISPOSED | REACTIVE_ZOMBIE))) {
@@ -8458,7 +9119,7 @@ function redraw(b) {
8458
9119
  schedule();
8459
9120
  }
8460
9121
  }
8461
- /** GlobalQueue._catch: status from a frame reader, nearest boundary first.
9122
+ /** `catchHook` (scheduler.ts): status from a frame reader, nearest boundary first.
8462
9123
  * A loading boundary on the way records a pending reader whether or not it
8463
9124
  * catches it (its `on` may collect it later). */
8464
9125
  function catchStatus(node, flags, error) {
@@ -8471,7 +9132,7 @@ function catchStatus(node, flags, error) {
8471
9132
  // lands with its run, not a round after). Others still waiting: nothing
8472
9133
  // on screen changes, the fallback stays as it is.
8473
9134
  if (collecting.size !== 0)
8474
- for (let b = boundaryOf(node); b !== undefined; b = b._parent ?? undefined)
9135
+ for (let b = boundaryOf(node); b; b = b._parent)
8475
9136
  if (
8476
9137
  !(node._statusFlags & unsettled(b)) &&
8477
9138
  b._readers.delete(node) &&
@@ -8482,28 +9143,29 @@ function catchStatus(node, flags, error) {
8482
9143
  }
8483
9144
  return false;
8484
9145
  }
8485
- for (let b = boundaryOf(node); b !== undefined; b = b._parent ?? undefined) {
9146
+ for (let b = boundaryOf(node); b; b = b._parent) {
8486
9147
  if (!(b._type & flags)) continue;
8487
9148
  if (isCollecting(b)) {
8488
9149
  caught(b, node, error);
8489
- return true;
9150
+ return b._armed ? CAUGHT_REARMED : true;
8490
9151
  }
8491
- b._readers.add(node);
8492
- collecting.add(b);
9152
+ record(b, node);
8493
9153
  }
8494
9154
  return false;
8495
9155
  }
8496
- /** The nearest boundary on `r`'s chain showing its fallback, if any. */
9156
+ /** The nearest loading boundary that would catch `node` was re-armed this
9157
+ * flush. */
9158
+ function armedOver(node) {
9159
+ for (let b = boundaryOf(node); b; b = b._parent)
9160
+ if (b._type & STATUS_PENDING && isCollecting(b)) return b._armed;
9161
+ }
9162
+ /** The nearest boundary on `r`'s chain showing its fallback, if any
9163
+ * (`hiddenHook`: `r` is behind a fallback). */
8497
9164
  function hiddenBy(r) {
8498
- for (let b = boundaryOf(r); b !== undefined; b = b._parent ?? undefined)
8499
- if (b._fallback) return b;
9165
+ for (let b = boundaryOf(r); b; b = b._parent) if (b._fallback) return b;
8500
9166
  return undefined;
8501
9167
  }
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
9168
+ /** `heldRunHook` (scheduler.ts): a queued run behind a fallback waits for the reveal
8507
9169
  * (maintainer, 2026-10-02: the queue is held, the synchronous first render
8508
9170
  * goes through). A user effect's, which would read a DOM that is not
8509
9171
  * attached ("many depend on reading the DOM, something that can't happen
@@ -8548,7 +9210,10 @@ function prune(b, pass) {
8548
9210
  for (const r of b._readers) {
8549
9211
  if (r._flags & REACTIVE_DISPOSED) b._readers.delete(r);
8550
9212
  else if (!(r._statusFlags & mask)) {
8551
- if (r !== b._tree && (r._config & (CONFIG_HELD | CONFIG_OVERRIDE)) === CONFIG_HELD) {
9213
+ if (
9214
+ (r !== b._tree || b._armed) &&
9215
+ (r._config & (CONFIG_HELD | CONFIG_OVERRIDE)) === CONFIG_HELD
9216
+ ) {
8552
9217
  // Landed, held: the content is that transaction's — the output's
8553
9218
  // pass enters it and the reveal lands with the reader's run. The
8554
9219
  // seam, past its park decision, waits for the commit instead. (A
@@ -8578,7 +9243,7 @@ function ready(b) {
8578
9243
  prune(b, false) === 0)
8579
9244
  );
8580
9245
  }
8581
- /** GlobalQueue._boundarySeam, end of the seam: readers that settled, died,
9246
+ /** `boundarySeamHook` (scheduler.ts), end of the seam: readers that settled, died,
8582
9247
  * or landed and committed are dropped; a fallback with none left reveals
8583
9248
  * next round. An arm resolves: nothing collected, nothing happened. */
8584
9249
  function boundarySeam() {
@@ -8591,6 +9256,18 @@ function boundarySeam() {
8591
9256
  // A parked frame's boundary (zombie) keeps its state for its revival.
8592
9257
  if (flags & REACTIVE_ZOMBIE) continue;
8593
9258
  prune(b, false);
9259
+ const o = b._output;
9260
+ if (
9261
+ b._armed &&
9262
+ !b._initialized &&
9263
+ o._config & CONFIG_HELD &&
9264
+ [...b._readers].every(
9265
+ r => !(r._statusFlags & STATUS_PENDING) && r._config & CONFIG_HELD && txOf(r) === txOf(o)
9266
+ )
9267
+ ) {
9268
+ b._readers.clear();
9269
+ b._initialized = true;
9270
+ }
8594
9271
  if (b._armed && !b._initialized && !b._ahead) reportOutsideHold(b);
8595
9272
  b._armed = false;
8596
9273
  if (b._readers.size === 0) {
@@ -8617,15 +9294,17 @@ function reportOutsideHold(b) {
8617
9294
  if (outside(s._sub, false)) return true;
8618
9295
  return false;
8619
9296
  }
8620
- if (hidden(reader)) return false;
8621
- for (let o = boundaryOf(reader); o !== undefined; o = o._parent ?? undefined)
8622
- if (o === b) return false;
9297
+ if (hiddenBy(reader)) return false;
9298
+ for (let o = boundaryOf(reader); o; o = o._parent) if (o === b) return false;
8623
9299
  return true;
8624
9300
  };
8625
9301
  for (const r of b._readers) {
8626
9302
  const sources = r._x?._pendingSources;
8627
9303
  if (sources === undefined) continue;
8628
9304
  for (const source of sources) {
9305
+ // Only the flight the change itself started (D-16): an outside reader
9306
+ // of an earlier write's flight holds nothing the change waits on.
9307
+ if (source._time !== clock) continue;
8629
9308
  for (let s = source._subs; s !== null; s = s._nextSub) {
8630
9309
  if (!outside(s._sub, true)) continue;
8631
9310
  const name = source._name;
@@ -8650,15 +9329,6 @@ function reportOutsideHold(b) {
8650
9329
  }
8651
9330
  }
8652
9331
  }
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
9332
  /** `on` changed after the mount: fresh again if anything under it is
8663
9333
  * pending now; collecting for the rest of the flush either way. */
8664
9334
  function arm(b) {
@@ -8682,20 +9352,32 @@ function arm(b) {
8682
9352
  return;
8683
9353
  }
8684
9354
  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
9355
+ // A verdict read in `on` (`isPending`, or a memo of one — #3528's
9356
+ // `createMemo(() => isPending(m2))`: the mark carries, core.ts `read`)
9357
+ // whose frame has no transaction yet: the swap is still display-ahead — the frame gets one now (it lands
8687
9358
  // at this very seam if nothing holds it), and the swap is its verdict
8688
9359
  // lane's work, shown ahead of the frame's own runs (#3528).
8689
9360
  let lane = passLane;
8690
- if (lane === null && globalQueue._running && verdictDerived(getOwner())) {
9361
+ if (lane === null && globalQueue._running && getOwner()._config & CONFIG_VERDICT) {
8691
9362
  joinFuture(null);
8692
9363
  lane = GlobalQueue._verdictLane(flushTransaction);
8693
9364
  }
8694
9365
  b._armLane = lane;
8695
9366
  b._ahead = lane !== null;
8696
9367
  collecting.add(b);
9368
+ const n = b._readers.size;
9369
+ heldUnder(b._owner, flushTransaction, r => record(b, r));
8697
9370
  for (const r of b._readers) {
8698
- if (r._statusFlags & STATUS_PENDING && !(r._flags & REACTIVE_DISPOSED)) {
9371
+ if (
9372
+ b._readers.size !== n ||
9373
+ (r._statusFlags & STATUS_PENDING && !(r._flags & REACTIVE_DISPOSED))
9374
+ ) {
9375
+ // What it collects was forwarded through it to the boundaries above
9376
+ // too (`catchStatus` records a reader in every boundary on the way).
9377
+ // It is this boundary's alone now (B5, the nearest catcher): an outer
9378
+ // re-armed by the same tick does not wait for it (D-14, U-4).
9379
+ for (let a = b._parent; a !== null; a = a._parent)
9380
+ for (const o of b._readers) a._readers.delete(o);
8699
9381
  flip(b);
8700
9382
  redraw(b);
8701
9383
  return;
@@ -8765,7 +9447,7 @@ function createBoundary(type, fn, show, onFn) {
8765
9447
  } catch (e) {
8766
9448
  if (!(e instanceof NotReadyError)) {
8767
9449
  if (!globalQueue.notify(getOwner(), STATUS_ERROR, STATUS_ERROR, e)) {
8768
- haltReactivity(e);
9450
+ if (true) haltReactivityAt(e, getOwner());
8769
9451
  throw e;
8770
9452
  }
8771
9453
  return;
@@ -8828,8 +9510,7 @@ function createBoundary(type, fn, show, onFn) {
8828
9510
  }
8829
9511
  // Forwarded: a loading boundary showing content keeps it and
8830
9512
  // holds the frame like any reader (recorded for its `on`).
8831
- b._readers.add(tree);
8832
- collecting.add(b);
9513
+ record(b, tree);
8833
9514
  }
8834
9515
  // A pending passes an error boundary by (A6); an error, a loading
8835
9516
  // one; a loading boundary showing content forwards its pending.
@@ -8869,8 +9550,7 @@ function createBoundary(type, fn, show, onFn) {
8869
9550
  }
8870
9551
  // Forwarded: a loading boundary showing content keeps it and
8871
9552
  // holds the frame like any reader (recorded for its `on`).
8872
- b._readers.add(tree);
8873
- collecting.add(b);
9553
+ record(b, tree);
8874
9554
  }
8875
9555
  // A pending passes an error boundary by (A6).
8876
9556
  throw e;
@@ -9031,12 +9711,17 @@ function createErrorBoundary(fn, fallback) {
9031
9711
  }
9032
9712
  // Installed at module evaluation — present exactly when something imports a
9033
9713
  // 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;
9714
+ installBoundaryHooks(
9715
+ catchStatus,
9716
+ (node, held) =>
9717
+ !held &&
9718
+ (node._statusFlags & STATUS_UNINITIALIZED
9719
+ ? catchStatus(node, STATUS_PENDING)
9720
+ : armedOver(node)),
9721
+ hiddenBy,
9722
+ boundarySeam,
9723
+ heldRun
9724
+ );
9040
9725
 
9041
9726
  /**
9042
9727
  * Reveal order — `createRevealOrder` (`<Reveal>`): the order in which a group
@@ -9279,6 +9964,7 @@ export {
9279
9964
  affects,
9280
9965
  configureClientErrors,
9281
9966
  createContext,
9967
+ createDeferred,
9282
9968
  createEffect,
9283
9969
  createErrorBoundary,
9284
9970
  createLoadingBoundary,
@@ -9324,6 +10010,9 @@ export {
9324
10010
  resolvedTable,
9325
10011
  runWithOwner,
9326
10012
  setContext,
10013
+ setReadWatch,
10014
+ setSignalIdentities,
10015
+ signalName,
9327
10016
  snapshot,
9328
10017
  sourceGet,
9329
10018
  sourceHas,
@@ -9332,6 +10021,7 @@ export {
9332
10021
  storeHasFamily,
9333
10022
  storeHasOptimisticFamily,
9334
10023
  storeIsShallow,
10024
+ storeName,
9335
10025
  storePath,
9336
10026
  until,
9337
10027
  untrack,