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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/dist/dev-shared.js +882 -405
  2. package/dist/dev.attribution.js +172 -66
  3. package/dist/dev.js +1529 -745
  4. package/dist/observe/affects-marks.js +86 -0
  5. package/dist/observe/affects.js +32 -80
  6. package/dist/observe/boundaries.js +202 -166
  7. package/dist/observe/core/async.js +192 -141
  8. package/dist/observe/core/attribution-queries.js +1 -1
  9. package/dist/observe/core/attribution.js +315 -214
  10. package/dist/observe/core/constants.js +23 -4
  11. package/dist/observe/core/context.js +3 -3
  12. package/dist/observe/core/core.js +532 -360
  13. package/dist/observe/core/dev.js +63 -35
  14. package/dist/observe/core/effect.js +47 -47
  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 +56 -42
  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 +242 -229
  21. package/dist/observe/core/owner.js +62 -50
  22. package/dist/observe/core/scheduler.js +322 -168
  23. package/dist/observe/core/truth.js +45 -0
  24. package/dist/observe/core/verdict.js +114 -64
  25. package/dist/observe/deferred.js +184 -0
  26. package/dist/observe/index.js +7 -1
  27. package/dist/observe/map.js +339 -254
  28. package/dist/observe/reveal.js +77 -77
  29. package/dist/observe/signals.js +54 -33
  30. package/dist/observe/store/affects.js +76 -69
  31. package/dist/observe/store/edges.js +39 -0
  32. package/dist/observe/store/hooks.js +13 -0
  33. package/dist/observe/store/index.js +0 -2
  34. package/dist/observe/store/optimistic.js +82 -44
  35. package/dist/observe/store/projection.js +83 -64
  36. package/dist/observe/store/reconcile.js +32 -30
  37. package/dist/observe/store/store.js +219 -123
  38. package/dist/prod/affects-marks.js +86 -0
  39. package/dist/prod/affects.js +33 -79
  40. package/dist/prod/boundaries.js +185 -149
  41. package/dist/prod/core/async.js +176 -135
  42. package/dist/prod/core/constants.js +23 -4
  43. package/dist/prod/core/context.js +3 -3
  44. package/dist/prod/core/core.js +521 -353
  45. package/dist/prod/core/effect.js +66 -66
  46. package/dist/prod/core/error.js +5 -3
  47. package/dist/prod/core/external.js +5 -6
  48. package/dist/prod/core/graph.js +56 -42
  49. package/dist/prod/core/heap.js +38 -38
  50. package/dist/prod/core/identities.js +62 -0
  51. package/dist/prod/core/lanes.js +237 -224
  52. package/dist/prod/core/owner.js +62 -51
  53. package/dist/prod/core/scheduler.js +312 -158
  54. package/dist/prod/core/truth.js +45 -0
  55. package/dist/prod/core/verdict.js +113 -63
  56. package/dist/prod/deferred.js +182 -0
  57. package/dist/prod/index.js +7 -1
  58. package/dist/prod/map.js +366 -281
  59. package/dist/prod/reveal.js +77 -77
  60. package/dist/prod/signals.js +59 -38
  61. package/dist/prod/store/affects.js +76 -69
  62. package/dist/prod/store/edges.js +39 -0
  63. package/dist/prod/store/hooks.js +13 -0
  64. package/dist/prod/store/index.js +0 -2
  65. package/dist/prod/store/optimistic.js +121 -84
  66. package/dist/prod/store/projection.js +46 -34
  67. package/dist/prod/store/reconcile.js +32 -30
  68. package/dist/prod/store/store.js +205 -116
  69. package/dist/types/affects-marks.d.ts +14 -0
  70. package/dist/types/boundaries.d.ts +5 -1
  71. package/dist/types/core/async.d.ts +10 -0
  72. package/dist/types/core/attribution.d.ts +6 -2
  73. package/dist/types/core/constants.d.ts +18 -1
  74. package/dist/types/core/core.d.ts +77 -5
  75. package/dist/types/core/dev.d.ts +43 -13
  76. package/dist/types/core/graph.d.ts +11 -0
  77. package/dist/types/core/identities.d.ts +17 -0
  78. package/dist/types/core/index.d.ts +2 -1
  79. package/dist/types/core/lanes.d.ts +2 -4
  80. package/dist/types/core/owner.d.ts +5 -3
  81. package/dist/types/core/scheduler.d.ts +59 -34
  82. package/dist/types/core/truth.d.ts +16 -0
  83. package/dist/types/core/types.d.ts +21 -6
  84. package/dist/types/core/verdict.d.ts +32 -0
  85. package/dist/types/deferred.d.ts +28 -0
  86. package/dist/types/index.d.ts +6 -0
  87. package/dist/types/map.d.ts +2 -2
  88. package/dist/types/signals.d.ts +28 -7
  89. package/dist/types/store/affects.d.ts +11 -0
  90. package/dist/types/store/edges.d.ts +9 -0
  91. package/dist/types/store/hooks.d.ts +48 -0
  92. package/dist/types/store/index.d.ts +10 -4
  93. package/dist/types/store/optimistic.d.ts +3 -2
  94. package/dist/types/store/projection.d.ts +4 -3
  95. package/dist/types/store/store.d.ts +4 -39
  96. package/dist/types/store/target.d.ts +16 -8
  97. package/dist/types/store/types.d.ts +14 -0
  98. package/package.json +1 -1
package/dist/dev.js CHANGED
@@ -1,163 +1,228 @@
1
1
  import {
2
2
  C as CONFIG_GUESS,
3
- s as schedule,
4
3
  N as NOT_PENDING,
5
- a as CONFIG_OVERRIDE,
4
+ a as NotReadyError,
5
+ b as CONFIG_AUTHORITATIVE,
6
+ e as enqueueSub,
7
+ c as CONFIG_OVERRIDE,
8
+ s as schedule,
6
9
  q as question,
7
10
  n as nextQuestion,
11
+ i as installLaneHooks,
8
12
  G as GlobalQueue,
9
- R as REACTIVE_DISPOSED,
10
- b as CONFIG_VERDICT,
11
- c as REACTIVE_PROBE_UNANSWERED,
12
- d as blocked,
13
- e as enqueueSub,
14
13
  r as resolveTx,
15
- f as CONFIG_AUTHORITATIVE,
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,
21
+ k as CONFIG_HELD,
16
22
  t as txOf,
17
- g as tracking,
18
- h as CONFIG_CHILDREN_FORBIDDEN,
19
- p as passLane,
20
- S as STATUS_PENDING,
21
- E as EFFECT_RENDER,
22
- i as sameLane,
23
- j as CONFIG_HELD,
24
- k as flushTransaction,
25
- l as STATUS_UNINITIALIZED,
26
- m as NotReadyError,
27
- o as REACTIVE_SCREEN_READ,
28
- u as REACTIVE_FRAME_READ,
29
- v as REACTIVE_LANE_READ,
30
- w as ext,
31
- x as REACTIVE_RECOMPUTING_DEPS,
32
- y as queuePendingNode,
33
- z as REACTIVE_LANE_DIRTY,
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 stagedRead,
47
- T as attrHooks,
48
- U as holdNode,
49
- V as joinFuture,
50
- W as CONFIG_SLOT_NODE,
51
- X as commitPendingNode,
52
- Y as REACTIVE_IN_HEAP,
53
- Z as REACTIVE_DIRTY,
54
- _ as REACTIVE_CHECK,
55
- $ as CONFIG_INPUTS_PUBLISHED,
56
- a0 as setVerdict,
57
- a1 as context,
58
- a2 as REACTIVE_PROBED,
59
- a3 as globalQueue,
60
- a4 as staleReader,
61
- a5 as REACTIVE_STAGED_READ,
62
- a6 as verdict,
63
- a7 as link,
64
- a8 as pullComputed,
65
- a9 as strictRead,
66
- aa as warnStrictReadUntracked,
67
- ab as markLateLinker,
68
- ac as spectating,
69
- ad as STATUS_ERROR,
70
- ae as unflushedValue,
71
- af as stagedReaders,
72
- ag as enterAction,
73
- ah as setEffectCallback,
74
- ai as exitAction,
75
- aj as isThenable,
76
- ak as actionDepth,
77
- al as flush,
78
- am as setQuestion,
79
- an as emitDiagnostic,
80
- ao as enterCallback,
81
- ap as exitCallback,
82
- aq as getOwner,
83
- ar as inEffectCallback,
84
- as as signal,
85
- at as setSignal,
86
- au as cleanup,
87
- av as read,
88
- aw as NoOwnerError,
89
- ax as ContextNotFoundError,
90
- ay as createEffectNode,
91
- az as recompute,
92
- aA as EFFECT_USER,
93
- aB as reportDiagnostic,
94
- aC as computed,
95
- aD as staleValues,
96
- aE as CONFIG_AUTO_DISPOSE,
97
- aF as EFFECT_TRACKED,
98
- aG as unwrapStatusError,
99
- aH as haltReactivity,
100
- aI as setStrictRead,
101
- aJ as StatusError,
102
- aK as trimStaleDeps,
103
- aL as setTrackedQueueCallback,
104
- aM as setEffectStatusNotify,
105
- aN as _hitUnhandledAsync,
106
- aO as resetUnhandledAsync,
107
- aP as ROOT_ERROR_HOOK,
108
- aQ as registerGraph,
109
- aR as runWithOwner,
110
- aS as setMemo,
111
- aT as $REFRESH,
112
- aU as markRefresh,
113
- aV as createRoot,
114
- aW as getObserver,
115
- aX as dispose,
116
- aY as TimeoutError,
117
- aZ as untrack,
118
- a_ as dirtyQueue,
119
- a$ as CONFIG_REDERIVE,
120
- b0 as passTx,
121
- b1 as CONFIG_FRESH_READ,
122
- b2 as createOwner,
123
- b3 as REACTIVE_WRITE_IN_OWNED_SCOPE_SIGNAL_MESSAGE,
124
- b4 as ownedScopeWriteMessage,
125
- b5 as DEV$1,
126
- b6 as isEqual,
127
- b7 as CONFIG_MANUAL_WRITE,
128
- b8 as notePromotedWrite,
129
- b9 as slotSignal,
130
- ba as stagedScreen,
131
- bb as CONFIG_PROMOTED,
132
- bc as setSlotUnobserved,
133
- bd as joinPassTx,
134
- be as asyncTailFlights,
135
- bf as untrackDepth,
136
- bg as checkPostAwaitRead,
137
- bh as insertIntoHeap,
138
- bi as isDisposed,
139
- bj as handleAsync,
140
- bk as addPendingSource,
141
- bl as propagateStatus,
142
- bm as forEachDependent,
143
- bn as settlePendingSource,
144
- bo as notifyEpoch,
145
- bp as SUPPORTS_PROXY,
146
- bq as REACTIVE_ZOMBIE,
147
- br as OBSERVE$1
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
+ W as holdNode,
51
+ X as joinFuture,
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
+ a7 as verdict,
64
+ a8 as link,
65
+ a9 as pullComputed,
66
+ aa as strictRead,
67
+ ab as warnStrictReadUntracked,
68
+ ac as staleReader,
69
+ ad as globalQueue,
70
+ ae as joinPass,
71
+ af as markLateLinker,
72
+ ag as spectating,
73
+ ah as unflushedValue,
74
+ ai as CONFIG_STAGED_ERROR,
75
+ aj as stagedReaders,
76
+ ak as bornInto,
77
+ al as enterAction,
78
+ am as setEffectCallback,
79
+ an as exitAction,
80
+ ao as isThenable,
81
+ ap as actionDepth,
82
+ aq as flush,
83
+ ar as setQuestion,
84
+ as as emitDiagnostic,
85
+ at as enterCallback,
86
+ au as exitCallback,
87
+ av as getOwner,
88
+ aw as inEffectCallback,
89
+ ax as setExternalHooks,
90
+ ay as signal,
91
+ az as setSignal,
92
+ aA as cleanup,
93
+ aB as read,
94
+ aC as DEV$1,
95
+ aD as CONFIG_TRANSPARENT,
96
+ aE as NoOwnerError,
97
+ aF as ContextNotFoundError,
98
+ aG as createEffectNode,
99
+ aH as recompute,
100
+ aI as EFFECT_USER,
101
+ aJ as reportDiagnostic,
102
+ aK as computed,
103
+ aL as staleValues,
104
+ aM as CONFIG_AUTO_DISPOSE,
105
+ aN as EFFECT_TRACKED,
106
+ aO as heldRunHook,
107
+ aP as unwrapStatusError,
108
+ aQ as haltReactivity,
109
+ aR as setStrictRead,
110
+ aS as StatusError,
111
+ aT as trimStaleDeps,
112
+ aU as setTrackedQueueCallback,
113
+ aV as setEffectStatusNotify,
114
+ aW as _hitUnhandledAsync,
115
+ aX as resetUnhandledAsync,
116
+ aY as ROOT_ERROR_HOOK,
117
+ aZ as registerGraph,
118
+ a_ as runWithOwner,
119
+ a$ as setMemo,
120
+ b0 as $REFRESH,
121
+ b1 as markRefresh,
122
+ b2 as createRoot,
123
+ b3 as getObserver,
124
+ b4 as dispose,
125
+ b5 as TimeoutError,
126
+ b6 as untrack,
127
+ b7 as dirtyQueue,
128
+ b8 as CONFIG_REDERIVE,
129
+ b9 as passTx,
130
+ ba as CONFIG_FRESH_READ,
131
+ bb as createOwner,
132
+ bc as writeHook,
133
+ bd as REACTIVE_WRITE_IN_OWNED_SCOPE_SIGNAL_MESSAGE,
134
+ be as ownedScopeWriteMessage,
135
+ bf as isEqual,
136
+ bg as CONFIG_MANUAL_WRITE,
137
+ bh as snapshotCaptureActive,
138
+ bi as captureWriteSnapshot,
139
+ bj as notePromotedWrite,
140
+ bk as slotSignal,
141
+ bl as stagedScreen,
142
+ bm as CONFIG_PROMOTED,
143
+ bn as setSlotUnobserved,
144
+ bo as installStoreHooks,
145
+ bp as joinPassTx,
146
+ bq as asyncTailFlights,
147
+ br as untrackDepth,
148
+ bs as checkPostAwaitRead,
149
+ bt as disposalDepth,
150
+ bu as insertIntoHeap,
151
+ bv as isDisposed,
152
+ bw as handleAsync,
153
+ bx as addPendingSource,
154
+ by as propagateStatus,
155
+ bz as forEachDependent,
156
+ bA as settlePendingSource,
157
+ bB as notifyEpoch,
158
+ bC as SUPPORTS_PROXY,
159
+ bD as releaseMarksHook,
160
+ bE as installMarkHooks,
161
+ bF as CONFIG_LAGS,
162
+ bG as REACTIVE_REASK,
163
+ bH as heldUnder,
164
+ bI as unhold,
165
+ bJ as laneStageHook,
166
+ bK as REACTIVE_ZOMBIE,
167
+ bL as installBoundaryHooks,
168
+ bM as CAUGHT_REARMED,
169
+ bN as OBSERVE$1
148
170
  } from "./dev-shared.js";
149
171
  export {
150
- bs as clearSnapshots,
151
- bt as enforceLoadingBoundary,
152
- bu as getNextChildId,
153
- bv as markSnapshotScope,
154
- bw as peekNextChildId,
155
- bx as releaseSnapshotScope,
156
- by as resetErrorHalt,
157
- bz as setConsoleFooter,
158
- bA as setSnapshotCapture
172
+ bO as clearSnapshots,
173
+ bP as enforceLoadingBoundary,
174
+ bQ as getNextChildId,
175
+ bR as markSnapshotScope,
176
+ bS as peekNextChildId,
177
+ bT as releaseSnapshotScope,
178
+ bU as resetErrorHalt,
179
+ bV as setConsoleFooter,
180
+ bW as setSnapshotCapture,
181
+ bX as setWriteHook
159
182
  } from "./dev-shared.js";
160
183
 
184
+ // Truth reads (`until`, `refresh`; CONFIG_AUTHORITATIVE) over lane nodes.
185
+ // Side-effect free and apart from lanes.ts so a truth reader alone does not
186
+ // retain the lane engine: its reads reach these only through `laneRead`,
187
+ // which exists once something installs lanes (`createOptimistic`, verdicts).
188
+ /** A lane node's current value: its guess, or the latest it derived — the
189
+ * staging a shown lane holds for its next reveal, else its slot; the
190
+ * committed value while it has none (its first lane pass in flight). */
191
+ function laneValueOf(el) {
192
+ if (!(el._config & CONFIG_GUESS) && el._pendingValue !== NOT_PENDING) return el._pendingValue;
193
+ const v = el._x._lane;
194
+ return v !== NOT_PENDING ? v : el._value;
195
+ }
196
+ /** Truth readers (`until`, `refresh`; CONFIG_AUTHORITATIVE) — pay-for-use:
197
+ * the first one installs these on GlobalQueue (signals.ts `watch`).
198
+ *
199
+ * A truth reader's read of a lane node: the lane's value when it is the
200
+ * truth (#3943), else the truth staged beneath a guess, else the committed
201
+ * value. */
202
+ function truthRead(el) {
203
+ if (truth(el)) return laneValueOf(el);
204
+ if (!(el._config & CONFIG_GUESS)) throw new NotReadyError(null);
205
+ return el._pendingValue !== NOT_PENDING ? el._pendingValue : el._value;
206
+ }
207
+ /** A guess confirmed: its lane's truth readers re-read — of the lane's
208
+ * derivations too, which may be the truth now (#3943). */
209
+ function truthConfirmed(l) {
210
+ for (let i = 0; i < l._nodes.length; i++)
211
+ for (let s = l._nodes[i]._subs; s !== null; s = s._nextSub)
212
+ if (s._sub._config & CONFIG_AUTHORITATIVE) enqueueSub(s._sub);
213
+ }
214
+ /** A lane node's value is the truth when every guess beneath it is
215
+ * confirmed — its source staged a value equal to the guess — and nothing
216
+ * beneath it is held in the parent: same inputs, same value. */
217
+ function truth(n) {
218
+ if (n._config & CONFIG_GUESS)
219
+ return n._pendingValue !== NOT_PENDING && n._equals && n._equals(n._x._lane, n._pendingValue);
220
+ if (n._pendingValue !== NOT_PENDING) return;
221
+ if (n._config & CONFIG_OVERRIDE)
222
+ for (let d = n._deps; d; d = d._nextDep) if (!truth(d._dep)) return;
223
+ return 1;
224
+ }
225
+
161
226
  // Lanes (plan sec. 28 replay, 2026-10-02; maintainer rulings 2026-10-01/02). A lane
162
227
  // is "a new base of a transition": the sub-frame an optimistic write opens
163
228
  // in the transition of the frame it is made in. It sees the screen plus its
@@ -190,19 +255,13 @@ export {
190
255
  // carries none of this.
191
256
  /** Live lanes (parented). Scanned at every seam while non-empty. */
192
257
  const lanes = [];
258
+ /** A pass left a shown slot the equality gate can no longer see (#3892). */
259
+ const LEFT_SHOWN = 1 << 20;
193
260
  function newLane(parent) {
194
261
  const l = newTransaction(true, parent);
195
262
  lanes.push(l);
196
263
  return l;
197
264
  }
198
- /** A lane node's current value: its guess, or the latest it derived — the
199
- * staging a shown lane holds for its next reveal, else its slot; the
200
- * committed value while it has none (its first lane pass in flight). */
201
- function laneValueOf(el) {
202
- if (!(el._config & CONFIG_GUESS) && el._pendingValue !== NOT_PENDING) return el._pendingValue;
203
- const v = el._x._lane;
204
- return v !== NOT_PENDING ? v : el._value;
205
- }
206
265
  /** What the screen shows for a lane's node: the lane's value once the lane
207
266
  * has revealed, the committed truth before. */
208
267
  function display(el) {
@@ -267,23 +326,28 @@ function applyGuesses(parent) {
267
326
  const n = pendingGuesses[i];
268
327
  const v = pendingGuesses[i + 1];
269
328
  let changed = true;
329
+ // The frame already holding the slot: a guess over it is a second
330
+ // suggestion for the slot, and two cannot finish apart (A34 (1)) — the
331
+ // frames entangle.
332
+ let holder = null;
270
333
  if (n._config & CONFIG_OVERRIDE) {
271
334
  // A lane's node already: a guess re-asked, or a derivation guessed
272
- // over (its staging is void — the guess is the value now). Two
273
- // suggestions for one slot cannot finish apart (A34 (1)): the frames
274
- // entangle. The same value re-asked renews the question; nothing to
275
- // notify.
335
+ // over (its staging is void — the guess is the value now). The same
336
+ // value re-asked renews the question; nothing to notify.
276
337
  changed = !n._equals || !n._equals(laneValueOf(n), v);
277
338
  if (!(n._config & CONFIG_GUESS)) {
278
339
  n._pendingValue = NOT_PENDING;
279
340
  n._config |= CONFIG_GUESS;
280
341
  }
281
342
  n._x._lane = v;
282
- const holder = txOf(n)._parent;
283
- if (parent !== null && holder !== null) merge$1(parent, holder);
343
+ holder = txOf(n)._parent;
284
344
  } else {
285
345
  let l;
286
- if (n._config & CONFIG_HELD) l = newLane(txOf(n));
346
+ // A held node: a truth staged there, or what a corrected guess showed.
347
+ // The guess composes on it, so it ends with that hold — and with its
348
+ // own frame's (F-11): it lands only when both have (the board's rapid
349
+ // adds — a later add's row reverted with an earlier add's landing).
350
+ if (n._config & CONFIG_HELD) l = newLane((holder = txOf(n)));
287
351
  else {
288
352
  if (parent === null && inFlight$1(n)) parent = newTransaction(false);
289
353
  if (parent === null) continue;
@@ -299,6 +363,7 @@ function applyGuesses(parent) {
299
363
  n._config |= CONFIG_OVERRIDE | CONFIG_GUESS;
300
364
  list(n, l);
301
365
  }
366
+ if (parent !== null && holder !== null) merge$1(parent, holder);
302
367
  ext(n)._q = pendingGuesses[i + 2];
303
368
  if (changed) {
304
369
  // The lane's members re-derive as its work (REACTIVE_LANE_DIRTY): a
@@ -326,8 +391,10 @@ function enterLane(l, c) {
326
391
  // The pass read a held write before its first lane read (it joined the
327
392
  // frame's future as mainline would): lane work sees the screen — the
328
393
  // seam re-derives it on the committed world next round (`stagedReaders`,
329
- // the park's repair), as it does a lane pass that read a staging.
330
- if (c._flags & REACTIVE_JOINED) stagedRead(c);
394
+ // the park's repair), as it does a lane pass that read a staging. Not a
395
+ // pass whose held reads were all of nodes born held (no staged read,
396
+ // `stagedRead`): it has no committed world to re-derive on (#3969).
397
+ if (c._flags & REACTIVE_JOINED && c._flags & REACTIVE_STAGED_READ) stagedRead(c);
331
398
  return;
332
399
  }
333
400
  if (sameLane(p, l)) return setPassLane(l);
@@ -402,17 +469,22 @@ function verdictLane(t) {
402
469
  * propagation (async.ts `propagateStatus`), the node gone pending. */
403
470
  function laneStage(el, l, create, errored) {
404
471
  if (!(create || errored || el._flags & REACTIVE_LANE_READ || el._config & CONFIG_GUESS)) {
405
- if (el._x !== null) {
406
- if (el._x._transaction?._lane) el._x._transaction = null;
407
- el._x._lane = NOT_PENDING;
408
- }
409
- el._config &= -2305;
472
+ const x = el._x;
473
+ if (x !== null) {
474
+ const tx = x._transaction;
475
+ // Cleared before equality, so a return to the committed value notifies
476
+ // nobody. The seam wakes it unless the frame is still held (#3892).
477
+ if (tx?._shown && !blocked(resolveTx(tx._parent))) el._flags |= LEFT_SHOWN;
478
+ if (tx?._lane) x._transaction = null;
479
+ x._lane = NOT_PENDING;
480
+ }
481
+ el._config &= -1050881;
410
482
  return false;
411
483
  }
412
484
  if (!el._type) {
413
485
  if (!(el._config & CONFIG_OVERRIDE)) {
414
486
  el._pendingValue = NOT_PENDING;
415
- el._config = (el._config & ~CONFIG_HELD) | CONFIG_OVERRIDE;
487
+ el._config = (el._config & -1048833) | CONFIG_OVERRIDE;
416
488
  ext(el);
417
489
  }
418
490
  // A written guess whose own pass is the lane's work — its source is a
@@ -464,6 +536,11 @@ function supersede(n, value, changed) {
464
536
  // lane opened (two actions guessing one slot entangle, A34 (1)) — the
465
537
  // truth is held by the transaction that lands, not the merged-away one.
466
538
  const parent = resolveTx(l._parent ?? l);
539
+ // A store row answered by a new object the store would reconcile into
540
+ // the guessed one unchanged is an echo, not a correction (#3898): the
541
+ // server's object never matches the guess by reference.
542
+ if (changed && n._config & CONFIG_SLOT_NODE && GlobalQueue._echoes?.(n._x._lane, value))
543
+ changed = false;
467
544
  if (changed) {
468
545
  // Observe: a displayed guess is being replaced by a differing truth — a
469
546
  // landing's (superseded), or the value it covered (reverted: the body
@@ -476,17 +553,15 @@ function supersede(n, value, changed) {
476
553
  if (l._shown) n._value = n._x._lane;
477
554
  n._config &= -10241;
478
555
  n._x._lane = NOT_PENDING;
479
- n._pendingValue = value;
480
556
  holdNode(n, parent);
481
- joinFuture(parent);
482
557
  if (n._subs !== null) insertSubs(n);
483
558
  } else {
484
- n._pendingValue = value;
485
559
  n._config |= CONFIG_HELD;
486
- joinFuture(parent);
487
- for (let s = n._subs; s !== null; s = s._nextSub)
488
- if (s._sub._config & CONFIG_AUTHORITATIVE) enqueueSub(s._sub);
560
+ // A confirmation is silent (A17); only truth readers are told.
561
+ GlobalQueue._confirmed?.(l);
489
562
  }
563
+ n._pendingValue = value;
564
+ joinFuture(parent);
490
565
  }
491
566
  /** A lane's end, two ways. `into === null`: the parent landed — the lane's
492
567
  * values are the truth now: a guess lands the truth staged beneath it or
@@ -586,7 +661,7 @@ function dissolveLane(l, into, except) {
586
661
  } else {
587
662
  if (x._pendingFirstChild !== null || x._pendingDisposal !== null)
588
663
  GlobalQueue._dispose(n, false, true);
589
- n._config &= -385;
664
+ n._config &= -1048961;
590
665
  }
591
666
  }
592
667
  }
@@ -621,11 +696,12 @@ function dissolveLane(l, into, except) {
621
696
  * member throws like any. */
622
697
  function laneRead(c, el) {
623
698
  const guess = el._config & CONFIG_GUESS;
624
- if (c !== null && c._config & CONFIG_AUTHORITATIVE)
625
- return guess && el._pendingValue !== NOT_PENDING ? el._pendingValue : el._value;
699
+ if (c !== null && c._config & CONFIG_AUTHORITATIVE) return GlobalQueue._truth(el);
700
+ const status = el._statusFlags;
701
+ // An errored member's lane value predates its error; the error is the answer (#4106).
702
+ if (!guess && status & STATUS_ERROR) return NOT_PENDING;
626
703
  if (c === null) return laneValueOf(el);
627
704
  const l = txOf(el);
628
- const status = el._statusFlags;
629
705
  if (!tracking || c._config & CONFIG_CHILDREN_FORBIDDEN) {
630
706
  // An untracked read inside lane work derives from the lane like a
631
707
  // tracked one, minus the subscription — `untrack` is about
@@ -734,6 +810,10 @@ function laneSeam(l, leaked) {
734
810
  reruns(l);
735
811
  for (let i = 0; i < l._nodes.length; i++) {
736
812
  const n = l._nodes[i];
813
+ if (n._flags & LEFT_SHOWN) {
814
+ n._flags ^= LEFT_SHOWN;
815
+ insertSubs(n);
816
+ }
737
817
  const x = n._x;
738
818
  if (x._transaction !== l) continue;
739
819
  if (!(n._config & CONFIG_GUESS) && !n._type && n._pendingValue !== NOT_PENDING) {
@@ -848,20 +928,16 @@ function answered(el) {
848
928
  }
849
929
  return q;
850
930
  }
851
- GlobalQueue._laneRead = laneRead;
852
- GlobalQueue._laneStage = laneStage;
853
- GlobalQueue._laneOutcome = laneOutcome;
854
- GlobalQueue._laneWrite = laneWrite;
855
931
  /** The lanes alive before this seam's guesses opened theirs (a prefix of
856
932
  * `lanes`; nothing dissolves one between here and the seam loop): a lane
857
933
  * born at the seam has no work to judge yet (its passes run next round) —
858
934
  * it is neither shown nor blocked until it has. */
859
935
  let judged = 0;
860
- GlobalQueue._applyGuesses = parent => {
936
+ const applyGuessesAt = parent => {
861
937
  judged = lanes.length;
862
938
  if (pendingGuesses.length !== 0) applyGuesses(parent);
863
939
  };
864
- GlobalQueue._laneSeams = leaks => {
940
+ const laneSeams = leaks => {
865
941
  // Passes that read a write the parked frame holds as the screen
866
942
  // (`stagedReaders`: lane work, a verdict reader before the verdict): they
867
943
  // re-derive on the committed world next round (and register as stale
@@ -886,14 +962,30 @@ GlobalQueue._laneSeams = leaks => {
886
962
  : !blocked(l._parent)))
887
963
  )
888
964
  continue;
965
+ // A leaked effect re-derives seated in its lane from its first read: it
966
+ // carries no lane value (`laneStage`), so without this its pass starts
967
+ // as mainline, joins the parked frame on its first held read and reads
968
+ // the staging, then re-enters the lane at its guess read and leaks
969
+ // again — every round (#4014). A computed is already seated by its
970
+ // override, and a written guess's own pass stays the frame's.
971
+ if (l != null && l._lane && r._type) r._flags |= REACTIVE_LANE_DIRTY;
889
972
  enqueueSub(r);
890
973
  if (l != null && l._lane && leaked.indexOf(l) === -1) leaked.push(l);
891
974
  }
892
975
  for (let k = Math.min(judged, lanes.length) - 1; k >= 0; k--)
893
976
  laneSeam(lanes[k], leaked.indexOf(lanes[k]) !== -1);
894
977
  };
895
- GlobalQueue._laneCorrections = () => lanes.length !== 0 && laneCorrections();
896
- GlobalQueue._endLanes = endLanes;
978
+ installLaneHooks(
979
+ laneRead,
980
+ laneStage,
981
+ laneOutcome,
982
+ laneWrite,
983
+ applyGuessesAt,
984
+ laneSeams,
985
+ () => lanes.length !== 0 && laneCorrections(),
986
+ endLanes,
987
+ lanesBlocked
988
+ );
897
989
  GlobalQueue._laneGuesses = t => {
898
990
  const out = [];
899
991
  for (let i = 0; i < lanes.length; i++) {
@@ -905,7 +997,6 @@ GlobalQueue._laneGuesses = t => {
905
997
  }
906
998
  return out;
907
999
  };
908
- GlobalQueue._lanesBlocked = lanesBlocked;
909
1000
  GlobalQueue._verdictLane = verdictLane;
910
1001
 
911
1002
  // Verdicts (plan sec. 28 replay, 2026-10-02; maintainer, 2026-10-01: verdicts are
@@ -957,9 +1048,12 @@ function route(c, t) {
957
1048
  * every flush (fuzzer F12). True when the reader is served the committed
958
1049
  * value — not over a flight committed beneath inputs already on screen (A15
959
1050
  * reveal corollary, #3305): that value would tear against them, and the
960
- * reveal observes the flight. */
1051
+ * reveal observes the flight. Nor for a reader born into the flight's
1052
+ * transaction (`bornInto`, #4010): it has no screen — it waits on the flight
1053
+ * with the rest of its frame. */
961
1054
  function observeFlight(c, el) {
962
- if (el._config & CONFIG_INPUTS_PUBLISHED) return false;
1055
+ if (el._config & CONFIG_INPUTS_PUBLISHED || (el._config & CONFIG_HELD && bornInto(c, txOf(el))))
1056
+ return false;
963
1057
  if (globalQueue._running) {
964
1058
  joinFuture(null);
965
1059
  if (!(el._config & CONFIG_HELD)) queuePendingNode(el);
@@ -983,9 +1077,10 @@ let probeFound = false;
983
1077
  * reader re-derives there. `latest` shows the proposal that lands — a
984
1078
  * rewrite or a landing reaches its reader through the link it holds — so a
985
1079
  * re-run would only repeat the frame. */
986
- function verdictRead(c, t, rerun = probing) {
1080
+ function verdictRead(c, t, plain) {
987
1081
  route(c, t);
988
- if (rerun) staleReader(c, t);
1082
+ if (plain) staleReader(c, t);
1083
+ else if (probing && t._reruns[t._reruns.length - 1] !== c) t._reruns.push(c);
989
1084
  }
990
1085
  /** A verdict read of a node staged or in flight but not held yet: whether it
991
1086
  * will be is the seam's.
@@ -1055,7 +1150,7 @@ function heldNotFinal(owner) {
1055
1150
  !latestActive &&
1056
1151
  !owner._x._reask &&
1057
1152
  !(owner._statusFlags & STATUS_UNINITIALIZED) &&
1058
- (!owner._equals || !owner._equals(owner._value, owner._pendingValue))
1153
+ (!!owner._x._shownError || !owner._equals || !owner._equals(owner._value, owner._pendingValue))
1059
1154
  );
1060
1155
  }
1061
1156
  /** `isPending`'s answer for an unheld node whose flight is up (cause ii). */
@@ -1068,11 +1163,14 @@ function pendingVerdict(c, el) {
1068
1163
  }
1069
1164
  }
1070
1165
  /** The pass entering a verdict window is a verdict reader (CONFIG_VERDICT):
1071
- * a dependency going pending re-derives it, the verdict having changed. */
1166
+ * a dependency going pending re-derives it, the verdict having changed. Not
1167
+ * an untracked window: that is a snapshot, like any untracked read (#3946 —
1168
+ * a router's link claim probing inside the computation that creates the
1169
+ * anchor). A throw still reaches the pass through `untrack`. */
1072
1170
  function markVerdictReader(window) {
1073
1171
  let c = context;
1074
1172
  if (c?._root) c = c._parentComputed;
1075
- if (c !== null) {
1173
+ if (c !== null && tracking) {
1076
1174
  c._config |= CONFIG_VERDICT;
1077
1175
  c._flags |= REACTIVE_PROBED;
1078
1176
  // Observe tier: which windows this pass entered (1 `isPending`, 2
@@ -1096,6 +1194,38 @@ function latest(fn) {
1096
1194
  setVerdict(prevVerdict);
1097
1195
  }
1098
1196
  }
1197
+ /**
1198
+ * Reports whether what `fn` reads is still waiting on an update. Read the
1199
+ * values in question inside `fn`: the probe answers `true` while any of
1200
+ * them depends on a change that is in flight — a write or an async source
1201
+ * that has started producing a new value whose result has not landed yet —
1202
+ * and `false` once the latest value is the one on screen. The answer is
1203
+ * tracked like any read, so a memo or effect that calls `isPending` re-runs
1204
+ * when it flips.
1205
+ *
1206
+ * A read that has no value yet is loading, not pending: inside `fn` it
1207
+ * follows the surrounding `Loading` path like any other read, and the probe
1208
+ * reports only updates to a value that already exists. A bare `refresh()`
1209
+ * does not flip `isPending`; pair it with `affects()` to make the re-ask
1210
+ * visible.
1211
+ *
1212
+ * @param fn expression to probe; read the values in question inside it
1213
+ *
1214
+ * @returns `true` while a read inside `fn` depends on a change that has not committed yet
1215
+ *
1216
+ * @example
1217
+ * ```ts
1218
+ * const [query, setQuery] = createSignal("");
1219
+ * const results = createMemo(() => searchProducts(query()));
1220
+ * const stale = createMemo(() => isPending(results));
1221
+ *
1222
+ * setQuery("shoes");
1223
+ * // stale() is true until the new results land, while results() keeps
1224
+ * // serving the previous list — dim it instead of showing a spinner.
1225
+ * ```
1226
+ *
1227
+ * @description https://v2.solidjs.com/reference/solid-js/reactivity/is-pending
1228
+ */
1099
1229
  function isPending(fn) {
1100
1230
  markVerdictReader(1);
1101
1231
  const prevProbing = probing;
@@ -1122,12 +1252,17 @@ function isPending(fn) {
1122
1252
  setVerdict(prevVerdict);
1123
1253
  }
1124
1254
  }
1255
+ /** The staging, or the staged error thrown (D-41). */
1256
+ function staged(el) {
1257
+ if (el._config & CONFIG_STAGED_ERROR) throw el._pendingValue;
1258
+ return el._pendingValue;
1259
+ }
1125
1260
  /** `latest()` of a held node: the flushed staging — the proposal — and, for
1126
1261
  * a tracked reader of a rewrite not yet flushed, the late-linker re-run in
1127
1262
  * the flush that carries it. */
1128
1263
  function heldLatest(el, c) {
1129
1264
  const u = unflushedValue(el);
1130
- if (u === NOT_PENDING) return el._pendingValue;
1265
+ if (u === NOT_PENDING) return staged(el);
1131
1266
  if (c !== null) markLateLinker(c);
1132
1267
  return u;
1133
1268
  }
@@ -1154,7 +1289,8 @@ function heldLatest(el, c) {
1154
1289
  * - an errored derivation throws for every reader (#2897). */
1155
1290
  function verdictValue(el, c) {
1156
1291
  const owner = el;
1157
- const tracked = c !== null && tracking && !(c._config & CONFIG_CHILDREN_FORBIDDEN);
1292
+ const owned = c !== null && !(c._config & CONFIG_CHILDREN_FORBIDDEN);
1293
+ const tracked = owned && tracking;
1158
1294
  if (c !== null && tracking) {
1159
1295
  link(el, c);
1160
1296
  if (owner._fn !== undefined) {
@@ -1174,7 +1310,10 @@ function verdictValue(el, c) {
1174
1310
  probeFound = true;
1175
1311
  if (tracked && flushTransaction !== null) verdictRead(c, flushTransaction);
1176
1312
  }
1177
- if (el._config & CONFIG_OVERRIDE) {
1313
+ if (
1314
+ el._config & CONFIG_OVERRIDE &&
1315
+ !(owner._statusFlags & STATUS_ERROR && !(el._config & CONFIG_GUESS))
1316
+ ) {
1178
1317
  const l = txOf(el);
1179
1318
  if (tracked) verdictRead(c, l._parent ?? l);
1180
1319
  const flying = (owner._statusFlags & STATUS_PENDING) !== 0 && !quietPending(owner);
@@ -1233,9 +1372,14 @@ function verdictValue(el, c) {
1233
1372
  }
1234
1373
  return el._value;
1235
1374
  }
1236
- // Born into the future: its staging is its only value.
1237
- if (el._pendingValue !== NOT_PENDING && !(owner._statusFlags & STATUS_PENDING))
1238
- return el._pendingValue;
1375
+ // Born into the future: its staging is its only value. It has no
1376
+ // committed value, so no verdict: `isPending` reads it as its read does —
1377
+ // the owner joins the hold (held with it, or caught by a boundary that
1378
+ // has not shown content) and answers for the commit, where it is final.
1379
+ if (el._pendingValue !== NOT_PENDING && !(owner._statusFlags & STATUS_PENDING)) {
1380
+ if (probing && owned) joinPass(c, el);
1381
+ return staged(el);
1382
+ }
1239
1383
  } else if (el._pendingValue !== NOT_PENDING) {
1240
1384
  if (owner._statusFlags & STATUS_PENDING && !uninitialized)
1241
1385
  pendingVerdict(tracked ? c : null, owner);
@@ -1251,7 +1395,7 @@ function verdictValue(el, c) {
1251
1395
  // A28: an unflushed write is not what `latest` serves; a tracked
1252
1396
  // reader derived from the flushed world runs again in the carrying
1253
1397
  // flush.
1254
- if (globalQueue._running) return el._pendingValue;
1398
+ if (globalQueue._running) return staged(el);
1255
1399
  if (c !== null) markLateLinker(c);
1256
1400
  }
1257
1401
  return el._value;
@@ -1273,7 +1417,7 @@ function verdictValue(el, c) {
1273
1417
  // Installed at module evaluation — present exactly when something imports
1274
1418
  // `latest` or `isPending`. (`read` dispatches to `verdictValue` through
1275
1419
  // `verdict`, set while a window is open.)
1276
- GlobalQueue._observeFlight = observeFlight;
1420
+ installVerdictHooks(observeFlight);
1277
1421
  /** A store read inside an untracked `isPending` probe found a live mark on
1278
1422
  * a record with no node to carry it (store/affects.ts): not final. */
1279
1423
  GlobalQueue._witnessMark = () => {
@@ -1519,7 +1663,7 @@ let externalSourceConfig = null;
1519
1663
  * ```
1520
1664
  */
1521
1665
  // Wires a freshly created computed through the active external-source bridge.
1522
- // Lives here (installed on GlobalQueue while a config is active) rather than
1666
+ // Lives here (installed as a scheduler hook while a config is active) rather than
1523
1667
  // inline in core: esbuild cannot literal-track the mutable config binding the
1524
1668
  // way rollup does, so an inline `if (externalSourceConfig)` block ships in
1525
1669
  // every bundle even though only enableExternalSource() can make it reachable.
@@ -1541,8 +1685,8 @@ function externalUntrack(fn) {
1541
1685
  // removed on reset) so core's presence checks stay equivalent to the old
1542
1686
  // `externalSourceConfig` truthiness checks.
1543
1687
  function syncExternalHooks() {
1544
- GlobalQueue._wireExternalSource = externalSourceConfig ? wireExternalSource : undefined;
1545
- GlobalQueue._externalUntrack = externalSourceConfig ? externalUntrack : undefined;
1688
+ if (externalSourceConfig) setExternalHooks(wireExternalSource, externalUntrack);
1689
+ else setExternalHooks(undefined, undefined);
1546
1690
  }
1547
1691
  function enableExternalSource(config) {
1548
1692
  const { factory, untrack: untrackFn = fn => fn() } = config;
@@ -1568,6 +1712,61 @@ function enableExternalSource(config) {
1568
1712
  syncExternalHooks();
1569
1713
  }
1570
1714
 
1715
+ // Deferred hydration, dependency edges: plain-signal identities.
1716
+ // While on (`setSignalIdentities`; the server-components client turns it on
1717
+ // before hydrating), a plain signal created under an id-bearing owner is
1718
+ // named `<owner id>~<n>`, n counting the plain signals (and plain store
1719
+ // roots: `createStore` over an object, store/edges.ts) created under that
1720
+ // owner in its current pass (transparent owners defer to the nearest
1721
+ // id-bearing one, as child ids do). The server names the same signal the
1722
+ // same way and restarts the count on every re-run, so an edge it shipped (a
1723
+ // slot fill read it) matches the client node; the write hook receives the
1724
+ // node and looks its name up (`signalName`). The count restarts when the owner's pass
1725
+ // generation (`_depGen`, bumped by every recompute) moves on. Only the
1726
+ // frames client imports this module, so nothing here ships without it.
1727
+ // Names live in a side table, not on the node: frames reads them across the
1728
+ // package boundary, where a mangled field would not survive.
1729
+ let counts = null;
1730
+ const names = new WeakMap();
1731
+ /** @internal The identity of a plain signal named while identities were on. */
1732
+ function signalName(node) {
1733
+ return node === undefined ? undefined : names.get(node);
1734
+ }
1735
+ function identify(node) {
1736
+ let o = getOwner();
1737
+ if (!o) return;
1738
+ while (o._config & CONFIG_TRANSPARENT && o._parent) o = o._parent;
1739
+ if (o.id == null) return;
1740
+ const gen = o._depGen;
1741
+ let c = counts.get(o);
1742
+ if (c === undefined || c[0] !== gen) counts.set(o, (c = [gen, 0]));
1743
+ const sid = o.id + "~" + c[1]++;
1744
+ // A store root named twice (one object made into two stores, or a store
1745
+ // handed to `createStore`) is one target under two server names: its
1746
+ // writes cannot be attributed (an empty name).
1747
+ names.set(node, names.has(node) ? "" : sid);
1748
+ }
1749
+ /** @internal Name plain signals from now on (`on`), or stop. */
1750
+ function setSignalIdentities(on) {
1751
+ counts = on ? counts || new WeakMap() : null;
1752
+ signalHook = on ? identify : null;
1753
+ }
1754
+ /**
1755
+ * @internal Called with every plain signal `createSignal` makes, and every
1756
+ * plain store root `createStore` makes, while set (the namer above). Never
1757
+ * set unless `setSignalIdentities` is imported, so a bundle without it keeps
1758
+ * only a null check.
1759
+ */
1760
+ let signalHook = null;
1761
+ /**
1762
+ * @internal Dev only: `watch` is called with every node read while set (the
1763
+ * dev hook `onRead`) — the frames client's identity-drift check watches the
1764
+ * named signals a deferred occurrence reads as it mounts. A no-op in prod.
1765
+ */
1766
+ function setReadWatch(watch) {
1767
+ DEV$1.hooks.onRead = watch || undefined;
1768
+ }
1769
+
1571
1770
  /**
1572
1771
  * Context provides a form of dependency injection. It is used to save from needing to pass
1573
1772
  * data as props through intermediate components. This function creates a new context object
@@ -1728,7 +1927,7 @@ function runEffect(node, type) {
1728
1927
  // hidden subtree. The synchronous first render on creation is not a queued
1729
1928
  // run and goes through. `_modified` stays set; the boundary re-queues the
1730
1929
  // run (`release`).
1731
- if (GlobalQueue._heldRun && GlobalQueue._heldRun(node)) return;
1930
+ if (heldRunHook?.(node)) return;
1732
1931
  // Error arm (#2840), user effects only: a compute-phase error that is still
1733
1932
  // the node's settled state at effect time runs the bundle's error handler in
1734
1933
  // this same imperative, writable scope. Unwrap the StatusError used for
@@ -1823,7 +2022,7 @@ function trackedEffect(fn, options) {
1823
2022
  // pass, but several passes in one flush each enqueue `_run` into the same
1824
2023
  // user queue, and this gate is what collapses them into one run.
1825
2024
  if (!node._modified || node._flags & REACTIVE_DISPOSED) return;
1826
- if (GlobalQueue._heldRun && GlobalQueue._heldRun(node)) return;
2025
+ if (heldRunHook?.(node)) return;
1827
2026
  setTrackedQueueCallback(true);
1828
2027
  try {
1829
2028
  node._modified = false;
@@ -1954,6 +2153,155 @@ function reportClientError(error, owner, thrower) {
1954
2153
  }
1955
2154
  }
1956
2155
 
2156
+ /**
2157
+ * Ownership stamp (#3360): every backing the store ALLOCATES (CoW clones,
2158
+ * privatized committed backings) carries its owning target under this
2159
+ * enumerable symbol. One property write replaces the two weak-collection
2160
+ * registrations (ownership set + raw→target map) a fresh object used to pay
2161
+ * per draft — V8's identity-hash + ephemeron cost dominated the one-key
2162
+ * write floor. Enumerable so a spread copy (the plain-data clone path) stays
2163
+ * on the fast path and carries the stamp along.
2164
+ *
2165
+ * Owned backings are never user-reachable (`snapshot` copies them, the traps
2166
+ * hide the key), so every raw key walk in the store must skip `$OWNER`, and
2167
+ * ownership is answered by `isOwned` — a user object never carries it.
2168
+ * Overlay drafts (`Object.create(v)` over an owned `v`) inherit the stamp.
2169
+ */
2170
+ const $OWNER = Symbol("STORE_OWNER");
2171
+ /** raw → target for plain (family-less) stores. Family stores use their own
2172
+ * map so one raw can wrap once per family (§7b). */
2173
+ const storeLookup = new WeakMap();
2174
+ function setLookup(raw, t) {
2175
+ (t.fam?.map ?? storeLookup).set(raw, t);
2176
+ }
2177
+ function isOwned(raw) {
2178
+ return raw[$OWNER] !== undefined;
2179
+ }
2180
+ /** The target wrapping `raw` in `fam` (or the plain lookup). An owned raw
2181
+ * answers from its stamp; a user raw from the map. */
2182
+ function lookupTarget(raw, fam) {
2183
+ const owner = raw[$OWNER];
2184
+ return owner !== undefined && owner.fam === fam ? owner : (fam?.map ?? storeLookup).get(raw);
2185
+ }
2186
+ function devAssertNeverUserMutation(target) {
2187
+ return;
2188
+ }
2189
+ /** Sticky descendants flag up the parent chain (§6d): adoption descends
2190
+ * into a changed child pair only where a target with nodes exists below. */
2191
+ function markDescendants(target) {
2192
+ let t = target;
2193
+ while (t && !t.d) {
2194
+ t.d = true;
2195
+ t = t.u;
2196
+ }
2197
+ }
2198
+ /** Observe tier: the owner in scope at `createStore` (attribution names and
2199
+ * owner paths); the `_owner` write never reaches the proxy. */
2200
+ const storeOwners = new WeakMap();
2201
+ /** Brand keys the proxy answers directly (never user data). */
2202
+ const $TRACK = Symbol("STORE_TRACK"),
2203
+ $TARGET = Symbol("STORE_TARGET"),
2204
+ $PROXY = Symbol("STORE_PROXY"),
2205
+ $RECORD = Symbol("VIEW_RECORD");
2206
+ // ---------------------------------------------------------------------------
2207
+ // raw marking (R42 / RUL-12: a value is never both wrapped and raw)
2208
+ const rawValues = new WeakSet();
2209
+ let rawValuesUsed = false;
2210
+ function isRawValue(value) {
2211
+ return rawValuesUsed && rawValues.has(value);
2212
+ }
2213
+ /** Mark a value so no store ever wraps it (sticky; it reads as a leaf). */
2214
+ function markRaw(value) {
2215
+ if (isWrappable(value)) {
2216
+ rawValuesUsed = true;
2217
+ rawValues.add(value);
2218
+ }
2219
+ return value;
2220
+ }
2221
+ /** A shallow store's ingested record: raw by contract (values served
2222
+ * verbatim, slots replaced by reference). */
2223
+ function markRawOne(v) {
2224
+ if (isWrappable(v)) {
2225
+ if (v[$TARGET] !== undefined) return;
2226
+ if (lookupTarget(v, null) !== undefined)
2227
+ throw new Error(
2228
+ "shallow store: an ingested record is already tracked as a deep store — one value cannot present both wrapped and raw"
2229
+ );
2230
+ rawValuesUsed = true;
2231
+ rawValues.add(v);
2232
+ }
2233
+ }
2234
+ function markRawIngest(container) {
2235
+ if (Array.isArray(container)) {
2236
+ for (let i = 0, len = container.length; i < len; i++) markRawOne(container[i]);
2237
+ } else {
2238
+ for (const k in container) markRawOne(container[k]);
2239
+ }
2240
+ }
2241
+ // ---------------------------------------------------------------------------
2242
+ // wrappability
2243
+ const OBJECT_PROTO = Object.prototype;
2244
+ const wrappableProtos = new WeakMap();
2245
+ function isWrappable(obj) {
2246
+ if (obj == null || typeof obj !== "object" || Object.isFrozen(obj)) return false;
2247
+ const proto = Object.getPrototypeOf(obj);
2248
+ if (proto === OBJECT_PROTO || proto === null) return true;
2249
+ if (Array.isArray(obj)) return true;
2250
+ let wrappable = wrappableProtos.get(proto);
2251
+ if (wrappable === undefined) {
2252
+ wrappable =
2253
+ Object.prototype.toString.call(obj) === "[object Object]" &&
2254
+ (typeof Node === "undefined" || !(obj instanceof Node));
2255
+ wrappableProtos.set(proto, wrappable);
2256
+ }
2257
+ return wrappable;
2258
+ }
2259
+ function ownEnumerableKeys(o) {
2260
+ return Reflect.ownKeys(o).filter(k => Object.prototype.propertyIsEnumerable.call(o, k));
2261
+ }
2262
+ // ---------------------------------------------------------------------------
2263
+ // write override: a projection's draft ops carry it (the derive is the
2264
+ // author — its writes must land outside the sync setter scope too, in a
2265
+ // continuation after an `await`/`yield`). Projections return in S3; the
2266
+ // traps already honour it.
2267
+ let writeOverride = false;
2268
+ function setWriteOverride(value) {
2269
+ writeOverride = value;
2270
+ }
2271
+
2272
+ /**
2273
+ * Deferred hydration, dependency edges for stores. Only the frames client
2274
+ * imports this module, so nothing here ships without it.
2275
+ *
2276
+ * A plain store root (`createStore` over an object) is named like a plain
2277
+ * signal (core/identities.ts: `<owner id>~<n>`, on the same count), and the
2278
+ * whole store goes by that one name: the server records it for any read of
2279
+ * the store, at any depth, and a setter call on the store — on its root or
2280
+ * on a proxy nested in it — is a write to it. Its readers mount at the
2281
+ * setter's entry, before the draft runs, as a plain signal's do.
2282
+ */
2283
+ /**
2284
+ * @internal The name of the store `x` belongs to — `x` a store proxy (a
2285
+ * setter's entry) or one of its property nodes (the setter notifying it) —
2286
+ * or undefined when it has none or it cannot be placed: a projection or
2287
+ * optimistic family, a store chained over another store's proxy, a root
2288
+ * named twice, or a proxy reached through a store other than the one it
2289
+ * was named as the root of.
2290
+ */
2291
+ function storeName(x) {
2292
+ if (x === undefined) return undefined;
2293
+ let t = x._config & CONFIG_SLOT_NODE ? x._host : x[$TARGET];
2294
+ if (t === undefined) return undefined;
2295
+ // Up to the root; a target on the way named as a root of its own is one
2296
+ // object in two stores.
2297
+ while (t.u !== null) {
2298
+ if (t.fam !== null || t.ch || signalName(t) !== undefined) return undefined;
2299
+ t = t.u;
2300
+ }
2301
+ if (t.fam !== null || t.ch) return undefined;
2302
+ return signalName(t) || undefined;
2303
+ }
2304
+
1957
2305
  /**
1958
2306
  * Low-level reactive-cleanup primitive. Registers a callback that runs when
1959
2307
  * the surrounding owner is disposed.
@@ -2042,6 +2390,7 @@ function createSignal(first, second) {
2042
2390
  }
2043
2391
  const node = signal(first, second);
2044
2392
  registerGraph(node, getOwner());
2393
+ if (signalHook !== null) signalHook(node);
2045
2394
  return [accessor(node), setSignal.bind(null, node)];
2046
2395
  }
2047
2396
  function createOptimistic(first, second) {
@@ -2123,7 +2472,7 @@ function createMemo(compute, options) {
2123
2472
  * );
2124
2473
  * ```
2125
2474
  *
2126
- * @description https://docs.solidjs.com/reference/basic-reactivity/create-effect
2475
+ * @description https://v2.solidjs.com/reference/solid-js/reactivity/create-effect
2127
2476
  */
2128
2477
  function createEffect(compute, effectFn, options) {
2129
2478
  if (effectFn === undefined) {
@@ -2172,7 +2521,7 @@ function createEffect(compute, effectFn, options) {
2172
2521
  * }
2173
2522
  * ```
2174
2523
  *
2175
- * @description https://docs.solidjs.com/reference/secondary-primitives/create-render-effect
2524
+ * @description https://v2.solidjs.com/reference/solid-js/advanced/specialized-reactivity/create-render-effect
2176
2525
  */
2177
2526
  function createRenderEffect(compute, effectFn, options) {
2178
2527
  effect(compute, effectFn, undefined, options);
@@ -2220,7 +2569,7 @@ function createRenderEffect(compute, effectFn, options) {
2220
2569
  * });
2221
2570
  * ```
2222
2571
  *
2223
- * @description https://docs.solidjs.com/reference/secondary-primitives/create-tracked-effect
2572
+ * @description https://v2.solidjs.com/reference/solid-js/advanced/specialized-reactivity/create-tracked-effect
2224
2573
  */
2225
2574
  function createTrackedEffect(compute, options) {
2226
2575
  trackedEffect(compute, options);
@@ -2249,7 +2598,7 @@ function createTrackedEffect(compute, options) {
2249
2598
  * setCount(1); // logs once, reaction re-armed for next change
2250
2599
  * ```
2251
2600
  *
2252
- * @description https://docs.solidjs.com/reference/secondary-primitives/create-reaction
2601
+ * @description https://v2.solidjs.com/reference/solid-js/advanced/specialized-reactivity/create-reaction
2253
2602
  */
2254
2603
  function createReaction(effectFn, options) {
2255
2604
  let cl = undefined;
@@ -2309,21 +2658,31 @@ function createReaction(effectFn, options) {
2309
2658
  * joined it (L2, born held — A29) and delivers at that commit: the
2310
2659
  * committed view, never a foreign hold's unrevealed frame. */
2311
2660
  function watch(fn, onValue, onError, config = 0, inPass, own = flushTransaction) {
2661
+ if (config & CONFIG_AUTHORITATIVE) {
2662
+ GlobalQueue._truth = truthRead;
2663
+ GlobalQueue._confirmed = truthConfirmed;
2664
+ }
2312
2665
  // `config` is on the node for its first pass too (the pass runs inside
2313
2666
  // `computed()`): `until`'s predicate is authoritative from its first read,
2314
2667
  // or an already-displayed guess would satisfy it in a later slice. (REDERIVE
2315
2668
  // after: the first pass's own error is the promise's rejection.)
2316
2669
  computed(
2317
2670
  () => {
2318
- let v;
2671
+ let v,
2672
+ failed = false;
2319
2673
  try {
2320
2674
  v = fn();
2321
2675
  } catch (e) {
2322
2676
  if (e instanceof NotReadyError) throw e;
2323
- queueMicrotask(() => onError(unwrapStatusError(e)));
2324
- return;
2677
+ // An error is an answer (D-41): delivered by the same frame rule.
2678
+ v = unwrapStatusError(e);
2679
+ failed = true;
2325
2680
  }
2326
2681
  const c = getOwner();
2682
+ // A truth reader waits out a `createDeferred` flight its value derives
2683
+ // from, which serves everyone else the stale answer (D-40).
2684
+ const lag = config & CONFIG_AUTHORITATIVE && GlobalQueue._lagging?.(c);
2685
+ if (lag) throw new NotReadyError(lag);
2327
2686
  // (Outside a flush a mainline pass joins through `passTx`.)
2328
2687
  const joined = flushTransaction ?? passTx;
2329
2688
  if (
@@ -2334,6 +2693,7 @@ function watch(fn, onValue, onError, config = 0, inPass, own = flushTransaction)
2334
2693
  staleReader(c, resolveTx(joined));
2335
2694
  return;
2336
2695
  }
2696
+ if (failed) return void queueMicrotask(() => onError(v));
2337
2697
  inPass?.(v);
2338
2698
  queueMicrotask(() => onValue(v));
2339
2699
  },
@@ -2446,17 +2806,24 @@ function refresh(target) {
2446
2806
  const own = flushTransaction;
2447
2807
  const promise = new Promise((res, rej) => {
2448
2808
  queueMicrotask(() => {
2449
- // No createRoot: the microtask has no ambient owner, so the watcher is
2450
- // naturally detached, and settle disposes the node directly (dev keeps
2451
- // a root husk purely to stay quiet about the missing owner). The
2452
- // capture runs inside the watcher's own pass, where the ambient owner
2453
- // IS the watcher — exactly what dispose() takes.
2809
+ // The root is load-bearing: an ownerless computed is auto-disposing,
2810
+ // and the settle walk releases an unobserved one instead of re-running
2811
+ // it, so the waiter would never see the refetch land (#3888). Settle
2812
+ // disposes the waiter directly; the capture runs inside the watcher's
2813
+ // own pass, where the ambient owner IS the watcher — exactly what
2814
+ // dispose() takes.
2454
2815
  let waiter = null;
2455
2816
  const make = () =>
2456
2817
  watch(
2457
2818
  () => {
2458
2819
  if (waiter === null) waiter = getOwner();
2459
- return read(node);
2820
+ // Authoritative: the truth beneath a guess (`until`'s view). A
2821
+ // guessed node's own re-ask is not answered by it (#3895): park
2822
+ // on that flight, or reject with its error.
2823
+ const v = read(node);
2824
+ if (node._config & CONFIG_GUESS && node._statusFlags & (STATUS_PENDING | STATUS_ERROR))
2825
+ throw node._x?._error;
2826
+ return v;
2460
2827
  },
2461
2828
  value => {
2462
2829
  res(typeof target === "function" ? value : target);
@@ -2466,7 +2833,7 @@ function refresh(target) {
2466
2833
  rej(err);
2467
2834
  dispose(waiter);
2468
2835
  },
2469
- CONFIG_FRESH_READ,
2836
+ CONFIG_FRESH_READ | CONFIG_AUTHORITATIVE,
2470
2837
  undefined,
2471
2838
  own
2472
2839
  );
@@ -2688,122 +3055,6 @@ function onSettled(callback) {
2688
3055
  });
2689
3056
  }
2690
3057
 
2691
- /**
2692
- * Ownership stamp (#3360): every backing the store ALLOCATES (CoW clones,
2693
- * privatized committed backings) carries its owning target under this
2694
- * enumerable symbol. One property write replaces the two weak-collection
2695
- * registrations (ownership set + raw→target map) a fresh object used to pay
2696
- * per draft — V8's identity-hash + ephemeron cost dominated the one-key
2697
- * write floor. Enumerable so a spread copy (the plain-data clone path) stays
2698
- * on the fast path and carries the stamp along.
2699
- *
2700
- * Owned backings are never user-reachable (`snapshot` copies them, the traps
2701
- * hide the key), so every raw key walk in the store must skip `$OWNER`, and
2702
- * ownership is answered by `isOwned` — a user object never carries it.
2703
- * Overlay drafts (`Object.create(v)` over an owned `v`) inherit the stamp.
2704
- */
2705
- const $OWNER = Symbol("STORE_OWNER");
2706
- /** raw → target for plain (family-less) stores. Family stores use their own
2707
- * map so one raw can wrap once per family (§7b). */
2708
- const storeLookup = new WeakMap();
2709
- function setLookup(raw, t) {
2710
- (t.fam?.map ?? storeLookup).set(raw, t);
2711
- }
2712
- function isOwned(raw) {
2713
- return raw[$OWNER] !== undefined;
2714
- }
2715
- /** The target wrapping `raw` in `fam` (or the plain lookup). An owned raw
2716
- * answers from its stamp; a user raw from the map. */
2717
- function lookupTarget(raw, fam) {
2718
- const owner = raw[$OWNER];
2719
- return owner !== undefined && owner.fam === fam ? owner : (fam?.map ?? storeLookup).get(raw);
2720
- }
2721
- function devAssertNeverUserMutation(target) {
2722
- return;
2723
- }
2724
- /** Sticky descendants flag up the parent chain (§6d): adoption descends
2725
- * into a changed child pair only where a target with nodes exists below. */
2726
- function markDescendants(target) {
2727
- let t = target;
2728
- while (t && !t.d) {
2729
- t.d = true;
2730
- t = t.u;
2731
- }
2732
- }
2733
- /** Observe tier: the owner in scope at `createStore` (attribution names and
2734
- * owner paths); the `_owner` write never reaches the proxy. */
2735
- const storeOwners = new WeakMap();
2736
- /** Brand keys the proxy answers directly (never user data). */
2737
- const $TRACK = Symbol("STORE_TRACK"),
2738
- $TARGET = Symbol("STORE_TARGET"),
2739
- $PROXY = Symbol("STORE_PROXY"),
2740
- $RECORD = Symbol("VIEW_RECORD");
2741
- // ---------------------------------------------------------------------------
2742
- // raw marking (R42 / RUL-12: a value is never both wrapped and raw)
2743
- const rawValues = new WeakSet();
2744
- let rawValuesUsed = false;
2745
- function isRawValue(value) {
2746
- return rawValuesUsed && rawValues.has(value);
2747
- }
2748
- /** Mark a value so no store ever wraps it (sticky; it reads as a leaf). */
2749
- function markRaw(value) {
2750
- if (isWrappable(value)) {
2751
- rawValuesUsed = true;
2752
- rawValues.add(value);
2753
- }
2754
- return value;
2755
- }
2756
- /** A shallow store's ingested record: raw by contract (values served
2757
- * verbatim, slots replaced by reference). */
2758
- function markRawOne(v) {
2759
- if (isWrappable(v)) {
2760
- if (v[$TARGET] !== undefined) return;
2761
- if (lookupTarget(v, null) !== undefined)
2762
- throw new Error(
2763
- "shallow store: an ingested record is already tracked as a deep store — one value cannot present both wrapped and raw"
2764
- );
2765
- rawValuesUsed = true;
2766
- rawValues.add(v);
2767
- }
2768
- }
2769
- function markRawIngest(container) {
2770
- if (Array.isArray(container)) {
2771
- for (let i = 0, len = container.length; i < len; i++) markRawOne(container[i]);
2772
- } else {
2773
- for (const k in container) markRawOne(container[k]);
2774
- }
2775
- }
2776
- // ---------------------------------------------------------------------------
2777
- // wrappability
2778
- const OBJECT_PROTO = Object.prototype;
2779
- const wrappableProtos = new WeakMap();
2780
- function isWrappable(obj) {
2781
- if (obj == null || typeof obj !== "object" || Object.isFrozen(obj)) return false;
2782
- const proto = Object.getPrototypeOf(obj);
2783
- if (proto === OBJECT_PROTO || proto === null) return true;
2784
- if (Array.isArray(obj)) return true;
2785
- let wrappable = wrappableProtos.get(proto);
2786
- if (wrappable === undefined) {
2787
- wrappable =
2788
- Object.prototype.toString.call(obj) === "[object Object]" &&
2789
- (typeof Node === "undefined" || !(obj instanceof Node));
2790
- wrappableProtos.set(proto, wrappable);
2791
- }
2792
- return wrappable;
2793
- }
2794
- function ownEnumerableKeys(o) {
2795
- return Reflect.ownKeys(o).filter(k => Object.prototype.propertyIsEnumerable.call(o, k));
2796
- }
2797
- // ---------------------------------------------------------------------------
2798
- // write override: a projection's draft ops carry it (the derive is the
2799
- // author — its writes must land outside the sync setter scope too, in a
2800
- // continuation after an `await`/`yield`). Projections return in S3; the
2801
- // traps already honour it.
2802
- let writeOverride = false;
2803
- function setWriteOverride(value) {
2804
- writeOverride = value;
2805
- }
2806
-
2807
3058
  function mapArray(list, map, options) {
2808
3059
  const keyFn = typeof options?.keyed === "function" ? options.keyed : undefined;
2809
3060
  const indexes = map.length > 1;
@@ -2977,6 +3228,79 @@ function commitSmallMove(data, newItems, newLen, srcPos, dstPos, runs) {
2977
3228
  }
2978
3229
  return true;
2979
3230
  }
3231
+ /** Stage a PURE REMOVAL without the key map: when the new window is the old
3232
+ * window with items dropped, in order (one-item removals, every-nth, the
3233
+ * table filters), a two-pointer walk lands every survivor in the window
3234
+ * stage with one key call per item and returns the leavers; a shape that is
3235
+ * not an ordered subsequence returns null with the node stage cleared, and
3236
+ * the map path below starts from scratch (it rewrites every window slot of
3237
+ * `temp` / `rows` / `indexes`, so only `tempNodes`' `!== undefined` probe
3238
+ * needs the reset). The stage arrays are scratch — nothing live is touched. */
3239
+ function stageRemoval(data, newItems, start, end, newEnd, temp, tempNodes, rows, indexes) {
3240
+ const key = data._key;
3241
+ const oldItems = data._items;
3242
+ const nodes = data._nodes;
3243
+ const removed = [];
3244
+ let i = start;
3245
+ for (let j = start; j <= newEnd; i++, j++) {
3246
+ const nk = key ? key(newItems[j]) : newItems[j];
3247
+ while (i <= end && (key ? key(oldItems[i]) : oldItems[i]) !== nk) removed.push(nodes[i++]);
3248
+ if (i > end) {
3249
+ tempNodes.fill(undefined);
3250
+ return null;
3251
+ }
3252
+ temp[j] = data._mappings[i];
3253
+ tempNodes[j - start] = nodes[i];
3254
+ if (rows) rows[j - start] = data._rows[i];
3255
+ if (indexes) indexes[j - start] = data._indexes[i];
3256
+ }
3257
+ for (; i <= end; i++) removed.push(nodes[i]);
3258
+ return removed;
3259
+ }
3260
+ /** Land a staged window into the live bookkeeping arrays in place: shift the
3261
+ * suffix by `dif` (a loop walked from the side that never overwrites an
3262
+ * unread slot), take the window from the stage, write survivors' row and
3263
+ * index signals. Its own function so the general path's body stays under
3264
+ * the JIT's optimization budget — the inlined commit measured ~10% slower on
3265
+ * full-window reorders (the small-move note has the same finding). */
3266
+ function commitWindow(data, newItems, newLen, start, end, newEnd, tempNodes, rows, indexes) {
3267
+ const nodes = data._nodes;
3268
+ const liveRows = data._rows;
3269
+ const liveIndexes = data._indexes;
3270
+ const oldLen = data._len;
3271
+ const dif = newLen - oldLen;
3272
+ let i, j;
3273
+ if (dif > 0) {
3274
+ // Grow first: a store past the end from the top is a third slower than
3275
+ // filling a pre-sized tail (measured on 1k refs, 2026-10-15).
3276
+ nodes.length = newLen;
3277
+ if (liveRows) liveRows.length = newLen;
3278
+ if (liveIndexes) liveIndexes.length = newLen;
3279
+ for (i = oldLen - 1, j = newLen - 1; i > end; i--, j--) {
3280
+ nodes[j] = nodes[i];
3281
+ if (liveRows) liveRows[j] = liveRows[i];
3282
+ if (liveIndexes) liveIndexes[j] = liveIndexes[i];
3283
+ }
3284
+ } else if (dif < 0) {
3285
+ for (i = end + 1, j = newEnd + 1; i < oldLen; i++, j++) {
3286
+ nodes[j] = nodes[i];
3287
+ if (liveRows) liveRows[j] = liveRows[i];
3288
+ if (liveIndexes) liveIndexes[j] = liveIndexes[i];
3289
+ }
3290
+ nodes.length = newLen;
3291
+ if (liveRows) liveRows.length = newLen;
3292
+ if (liveIndexes) liveIndexes.length = newLen;
3293
+ }
3294
+ for (j = start; j <= newEnd; j++) {
3295
+ nodes[j] = tempNodes[j - start];
3296
+ if (liveRows) setSignal((liveRows[j] = rows[j - start]), newItems[j]);
3297
+ if (liveIndexes) setSignal((liveIndexes[j] = indexes[j - start]), j);
3298
+ }
3299
+ for (j = newEnd + 1; j < newLen; j++) {
3300
+ if (liveRows) setSignal(liveRows[j], newItems[j]);
3301
+ if (liveIndexes && dif !== 0) setSignal(liveIndexes[j], j);
3302
+ }
3303
+ }
2980
3304
  function updateKeyedMap() {
2981
3305
  // Untracked reads inside the internal owner resolve via _parentComputed:
2982
3306
  // routed through the node, a store-proxy lookup sees pending writes (not a
@@ -2991,6 +3315,10 @@ function updateKeyedMap() {
2991
3315
  runWithOwner(this._owner, () => {
2992
3316
  let i,
2993
3317
  j,
3318
+ // The stage slot a mapper writes its row/index signal into: `j` on the
3319
+ // create-all path, `j - start` on the general path, whose stage arrays
3320
+ // are sized to the changed WINDOW, not the list.
3321
+ slot,
2994
3322
  rows,
2995
3323
  indexes,
2996
3324
  // Mappers write freshly-created row/index signals into the STAGE
@@ -2998,19 +3326,19 @@ function updateKeyedMap() {
2998
3326
  mapper = this._rows
2999
3327
  ? this._byIndex
3000
3328
  ? () => {
3001
- rows[j] = signal(newItems[j], pureOptions);
3002
- return this._map(accessor(rows[j]), j);
3329
+ rows[slot] = signal(newItems[j], pureOptions);
3330
+ return this._map(accessor(rows[slot]), j);
3003
3331
  }
3004
3332
  : () => {
3005
- rows[j] = signal(newItems[j], pureOptions);
3006
- indexes && (indexes[j] = signal(j, pureOptions));
3007
- return this._map(accessor(rows[j]), indexes ? accessor(indexes[j]) : undefined);
3333
+ rows[slot] = signal(newItems[j], pureOptions);
3334
+ indexes && (indexes[slot] = signal(j, pureOptions));
3335
+ return this._map(accessor(rows[slot]), indexes ? accessor(indexes[slot]) : undefined);
3008
3336
  }
3009
3337
  : this._indexes
3010
3338
  ? () => {
3011
3339
  const item = newItems[j];
3012
- indexes[j] = signal(j, pureOptions);
3013
- return this._map(item, accessor(indexes[j]));
3340
+ indexes[slot] = signal(j, pureOptions);
3341
+ return this._map(item, accessor(indexes[slot]));
3014
3342
  }
3015
3343
  : () => {
3016
3344
  const item = newItems[j];
@@ -3041,7 +3369,10 @@ function updateKeyedMap() {
3041
3369
  rows = this._rows && new Array(newLen);
3042
3370
  indexes = this._indexes && new Array(newLen);
3043
3371
  try {
3044
- for (j = 0; j < newLen; j++) mappings[j] = runWithOwner((nodes[j] = createOwner()), mapper);
3372
+ for (j = 0; j < newLen; j++) {
3373
+ slot = j;
3374
+ mappings[j] = runWithOwner((nodes[j] = createOwner()), mapper);
3375
+ }
3045
3376
  } catch (err) {
3046
3377
  for (i = 0; i <= j; i++) nodes[i]?.dispose();
3047
3378
  throw err;
@@ -3143,81 +3474,89 @@ function updateKeyedMap() {
3143
3474
  if (k <= hi && trySmallMove(this, newItems, newLen, start)) return;
3144
3475
  }
3145
3476
  const dif = newLen - this._len;
3477
+ // WINDOW-SIZED STAGING, IN-PLACE COMMIT (2026-10-15). The strong-abort
3478
+ // contract (#2903) only needs the pass to touch nothing live before
3479
+ // every mapper has succeeded; it never needed four fresh newLen-sized
3480
+ // arrays. Only the changed window [start, newEnd] is staged — the
3481
+ // prefix and suffix are, by construction, the rows that did not move
3482
+ // relative to each other — and at commit the bookkeeping arrays
3483
+ // (`_nodes`, `_rows`, `_indexes`) take the window in place with the
3484
+ // suffix shifted by a plain loop (`copyWithin` is a generic
3485
+ // per-element builtin in V8 — 30× slower than the loop on 1k refs,
3486
+ // measured 2026-10-15). `_mappings` alone is
3487
+ // rebuilt: its fresh identity is the change signal downstream relies
3488
+ // on. A one-row edit on a 500-row list used to allocate ~20 KB of
3489
+ // staging plus a position-sized chain array per commit (uibench's
3490
+ // one-item tree ops at 4–6× Octane, jfb-reorder `removefirst` at
3491
+ // 2.5×); now it stages one slot.
3492
+ const wLen = newEnd - start + 1;
3146
3493
  const temp = new Array(newLen);
3147
- const tempNodes = new Array(newLen);
3148
- rows = this._rows ? new Array(newLen) : undefined;
3149
- indexes = this._indexes ? new Array(newLen) : undefined;
3150
- // 0) prepare a map of all indices in the changed window of newItems,
3151
- // scanning backwards so we encounter them in natural order
3152
- newIndices = new Map();
3153
- newIndicesNext = new Array(newEnd + 1);
3154
- for (j = newEnd; j >= start; j--) {
3155
- item = newItems[j];
3156
- key = this._key ? this._key(item) : item;
3157
- i = newIndices.get(key);
3158
- newIndicesNext[j] = i === undefined ? -1 : i;
3159
- newIndices.set(key, j);
3160
- }
3161
- // 1) step through the old changed window and see if items can be found
3162
- // in the new set; if so, stage them at their new positions; if not,
3163
- // queue them for disposal at commit
3164
- for (i = start; i <= end; i++) {
3165
- item = this._items[i];
3166
- key = this._key ? this._key(item) : item;
3167
- j = newIndices.get(key);
3168
- if (j !== undefined && j !== -1) {
3169
- temp[j] = this._mappings[i];
3170
- tempNodes[j] = this._nodes[i];
3171
- rows && (rows[j] = this._rows[i]);
3172
- indexes && (indexes[j] = this._indexes[i]);
3173
- j = newIndicesNext[j];
3494
+ const tempNodes = new Array(wLen);
3495
+ rows = this._rows ? new Array(wLen) : undefined;
3496
+ indexes = this._indexes ? new Array(wLen) : undefined;
3497
+ // A shrinking pass is tried as a pure removal first (stageRemoval: no
3498
+ // key map, one key call per item); anything else takes the map path.
3499
+ const pure =
3500
+ dif < 0
3501
+ ? stageRemoval(this, newItems, start, end, newEnd, temp, tempNodes, rows, indexes)
3502
+ : null;
3503
+ if (pure !== null) {
3504
+ // (The list-churn census below wants removals AND creations in one
3505
+ // pass; a pure removal has none, so no item ledger is kept here.)
3506
+ if (pure.length !== 0) removed = pure;
3507
+ } else {
3508
+ // 0) prepare a map of all indices in the changed window of newItems,
3509
+ // scanning backwards so we encounter them in natural order
3510
+ newIndices = new Map();
3511
+ newIndicesNext = new Array(wLen);
3512
+ for (j = newEnd; j >= start; j--) {
3513
+ item = newItems[j];
3514
+ key = this._key ? this._key(item) : item;
3515
+ i = newIndices.get(key);
3516
+ newIndicesNext[j - start] = i === undefined ? -1 : i;
3174
3517
  newIndices.set(key, j);
3175
- } else {
3176
- (removed ??= []).push(this._nodes[i]);
3177
- if (true && attrHooks !== null) (removedItems ??= []).push(item);
3178
3518
  }
3179
- }
3180
- // 2) create new rows into the temp arrays; an abort disposes only these
3181
- try {
3182
- for (j = start; j <= newEnd; j++) {
3183
- if (tempNodes[j] !== undefined) continue;
3184
- (created ??= []).push((tempNodes[j] = createOwner()));
3185
- if (true && attrHooks !== null) (createdItems ??= []).push(newItems[j]);
3186
- temp[j] = runWithOwner(tempNodes[j], mapper);
3187
- }
3188
- } catch (err) {
3189
- if (created) for (i = 0; i < created.length; i++) created[i].dispose();
3190
- throw err;
3191
- }
3192
- // 3) commit: land the retained prefix and suffix plus the staged window
3193
- // into the fresh arrays, swap them in (new identity for downstream
3194
- // change propagation), then dispose exited rows
3195
- for (i = 0; i < start; i++) {
3196
- temp[i] = this._mappings[i];
3197
- tempNodes[i] = this._nodes[i];
3198
- rows && (rows[i] = this._rows[i]);
3199
- indexes && (indexes[i] = this._indexes[i]);
3200
- }
3201
- for (j = start; j <= newEnd; j++) {
3202
- if (rows) write(rows[j], newItems[j]);
3203
- if (indexes) write(indexes[j], j);
3204
- }
3205
- for (j = newEnd + 1; j < newLen; j++) {
3206
- temp[j] = this._mappings[j - dif];
3207
- tempNodes[j] = this._nodes[j - dif];
3208
- if (rows) {
3209
- rows[j] = this._rows[j - dif];
3210
- write(rows[j], newItems[j]);
3519
+ // 1) step through the old changed window and see if items can be found
3520
+ // in the new set; if so, stage them at their new positions; if not,
3521
+ // queue them for disposal at commit
3522
+ for (i = start; i <= end; i++) {
3523
+ item = this._items[i];
3524
+ key = this._key ? this._key(item) : item;
3525
+ j = newIndices.get(key);
3526
+ if (j !== undefined && j !== -1) {
3527
+ temp[j] = this._mappings[i];
3528
+ tempNodes[j - start] = this._nodes[i];
3529
+ rows && (rows[j - start] = this._rows[i]);
3530
+ indexes && (indexes[j - start] = this._indexes[i]);
3531
+ j = newIndicesNext[j - start];
3532
+ newIndices.set(key, j);
3533
+ } else {
3534
+ (removed ??= []).push(this._nodes[i]);
3535
+ if (true && attrHooks !== null) (removedItems ??= []).push(item);
3536
+ }
3211
3537
  }
3212
- if (indexes) {
3213
- indexes[j] = this._indexes[j - dif];
3214
- if (dif !== 0) write(indexes[j], j);
3538
+ // 2) create new rows into the window stage; an abort disposes only these
3539
+ try {
3540
+ for (j = start; j <= newEnd; j++) {
3541
+ if (tempNodes[j - start] !== undefined) continue;
3542
+ (created ??= []).push((tempNodes[j - start] = createOwner()));
3543
+ if (true && attrHooks !== null) (createdItems ??= []).push(newItems[j]);
3544
+ slot = j - start;
3545
+ temp[j] = runWithOwner(tempNodes[j - start], mapper);
3546
+ }
3547
+ } catch (err) {
3548
+ if (created) for (i = 0; i < created.length; i++) created[i].dispose();
3549
+ throw err;
3215
3550
  }
3216
3551
  }
3552
+ // 3) commit: the retained prefix and suffix land in the fresh mapped
3553
+ // array; the bookkeeping arrays take the staged window IN PLACE (own
3554
+ // function: kept out of this body on purpose, see commitWindow); then
3555
+ // the exited rows dispose
3556
+ for (i = 0; i < start; i++) temp[i] = this._mappings[i];
3557
+ for (j = newEnd + 1; j < newLen; j++) temp[j] = this._mappings[j - dif];
3558
+ commitWindow(this, newItems, newLen, start, end, newEnd, tempNodes, rows, indexes);
3217
3559
  this._mappings = temp;
3218
- this._nodes = tempNodes;
3219
- rows && (this._rows = rows);
3220
- indexes && (this._indexes = indexes);
3221
3560
  this._len = newLen;
3222
3561
  // save a copy of the mapped items for the next update
3223
3562
  this._items = newItems.slice(0);
@@ -3247,7 +3586,7 @@ function updateKeyedMap() {
3247
3586
  * const view = repeat(count, i => `Item ${i}`, { fallback: () => "empty" });
3248
3587
  * ```
3249
3588
  *
3250
- * @description https://docs.solidjs.com/reference/reactive-utilities/repeat
3589
+ * @description https://v2.solidjs.com/reference/solid-js/advanced/jsx-component-primitives/repeat
3251
3590
  */
3252
3591
  function repeat(count, map, options) {
3253
3592
  const wrappedMap = options?.name
@@ -3343,6 +3682,15 @@ function compare(key, a, b) {
3343
3682
  return key ? key(a) === key(b) : true;
3344
3683
  }
3345
3684
 
3685
+ let optHooks = null;
3686
+ function installOptHooks(hooks) {
3687
+ optHooks = hooks;
3688
+ }
3689
+ let affectsHooks = null;
3690
+ function installAffectsHooks(hooks) {
3691
+ affectsHooks = hooks;
3692
+ }
3693
+
3346
3694
  /**
3347
3695
  * Store — plain stores on L2 (plan sec. 31, S1).
3348
3696
  *
@@ -3373,14 +3721,6 @@ function compare(key, a, b) {
3373
3721
  * setter (`wk`); overlay drafts for large plain records; no new named
3374
3722
  * fields on array targets; zero allocation for adopted-but-unread objects.
3375
3723
  */
3376
- let optHooks = null;
3377
- function installOptHooks(hooks) {
3378
- optHooks = hooks;
3379
- }
3380
- let affectsHooks = null;
3381
- function installAffectsHooks(hooks) {
3382
- affectsHooks = hooks;
3383
- }
3384
3724
  /** Key of the deep-witness slot node (never a user key). */
3385
3725
  const $DEEP = Symbol("STORE_DEEP");
3386
3726
  // ---------------------------------------------------------------------------
@@ -3570,17 +3910,22 @@ function releaseSlot(node) {
3570
3910
  function noteNode(target, node, key) {
3571
3911
  if (target.fam !== null) target.fam.live.add(target);
3572
3912
  markDescendants(target);
3573
- if (affectsHooks !== null) affectsHooks.born(target, node, key);
3913
+ if (affectsHooks !== null) affectsHooks._born(target, node, key);
3574
3914
  }
3575
3915
  // Shared slot-node release handler: registered once; the core sweep
3576
3916
  // dispatches CONFIG_SLOT_NODE nodes here (graph.ts) instead of holding a
3577
3917
  // per-node closure in a per-node NodeExtension. A dropped node is
3578
3918
  // unreachable through the store — a fresh read makes a fresh node.
3919
+ // A staged write — or a lane's value (a guess, S4) — is state only the node
3920
+ // holds: its release waits for the commit that resolves it. So does a leaf of
3921
+ // a held container (#4146): a mainline write it carried is in the held
3922
+ // staging too, where it would read as the hold's until the landing.
3923
+ const slotKept = node =>
3924
+ node._pendingValue !== NOT_PENDING ||
3925
+ node._config & CONFIG_OVERRIDE ||
3926
+ node._host.k?._config & CONFIG_HELD;
3579
3927
  setSlotUnobserved(node => {
3580
- // A staged write — or a lane's value (a guess, S4) — is state only the
3581
- // node holds: defer the release to the commit that resolves it.
3582
- if (node._pendingValue !== NOT_PENDING || node._config & CONFIG_OVERRIDE)
3583
- deferredReleases.add(node);
3928
+ if (slotKept(node)) deferredReleases.add(node);
3584
3929
  else releaseSlot(node);
3585
3930
  });
3586
3931
  /** A node born while its container has a staging is born from the two
@@ -3640,7 +3985,9 @@ function getNode(
3640
3985
  // the hottest store path, and the disabled cost must stay one null
3641
3986
  // check (nodes created before enable() stay generically named).
3642
3987
  if (attrHooks !== null) {
3643
- created._name = storeLabel(target) + "." + String(key);
3988
+ const root = storeRoot(target);
3989
+ created._name = (storeNames.get(root) ?? "store") + "." + String(key);
3990
+ created._loc = storeLocs.get(root);
3644
3991
  stampNodeOwner(created, target);
3645
3992
  }
3646
3993
  nodes[key] = node;
@@ -3693,7 +4040,7 @@ function getContainerNode(target) {
3693
4040
  * staging: nothing allocates, nothing is swept at the commit (dbmon's tick
3694
4041
  * re-created, queued, swept and dropped ~7000 container nodes). If the
3695
4042
  * flush parks, the seam materializes and holds the node then
3696
- * (`_storePark`) — the one case the staging home exists for. */
4043
+ * (`storeParkHook`) — the one case the staging home exists for. */
3697
4044
  function stageOn(target, pb) {
3698
4045
  if (
3699
4046
  target.k === null &&
@@ -3716,7 +4063,7 @@ function deriveHolds(target) {
3716
4063
  const fw = target.fam?.node;
3717
4064
  return fw != null && !userWrite && (fw._config & CONFIG_HELD) !== 0;
3718
4065
  }
3719
- GlobalQueue._storePark = t => {
4066
+ const storePark = t => {
3720
4067
  for (let i = 0; i < foldList.length; i++) {
3721
4068
  const target = foldList[i];
3722
4069
  if (target.k === null && target.pb !== null) holdNode(getContainerNode(target), t);
@@ -3746,6 +4093,7 @@ function notifyContainer(k, pb) {
3746
4093
  if (k._config & CONFIG_OVERRIDE) setSignal(k, pb);
3747
4094
  else {
3748
4095
  if (k._config & CONFIG_HELD) joinFuture(txOf(k));
4096
+ if (snapshotCaptureActive) captureWriteSnapshot(k, k._value);
3749
4097
  insertSubs(k);
3750
4098
  schedule();
3751
4099
  }
@@ -3763,11 +4111,16 @@ function stampNodeOwner(created, target) {
3763
4111
  created._owner = storeRootOwner(target) ?? null;
3764
4112
  }
3765
4113
  const storeNames = new WeakMap();
3766
- function nameStore(proxy, name) {
3767
- if (attrHooks !== null && name) storeNames.set(proxy[$TARGET], name);
3768
- }
3769
- function storeLabel(target) {
3770
- return storeNames.get(storeRoot(target)) ?? "store";
4114
+ // The store's `loc` option, carried onto every property node beside its
4115
+ // label (the same gate: nodes created before the engine is installed carry
4116
+ // neither).
4117
+ const storeLocs = new WeakMap();
4118
+ function nameStore(proxy, name, loc) {
4119
+ if (attrHooks !== null) {
4120
+ const target = proxy[$TARGET];
4121
+ if (name) storeNames.set(target, name);
4122
+ if (loc) storeLocs.set(target, loc);
4123
+ }
3771
4124
  }
3772
4125
  // ---------------------------------------------------------------------------
3773
4126
  // clones (CoW privatization)
@@ -3851,7 +4204,8 @@ function overlayRebuilds(t, pb) {
3851
4204
  if (t.kc > OVERLAY_REBUILD_MAX_KEYS) return false;
3852
4205
  if (t.del !== null && t.del.size !== 0) return true;
3853
4206
  const wk = t.wk;
3854
- if (wk !== null && wk !== WK_ALL && wk.size <= OVERLAY_REBUILD_MIN_ADDS) return false;
4207
+ if (wk !== null && wk !== WK_ALL && wk.u === undefined && wk.size <= OVERLAY_REBUILD_MIN_ADDS)
4208
+ return false;
3855
4209
  const v = t.v;
3856
4210
  let adds = 0;
3857
4211
  for (const key of Reflect.ownKeys(pb))
@@ -4081,11 +4435,7 @@ function drainFolds() {
4081
4435
  }
4082
4436
  if (deferredReleases.size !== 0) {
4083
4437
  for (const node of deferredReleases) {
4084
- if (
4085
- node._pendingValue === NOT_PENDING &&
4086
- !(node._config & CONFIG_OVERRIDE) &&
4087
- node._subs === null
4088
- ) {
4438
+ if (node._subs === null && !slotKept(node)) {
4089
4439
  deferredReleases.delete(node);
4090
4440
  releaseSlot(node);
4091
4441
  }
@@ -4114,10 +4464,14 @@ GlobalQueue._slotCovered = n => {
4114
4464
  }
4115
4465
  return n._value;
4116
4466
  };
4117
- GlobalQueue._storeCommit = () => {
4467
+ installStoreHooks(storePark, () => {
4118
4468
  drainFolds();
4119
4469
  optHooks?.sweep();
4120
- };
4470
+ });
4471
+ /** The store half of `affects()` (store/affects.ts) makes its marks' nodes
4472
+ * and walks records through these: it never imports the engine. */
4473
+ GlobalQueue._storeNode = getNode;
4474
+ GlobalQueue._storeWrappable = isWrappable;
4121
4475
  /** A projection's creation run commits directly (a memo's first value is
4122
4476
  * its `_value`, not a staging): the draft still writes a clone of the seed,
4123
4477
  * nothing is staged on the nodes, and the setter's exit folds the touched
@@ -4144,7 +4498,10 @@ function foldTarget(t, old) {
4144
4498
  // the adopted object (shared ownership — never cloned).
4145
4499
  t.pb = null;
4146
4500
  t.wk = null;
4147
- } else if (t.ovl && (t.v !== old || !overlayRebuilds(t, pb))) {
4501
+ } else if (
4502
+ t.ovl &&
4503
+ (t.v !== old || (!overlayRebuilds(t, pb) && k?._x?._snapshotValue !== old))
4504
+ ) {
4148
4505
  // Overlay flatten (#3044): the backing keeps its identity, so the
4149
4506
  // `t.v === old` gate below skips path copying (the parent slot
4150
4507
  // already points here) and the adopted-notify (setter
@@ -4158,10 +4515,11 @@ function foldTarget(t, old) {
4158
4515
  const v = t.v;
4159
4516
  const wk = t.wk;
4160
4517
  if (wk !== null && wk !== WK_ALL) {
4161
- for (const key of wk) {
4162
- if (hasOwn$1.call(pb, key)) copyOwn(v, pb, key);
4163
- else delete v[key];
4164
- }
4518
+ for (let w = wk; w !== undefined; w = w.u)
4519
+ for (const key of w) {
4520
+ if (hasOwn$1.call(pb, key)) copyOwn(v, pb, key);
4521
+ else delete v[key];
4522
+ }
4165
4523
  } else {
4166
4524
  for (const key of Reflect.ownKeys(pb)) {
4167
4525
  const d = Object.getOwnPropertyDescriptor(pb, key);
@@ -4334,7 +4692,10 @@ function notifyWrites(t) {
4334
4692
  // value can change when ANY key is written), or a non-plain prototype
4335
4693
  // (class instances: prototype getters derive from arbitrary fields).
4336
4694
  // Overlay pbs chain to the COMMITTED object (#3044) — plainness is the
4337
- // committed container's prototype, not the overlay's.
4695
+ // committed container's prototype, not the overlay's. THIS setter's keys
4696
+ // only (D-8, #3743): a key an earlier setter of the batch wrote and this
4697
+ // one did not is no second proposal — its node was told then, and telling
4698
+ // a held node its unchanged value again would join this tick to the hold.
4338
4699
  const wk0 = t.wk;
4339
4700
  const writtenKeys = wk0 === WK_ALL || t.a === true || !plainProto(t.ovl ? t.v : pb) ? null : wk0;
4340
4701
  if (attrHooks !== null) reportReplacedContainers(t, old, pb, writtenKeys);
@@ -4355,16 +4716,19 @@ function notifyWrites(t) {
4355
4716
  ) {
4356
4717
  // Accessor keys: the node is linked for shape-change notification,
4357
4718
  // its value is never served (the getter runs with the proxy
4358
- // receiver on read) — FORCE wakes the readers.
4719
+ // receiver on read) — FORCE wakes the readers. A data descriptor
4720
+ // falls through so the cleared-acc hot read serves it (#3949);
4721
+ // an own getter's descriptor has no `value`, so the compare below
4722
+ // would otherwise keep the getter's result (or FORCE) for `undefined`.
4359
4723
  node.acc = isOwnAccessor(pb, key);
4360
4724
  const od = Object.getOwnPropertyDescriptor(old, key);
4361
4725
  const nd = Object.getOwnPropertyDescriptor(pb, key);
4362
- if ((od && (od.get || od.set)) || (nd && (nd.get || nd.set))) {
4726
+ if (!nd || nd.get || nd.set) {
4363
4727
  if (od?.get !== nd?.get || od?.set !== nd?.set || od?.value !== nd?.value)
4364
4728
  setSignal(node, () => FORCE);
4365
4729
  continue;
4366
4730
  }
4367
- if (!isEqual(od?.value, nd?.value)) setSignal(node, () => nd?.value);
4731
+ if (od?.get || od?.set || !isEqual(od?.value, nd.value)) setSignal(node, () => nd.value);
4368
4732
  continue;
4369
4733
  }
4370
4734
  const nv = t.del !== null && t.del.has(key) ? undefined : pb[key];
@@ -4462,14 +4826,15 @@ function notifyKeyDiff(node, key, old, neu, probe = true) {
4462
4826
  node.acc = isOwnAccessor(neu, key);
4463
4827
  const od = Object.getOwnPropertyDescriptor(old, key);
4464
4828
  const nd = Object.getOwnPropertyDescriptor(neu, key);
4465
- if ((od && (od.get || od.set)) || (nd && (nd.get || nd.set))) {
4829
+ // Data descriptors fall through, as in notifyWrites (#3949).
4830
+ if (!nd || nd.get || nd.set) {
4466
4831
  if (od?.get !== nd?.get || od?.set !== nd?.set || od?.value !== nd?.value)
4467
4832
  setSignal(node, () => FORCE);
4468
4833
  return;
4469
4834
  }
4470
4835
  const ov = od?.value;
4471
4836
  const nv = nd?.value;
4472
- if (!isEqual(ov, nv) && !targetsEqual(ov, nv))
4837
+ if (od?.get || od?.set || (!isEqual(ov, nv) && !targetsEqual(ov, nv)))
4473
4838
  setSignal(node, typeof nv === "function" ? () => nv : nv);
4474
4839
  } else {
4475
4840
  const ov = old[key];
@@ -4710,7 +5075,67 @@ function readSource(target, key, shape = false) {
4710
5075
  /** Did the HELD staging (the container node's, not a mainline layer above
4711
5076
  * it) change `key`? */
4712
5077
  function heldKeyChanged(target, key, shape = true) {
4713
- return changedBetween(target, target.k._value, target.k._pendingValue, key, shape);
5078
+ return (
5079
+ changedBetween(target, target.k._value, target.k._pendingValue, key, shape) &&
5080
+ !adoptChild(target, key)
5081
+ );
5082
+ }
5083
+ /** The values of a held frame, built once per frame: neither the committed
5084
+ * frame nor the held staging changes while the hold stands (a draft writes a
5085
+ * clone), so the set stays true for every first read of the hold. */
5086
+ const frameRaws = new WeakMap();
5087
+ function rawsOf(frame) {
5088
+ let raws = frameRaws.get(frame);
5089
+ if (raws === undefined) frameRaws.set(frame, (raws = new Set(Object.values(frame))));
5090
+ return raws;
5091
+ }
5092
+ /** The same row at a slot, as the adoption pairs rows (reconcile.ts): two
5093
+ * objects whose keys agree — positionally for an unkeyed family. */
5094
+ function sameRow$1(pv, nv, keyFn) {
5095
+ return (
5096
+ pv !== null &&
5097
+ typeof pv === "object" &&
5098
+ nv !== null &&
5099
+ typeof nv === "object" &&
5100
+ (keyFn === null || sameKey(keyFn(pv), keyFn(nv)))
5101
+ );
5102
+ }
5103
+ /** A key of a held adoption whose old and new values are the same row that
5104
+ * nothing had proxied when the adoption ran, so `descend` never paired
5105
+ * them: paired now, as it would have been (#3712) — one target, the old
5106
+ * raw its committed frame, the new raw its staging, held by the same
5107
+ * transaction. Both raws resolve to it, so the slot reads as unchanged and
5108
+ * the row's own leaves decide: `id` the same in both frames holds no one,
5109
+ * `version` holds. A record's keys pair by name; an array's by the family's
5110
+ * key — without a family the pairing is not known here and the hold stands. */
5111
+ function adoptChild(target, key) {
5112
+ const k = target.k;
5113
+ const old = asBacking(k._value);
5114
+ const ov = old[key];
5115
+ const nv = k._pendingValue[key];
5116
+ const keyFn = Array.isArray(old) ? target.fam?.key : null;
5117
+ if (
5118
+ target.s ||
5119
+ keyFn === undefined ||
5120
+ !sameRow$1(ov, nv, keyFn) ||
5121
+ !isWrappable(ov) ||
5122
+ !isWrappable(nv) ||
5123
+ Array.isArray(ov) !== Array.isArray(nv) ||
5124
+ lookupTarget(ov, target.fam) !== undefined ||
5125
+ lookupTarget(nv, target.fam) !== undefined ||
5126
+ // A raw in both frames (the same objects swapped or reordered) is its
5127
+ // own row: pairing it with another would alias the two after the commit.
5128
+ rawsOf(old).has(nv) ||
5129
+ rawsOf(k._pendingValue).has(ov)
5130
+ )
5131
+ return false;
5132
+ // A raw-marked value wraps to itself, a proxy to its own target: neither
5133
+ // is a fresh target around `ov`.
5134
+ const ct = wrap(ov, target, key)[$TARGET];
5135
+ if (ct === undefined || ct.v !== ov) return false;
5136
+ adoptPB(ct, nv, false);
5137
+ holdNode(getContainerNode(ct), txOf(k));
5138
+ return true;
4714
5139
  }
4715
5140
  /** A family target's unheld staging, read with no pass (a handler). */
4716
5141
  function familyAhead(target) {
@@ -4839,11 +5264,13 @@ function pullFamily(target) {
4839
5264
  // One that already observes the flight (linked by the pass that saw it
4840
5265
  // go up — the frame's hold, `blocked`) keeps observing it: a re-run for
4841
5266
  // another reason (a guess it read, the seam) that dropped the link would
4842
- // release the hold with the flight still up.
5267
+ // release the hold with the flight still up. An errored derive has no
5268
+ // landing to learn of: the error is what the frame shows.
4843
5269
  const c = context;
4844
5270
  if (
4845
5271
  c !== null &&
4846
5272
  c._type === EFFECT_RENDER &&
5273
+ !(fw._statusFlags & STATUS_ERROR) &&
4847
5274
  fw._config & CONFIG_HELD &&
4848
5275
  flushTransaction !== txOf(fw) &&
4849
5276
  !linkedTo(c, fw)
@@ -4928,6 +5355,22 @@ function optRead(target) {
4928
5355
  const UNSAFE_KEYS = new Set(["__proto__", "prototype", "constructor"]);
4929
5356
  // ---------------------------------------------------------------------------
4930
5357
  // traps
5358
+ /** A trap write: the target is told at the setter's exit, the key recorded
5359
+ * in this setter's `wk`. A setter's first write to a target still staged by
5360
+ * an earlier setter of the batch opens a set of its own (the notify visits
5361
+ * it, D-8) and chains the earlier one behind for the fold's union. */
5362
+ function touch(target, key) {
5363
+ let wk = target.wk;
5364
+ if (!pendingNotify.has(target)) {
5365
+ pendingNotify.add(target);
5366
+ if (wk !== null && wk !== WK_ALL) {
5367
+ wk = new Set();
5368
+ wk.u = target.wk;
5369
+ target.wk = wk;
5370
+ }
5371
+ }
5372
+ if (wk !== WK_ALL) (target.wk ??= new Set()).add(key);
5373
+ }
4931
5374
  const traps = {
4932
5375
  get(target, key, receiver) {
4933
5376
  // One typeof gates every brand-symbol compare off the hot string path.
@@ -4955,7 +5398,7 @@ const traps = {
4955
5398
  // (The witness before the pull: a mark on an uninitialized derived
4956
5399
  // store is witnessed, then the pull throws — loading, declared pending.)
4957
5400
  if (verdict !== null && affectsHooks !== null && getObserver() === null)
4958
- affectsHooks.witness(target, key);
5401
+ affectsHooks._witness(target, key);
4959
5402
  if (target.fam !== null) pullFamily(target);
4960
5403
  // Hot inline case: existing PLAIN node (non-accessor) read outside any
4961
5404
  // draft — the dbmon/uibench effect re-read shape. Core's `read()` serves
@@ -5102,7 +5545,7 @@ const traps = {
5102
5545
  // (The witness before the pull: a mark on an uninitialized derived
5103
5546
  // store is witnessed, then the pull throws — loading, declared pending.)
5104
5547
  if (verdict !== null && affectsHooks !== null && getObserver() === null)
5105
- affectsHooks.witness(target, key);
5548
+ affectsHooks._witness(target, key);
5106
5549
  if (target.fam !== null) pullFamily(target);
5107
5550
  const src = readSource(target, key);
5108
5551
  // A tracked reader's presence node answers (born from the two frames;
@@ -5127,7 +5570,7 @@ const traps = {
5127
5570
  },
5128
5571
  ownKeys(target) {
5129
5572
  if (verdict !== null && affectsHooks !== null && getObserver() === null)
5130
- affectsHooks.witness(target, undefined);
5573
+ affectsHooks._witness(target, undefined);
5131
5574
  if (target.fam !== null) pullFamily(target);
5132
5575
  return visibleKeys(target, enumerationSource(target));
5133
5576
  },
@@ -5135,7 +5578,7 @@ const traps = {
5135
5578
  if (key === $OWNER || key === $RECORD) return undefined;
5136
5579
  const obs = getObserver();
5137
5580
  if (verdict !== null && affectsHooks !== null && obs === null)
5138
- affectsHooks.witness(target, key);
5581
+ affectsHooks._witness(target, key);
5139
5582
  if (target.fam !== null) pullFamily(target);
5140
5583
  // An enumerator (spread, Object.entries) already holds the container
5141
5584
  // node and reads its frame; a descriptor read on its own tracks
@@ -5175,16 +5618,23 @@ const traps = {
5175
5618
  if (key === "__proto__") return true; // pollution guard (core R30)
5176
5619
  const uv = target.s ? value : unwrapValue(value);
5177
5620
  const pb = ensurePB(target);
5178
- pendingNotify.add(target);
5621
+ // #4146 (R1: node existence is unobservable; #3706: the hold's unit is
5622
+ // the key): a user setter writing a key a HELD container's staging left
5623
+ // unchanged would write it into the held staging — read as the hold's
5624
+ // until its landing, while the same key with a leaf (a live reader)
5625
+ // publishes at the flush. The key gets its leaf first, born from the two
5626
+ // frames (`bornStaged`: a key the hold left unchanged — or added — is
5627
+ // unstaged and held by no one; a key the hold changed is born held, A34
5628
+ // (1)), and the write stages on it (A28; a write by the holder joins it,
5629
+ // A34) — as it would with a reader's leaf. Kept while the container is
5630
+ // held (`deferredReleases`).
5631
+ if (userWrite && target.k?._config & CONFIG_HELD) deferredReleases.add(getNode(target, key));
5179
5632
  if (Array.isArray(pb)) {
5633
+ touch(target, key);
5180
5634
  if (key === "length") target.wk = WK_ALL;
5181
- else if (target.wk !== WK_ALL) {
5182
- const wk = (target.wk ??= new Set());
5183
- wk.add(key);
5184
- wk.add("length");
5185
- }
5635
+ else touch(target, "length");
5186
5636
  } else {
5187
- if (target.wk !== WK_ALL) (target.wk ??= new Set()).add(key);
5637
+ touch(target, key);
5188
5638
  if (!(key in pb)) target.kc++;
5189
5639
  }
5190
5640
  if (UNSAFE_KEYS.has(key)) {
@@ -5219,8 +5669,7 @@ const traps = {
5219
5669
  if ("value" in desc) desc = { ...desc, value: unwrapValue(desc.value) };
5220
5670
  const pb = ensurePB(target);
5221
5671
  if (target.a || !(desc.enumerable && desc.writable && desc.configurable)) target.sc = 1;
5222
- pendingNotify.add(target);
5223
- if (target.wk !== WK_ALL) (target.wk ??= new Set()).add(key);
5672
+ touch(target, key);
5224
5673
  Object.defineProperty(pb, key, desc);
5225
5674
  if (target.del !== null) target.del.delete(key);
5226
5675
  if (override) notifyWrites(target);
@@ -5231,10 +5680,22 @@ const traps = {
5231
5680
  const override = !draft && writeOverride;
5232
5681
  if (!draft && !override) return true;
5233
5682
  const pb = ensurePB(target);
5234
- pendingNotify.add(target);
5235
- if (target.wk !== WK_ALL) (target.wk ??= new Set()).add(key);
5236
- delete pb[key];
5237
- if (target.ovl && hasOwn$1.call(target.v, key)) (target.del ??= new Set()).add(key);
5683
+ touch(target, key);
5684
+ // The key count (`kc`, the overlay gate and the rebuild cap) follows a
5685
+ // delete the way it follows an add: an own key of the draft leaving — a
5686
+ // clone's key, or an overlay's own add — is one fewer now. An overlay's
5687
+ // override of a committed key is the exception: the committed key's
5688
+ // deletion is recorded in `del` and counted at the rebuild, as before.
5689
+ // Without the decrement the natural "delete the old key, set the new
5690
+ // one" shape drifted the count up by one per write until a three-key
5691
+ // object crossed the overlay threshold and paid a rebuild on every
5692
+ // commit after.
5693
+ const committed = target.ovl && hasOwn$1.call(target.v, key);
5694
+ if (hasOwn$1.call(pb, key)) {
5695
+ delete pb[key];
5696
+ committed || target.kc--;
5697
+ }
5698
+ if (committed) (target.del ??= new Set()).add(key);
5238
5699
  if (override) notifyWrites(target);
5239
5700
  return true;
5240
5701
  }
@@ -5262,7 +5723,7 @@ const ASYNC_STORE_SETTER_MESSAGE =
5262
5723
  * write violation — the user guard (`mapArray`'s pattern: the node-level
5263
5724
  * writes carry `ownedWrite`; the setter asks). */
5264
5725
  function devGuardStoreSetterWrite() {
5265
- if (context && !(context._config & CONFIG_CHILDREN_FORBIDDEN)) {
5726
+ if (context && !(context._config & CONFIG_CHILDREN_FORBIDDEN) && !disposalDepth) {
5266
5727
  emitDiagnostic({
5267
5728
  code: "REACTIVE_WRITE_IN_OWNED_SCOPE",
5268
5729
  kind: "write",
@@ -5298,6 +5759,7 @@ function devGuardStoreSetterResult(result) {
5298
5759
  }
5299
5760
  }
5300
5761
  function storeSetter(proxy, fn, guard = true) {
5762
+ if (writeHook !== null) writeHook(proxy);
5301
5763
  if (guard) devGuardStoreSetterWrite();
5302
5764
  const target = proxy[$TARGET];
5303
5765
  const prevScopes = writeScopes;
@@ -5365,6 +5827,8 @@ function createStore$1(initialValue, options) {
5365
5827
  throw new Error("createStore({ shallow }): value is already a store proxy");
5366
5828
  }
5367
5829
  const proxy = wrap(initialValue);
5830
+ // Dependency edges (store/edges.ts): the root is named like a plain signal.
5831
+ if (signalHook !== null) signalHook(proxy[$TARGET]);
5368
5832
  if (shallow) {
5369
5833
  proxy[$TARGET].s = true;
5370
5834
  markRawIngest(initialValue);
@@ -5379,7 +5843,7 @@ function createStore$1(initialValue, options) {
5379
5843
  // disabled, buys nothing.
5380
5844
  if (attrHooks !== null) {
5381
5845
  storeOwners.set(proxy[$TARGET], owner);
5382
- nameStore(proxy, options?.name);
5846
+ nameStore(proxy, options?.name, options?.loc);
5383
5847
  }
5384
5848
  }
5385
5849
  const setter = fn => storeSetter(proxy, fn);
@@ -5638,150 +6102,29 @@ function updatePath(current, args, i = 0) {
5638
6102
  * Path parts can be:
5639
6103
  * - a single key — `"user"`, `0`
5640
6104
  * - an array of keys — `[0, 1, 2]`
5641
- * - a range over an array — `{ from?, to?, by? }`
5642
- * - a filter `(item, index) => boolean` for arrays
5643
- *
5644
- * The final argument is the new value or an updater `(prev) => next`. Use
5645
- * `storePath.DELETE` to remove a property.
5646
- *
5647
- * @example
5648
- * ```ts
5649
- * const [state, setState] = createStore({ user: { name: "Ada" }, todos: [] });
5650
- *
5651
- * setState(storePath("user", "name", "Grace"));
5652
- * setState(storePath("todos", t => !t.done, "done", true)); // mark all undone as done
5653
- * setState(storePath("user", "nickname", storePath.DELETE));
5654
- * ```
5655
- */
5656
- const storePath = /* @__PURE__ */ Object.assign(
5657
- function storePath(...args) {
5658
- return state => {
5659
- updatePath(state, args);
5660
- };
5661
- },
5662
- { DELETE }
5663
- );
5664
-
5665
- /**
5666
- * Store — the store half of `affects()` (plan sec. 29, plan sec. 37; A24 (4), #2882,
5667
- * #2893, #2904).
5668
- *
5669
- * A mark on a store is a mark on nodes (affects.ts: a count on the node, a
5670
- * dependency walk at probe time): `affects(record, key)` marks the slot's
5671
- * leaf; `affects(record)` marks a carrier (the record's `$AFFECTS` leaf)
5672
- * and every live node in the record's subtree — the edges existing readers
5673
- * subscribed through. Coverage is by RAW IDENTITY, not read path (#2882: a
5674
- * captured row proxy, a derived store sharing the source's raw, never read
5675
- * through the declaration's proxy): the identities reachable at the
5676
- * declaration are the mark's `scope`, so a node born inside the window on
5677
- * a covered record inherits the mark (released with the carrier's last),
5678
- * and an untracked probe through a record with no nodes is witnessed into
5679
- * the verdict. A mark declared on a draft (inside a setter) or over an
5680
- * optimistic family walks the view the writer sees.
5681
- *
5682
- * Installed by `store/index.ts`: a program with stores carries it; one
5683
- * without pays nothing (affects.ts asks `GlobalQueue._storeMarks`).
5684
- */
5685
- /** The keyless mark's carrier key: a leaf of the record no read serves. */
5686
- const $AFFECTS = Symbol("STORE_AFFECTS");
5687
- const scopes = new Map();
5688
- /** The nodes an `affects(store[, key])` declaration marks (affects.ts
5689
- * registers each). */
5690
- function storeMarks(t, key) {
5691
- if (key === undefined) {
5692
- const carrier = getNode(t, $AFFECTS);
5693
- let entry = scopes.get(carrier);
5694
- if (entry === undefined) scopes.set(carrier, (entry = { scope: new Set(), inherited: [] }));
5695
- const found = [carrier];
5696
- walk(t.px, entry, found, t.fam, new Set());
5697
- return found;
5698
- }
5699
- const node = t.n?.[key] ?? getNode(t, key);
5700
- let entry = scopes.get(node);
5701
- if (entry === undefined) scopes.set(node, (entry = { scope: new Set(), inherited: [], key }));
5702
- entry.scope.add(t.v);
5703
- if (t.pb !== null) entry.scope.add(t.pb);
5704
- return [node];
5705
- }
5706
- /** The identities reachable from `value` into the scope (the draft's and
5707
- * the committed backing both, when a draft is open; an optimistic family's
5708
- * view), every live node under each record into `found`. Untracked by
5709
- * construction: raws, never traps. */
5710
- function walk(value, entry, found, fam, visited) {
5711
- if (!isWrappable(value)) return;
5712
- const t = value[$TARGET] ?? lookupTarget(value, fam);
5713
- let raw = t !== undefined ? (t.pb ?? t.v) : value;
5714
- if (visited.has(raw)) return;
5715
- visited.add(raw);
5716
- entry.scope.add(raw);
5717
- if (t !== undefined) {
5718
- if (t.pb !== null) entry.scope.add(t.v);
5719
- // The writer's view: the tick's own unflushed guesses are in motion too.
5720
- if (t.fam?.opt === true) raw = optHooks.view(t, raw, true);
5721
- const nodes = t.n;
5722
- if (nodes !== null)
5723
- for (const k of Reflect.ownKeys(nodes))
5724
- // Another mark's carrier is its own channel: counting it here would
5725
- // extend that scope's lifetime to this declaration's.
5726
- if (k !== $AFFECTS) found.push(nodes[k]);
5727
- const has = t.h;
5728
- if (has !== null) for (const k of Reflect.ownKeys(has)) found.push(has[k]);
5729
- if (t.k !== null) found.push(t.k);
5730
- if (t.dk !== null) found.push(t.dk);
5731
- fam = t.fam ?? fam;
5732
- }
5733
- for (const k of Reflect.ownKeys(raw)) {
5734
- if (k === $OWNER) continue;
5735
- const d = Object.getOwnPropertyDescriptor(raw, k);
5736
- if (d === undefined || d.get !== undefined) continue;
5737
- walk(d.value, entry, found, fam, visited);
5738
- }
5739
- }
5740
- /** A live scope covering `t`'s identity (keyed: for `key`). Chained
5741
- * backings (§7b): a view's backing is another store's proxy — marks cover
5742
- * the BASE raw, so every identity along the chain is checked. */
5743
- function covering(t, key, skip) {
5744
- for (const [carrier, entry] of scopes) {
5745
- if (carrier === skip || (entry.key !== undefined && entry.key !== key)) continue;
5746
- for (let r = t.v; ; ) {
5747
- if (entry.scope.has(r)) return carrier;
5748
- const inner = r?.[$TARGET];
5749
- if (inner === undefined) break;
5750
- const backing = inner.pb ?? inner.v;
5751
- if (backing === r) break;
5752
- r = backing;
5753
- }
5754
- }
5755
- return null;
5756
- }
5757
- installAffectsHooks({
5758
- // A node born on a record a live mark covers inherits it (released with
5759
- // the carrier's last registration).
5760
- born(t, node, key) {
5761
- if (key === $AFFECTS) return;
5762
- const carrier = covering(t, key, node);
5763
- if (carrier === null) return;
5764
- GlobalQueue._mark(node);
5765
- scopes.get(carrier).inherited.push(node);
6105
+ * - a range over an array — `{ from?, to?, by? }`
6106
+ * - a filter `(item, index) => boolean` for arrays
6107
+ *
6108
+ * The final argument is the new value or an updater `(prev) => next`. Use
6109
+ * `storePath.DELETE` to remove a property.
6110
+ *
6111
+ * @example
6112
+ * ```ts
6113
+ * const [state, setState] = createStore({ user: { name: "Ada" }, todos: [] });
6114
+ *
6115
+ * setState(storePath("user", "name", "Grace"));
6116
+ * setState(storePath("todos", t => !t.done, "done", true)); // mark all undone as done
6117
+ * setState(storePath("user", "nickname", storePath.DELETE));
6118
+ * ```
6119
+ */
6120
+ const storePath = /* @__PURE__ */ Object.assign(
6121
+ function storePath(...args) {
6122
+ return state => {
6123
+ updatePath(state, args);
6124
+ };
5766
6125
  },
5767
- // An untracked probe (`isPending(() => s.x)` with no observer) reading a
5768
- // record a live mark covers: no node carries the mark for it — the
5769
- // verdict is told directly.
5770
- witness(t, key) {
5771
- const own = t.n?.[$AFFECTS];
5772
- if ((own !== undefined && own._x !== null && own._x._marks !== 0) || covering(t, key) !== null)
5773
- GlobalQueue._witnessMark();
5774
- }
5775
- });
5776
- GlobalQueue._storeMarks = storeMarks;
5777
- /** The carrier's last mark released (affects.ts): its scope dies, and the
5778
- * nodes that inherited the mark release theirs. */
5779
- GlobalQueue._releaseMarkScope = carrier => {
5780
- const entry = scopes.get(carrier);
5781
- if (entry === undefined) return;
5782
- scopes.delete(carrier);
5783
- GlobalQueue._releaseMarks(entry.inherited);
5784
- };
6126
+ { DELETE }
6127
+ );
5785
6128
 
5786
6129
  /**
5787
6130
  * Store — reconcile: the immutable-diff adoption channel (INTERNALS §3).
@@ -5874,17 +6217,7 @@ function applyAdopt(t, incoming, keyFn, proj = false) {
5874
6217
  for (const end = Math.min(plen, nlen); i < end; i++) {
5875
6218
  const nv = nextRows[i];
5876
6219
  const pvRaw = prevRows[i];
5877
- if (
5878
- pvRaw !== nv &&
5879
- !(
5880
- pvRaw !== null &&
5881
- typeof pvRaw === "object" &&
5882
- nv !== null &&
5883
- typeof nv === "object" &&
5884
- sameKey(keyFn(pvRaw), keyFn(nv))
5885
- )
5886
- )
5887
- break; // misaligned: fall to the keyed remainder below
6220
+ if (pvRaw !== nv && !sameRow$1(pvRaw, nv, keyFn)) break; // misaligned: fall to the keyed remainder below
5888
6221
  if (
5889
6222
  (pvRaw !== nv || (nv !== null && typeof nv === "object" && isOwned(nv))) &&
5890
6223
  nv !== null &&
@@ -6274,6 +6607,11 @@ function runProjectionComputed(wrappedStore, fn, key, wrapCommit, aroundDraftWri
6274
6607
  // A landing (not the sync pass's own commit): the readers held
6275
6608
  // pending on the derive settle.
6276
6609
  if (!first || !(owner._statusFlags & STATUS_UNINITIALIZED)) settleFamily(fam);
6610
+ // A commit is the derive's change for the readers that observed its
6611
+ // flight (linked by `pullFamily`; a settled pull does not link), as
6612
+ // a memo's landing is for its readers: one that caught the NotReady
6613
+ // (a fallback value, not pending) re-runs too (#4070).
6614
+ insertSubs(owner);
6277
6615
  };
6278
6616
  const sync = handleAsync(owner, result, commit, (error, pending) => {
6279
6617
  // The flight rejected: the leaves' readers learn it from here —
@@ -6296,7 +6634,9 @@ function runProjectionComputed(wrappedStore, fn, key, wrapCommit, aroundDraftWri
6296
6634
  // A flight went up (NotReady out of the pass — the derive's own, with
6297
6635
  // the derive as its source): the leaves' readers learn it from here —
6298
6636
  // they do not subscribe to the derive.
6299
- if (e instanceof NotReadyError && !first) wakeFamily(fam, e);
6637
+ // A synchronous error the same way (#3956: a rerun that finds the
6638
+ // entry failed throws): every live reader re-derives and meets it.
6639
+ if (!first) e instanceof NotReadyError ? wakeFamily(fam, e) : errorFamily(fam);
6300
6640
  throw e;
6301
6641
  }
6302
6642
  // A pass that returned with a flight up (an iterator's sync first yield):
@@ -6313,11 +6653,23 @@ function runProjectionComputed(wrappedStore, fn, key, wrapCommit, aroundDraftWri
6313
6653
  return owner;
6314
6654
  }
6315
6655
  function createProjectionInternal(fn, seed, options) {
6656
+ // Seedless (`fn` takes no draft): the root starts as an empty record. It is
6657
+ // a draft like any seed, never observable before the first landing (A25).
6658
+ seed ||= {};
6659
+ const keyOption = options?.key === undefined ? "id" : options.key;
6316
6660
  const fam = {
6317
6661
  map: new WeakMap(),
6318
6662
  node: null,
6319
6663
  live: new Set(),
6320
- shallow: !!options?.shallow
6664
+ shallow: !!options?.shallow,
6665
+ // The row key, for a child paired after the adoption (store.ts
6666
+ // `adoptChild`, #3712) as the adoption pairs them.
6667
+ key:
6668
+ typeof keyOption === "function"
6669
+ ? keyOption
6670
+ : keyOption === null
6671
+ ? null
6672
+ : row => (isWrappable(row) ? row[keyOption] : undefined)
6321
6673
  };
6322
6674
  const store = wrap(seed, null, null, fam);
6323
6675
  if (fam.shallow) {
@@ -6326,24 +6678,25 @@ function createProjectionInternal(fn, seed, options) {
6326
6678
  }
6327
6679
  let nodeOptions;
6328
6680
  if (options?.seedLoadingValue) nodeOptions = { loadingValue: undefined };
6329
- if (options?.name) {
6330
- nodeOptions = { ...nodeOptions, name: options.name };
6331
- nameStore(store, options.name);
6681
+ if (attrHooks !== null)
6682
+ // As createStore: the owner in scope stamps the projection's property
6683
+ // nodes, so attribution places them under it (#4035).
6684
+ storeOwners.set(store[$TARGET], getOwner());
6685
+ if (options?.name || options?.loc) {
6686
+ nodeOptions = { ...nodeOptions, name: options.name, loc: options.loc };
6687
+ nameStore(store, options.name, options.loc);
6332
6688
  }
6333
6689
  const node = computed(() => {
6334
6690
  if (!fam.node) fam.node = getOwner();
6335
- runProjectionComputed(store, fn, options?.key === undefined ? "id" : options.key);
6691
+ runProjectionComputed(store, fn, keyOption);
6336
6692
  }, nodeOptions);
6337
6693
  node._config &= ~CONFIG_AUTO_DISPOSE;
6338
6694
  fam.node = node;
6695
+ // The derive's leaves are not its dependents: a mark's repoll
6696
+ // (affects-marks.ts) reaches their readers through this.
6697
+ node.fam = fam;
6339
6698
  return { store, node };
6340
6699
  }
6341
- /**
6342
- * A derived store: `fn` runs in a computed and writes its draft; readers of
6343
- * the store's keys re-run only when the keys they read change (the
6344
- * firewall). `fn` may be async or an async iterator; the seed is a draft,
6345
- * never an observable value, until the first landing (A25).
6346
- */
6347
6700
  function createProjection(fn, seed, options) {
6348
6701
  return createProjectionInternal(fn, seed, options).store;
6349
6702
  }
@@ -6494,8 +6847,38 @@ function rowSame(a, b, keyFn) {
6494
6847
  if (keyFn === null || !isWrappable(a) || !isWrappable(b)) return false;
6495
6848
  const ka = keyFn(unwrapValue(a));
6496
6849
  const kb = keyFn(unwrapValue(b));
6850
+ // Keyless rows are identified by position — reconcile's fallback when the
6851
+ // key is missing. The caller already paired these by index, so two keyless
6852
+ // rows are the same row and the landing confirms the arrangement instead of
6853
+ // dissolving it. Dissolving held an external echo under the action, and a
6854
+ // mainline `until` then timed out on a source that already contained it
6855
+ // (#3898). Keyed rows still match only on the key.
6856
+ if (ka === undefined && kb === undefined) return true;
6497
6857
  return ka !== undefined && sameKey(ka, kb);
6498
6858
  }
6859
+ /** Whether reconcile would write anything into `a` from `b`: wrappable
6860
+ * pairs descend, leaves compare as its writes do. A key mismatch is a
6861
+ * replacement, which a content difference already says. Plain data — the
6862
+ * server's echo of a guessed row is a new object with the same contents. */
6863
+ function echoes(a, b) {
6864
+ if (a === b) return true;
6865
+ if (!isWrappable(a) || !isWrappable(b)) return false;
6866
+ a = unwrapValue(a);
6867
+ b = unwrapValue(b);
6868
+ const ak = Object.keys(a);
6869
+ if (Array.isArray(a) !== Array.isArray(b) || ak.length !== Object.keys(b).length) return false;
6870
+ for (const k of ak) if (!hasOwn.call(b, k) || !echoes(a[k], b[k])) return false;
6871
+ return true;
6872
+ }
6873
+ // A cycle (a row that references itself) is not plain data: no match, so
6874
+ // the landing stays a correction.
6875
+ GlobalQueue._echoes = (a, b) => {
6876
+ try {
6877
+ return echoes(a, b);
6878
+ } catch {
6879
+ return false;
6880
+ }
6881
+ };
6499
6882
  function arrangementEqual(a, b, keyFn) {
6500
6883
  if (Array.isArray(a) !== Array.isArray(b)) return false;
6501
6884
  if (Array.isArray(a)) {
@@ -6840,7 +7223,9 @@ function optimisticView(
6840
7223
  function createOptimisticStore(first, second, third) {
6841
7224
  const derived = typeof first === "function";
6842
7225
  const options = derived ? third : second;
6843
- const initialValue = derived ? second : first;
7226
+ // Seedless derived form: the root starts as an empty record (see
7227
+ // createProjectionInternal).
7228
+ const initialValue = derived ? second || {} : first;
6844
7229
  const keyOption = options?.key === undefined ? "id" : options.key;
6845
7230
  const fam = {
6846
7231
  map: new WeakMap(),
@@ -6856,7 +7241,7 @@ function createOptimisticStore(first, second, third) {
6856
7241
  : row => (isWrappable(row) ? row[keyOption] : undefined)
6857
7242
  };
6858
7243
  const store = wrap(initialValue, null, null, fam);
6859
- nameStore(store, options?.name);
7244
+ nameStore(store, options?.name, options?.loc);
6860
7245
  if (fam.shallow) {
6861
7246
  store[$TARGET].s = true;
6862
7247
  markRawIngest(initialValue);
@@ -6865,7 +7250,8 @@ function createOptimisticStore(first, second, third) {
6865
7250
  const fn = first;
6866
7251
  let nodeOptions;
6867
7252
  if (options?.seedLoadingValue) nodeOptions = { loadingValue: undefined };
6868
- if (options?.name) nodeOptions = { ...nodeOptions, name: options.name };
7253
+ if (options?.name || options?.loc)
7254
+ nodeOptions = { ...nodeOptions, name: options.name, loc: options.loc };
6869
7255
  const node = computed(() => {
6870
7256
  if (!fam.node) fam.node = getOwner();
6871
7257
  runProjectionComputed(store, fn, keyOption);
@@ -7934,55 +8320,59 @@ function deep(value) {
7934
8320
  return deep$1(value);
7935
8321
  }
7936
8322
 
7937
- // `affects()` — the declaration verb of the pending model (A24 (4), ruled
7938
- // 2026-07-13; the #2893 audit corollaries; rebuilt on L2 2026-10-02 as ruled:
7939
- // transaction-inert).
7940
- //
7941
- // A mark is a declared in-flight change: the marked node — and everything
7942
- // derived from it — reads pending (`isPending` → true) from the declaration
7943
- // until the surrounding transaction lands (an ambient mark, declared outside
7944
- // one, releases at the next seam). The marked values stay readable: no reader
7945
- // suspends on a mark, a mark holds nothing and entangles nothing — it is not
7946
- // a node of its transaction (`blocked` never sees it), and a reader of marked
7947
- // data joins nothing through it. A mark is never a re-ask, so a declared
7948
- // reload's own `refresh()` cannot silence it; a real error outranks it.
8323
+ // The mark core: a declared not-final state on a node that reads pending
8324
+ // (`isPending` → true) on the node and everything derived from it, without
8325
+ // holding anything or entangling a reader. Two declarers share it:
8326
+ // `affects()` (affects.ts — a mark scoped to a transaction, with a store
8327
+ // half) and `createDeferred` (deferred.ts — a mark whose lifetime is the
8328
+ // node's own flight). Split out so the second does not ship the first.
7949
8329
  //
7950
- // Representation: a count on the marked node (`_x._marks`), listed with its
7951
- // scope (the transaction's `_marks`, or the ambient list). Coverage of the
8330
+ // Representation: a count on the marked node (`_x._marks`). Coverage of the
7952
8331
  // derivations is pull-derived at probe time — `isPending`'s read walks the
7953
8332
  // probed node's dependencies for a marked one (verdict.ts → `_marked`) — so
7954
8333
  // nothing is stored downstream and a mid-window recompute strands nothing.
7955
- // The push half is the verdict readers' re-derivation at registration and at
7956
- // release (`repoll`): the only notification a mark makes.
7957
- //
7958
- // Installed on `GlobalQueue` when imported: a program that never declares a
7959
- // mark carries none of this.
7960
- /** Live marks, every scope — the gate on the probe's walk. */
8334
+ // The push half is the verdict readers' re-derivation when a node's marks
8335
+ // start and end (`repoll`): the only notification a mark makes.
8336
+ /** Live marks, every declarer — the gate on the probe's walk. */
7961
8337
  let active = 0;
7962
- /** Marks declared outside a transaction: released at the next seam. */
7963
- const ambient = [];
7964
8338
  /** The verdict readers downstream of `node` — through derivations, stopping
7965
8339
  * at effects — re-derive: the mark's one notification (a probe's answer
7966
- * changed with no value notifying anyone). */
7967
- function repoll(node, seen = new Set()) {
8340
+ * changed with no value notifying anyone). Truth readers (`until`, the
8341
+ * `refresh` waiter) too, which a lagging flight parked (deferred.ts) — but
8342
+ * not through a derived store, whose leaves change only once its derive
8343
+ * re-runs: they re-derive at that write. */
8344
+ function repoll(node, seen = new Set(), readers = CONFIG_VERDICT | CONFIG_AUTHORITATIVE) {
7968
8345
  for (let s = node._subs; s !== null; s = s._nextSub) {
7969
8346
  const sub = s._sub;
7970
8347
  if (seen.has(sub)) continue;
7971
8348
  seen.add(sub);
7972
- if (sub._config & CONFIG_VERDICT) enqueueSub(sub);
7973
- if (!sub._type) repoll(sub, seen);
8349
+ if (sub._config & readers) enqueueSub(sub);
8350
+ if (!sub._type) repoll(sub, seen, readers);
8351
+ // A derived store's derive: its leaves' readers derive from it too
8352
+ // (store/projection.ts; a live target's property, has and keys nodes).
8353
+ const fam = sub.fam;
8354
+ if (fam)
8355
+ for (const t of fam.live)
8356
+ for (const o of [t.n, t.h, { k: t.k, d: t.dk }])
8357
+ if (o) for (const k of Reflect.ownKeys(o)) o[k] && repoll(o[k], seen, CONFIG_VERDICT);
7974
8358
  }
7975
8359
  }
7976
8360
  /** A node is covered by a live mark iff it carries one, or derives — through
7977
- * its current dependencies — from one that does. A real error outranks an
7978
- * inherited mark (A24 (c)): coverage does not flow through an errored node
7979
- * (a direct mark on one still reads pending). Mid-recompute, only the
7980
- * validated prefix of the dependency list is this pass's. */
8361
+ * its current dependencies — from one that does. A store slot derives from
8362
+ * its family's derive (a derived store's leaves are not the derive's
8363
+ * dependents, store/projection.ts), so coverage hops there. A real error
8364
+ * outranks an inherited mark (A24 (c)): coverage does not flow through an
8365
+ * errored node (a direct mark on one still reads pending). Mid-recompute,
8366
+ * only the validated prefix of the dependency list is this pass's. */
7981
8367
  function marked(el, seen) {
7982
8368
  if (el._x !== null && el._x._marks !== 0) return true;
7983
8369
  const c = el;
7984
8370
  if (c._statusFlags & STATUS_ERROR || seen.has(el)) return false;
7985
8371
  seen.add(el);
8372
+ if (c._config & CONFIG_SLOT_NODE) {
8373
+ const derive = c._host?.fam?.node;
8374
+ return derive != null && marked(derive, seen);
8375
+ }
7986
8376
  const tail = c._flags & REACTIVE_RECOMPUTING_DEPS ? c._depsTail : undefined;
7987
8377
  if (tail !== null)
7988
8378
  for (let d = c._deps ?? null; d !== null; d = d._nextDep) {
@@ -7991,18 +8381,183 @@ function marked(el, seen) {
7991
8381
  }
7992
8382
  return false;
7993
8383
  }
8384
+ /** One mark on `node`. The declarer repolls once its bookkeeping is done. */
8385
+ function mark(node) {
8386
+ ext(node)._marks++;
8387
+ active++;
8388
+ }
8389
+ /** Drops one mark from `node`: true when it was the last — the node's
8390
+ * verdict readers have re-derived. */
8391
+ function unmark(node) {
8392
+ active--;
8393
+ if (--node._x._marks !== 0) return false;
8394
+ repoll(node);
8395
+ return true;
8396
+ }
8397
+ GlobalQueue._marked = el => active !== 0 && marked(el, new Set());
8398
+ GlobalQueue._mark = mark;
8399
+
8400
+ /**
8401
+ * Store — the store half of `affects()` (plan sec. 29, plan sec. 37; A24 (4), #2882,
8402
+ * #2893, #2904).
8403
+ *
8404
+ * A mark on a store is a mark on nodes (affects.ts: a count on the node, a
8405
+ * dependency walk at probe time): `affects(record, key)` marks the slot's
8406
+ * leaf; `affects(record)` marks a carrier (the record's `$AFFECTS` leaf)
8407
+ * and every live node in the record's subtree — the edges existing readers
8408
+ * subscribed through. Coverage is by RAW IDENTITY, not read path (#2882: a
8409
+ * captured row proxy, a derived store sharing the source's raw, never read
8410
+ * through the declaration's proxy): the identities reachable at the
8411
+ * declaration are the mark's `scope`, so a node born inside the window on
8412
+ * a covered record inherits the mark (released with the carrier's last),
8413
+ * and an untracked probe through a record with no nodes is witnessed into
8414
+ * the verdict. A mark declared on a draft (inside a setter) or over an
8415
+ * optimistic family walks the view the writer sees.
8416
+ *
8417
+ * Imported by `affects()`, which installs it on its first store target
8418
+ * (#3891, after #3912): a program with stores that never declares a mark
8419
+ * carries none of it. It never imports store.ts — the engine's top level —
8420
+ * so a program declaring marks without stores carries this module alone:
8421
+ * the store's nodes and its wrappability test come through `GlobalQueue`
8422
+ * (`_storeNode`, `_storeWrappable`), and node birth and the untracked traps
8423
+ * reach back through `affectsHooks` (hooks.ts).
8424
+ */
8425
+ /** The keyless mark's carrier key: a leaf of the record no read serves. */
8426
+ const $AFFECTS = Symbol("STORE_AFFECTS");
8427
+ const scopes = new Map();
8428
+ function scopeOf(carrier, key) {
8429
+ let entry = scopes.get(carrier);
8430
+ if (entry === undefined)
8431
+ scopes.set(carrier, (entry = { _scope: new Set(), _inherited: [], _key: key }));
8432
+ return entry;
8433
+ }
8434
+ /** The nodes an `affects(store[, key])` declaration marks (affects.ts
8435
+ * registers each). */
8436
+ function storeMarks(t, key) {
8437
+ const node = GlobalQueue._storeNode(t, key === undefined ? $AFFECTS : key);
8438
+ const entry = scopeOf(node, key);
8439
+ if (key === undefined) {
8440
+ const found = [node];
8441
+ walk(t.px, entry, found, t.fam, new Set());
8442
+ return found;
8443
+ }
8444
+ entry._scope.add(t.v);
8445
+ if (t.pb !== null) entry._scope.add(t.pb);
8446
+ return [node];
8447
+ }
8448
+ /** The identities reachable from `value` into the scope (the draft's and
8449
+ * the committed backing both, when a draft is open; an optimistic family's
8450
+ * view), every live node under each record into `found`. Untracked by
8451
+ * construction: raws, never traps. */
8452
+ function walk(value, entry, found, fam, visited) {
8453
+ if (!GlobalQueue._storeWrappable(value)) return;
8454
+ const t = value[$TARGET] ?? lookupTarget(value, fam);
8455
+ let raw = t !== undefined ? (t.pb ?? t.v) : value;
8456
+ if (visited.has(raw)) return;
8457
+ visited.add(raw);
8458
+ entry._scope.add(raw);
8459
+ if (t !== undefined) {
8460
+ if (t.pb !== null) entry._scope.add(t.v);
8461
+ // The writer's view: the tick's own unflushed guesses are in motion too.
8462
+ if (t.fam?.opt === true) raw = optHooks.view(t, raw, true);
8463
+ const nodes = t.n;
8464
+ if (nodes !== null)
8465
+ for (const k of Reflect.ownKeys(nodes))
8466
+ // Another mark's carrier is its own channel: counting it here would
8467
+ // extend that scope's lifetime to this declaration's.
8468
+ if (k !== $AFFECTS) found.push(nodes[k]);
8469
+ const has = t.h;
8470
+ if (has !== null) for (const k of Reflect.ownKeys(has)) found.push(has[k]);
8471
+ if (t.k !== null) found.push(t.k);
8472
+ if (t.dk !== null) found.push(t.dk);
8473
+ fam = t.fam ?? fam;
8474
+ }
8475
+ for (const k of Reflect.ownKeys(raw))
8476
+ if (k !== $OWNER) {
8477
+ const d = Object.getOwnPropertyDescriptor(raw, k);
8478
+ if (d.get === undefined) walk(d.value, entry, found, fam, visited);
8479
+ }
8480
+ }
8481
+ /** A live scope covering `t`'s identity (keyed: for `key`). Chained
8482
+ * backings (§7b): a view's backing is another store's proxy — marks cover
8483
+ * the BASE raw, so every identity along the chain is checked. */
8484
+ function covering(t, key, skip) {
8485
+ for (const [carrier, entry] of scopes) {
8486
+ if (carrier === skip || (entry._key !== undefined && entry._key !== key)) continue;
8487
+ for (let r = t.v; ; ) {
8488
+ if (entry._scope.has(r)) return carrier;
8489
+ const inner = r?.[$TARGET];
8490
+ if (inner === undefined) break;
8491
+ const backing = inner.pb ?? inner.v;
8492
+ if (backing === r) break;
8493
+ r = backing;
8494
+ }
8495
+ }
8496
+ return null;
8497
+ }
8498
+ /** The store's side: node birth and the untracked traps call it (store.ts,
8499
+ * through `affectsHooks`) — with no live mark too: an empty scope map is
8500
+ * no work. */
8501
+ const hooks = {
8502
+ // A node born on a record a live mark covers inherits it (released with
8503
+ // the carrier's last registration).
8504
+ _born(t, node, key) {
8505
+ if (scopes.size === 0 || key === $AFFECTS) return;
8506
+ const carrier = covering(t, key, node);
8507
+ if (!carrier) return;
8508
+ GlobalQueue._mark(node);
8509
+ scopes.get(carrier)._inherited.push(node);
8510
+ },
8511
+ // An untracked probe (`isPending(() => s.x)` with no observer) reading a
8512
+ // record a live mark covers: no node carries the mark for it — the
8513
+ // verdict is told directly.
8514
+ _witness(t, key) {
8515
+ if (t.n?.[$AFFECTS]?._x?._marks || covering(t, key)) GlobalQueue._witnessMark();
8516
+ }
8517
+ };
8518
+ /** `affects()` calls it before a store-targeted declaration (idempotent). */
8519
+ function installStoreAffects() {
8520
+ installAffectsHooks(hooks);
8521
+ }
8522
+ /** The carrier's last mark released (affects.ts): its scope dies, and the
8523
+ * nodes that inherited the mark release theirs. */
8524
+ function releaseMarkScope(carrier) {
8525
+ const entry = scopes.get(carrier);
8526
+ if (entry === undefined) return;
8527
+ scopes.delete(carrier);
8528
+ releaseMarksHook(entry._inherited);
8529
+ }
8530
+
8531
+ // `affects()` — the declaration verb of the pending model (A24 (4), ruled
8532
+ // 2026-07-13; the #2893 audit corollaries; rebuilt on L2 2026-10-02 as ruled:
8533
+ // transaction-inert).
8534
+ //
8535
+ // A mark is a declared in-flight change: the marked node — and everything
8536
+ // derived from it — reads pending (`isPending` → true) from the declaration
8537
+ // until the surrounding transaction lands (an ambient mark, declared outside
8538
+ // one, releases at the next seam). The marked values stay readable: no reader
8539
+ // suspends on a mark, a mark holds nothing and entangles nothing — it is not
8540
+ // a node of its transaction (`blocked` never sees it), and a reader of marked
8541
+ // data joins nothing through it. A mark is never a re-ask, so a declared
8542
+ // reload's own `refresh()` cannot silence it; a real error outranks it.
8543
+ //
8544
+ // Representation: the mark core (affects-marks.ts) — a count on the marked
8545
+ // node, coverage pull-derived at probe time, readers repolled at registration
8546
+ // and release. Here: each mark's scope (the transaction's `_marks`, or the
8547
+ // ambient list) and the store half.
8548
+ //
8549
+ // Installed on `GlobalQueue` when imported: a program that never declares a
8550
+ // mark carries none of this.
8551
+ /** Marks declared outside a transaction: released at the next seam. */
8552
+ const ambient = [];
7994
8553
  /** Releases one scope's marks (a landed transaction's, the ambient list at
7995
8554
  * the seam): a node whose last mark drops re-derives its verdict readers. */
7996
8555
  function release$1(nodes) {
7997
8556
  for (let i = 0; i < nodes.length; i++) {
7998
8557
  const n = nodes[i];
7999
- active--;
8000
- if (--n._x._marks === 0) {
8001
- repoll(n);
8002
- // A store mark's carrier: its scope (and the marks the nodes born in
8003
- // its window inherited) goes with it.
8004
- GlobalQueue._releaseMarkScope?.(n);
8005
- }
8558
+ // A store mark's carrier: its scope (and the marks the nodes born in
8559
+ // its window inherited) goes with the last mark.
8560
+ if (unmark(n)) releaseMarkScope(n);
8006
8561
  }
8007
8562
  nodes.length = 0;
8008
8563
  }
@@ -8018,10 +8573,6 @@ function register(node) {
8018
8573
  }
8019
8574
  repoll(node);
8020
8575
  }
8021
- function mark(node) {
8022
- ext(node)._marks++;
8023
- active++;
8024
- }
8025
8576
  function affects(target, key) {
8026
8577
  if (arguments.length > 2)
8027
8578
  invalid(
@@ -8032,7 +8583,8 @@ function affects(target, key) {
8032
8583
  // record's carrier and every live node under it.
8033
8584
  const t = target?.[$TARGET];
8034
8585
  if (t !== undefined) {
8035
- const nodes = GlobalQueue._storeMarks(t, key);
8586
+ installStoreAffects();
8587
+ const nodes = storeMarks(t, key);
8036
8588
  for (let i = 0; i < nodes.length; i++) register(nodes[i]);
8037
8589
  schedule();
8038
8590
  return;
@@ -8058,10 +8610,7 @@ function invalid(detail) {
8058
8610
  emitDiagnostic({ code: "INVALID_AFFECTS_TARGET", kind: "write", severity: "error", message });
8059
8611
  throw new Error(message);
8060
8612
  }
8061
- GlobalQueue._marked = el => active !== 0 && marked(el, new Set());
8062
- GlobalQueue._mark = mark;
8063
- GlobalQueue._releaseMarks = release$1;
8064
- GlobalQueue._releaseAmbientMarks = parked => {
8613
+ installMarkHooks(release$1, parked => {
8065
8614
  if (ambient.length === 0) return;
8066
8615
  // The flush parked: the declaration's window is the frame's — the marks
8067
8616
  // are its transaction's ("nothing async below ⇒ no window").
@@ -8084,7 +8633,7 @@ GlobalQueue._releaseAmbientMarks = parked => {
8084
8633
  }
8085
8634
  ambient.length = kept;
8086
8635
  if (done.length !== 0) release$1(done);
8087
- };
8636
+ });
8088
8637
  function inFlight(n) {
8089
8638
  return (
8090
8639
  (n._statusFlags & STATUS_PENDING) !== 0 ||
@@ -8092,6 +8641,174 @@ function inFlight(n) {
8092
8641
  );
8093
8642
  }
8094
8643
 
8644
+ // `createDeferred` — an async memo that may lag, but never leads (D-40,
8645
+ // docs/create-deferred.md).
8646
+ //
8647
+ // Once the node has a committed value, its own non-final work is invisible
8648
+ // to the graph: its readers keep the committed value and hold nothing, its
8649
+ // landings are changes of their own, and only the verdict (`isPending`) and
8650
+ // the truth readers (`until`, `refresh`'s waiter) see the flight.
8651
+ //
8652
+ // Built from parts the engine already has, so core pays nothing for it:
8653
+ //
8654
+ // - **Read side: the loading window** (`_loading`, async.ts). Every pass of
8655
+ // a node with a committed value opens the window before it asks, so a
8656
+ // flight serves the committed value instead of throwing, and an unready
8657
+ // source parks the node instead of pending it. `CONFIG_REDERIVE` makes a
8658
+ // source going pending a question for the node's pass (which parks) rather
8659
+ // than a status pushed through it to its readers.
8660
+ // - **Verdict: a mark** (affects-marks.ts) whose lifetime is the flight —
8661
+ // started when a pass asks a new question (a `refresh()` re-ask is quiet, F-9),
8662
+ // dropped at the landing, the rejection, a sync answer or the node's death.
8663
+ // - **Never leads:** a flight that lands while an input it read is held
8664
+ // derived from that hold's world, so it lands into the hold (`holdNode`;
8665
+ // `setSignal` stages it there). An input that changed after the pass
8666
+ // would have superseded the flight, so a held input at the landing is one
8667
+ // the pass read held.
8668
+ // - **Truth readers** wait the flight out (`GlobalQueue._lagging`, signals.ts
8669
+ // `watch`): `await refresh(d)` delivers the landing, `until` sees only
8670
+ // settled values.
8671
+ /** Deferred nodes with a flight up. */
8672
+ const lagging = new Set();
8673
+ /** The deferred node with a flight up that `c`'s value derives from, if
8674
+ * any: `c` itself, or one reached through its current dependencies. */
8675
+ function lagOf(c, seen) {
8676
+ if (lagging.has(c)) return c;
8677
+ if (seen.has(c)) return;
8678
+ seen.add(c);
8679
+ if (c._config & CONFIG_SLOT_NODE) return lagOf(c._host?.fam?.node ?? c, seen);
8680
+ const tail = c._flags & REACTIVE_RECOMPUTING_DEPS ? c._depsTail : undefined;
8681
+ if (tail !== null)
8682
+ for (let d = c._deps ?? null; d !== null; d = d._nextDep) {
8683
+ const l = lagOf(d._dep, seen);
8684
+ if (l) return l;
8685
+ if (d === tail) break;
8686
+ }
8687
+ }
8688
+ GlobalQueue._lagging = c => (lagging.size !== 0 ? lagOf(c, new Set()) : undefined);
8689
+ /**
8690
+ * Creates an async memo that may lag, but never leads. Before its first
8691
+ * answer it is an ordinary async memo: readers suspend to the nearest
8692
+ * `<Loading>`. After it, a refetch holds nothing: readers keep the last
8693
+ * committed value until the new one lands, and the rest of the screen moves
8694
+ * on without it. `isPending` reports the refetch, on the node and on what
8695
+ * derives from it. Use it for independent widgets (dashboards, feeds,
8696
+ * search results) where the previous answer with a "refreshing" indicator is
8697
+ * better than waiting with everything else.
8698
+ *
8699
+ * - It never shows a value ahead of a held change: a refetch that read a
8700
+ * held value lands with that hold.
8701
+ * - A rejected refetch errors like any memo; the stale value never masks it.
8702
+ * - `refresh(d)` is a quiet re-ask, and `await refresh(d)` and `until`
8703
+ * wait for the landing rather than the stale value.
8704
+ * - Combine with `loadingValue` for a node that never suspends at all.
8705
+ *
8706
+ * @example
8707
+ * ```tsx
8708
+ * const rows = createDeferred(() => fetchRows(store.period));
8709
+ *
8710
+ * <section class={{ stale: isPending(() => rows()) }}>
8711
+ * <For each={rows()}>{row => <Row row={row} />}</For>
8712
+ * </section>
8713
+ * ```
8714
+ */
8715
+ function createDeferred(fn, options) {
8716
+ let node;
8717
+ /** The flight in force: a promise, a stream, or the error an unready
8718
+ * source parked the node with. */
8719
+ let flight = null;
8720
+ let marked = false;
8721
+ let answered = false;
8722
+ /** The flight `r` is over. An async answer (`held`) arriving while an
8723
+ * input is held lands into that hold first. */
8724
+ const land = (r, held) => {
8725
+ if (r === null || flight !== r) return;
8726
+ flight = null;
8727
+ lagging.delete(node);
8728
+ if (held)
8729
+ for (let d = node._deps; d !== null; d = d._nextDep)
8730
+ if (d._dep._config & CONFIG_HELD) {
8731
+ holdNode(node, txOf(d._dep));
8732
+ break;
8733
+ }
8734
+ if (marked) {
8735
+ marked = false;
8736
+ unmark(node);
8737
+ schedule();
8738
+ }
8739
+ };
8740
+ const ask = r => {
8741
+ flight = r;
8742
+ lagging.add(node);
8743
+ if (!marked && !(node._flags & REACTIVE_REASK)) {
8744
+ marked = true;
8745
+ mark(node);
8746
+ repoll(node);
8747
+ }
8748
+ };
8749
+ /** Registers the flight and serves the pass's value: the committed one,
8750
+ * or over a held landing the landing itself, which a lagging question
8751
+ * does not replace (D-40) — restaged as the committed value, the hold's
8752
+ * readers would re-derive against the old input. */
8753
+ const serve = r => {
8754
+ const v = handleAsync(node, r);
8755
+ return node._config & CONFIG_HELD && node._pendingValue !== NOT_PENDING
8756
+ ? node._pendingValue
8757
+ : v;
8758
+ };
8759
+ const end = () => node._flags & REACTIVE_DISPOSED && land(flight);
8760
+ return accessor(
8761
+ computed(prev => {
8762
+ node = getOwner();
8763
+ node._config |= CONFIG_REDERIVE;
8764
+ cleanup(end);
8765
+ // Answered: the node has a real committed answer — not a first load,
8766
+ // not a `loadingValue` window still open (both are loading, F-9).
8767
+ // Sticky, and read before this wrapper ever opens the window itself.
8768
+ const open = (answered ||= !(node._statusFlags & STATUS_UNINITIALIZED) && !node._loading);
8769
+ if (open) ((node._loading = true), (node._config |= CONFIG_LAGS));
8770
+ let r;
8771
+ try {
8772
+ r = fn(prev);
8773
+ } catch (e) {
8774
+ // The wrapping form: an unready source parks the node in the window,
8775
+ // and the wait is its flight until the source's settle re-runs it.
8776
+ if (open && e instanceof NotReadyError) ask(e);
8777
+ throw e;
8778
+ }
8779
+ if (open && typeof r === "object" && r !== null) {
8780
+ if (isThenable(r)) {
8781
+ ask(r);
8782
+ const done = () => land(r, true);
8783
+ r.then(done, done);
8784
+ return serve(r);
8785
+ }
8786
+ const iterate = r[Symbol.asyncIterator];
8787
+ if (iterate) {
8788
+ // A stream's flight ends at its first yield (or its end).
8789
+ const it = iterate.call(r);
8790
+ let first = true;
8791
+ const done = () => land(s, true);
8792
+ const s = {
8793
+ // Registered ahead of the engine's own reaction to the same
8794
+ // promise, as for a plain flight: verdict and value in one flush.
8795
+ next: v => {
8796
+ const p = it.next(v);
8797
+ if (first) ((first = false), p.then(done, done));
8798
+ return p;
8799
+ },
8800
+ return: v => it.return?.(v) ?? Promise.resolve({ done: true, value: v })
8801
+ };
8802
+ ask(s);
8803
+ return serve({ [Symbol.asyncIterator]: () => s });
8804
+ }
8805
+ }
8806
+ land(flight);
8807
+ return r;
8808
+ }, options)
8809
+ );
8810
+ }
8811
+
8095
8812
  // CARVE 4: `flatten` lived in boundaries.ts but is the general children
8096
8813
  // resolver the renderers (`web`, `universal`, `solid`'s `children()`) walk
8097
8814
  // through — not a boundary feature. Moved verbatim so the boundary carve
@@ -8183,7 +8900,7 @@ function flattenArray(children, results = [], options) {
8183
8900
  * the root holds the frame as for any reader. The tree's own status takes
8184
8901
  * the same route from the output's pass (`read(tree)` throws it).
8185
8902
  *
8186
- * The hold (`blocked`, scheduler.ts) asks `hidden`: a reader behind a
8903
+ * The hold (`blocked`, scheduler.ts) asks `hiddenBy`: a reader behind a
8187
8904
  * fallback — any boundary on its chain whose last pass chose the fallback —
8188
8905
  * is not on screen and holds nothing. An error boundary flipping to its
8189
8906
  * fallback is the same as a loading one resetting: every reader under it
@@ -8218,6 +8935,11 @@ const boundaryOf = node => node._context[BOUNDARY];
8218
8935
  /** Catches what reaches it: an error boundary always; a loading one until it
8219
8936
  * has shown content, or while re-armed. */
8220
8937
  const isCollecting = b => b._type === STATUS_ERROR || !b._initialized || b._armed;
8938
+ /** A reader `b` waits on: recorded for its `on`, and `b` visited at the seam. */
8939
+ function record(b, node) {
8940
+ b._readers.add(node);
8941
+ collecting.add(b);
8942
+ }
8221
8943
  /** The reader goes to the fallback: recorded, and the output re-derives
8222
8944
  * (a no-op while the output's own pass is running — its post-check
8223
8945
  * decides). */
@@ -8234,8 +8956,7 @@ function caught(b, node, error) {
8234
8956
  setSignal(b._error, caught);
8235
8957
  reportClientError(caught, b._owner, node);
8236
8958
  }
8237
- b._readers.add(node);
8238
- collecting.add(b);
8959
+ record(b, node);
8239
8960
  if (b._armed) flip(b);
8240
8961
  // Already showing the fallback: one more reader to wait on changes
8241
8962
  // nothing on screen. The fallback is instantiated once per show — a later
@@ -8251,8 +8972,9 @@ function caught(b, node, error) {
8251
8972
  function flip(b) {
8252
8973
  b._initialized = false;
8253
8974
  b._lane = b._armLane;
8254
- if (b._lane !== null && b._output !== null)
8255
- GlobalQueue._laneStage(b._output, b._lane, true, false);
8975
+ const o = b._output;
8976
+ if (!b._lane && o._config & CONFIG_HELD && txOf(o) !== flushTransaction) unhold(o);
8977
+ if (b._lane !== null && b._output !== null) laneStageHook(b._output, b._lane, true, false);
8256
8978
  }
8257
8979
  function redraw(b) {
8258
8980
  if (b._output !== null && !(b._output._flags & (REACTIVE_DISPOSED | REACTIVE_ZOMBIE))) {
@@ -8260,7 +8982,7 @@ function redraw(b) {
8260
8982
  schedule();
8261
8983
  }
8262
8984
  }
8263
- /** GlobalQueue._catch: status from a frame reader, nearest boundary first.
8985
+ /** `catchHook` (scheduler.ts): status from a frame reader, nearest boundary first.
8264
8986
  * A loading boundary on the way records a pending reader whether or not it
8265
8987
  * catches it (its `on` may collect it later). */
8266
8988
  function catchStatus(node, flags, error) {
@@ -8273,7 +8995,7 @@ function catchStatus(node, flags, error) {
8273
8995
  // lands with its run, not a round after). Others still waiting: nothing
8274
8996
  // on screen changes, the fallback stays as it is.
8275
8997
  if (collecting.size !== 0)
8276
- for (let b = boundaryOf(node); b !== undefined; b = b._parent ?? undefined)
8998
+ for (let b = boundaryOf(node); b; b = b._parent)
8277
8999
  if (
8278
9000
  !(node._statusFlags & unsettled(b)) &&
8279
9001
  b._readers.delete(node) &&
@@ -8284,28 +9006,29 @@ function catchStatus(node, flags, error) {
8284
9006
  }
8285
9007
  return false;
8286
9008
  }
8287
- for (let b = boundaryOf(node); b !== undefined; b = b._parent ?? undefined) {
9009
+ for (let b = boundaryOf(node); b; b = b._parent) {
8288
9010
  if (!(b._type & flags)) continue;
8289
9011
  if (isCollecting(b)) {
8290
9012
  caught(b, node, error);
8291
- return true;
9013
+ return b._armed ? CAUGHT_REARMED : true;
8292
9014
  }
8293
- b._readers.add(node);
8294
- collecting.add(b);
9015
+ record(b, node);
8295
9016
  }
8296
9017
  return false;
8297
9018
  }
8298
- /** The nearest boundary on `r`'s chain showing its fallback, if any. */
9019
+ /** The nearest loading boundary that would catch `node` was re-armed this
9020
+ * flush. */
9021
+ function armedOver(node) {
9022
+ for (let b = boundaryOf(node); b; b = b._parent)
9023
+ if (b._type & STATUS_PENDING && isCollecting(b)) return b._armed;
9024
+ }
9025
+ /** The nearest boundary on `r`'s chain showing its fallback, if any
9026
+ * (`hiddenHook`: `r` is behind a fallback). */
8299
9027
  function hiddenBy(r) {
8300
- for (let b = boundaryOf(r); b !== undefined; b = b._parent ?? undefined)
8301
- if (b._fallback) return b;
9028
+ for (let b = boundaryOf(r); b; b = b._parent) if (b._fallback) return b;
8302
9029
  return undefined;
8303
9030
  }
8304
- /** GlobalQueue._hidden: behind a fallback. */
8305
- function hidden(r) {
8306
- return hiddenBy(r) !== undefined;
8307
- }
8308
- /** GlobalQueue._heldRun: a queued run behind a fallback waits for the reveal
9031
+ /** `heldRunHook` (scheduler.ts): a queued run behind a fallback waits for the reveal
8309
9032
  * (maintainer, 2026-10-02: the queue is held, the synchronous first render
8310
9033
  * goes through). A user effect's, which would read a DOM that is not
8311
9034
  * attached ("many depend on reading the DOM, something that can't happen
@@ -8350,12 +9073,18 @@ function prune(b, pass) {
8350
9073
  for (const r of b._readers) {
8351
9074
  if (r._flags & REACTIVE_DISPOSED) b._readers.delete(r);
8352
9075
  else if (!(r._statusFlags & mask)) {
8353
- if (r !== b._tree && (r._config & (CONFIG_HELD | CONFIG_OVERRIDE)) === CONFIG_HELD) {
9076
+ if (
9077
+ (r !== b._tree || b._armed) &&
9078
+ (r._config & (CONFIG_HELD | CONFIG_OVERRIDE)) === CONFIG_HELD
9079
+ ) {
8354
9080
  // Landed, held: the content is that transaction's — the output's
8355
9081
  // pass enters it and the reveal lands with the reader's run. The
8356
9082
  // seam, past its park decision, waits for the commit instead. (A
8357
- // lane's reader reveals with the lane, this round.)
8358
- if (!pass) continue;
9083
+ // lane's reader reveals with the lane, this round.) A boundary that
9084
+ // has not shown content catches it instead: the fallback stays until
9085
+ // the reader is committed (#3540: what a fresh boundary catches holds
9086
+ // nothing outside it).
9087
+ if (!pass || !b._initialized) continue;
8359
9088
  joinFuture(txOf(r));
8360
9089
  }
8361
9090
  b._readers.delete(r);
@@ -8377,7 +9106,7 @@ function ready(b) {
8377
9106
  prune(b, false) === 0)
8378
9107
  );
8379
9108
  }
8380
- /** GlobalQueue._boundarySeam, end of the seam: readers that settled, died,
9109
+ /** `boundarySeamHook` (scheduler.ts), end of the seam: readers that settled, died,
8381
9110
  * or landed and committed are dropped; a fallback with none left reveals
8382
9111
  * next round. An arm resolves: nothing collected, nothing happened. */
8383
9112
  function boundarySeam() {
@@ -8390,6 +9119,18 @@ function boundarySeam() {
8390
9119
  // A parked frame's boundary (zombie) keeps its state for its revival.
8391
9120
  if (flags & REACTIVE_ZOMBIE) continue;
8392
9121
  prune(b, false);
9122
+ const o = b._output;
9123
+ if (
9124
+ b._armed &&
9125
+ !b._initialized &&
9126
+ o._config & CONFIG_HELD &&
9127
+ [...b._readers].every(
9128
+ r => !(r._statusFlags & STATUS_PENDING) && r._config & CONFIG_HELD && txOf(r) === txOf(o)
9129
+ )
9130
+ ) {
9131
+ b._readers.clear();
9132
+ b._initialized = true;
9133
+ }
8393
9134
  if (b._armed && !b._initialized && !b._ahead) reportOutsideHold(b);
8394
9135
  b._armed = false;
8395
9136
  if (b._readers.size === 0) {
@@ -8416,15 +9157,17 @@ function reportOutsideHold(b) {
8416
9157
  if (outside(s._sub, false)) return true;
8417
9158
  return false;
8418
9159
  }
8419
- if (hidden(reader)) return false;
8420
- for (let o = boundaryOf(reader); o !== undefined; o = o._parent ?? undefined)
8421
- if (o === b) return false;
9160
+ if (hiddenBy(reader)) return false;
9161
+ for (let o = boundaryOf(reader); o; o = o._parent) if (o === b) return false;
8422
9162
  return true;
8423
9163
  };
8424
9164
  for (const r of b._readers) {
8425
9165
  const sources = r._x?._pendingSources;
8426
9166
  if (sources === undefined) continue;
8427
9167
  for (const source of sources) {
9168
+ // Only the flight the change itself started (D-16): an outside reader
9169
+ // of an earlier write's flight holds nothing the change waits on.
9170
+ if (source._time !== clock) continue;
8428
9171
  for (let s = source._subs; s !== null; s = s._nextSub) {
8429
9172
  if (!outside(s._sub, true)) continue;
8430
9173
  const name = source._name;
@@ -8449,15 +9192,6 @@ function reportOutsideHold(b) {
8449
9192
  }
8450
9193
  }
8451
9194
  }
8452
- /** The `on` pass is a verdict's: it probed itself (CONFIG_VERDICT), or it
8453
- * read a memo that did (#3528's `createMemo(() => isPending(m2))`) — its
8454
- * swap is display-ahead either way. */
8455
- function verdictDerived(c) {
8456
- if (c._config & CONFIG_VERDICT) return true;
8457
- for (let d = c._deps; d !== null; d = d._nextDep)
8458
- if (d._dep._config & CONFIG_VERDICT) return true;
8459
- return false;
8460
- }
8461
9195
  /** `on` changed after the mount: fresh again if anything under it is
8462
9196
  * pending now; collecting for the rest of the flush either way. */
8463
9197
  function arm(b) {
@@ -8481,20 +9215,32 @@ function arm(b) {
8481
9215
  return;
8482
9216
  }
8483
9217
  b._armed = true;
8484
- // A verdict read in `on` (`isPending`) whose frame has no transaction
8485
- // yet: the swap is still display-ahead — the frame gets one now (it lands
9218
+ // A verdict read in `on` (`isPending`, or a memo of one — #3528's
9219
+ // `createMemo(() => isPending(m2))`: the mark carries, core.ts `read`)
9220
+ // whose frame has no transaction yet: the swap is still display-ahead — the frame gets one now (it lands
8486
9221
  // at this very seam if nothing holds it), and the swap is its verdict
8487
9222
  // lane's work, shown ahead of the frame's own runs (#3528).
8488
9223
  let lane = passLane;
8489
- if (lane === null && globalQueue._running && verdictDerived(getOwner())) {
9224
+ if (lane === null && globalQueue._running && getOwner()._config & CONFIG_VERDICT) {
8490
9225
  joinFuture(null);
8491
9226
  lane = GlobalQueue._verdictLane(flushTransaction);
8492
9227
  }
8493
9228
  b._armLane = lane;
8494
9229
  b._ahead = lane !== null;
8495
9230
  collecting.add(b);
9231
+ const n = b._readers.size;
9232
+ heldUnder(b._owner, flushTransaction, r => record(b, r));
8496
9233
  for (const r of b._readers) {
8497
- if (r._statusFlags & STATUS_PENDING && !(r._flags & REACTIVE_DISPOSED)) {
9234
+ if (
9235
+ b._readers.size !== n ||
9236
+ (r._statusFlags & STATUS_PENDING && !(r._flags & REACTIVE_DISPOSED))
9237
+ ) {
9238
+ // What it collects was forwarded through it to the boundaries above
9239
+ // too (`catchStatus` records a reader in every boundary on the way).
9240
+ // It is this boundary's alone now (B5, the nearest catcher): an outer
9241
+ // re-armed by the same tick does not wait for it (D-14, U-4).
9242
+ for (let a = b._parent; a !== null; a = a._parent)
9243
+ for (const o of b._readers) a._readers.delete(o);
8498
9244
  flip(b);
8499
9245
  redraw(b);
8500
9246
  return;
@@ -8541,7 +9287,8 @@ function createBoundary(type, fn, show, onFn) {
8541
9287
  _gated: false,
8542
9288
  _collapsed: false,
8543
9289
  _error: type === STATUS_ERROR ? signal(undefined, ERROR_SIGNAL) : null,
8544
- _show: show
9290
+ _show: show,
9291
+ _fb: null
8545
9292
  };
8546
9293
  const controller = type === STATUS_PENDING ? owner._context[REVEAL] : null;
8547
9294
  if (getOwner())
@@ -8626,8 +9373,7 @@ function createBoundary(type, fn, show, onFn) {
8626
9373
  }
8627
9374
  // Forwarded: a loading boundary showing content keeps it and
8628
9375
  // holds the frame like any reader (recorded for its `on`).
8629
- b._readers.add(tree);
8630
- collecting.add(b);
9376
+ record(b, tree);
8631
9377
  }
8632
9378
  // A pending passes an error boundary by (A6); an error, a loading
8633
9379
  // one; a loading boundary showing content forwards its pending.
@@ -8667,8 +9413,7 @@ function createBoundary(type, fn, show, onFn) {
8667
9413
  }
8668
9414
  // Forwarded: a loading boundary showing content keeps it and
8669
9415
  // holds the frame like any reader (recorded for its `on`).
8670
- b._readers.add(tree);
8671
- collecting.add(b);
9416
+ record(b, tree);
8672
9417
  }
8673
9418
  // A pending passes an error boundary by (A6).
8674
9419
  throw e;
@@ -8682,6 +9427,20 @@ function createBoundary(type, fn, show, onFn) {
8682
9427
  if (b._fallback && b._type & STATUS_PENDING && attrHooks !== null)
8683
9428
  attrHooks.boundaryFallback(b, b._tree, false);
8684
9429
  b._fallback = false;
9430
+ // The fallback dies when the content that replaces it is on screen —
9431
+ // with this flush's commits when the pass is the frame's, at the
9432
+ // landing when the frame parks (the run is held with it, as the old
9433
+ // children of a held pass are) — not now: a held swap keeps the
9434
+ // fallback on screen, live, until it commits. A pass back to the
9435
+ // fallback before then finds it still built and keeps it.
9436
+ const fb = b._fb;
9437
+ if (fb !== null)
9438
+ globalQueue.enqueue(EFFECT_RENDER, () => {
9439
+ if (!b._fallback && b._fb === fb) {
9440
+ dispose(fb);
9441
+ b._fb = null;
9442
+ }
9443
+ });
8685
9444
  release(b);
8686
9445
  // Shown: the slot leaves its reveal order (a later re-arm gates nobody
8687
9446
  // else; the frontier moves on).
@@ -8724,7 +9483,22 @@ function fallback(b) {
8724
9483
  if (!globalQueue._running) schedule();
8725
9484
  }
8726
9485
  b._fallback = true;
8727
- return b._show(b);
9486
+ // Built in its own scope, like `Show`'s function child, once per show
9487
+ // (D-39: the fallback is the display until a new answer replaces the
9488
+ // error): a re-derive of the output while it shows — a second reader
9489
+ // caught, the same error propagated again through a sibling's re-run, a
9490
+ // retry pending after `reset()`, a later error — keeps the built
9491
+ // fallback, its DOM and focus with it (#3991). A later error reaches it
9492
+ // through the error accessor; only a fallback reading the accessor at its
9493
+ // top level is rebuilt by it. Created beside the output — in its owner,
9494
+ // outside the boundary, as the output is: its errors and runs are the
9495
+ // enclosing boundary's — with the output's id, so what it builds gets the
9496
+ // ids a build under the output would; disposed once content is on screen.
9497
+ if (b._fb === null) {
9498
+ const out = getOwner();
9499
+ b._fb = runWithOwner(out._parent, () => computed(() => b._show(b), { id: out.id }));
9500
+ }
9501
+ return read(b._fb);
8728
9502
  }
8729
9503
  const ERROR_SIGNAL = { ownedWrite: true, _noSnapshot: true };
8730
9504
  /**
@@ -8800,12 +9574,17 @@ function createErrorBoundary(fn, fallback) {
8800
9574
  }
8801
9575
  // Installed at module evaluation — present exactly when something imports a
8802
9576
  // boundary. An app without one pays the three null checks and nothing else.
8803
- GlobalQueue._catch = catchStatus;
8804
- GlobalQueue._fresh = node =>
8805
- node._statusFlags & STATUS_UNINITIALIZED && catchStatus(node, STATUS_PENDING);
8806
- GlobalQueue._hidden = hidden;
8807
- GlobalQueue._boundarySeam = boundarySeam;
8808
- GlobalQueue._heldRun = heldRun;
9577
+ installBoundaryHooks(
9578
+ catchStatus,
9579
+ (node, held) =>
9580
+ !held &&
9581
+ (node._statusFlags & STATUS_UNINITIALIZED
9582
+ ? catchStatus(node, STATUS_PENDING)
9583
+ : armedOver(node)),
9584
+ hiddenBy,
9585
+ boundarySeam,
9586
+ heldRun
9587
+ );
8809
9588
 
8810
9589
  /**
8811
9590
  * Reveal order — `createRevealOrder` (`<Reveal>`): the order in which a group
@@ -9048,6 +9827,7 @@ export {
9048
9827
  affects,
9049
9828
  configureClientErrors,
9050
9829
  createContext,
9830
+ createDeferred,
9051
9831
  createEffect,
9052
9832
  createErrorBoundary,
9053
9833
  createLoadingBoundary,
@@ -9093,6 +9873,9 @@ export {
9093
9873
  resolvedTable,
9094
9874
  runWithOwner,
9095
9875
  setContext,
9876
+ setReadWatch,
9877
+ setSignalIdentities,
9878
+ signalName,
9096
9879
  snapshot,
9097
9880
  sourceGet,
9098
9881
  sourceHas,
@@ -9101,6 +9884,7 @@ export {
9101
9884
  storeHasFamily,
9102
9885
  storeHasOptimisticFamily,
9103
9886
  storeIsShallow,
9887
+ storeName,
9104
9888
  storePath,
9105
9889
  until,
9106
9890
  untrack,