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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/dist/dev-shared.js +553 -264
  2. package/dist/dev.attribution.js +18 -18
  3. package/dist/dev.js +1030 -439
  4. package/dist/observe/affects.js +12 -2
  5. package/dist/observe/boundaries.js +168 -115
  6. package/dist/observe/core/async.js +57 -37
  7. package/dist/observe/core/attribution.js +6 -6
  8. package/dist/observe/core/constants.js +9 -1
  9. package/dist/observe/core/copies.js +10 -0
  10. package/dist/observe/core/core.js +261 -181
  11. package/dist/observe/core/effect.js +32 -27
  12. package/dist/observe/core/graph.js +7 -7
  13. package/dist/observe/core/heap.js +29 -29
  14. package/dist/observe/core/lanes.js +301 -101
  15. package/dist/observe/core/owner.js +13 -13
  16. package/dist/observe/core/scheduler.js +124 -69
  17. package/dist/observe/core/truth.js +9 -7
  18. package/dist/observe/core/verdict.js +160 -98
  19. package/dist/observe/deferred.js +1 -1
  20. package/dist/observe/index.js +2 -0
  21. package/dist/observe/map.js +120 -116
  22. package/dist/observe/reveal.js +48 -48
  23. package/dist/observe/signals.js +5 -5
  24. package/dist/observe/store/affects.js +2 -2
  25. package/dist/observe/store/optimistic.js +272 -176
  26. package/dist/observe/store/projection.js +21 -10
  27. package/dist/observe/store/reconcile.js +8 -2
  28. package/dist/observe/store/store.js +91 -55
  29. package/dist/observe/store/types.js +11 -1
  30. package/dist/observe/store/utils.js +1 -1
  31. package/dist/prod/affects.js +12 -2
  32. package/dist/prod/boundaries.js +164 -109
  33. package/dist/prod/core/async.js +201 -181
  34. package/dist/prod/core/constants.js +9 -1
  35. package/dist/prod/core/copies.js +10 -0
  36. package/dist/prod/core/core.js +244 -164
  37. package/dist/prod/core/dev.js +108 -3
  38. package/dist/prod/core/effect.js +28 -23
  39. package/dist/prod/core/graph.js +9 -9
  40. package/dist/prod/core/heap.js +16 -16
  41. package/dist/prod/core/identities.js +1 -1
  42. package/dist/prod/core/lanes.js +307 -95
  43. package/dist/prod/core/owner.js +22 -22
  44. package/dist/prod/core/scheduler.js +266 -211
  45. package/dist/prod/core/truth.js +9 -7
  46. package/dist/prod/core/verdict.js +161 -99
  47. package/dist/prod/deferred.js +1 -1
  48. package/dist/prod/index.js +2 -0
  49. package/dist/prod/map.js +78 -74
  50. package/dist/prod/reveal.js +34 -34
  51. package/dist/prod/signals.js +3 -3
  52. package/dist/prod/store/affects.js +15 -15
  53. package/dist/prod/store/optimistic.js +185 -89
  54. package/dist/prod/store/projection.js +49 -38
  55. package/dist/prod/store/reconcile.js +8 -2
  56. package/dist/prod/store/store.js +99 -63
  57. package/dist/prod/store/types.js +11 -1
  58. package/dist/types/affects.d.ts +3 -2
  59. package/dist/types/boundaries.d.ts +14 -2
  60. package/dist/types/core/async.d.ts +2 -1
  61. package/dist/types/core/constants.d.ts +8 -0
  62. package/dist/types/core/copies.d.ts +54 -0
  63. package/dist/types/core/core.d.ts +15 -0
  64. package/dist/types/core/dev.d.ts +1 -1
  65. package/dist/types/core/lanes.d.ts +8 -1
  66. package/dist/types/core/scheduler.d.ts +36 -4
  67. package/dist/types/core/truth.d.ts +3 -1
  68. package/dist/types/core/types.d.ts +6 -0
  69. package/dist/types/index.d.ts +2 -0
  70. package/dist/types/store/hooks.d.ts +15 -1
  71. package/dist/types/store/store.d.ts +2 -1
  72. package/dist/types/store/target.d.ts +5 -0
  73. package/dist/types/store/types.d.ts +7 -0
  74. package/package.json +1 -1
@@ -14,6 +14,8 @@ import { getOwner, isDisposed } from "../core/owner.js";
14
14
 
15
15
  import { question, setQuestion, insertSubs, schedule } from "../core/scheduler.js";
16
16
 
17
+ import { optHooks } from "./hooks.js";
18
+
17
19
  import { reconcileState } from "./reconcile.js";
18
20
 
19
21
  import { wrap, nameStore, storeSetter, runDirect } from "./store.js";
@@ -129,6 +131,7 @@ function cloneState(e, t) {
129
131
  // reader the landing leaves unchanged does not re-run to clear it (#4305).
130
132
  const r = new NotReadyError(t);
131
133
  e.woke = true;
134
+ optHooks?.ask(e);
132
135
  forEachFamilyNode(e, e => {
133
136
  if (e.o === null) return;
134
137
  // The leaf carries the derive's pending as a propagated copy, as a memo
@@ -164,6 +167,10 @@ function cloneState(e, t) {
164
167
  forEachFamilyNode(e, e => {
165
168
  if (e.o !== null) settlePendingSource(e, t);
166
169
  });
170
+ // The landing is the derive's change for a reader parked on the derive
171
+ // itself, whatever status the landing pass began from (an errored derive
172
+ // re-asked: a reader that met the error has no leaf to learn it from).
173
+ insertSubs(t);
167
174
  }
168
175
 
169
176
  /** A source's status reached the derive by propagation (core
@@ -192,16 +199,16 @@ function forEachFamilyNode(e, t) {
192
199
  const s = e[$TARGET];
193
200
  const c = s.fam;
194
201
  const a = c.run = (c.run || 0) + 1;
195
- let l;
202
+ let u;
196
203
  // Open loading window (seedLoadingValue): the observable store IS commit #0
197
204
  // for the whole first flight — the derive works a detached shadow of the
198
205
  // seed so draft writes cannot tear through to readers (#2988). Every
199
206
  // commit point reconciles the shadow through the normal commit path.
200
- const u = i.ce ? cloneState(s.v, s.s) : null;
207
+ const l = i.ce ? cloneState(s.v, s.s) : null;
201
208
  // The question a write of this run answers: inside a slice, the slice's
202
209
  // (the sync pass); in a continuation — the flight still registered — the
203
210
  // flight's (`handleAsync` stamped it with the action that asked).
204
- const asking = () => question || (i.T !== null && i.T.Ee !== null ? i.T.de : 0);
211
+ const asking = () => question || (i.T !== null && i.T.Ee !== null ? i.T.Ie : 0);
205
212
  const f = wrapDraft(e, () => c.run === a && !isDisposed(i), n, s.s,
206
213
  // A write after the run returned (a continuation, a callback) arms the
207
214
  // drain itself when no landing will.
@@ -213,12 +220,12 @@ function forEachFamilyNode(e, t) {
213
220
  // — stages and commits with the flush like a memo's recompute.
214
221
  const d = a === 1;
215
222
  const pass = () => storeSetter(f, n => {
216
- l = t(u ?? n);
217
- const commit = t => {
223
+ u = t(l ?? n);
224
+ const commit = (t, a = 1) => {
218
225
  // Shadow run: commit a detached snapshot, never the shadow itself
219
226
  // (adoption takes the value by identity — handing it the live
220
227
  // shadow would fuse the draft to the observable store).
221
- if (u && (t === undefined || t === u)) t = cloneState(u, s.s);
228
+ if (l && (t === undefined || t === l)) t = cloneState(l, s.s);
222
229
  if (t !== n && t !== undefined) {
223
230
  const write = () => {
224
231
  const o = question;
@@ -237,10 +244,13 @@ function forEachFamilyNode(e, t) {
237
244
  // A commit is the derive's change for the readers that observed its
238
245
  // flight (linked by `pullFamily`; a settled pull does not link), as
239
246
  // a memo's landing is for its readers: one that caught the NotReady
240
- // (a fallback value, not pending) re-runs too (#4070).
241
- insertSubs(i);
247
+ // (a fallback value, not pending) re-runs too (#4070). A pass of a
248
+ // settled derive has none (`wake` 0): a link left is a stale one a
249
+ // held frame keeps (A30), from a reader that re-ran since. Woken by
250
+ // every pass, a reader feeding a key the pass peeks at looped (#4342).
251
+ if (a) insertSubs(i);
242
252
  };
243
- const a = handleAsync(i, l, commit, (e, t) => {
253
+ const a = handleAsync(i, u, commit, (e, t) => {
244
254
  // The flight rejected: the leaves' readers learn it from here —
245
255
  // they do not subscribe to the derive. Pending again (a NotReady
246
256
  // rejection): the wake, as for the pass. Errored: every live
@@ -250,7 +260,8 @@ function forEachFamilyNode(e, t) {
250
260
  if (d && i.m & STATUS_UNINITIALIZED) return;
251
261
  if (t) wakeFamily(c); else errorFamily(c);
252
262
  });
253
- if (!i.ce) commit(a);
263
+ // (The status is the one before this pass: `recompute` sets it after.)
264
+ if (!i.ce) commit(a, i.m & (STATUS_PENDING | STATUS_ERROR));
254
265
  }, false);
255
266
  try {
256
267
  d ? runDirect(pass) : pass();
@@ -79,7 +79,10 @@ import { $TARGET, markRawIngest, isWrappable, rawValuesUsed, isRawValue } from "
79
79
  if (r) markRawIngest(n);
80
80
  // The deep witness is bumped once per adoption (none to bump: done).
81
81
  let u = e.dk === null;
82
- if (Array.isArray(i) !== l) {
82
+ // A live chained base (a store proxy, §7b) reads the inner store's
83
+ // current frame, not what the nodes were last told — nothing to diff
84
+ // against: the fold writes every node (#4361).
85
+ if (Array.isArray(i) !== l || f[$TARGET]) {
83
86
  notifyFold(e, f, n);
84
87
  return;
85
88
  }
@@ -186,7 +189,10 @@ import { $TARGET, markRawIngest, isWrappable, rawValuesUsed, isRawValue } from "
186
189
  let a = 0;
187
190
  for (const l in n) {
188
191
  const c = n[l];
189
- const d = f[l];
192
+ let d = f[l];
193
+ // A key under a guess is judged against the guess too (D-34): a truth
194
+ // equal to the value the guess covered still answers it.
195
+ if (optHooks && d === c && i !== f) d = i[l];
190
196
  const p = c !== null && typeof c === "object";
191
197
  if (d === c && (!p || !isOwned(c)) && (s === null || s[l] === undefined || !hasAccessorFlag(s[l]))) {
192
198
  if (s?.[l] !== undefined) a++;
@@ -1,14 +1,14 @@
1
1
  import { attrHooks } from "../core/attribution-hooks.js";
2
2
 
3
- import { CONFIG_MANUAL_WRITE, CONFIG_OVERRIDE, CONFIG_HELD, $REFRESH, NOT_PENDING, STATUS_PENDING, STATUS_ERROR, EFFECT_RENDER, REACTIVE_RECOMPUTING_DEPS, REACTIVE_STAGED_READ, CONFIG_VERDICT, CONFIG_CHILDREN_FORBIDDEN, CONFIG_PROMOTED, REACTIVE_DIRTY, REACTIVE_CHECK } from "../core/constants.js";
3
+ import { CONFIG_MANUAL_WRITE, CONFIG_OVERRIDE, CONFIG_HELD, $REFRESH, NOT_PENDING, STATUS_PENDING, STATUS_ERROR, EFFECT_RENDER, REACTIVE_RECOMPUTING_DEPS, REACTIVE_STAGED_READ, CONFIG_VERDICT, CONFIG_CHILDREN_FORBIDDEN, STATUS_UNINITIALIZED, CONFIG_PROMOTED, REACTIVE_DIRTY, REACTIVE_CHECK } from "../core/constants.js";
4
4
 
5
- import { writeHook, setSignal, isEqual, read, verdict, snapshotCaptureActive, captureWriteSnapshot, context, notePromotedWrite, untrack, slotSignal, stagedScreen, stagedRead } from "../core/core.js";
5
+ import { writeHook, setSignal, isEqual, read, verdict, snapshotCaptureActive, captureWriteSnapshot, context, notePromotedWrite, untrack, slotSignal, stagedScreen, stagedRead, tracking } from "../core/core.js";
6
6
 
7
7
  import { registerGraph } from "../core/dev.js";
8
8
 
9
9
  import { signalHook } from "../core/identities.js";
10
10
 
11
- import { setSlotUnobserved } from "../core/graph.js";
11
+ import { link, setSlotUnobserved } from "../core/graph.js";
12
12
 
13
13
  import { getOwner, getObserver } from "../core/owner.js";
14
14
 
@@ -20,7 +20,7 @@ import { lookupTarget, storeOwners, setLookup, $OWNER, isOwned, markDescendants
20
20
 
21
21
  import { optHooks, affectsHooks } from "./hooks.js";
22
22
 
23
- import { $TARGET, rawValuesUsed, isRawValue, isWrappable, markRawIngest, writeOverride, markRawOne, $RECORD, $PROXY, $TRACK } from "./types.js";
23
+ import { $TARGET, rawValuesUsed, isRawValue, isWrappable, markRawIngest, writeOverride, markRawOne, $RECORD, $PROXY, $TRACK, installShallowView } from "./types.js";
24
24
 
25
25
  export { markRaw } from "./types.js";
26
26
 
@@ -224,7 +224,7 @@ function sameLogicalSlot(e, t, n) {
224
224
 
225
225
  function releaseSlot(e) {
226
226
  const t = e.O;
227
- const n = e.Vt;
227
+ const n = e.gt;
228
228
  if (n === $TRACK) {
229
229
  if (t.k === e) t.k = null;
230
230
  } else if (n === $DEEP) {
@@ -251,7 +251,7 @@ function releaseSlot(e) {
251
251
  // holds: its release waits for the commit that resolves it. So does a leaf of
252
252
  // a held container (#4146): a mainline write it carried is in the held
253
253
  // staging too, where it would read as the hold's until the landing.
254
- const slotKept = e => e.ie !== NOT_PENDING || e.l & CONFIG_OVERRIDE || e.O.k?.l & CONFIG_HELD;
254
+ const slotKept = e => e.ue !== NOT_PENDING || e.l & CONFIG_OVERRIDE || e.O.k?.l & CONFIG_HELD;
255
255
 
256
256
  setSlotUnobserved(e => {
257
257
  if (slotKept(e)) deferredReleases.add(e); else releaseSlot(e);
@@ -264,7 +264,7 @@ setSlotUnobserved(e => {
264
264
  * unchanged stages nothing and holds no one). */ function bornStaged(e, t, n, r) {
265
265
  if (t.je && t.je(t.le, n)) return;
266
266
  queuePendingNode(t);
267
- t.ie = n;
267
+ t.ue = n;
268
268
  const o = e.k;
269
269
  if (o === null) return;
270
270
  // a node-less staging holds nothing (`stageOn`)
@@ -344,7 +344,7 @@ function getHasNode(e, t) {
344
344
  // would have read had the node been there from the write.
345
345
  if (e.pb !== null && !directCommit) {
346
346
  t.le = preBatch(e);
347
- t.ie = e.pb;
347
+ t.ue = e.pb;
348
348
  queuePendingNode(t);
349
349
  }
350
350
  }
@@ -361,10 +361,10 @@ function getHasNode(e, t) {
361
361
  * re-created, queued, swept and dropped ~7000 container nodes). If the
362
362
  * flush parks, the seam materializes and holds the node then
363
363
  * (`storeParkHook`) — the one case the staging home exists for. */ function stageOn(e, t) {
364
- if (e.k === null && passLane === null && (context === null || globalQueue.q) && !deriveHolds(e)) return;
364
+ if (e.k === null && passLane === null && (context === null || globalQueue.Y) && !deriveHolds(e)) return;
365
365
  const n = getContainerNode(e);
366
- if (n.ie === NOT_PENDING) queuePendingNode(n);
367
- n.ie = t;
366
+ if (n.ue === NOT_PENDING) queuePendingNode(n);
367
+ n.ue = t;
368
368
  // A28 (4): a write issued inside a pass outside a flush (a creation-time
369
369
  // derive) is promoted at the pass's end — the batch with it.
370
370
  if (context !== null) notePromotedWrite(n);
@@ -382,6 +382,7 @@ const storePark = e => {
382
382
  const n = foldList[t];
383
383
  if (n.k === null && n.pb !== null) holdNode(getContainerNode(n), e);
384
384
  }
385
+ optHooks?.park(e);
385
386
  };
386
387
 
387
388
  function getDeepNode(e) {
@@ -468,16 +469,23 @@ function isOwnAccessor(e, t) {
468
469
  n[$OWNER] = t;
469
470
  return n;
470
471
  }
471
- const n = Object.getOwnPropertyDescriptors(e);
472
- for (const r of Reflect.ownKeys(n)) {
473
- const o = n[r];
474
- if (r === "length" && Array.isArray(e)) continue;
475
- o.configurable = true;
476
- if (!o.get && !o.set) o.writable = true; else t.a = true;
472
+ // A plain array with no accessors and no named keys beyond `length`
473
+ // (`kc` counts own keys) clones by `slice()`: the descriptor path
474
+ // allocates one descriptor per element, O(n) per first draft write (10k
475
+ // rows: ~10 ms per push).
476
+ let n;
477
+ if (!t.a && t.kc <= e.length + 1 && Object.getPrototypeOf(e) === Array.prototype) n = e.slice(); else {
478
+ const r = Object.getOwnPropertyDescriptors(e);
479
+ for (const n of Reflect.ownKeys(r)) {
480
+ const o = r[n];
481
+ if (n === "length" && Array.isArray(e)) continue;
482
+ o.configurable = true;
483
+ if (!o.get && !o.set) o.writable = true; else t.a = true;
484
+ }
485
+ n = Array.isArray(e) ? Object.defineProperties([], r) : Object.create(Object.getPrototypeOf(e), r);
477
486
  }
478
- const r = Array.isArray(e) ? Object.defineProperties([], n) : Object.create(Object.getPrototypeOf(e), n);
479
- r[$OWNER] = t;
480
- return r;
487
+ n[$OWNER] = t;
488
+ return n;
481
489
  }
482
490
 
483
491
  /** Wide plain clone (many keys): null-prototype build, then reset. */ function wideClone(e, t) {
@@ -543,7 +551,7 @@ const plainProto = e => {
543
551
  const n = overlayOnto(e, e.sc === 2 ? wideClone(e.v, e) : cloneRaw(e.v, e), t);
544
552
  e.pb = n;
545
553
  // The staging IS the pending backing: re-point the container node.
546
- if (e.k?.ie === t) e.k.ie = n;
554
+ if (e.k?.ue === t) e.k.ue = n;
547
555
  return n;
548
556
  }
549
557
 
@@ -564,7 +572,7 @@ const plainProto = e => {
564
572
  // mainline setter mid-hold publishes mainline; the held keys stay held).
565
573
  t = e.pb = cloneRaw(t, e);
566
574
  const n = e.k;
567
- if (n !== null && !(n.l & CONFIG_HELD)) n.ie = t;
575
+ if (n !== null && !(n.l & CONFIG_HELD)) n.ue = t;
568
576
  } else if (t === null) {
569
577
  const n = e.v;
570
578
  if (!directCommit) queueFold(e);
@@ -741,7 +749,7 @@ let foldIndex = null;
741
749
  if (r.pb === null) continue;
742
750
  // folded already (a direct commit)
743
751
  const o = r.k;
744
- if (o !== null && o.ie !== NOT_PENDING) {
752
+ if (o !== null && o.ue !== NOT_PENDING) {
745
753
  // held: commits with its hold
746
754
  foldList.push(r);
747
755
  foldOlds.push(t[n]);
@@ -771,10 +779,10 @@ let foldIndex = null;
771
779
  /** Core asks (lanes.ts `covered`): the value a slot node's guess covered.
772
780
  * A chained link's committed value is the inner store's at the last
773
781
  * read-through — refreshed here, since a guess served the reads since
774
- * (#3672: the revert compares against the base's live value). */ GlobalQueue.fn = e => {
782
+ * (#3672: the revert compares against the base's live value). */ GlobalQueue.an = e => {
775
783
  const t = e.O;
776
784
  if (t.ch) {
777
- const n = e.Vt;
785
+ const n = e.gt;
778
786
  const r = t.v;
779
787
  if (t.h?.[n] === e) e.le = untrack(() => n in r); else if (t.n?.[n] === e) e.le = untrack(() => unwrapValue(r[n]));
780
788
  }
@@ -945,13 +953,18 @@ function heldDerivation(e, t, n) {
945
953
  // Optimistic channel: a user's writes on an optimistic family become
946
954
  // guesses on the nodes (lanes); the draft is discarded, the committed
947
955
  // backing never touched.
948
- if (optHooks && userWrite && e.fam?.opt === true) {
949
- // The draft is discarded first: the diff reads the target (its nodes
950
- // are born from the committed frame, not the draft).
951
- e.pb = null;
952
- e.wk = null;
953
- e.pb = optHooks.writes(e, t);
954
- return;
956
+ if (optHooks && e.fam?.opt === true) {
957
+ if (userWrite) {
958
+ // The draft is discarded first: the diff reads the target (its nodes
959
+ // are born from the committed frame, not the draft).
960
+ e.pb = null;
961
+ e.wk = null;
962
+ e.pb = optHooks.writes(e, t);
963
+ return;
964
+ }
965
+ // The derive's draft: new rows under guessed rows are paired with them
966
+ // first (D-34, #4329).
967
+ optHooks.pair(e, t);
955
968
  }
956
969
  const n = e.v;
957
970
  const r = e.n;
@@ -1096,7 +1109,10 @@ function membershipChanged(e, t) {
1096
1109
  } else {
1097
1110
  const o = n[t];
1098
1111
  const i = r[t];
1099
- if (!isEqual(o, i) && !targetsEqual(o, i)) setSignal(e, typeof i === "function" ? () => i : i);
1112
+ // A live chained `old` (a store proxy, §7b) already reads the inner
1113
+ // store's next frame, not what the node was last told: written, the
1114
+ // node's own equality judging it (#4361).
1115
+ if (n[$TARGET] || !isEqual(o, i) && !targetsEqual(o, i)) setSignal(e, typeof i === "function" ? () => i : i);
1100
1116
  }
1101
1117
  }
1102
1118
 
@@ -1122,7 +1138,8 @@ function hasAccessorFlag(e) {
1122
1138
  * unrelated `a.value` stayed stale until the action settled). A real
1123
1139
  * change still writes, and on a held node still proposes. A live chained
1124
1140
  * `old` (a store proxy, §7b) reflects the inner store, not what the node
1125
- * was last told — written unconditionally, as before.
1141
+ * was last told — written unconditionally, as before; its container too
1142
+ * (#4361).
1126
1143
  *
1127
1144
  * The container is told of an arrangement change when it has structural
1128
1145
  * subscribers OR carries an arrangement guess (S4): for a guessed
@@ -1133,14 +1150,14 @@ function hasAccessorFlag(e) {
1133
1150
  * answers the arrangement — before, only the presence write reached the
1134
1151
  * lane, by accident of being unconditional. */ function notifyFoldTail(e, t, n) {
1135
1152
  const r = e.h;
1153
+ const o = t[$TARGET];
1136
1154
  if (r !== null) {
1137
- const e = t[$TARGET];
1138
- for (const o of Reflect.ownKeys(r)) if (e || o in t !== o in n) setSignal(r[o], o in n);
1155
+ for (const e of Reflect.ownKeys(r)) if (o || e in t !== e in n) setSignal(r[e], e in n);
1139
1156
  }
1140
- const o = e.k;
1141
- if (o !== null && (o.o !== null || o.l & CONFIG_OVERRIDE)) {
1142
- const e = Array.isArray(n) && Array.isArray(t) ? arrayStructureChanged(t, n) : membershipChanged(t, n);
1143
- if (e) notifyContainer(o, n);
1157
+ const i = e.k;
1158
+ if (i !== null && (i.o !== null || i.l & CONFIG_OVERRIDE)) {
1159
+ const e = o || (Array.isArray(n) && Array.isArray(t) ? arrayStructureChanged(t, n) : membershipChanged(t, n));
1160
+ if (e) notifyContainer(i, n);
1144
1161
  }
1145
1162
  }
1146
1163
 
@@ -1195,7 +1212,7 @@ function inDraft(e) {
1195
1212
  * "adoption is eager by contract" — and the node keeps the frame committed
1196
1213
  * readers under a hold still see), `t.v` otherwise. */ function committed(e) {
1197
1214
  const t = e.k;
1198
- if (t !== null) return t.ie !== NOT_PENDING ? asBacking(t.le) : e.v;
1215
+ if (t !== null) return t.ue !== NOT_PENDING ? asBacking(t.le) : e.v;
1199
1216
  // A node-less staging (`stageOn`): the fold queue remembers the frame.
1200
1217
  return e.pb !== null ? preBatch(e) : e.v;
1201
1218
  }
@@ -1207,7 +1224,7 @@ function inDraft(e) {
1207
1224
  * committed frame. */ function stagingReader() {
1208
1225
  let e = context;
1209
1226
  if (e === null) return null;
1210
- if (e.Pt) e = e.ht ?? null;
1227
+ if (e.Pt) e = e.Ht ?? null;
1211
1228
  return e === null || e.l & CONFIG_CHILDREN_FORBIDDEN ? null : e;
1212
1229
  }
1213
1230
 
@@ -1314,7 +1331,7 @@ function inDraft(e) {
1314
1331
 
1315
1332
  /** Did the HELD staging (the container node's, not a mainline layer above
1316
1333
  * it) change `key`? */ function heldKeyChanged(e, t, n = true) {
1317
- return changedBetween(e, e.k.le, e.k.ie, t, n) && !adoptChild(e, t);
1334
+ return changedBetween(e, e.k.le, e.k.ue, t, n) && !adoptChild(e, t);
1318
1335
  }
1319
1336
 
1320
1337
  /** The values of a held frame, built once per frame: neither the committed
@@ -1343,12 +1360,12 @@ function rawsOf(e) {
1343
1360
  const n = e.k;
1344
1361
  const r = asBacking(n.le);
1345
1362
  const o = r[t];
1346
- const i = n.ie[t];
1363
+ const i = n.ue[t];
1347
1364
  const l = Array.isArray(r) ? e.fam?.key : null;
1348
1365
  if (e.s || l === undefined || !sameRow(o, i, l) || !isWrappable(o) || !isWrappable(i) || Array.isArray(o) !== Array.isArray(i) || lookupTarget(o, e.fam) !== undefined || lookupTarget(i, e.fam) !== undefined ||
1349
1366
  // A raw in both frames (the same objects swapped or reordered) is its
1350
1367
  // own row: pairing it with another would alias the two after the commit.
1351
- rawsOf(r).has(i) || rawsOf(n.ie).has(o)) return false;
1368
+ rawsOf(r).has(i) || rawsOf(n.ue).has(o)) return false;
1352
1369
  // A raw-marked value wraps to itself, a proxy to its own target: neither
1353
1370
  // is a fresh target around `ov`.
1354
1371
  const u = wrap(o, e, t)[$TARGET];
@@ -1391,9 +1408,16 @@ function changedBetween(e, t, n, r, o) {
1391
1408
 
1392
1409
  /** Value of a key with a leaf: core's — `read()` links a tracked reader and,
1393
1410
  * tracked or not, applies the hold and A28 rules a signal's read does. A
1394
- * projection's unheld staging is ahead for a handler (`familyAhead`). */ function nodeValue(e, t, n) {
1395
- let r = read(e);
1396
- if (e.ie !== NOT_PENDING && !(e.l & CONFIG_HELD) && context === null && n.fam !== null) r = e.ie;
1411
+ * projection's unheld staging is ahead for a handler (`familyAhead`), and is
1412
+ * the only value while its derive has never committed: the committed side
1413
+ * is the seed, never observable (A25) — a reader core serves the screen (a
1414
+ * verdict reader, #4355) is served the staging, as for a memo born staged
1415
+ * (core `serve`). */ function nodeValue(e, t, n) {
1416
+ let r;
1417
+ if (e.ue !== NOT_PENDING && !(e.l & CONFIG_HELD) && n.fam !== null && (context === null || n.fam.node?.m & STATUS_UNINITIALIZED)) {
1418
+ if (context !== null && tracking) link(e, context);
1419
+ r = e.ue;
1420
+ } else r = read(e);
1397
1421
  return r === FORCE ? t : r;
1398
1422
  }
1399
1423
 
@@ -1471,9 +1495,9 @@ function changedBetween(e, t, n, r, o) {
1471
1495
  // (committed plus the guesses) and does not suspend on the derive; a
1472
1496
  // verdict window still asks it (`isPending` is true while the flight is
1473
1497
  // up). The family's, not the target's: the edit stands in for the one
1474
- // flight the family has.
1475
- const r = e.fam.overlaid;
1476
- if (verdict === null && t.m & STATUS_PENDING && r !== undefined && r.size !== 0) return;
1498
+ // flight the family has — not over a new question they do not answer
1499
+ // (optimistic.ts `stands`, #4336).
1500
+ if (verdict === null && t.m & STATUS_PENDING && optHooks?.stands(e)) return;
1477
1501
  read(t);
1478
1502
  } else untrack(() => read(t));
1479
1503
  }
@@ -1486,7 +1510,7 @@ function changedBetween(e, t, n, r, o) {
1486
1510
  * classification, as core reads it — `heldNotFinal`). */ function holdWithDerive(e, t) {
1487
1511
  const n = e.fam?.node;
1488
1512
  if (n !== undefined && n !== null && !userWrite && // the derive's own write (its draft or landing), not a user setter's
1489
- n.l & CONFIG_HELD && t.ie !== NOT_PENDING &&
1513
+ n.l & CONFIG_HELD && t.ue !== NOT_PENDING &&
1490
1514
  // A node carrying a guess is the lane's: its staging is the truth beneath
1491
1515
  // the guess (`setSignal` → `laneWrite`), never re-homed by the store.
1492
1516
  !(t.l & (CONFIG_HELD | CONFIG_OVERRIDE))) {
@@ -1655,7 +1679,8 @@ const traps = {
1655
1679
  // Reading a currently-absent own key subscribes to it (R12) — for any
1656
1680
  // target OUTSIDE its own draft scope, even mid-setter (#3037, above).
1657
1681
  if (u === undefined && !inDraft(e)) {
1658
- if (getObserver() !== null) read(getNode(e, t, i));
1682
+ // (`nodeValue` reads — and links — the node it creates.)
1683
+ if (getObserver() !== null) getNode(e, t, i);
1659
1684
  const n = e.n?.[t];
1660
1685
  if (n) {
1661
1686
  const r = nodeValue(n, undefined, e);
@@ -1834,8 +1859,8 @@ const traps = {
1834
1859
  * to the key's presence node or a later add never re-runs it. */ function observerHoldsContainer(e, t) {
1835
1860
  const n = e.k;
1836
1861
  if (n === null) return false;
1837
- const r = n.Ct;
1838
- return r !== null && r.u === t && (!(t.R & REACTIVE_RECOMPUTING_DEPS) || r.Ie === t.Ne);
1862
+ const r = n.Ot;
1863
+ return r !== null && r.u === t && (!(t.R & REACTIVE_RECOMPUTING_DEPS) || r.de === t.Ne);
1839
1864
  }
1840
1865
 
1841
1866
  function storeSetter(e, t, n = true) {
@@ -1914,6 +1939,17 @@ function createStore(e, t) {
1914
1939
  return [ r, setter ];
1915
1940
  }
1916
1941
 
1942
+ /** The list driver's slot view of a shallow store (types.ts `shallowView`):
1943
+ * one `readSource` for the container instead of a trap per slot. A shallow
1944
+ * store's slots hold raw records, so the backing the container read serves
1945
+ * is what each slot read would — except where a slot's own leaf can answer
1946
+ * differently: an optimistic family's guesses, a chained backing, accessor
1947
+ * keys, or a read inside a draft or write scope. Those keep the trap. */ installShallowView(e => {
1948
+ // Any open write scope keeps the trap (it covers `inDraft`).
1949
+ if (e?.s && !(e.ch || e.a || writeScopes || writeOverride || e.fam?.opt)) return e.fam && pullFamily(e),
1950
+ readSource(e);
1951
+ });
1952
+
1917
1953
  /** True when `proxy` is a SHALLOW store (children served verbatim, slots
1918
1954
  * replaced by reference — #2932). The list driver uses this to choose the
1919
1955
  * slot-patch channel (collected row bodies) over per-record registration. */ function storeIsShallow(e) {
@@ -70,4 +70,14 @@ function setWriteOverride(e) {
70
70
  writeOverride = e;
71
71
  }
72
72
 
73
- export { $PROXY, $RECORD, $TARGET, $TRACK, isRawValue, isWrappable, markRaw, markRawIngest, markRawOne, ownEnumerableKeys, rawValuesUsed, setWriteOverride, writeOverride };
73
+ /** The list driver's view of a shallow store (map.ts): the backing a read of
74
+ * every slot serves this reader, or `undefined` when the list is no shallow
75
+ * store or a slot read could differ from it. Installed by store.ts's top
76
+ * level, so a `<For>` in an app without stores carries none of it; it lives
77
+ * beside `$TRACK` so map.ts reaches no other store module. */ let shallowView = null;
78
+
79
+ function installShallowView(e) {
80
+ shallowView = e;
81
+ }
82
+
83
+ export { $PROXY, $RECORD, $TARGET, $TRACK, installShallowView, isRawValue, isWrappable, markRaw, markRawIngest, markRawOne, ownEnumerableKeys, rawValuesUsed, setWriteOverride, shallowView, writeOverride };
@@ -925,7 +925,7 @@ const omitTraps = {
925
925
  // CONFIG_KEY_BAG); prod passes no options.
926
926
  n[r] = createMemo(o, {
927
927
  name: "merge",
928
- St: CONFIG_KEY_BAG
928
+ ct: CONFIG_KEY_BAG
929
929
  });
930
930
  t[r++] = SOURCE_MEMO;
931
931
  continue;
@@ -16,7 +16,8 @@ import { $TARGET } from "./store/types.js";
16
16
  // A mark is a declared in-flight change: the marked node — and everything
17
17
  // derived from it — reads pending (`isPending` → true) from the declaration
18
18
  // until the surrounding transaction lands (an ambient mark, declared outside
19
- // one, releases at the next seam). The marked values stay readable: no reader
19
+ // one, releases at the next seam; either, on a node whose own flight is
20
+ // still up then, at the seam after that flight lands). The marked values stay readable: no reader
20
21
  // suspends on a mark, a mark holds nothing and entangles nothing — it is not
21
22
  // a node of its transaction (`blocked` never sees it), and a reader of marked
22
23
  // data joins nothing through it. A mark is never a re-ask, so a declared
@@ -71,7 +72,16 @@ function affects(e, t) {
71
72
  schedule();
72
73
  }
73
74
 
74
- installMarkHooks(release, e => {
75
+ installMarkHooks(e => {
76
+ // A landing's mark on a node whose own flight is up (a declared reload's
77
+ // quiet re-ask the action did not wait for) stays to the seam after
78
+ // that flight lands, as an ambient one does: the reload's window is the
79
+ // flight's (#4349). The rest release with the transaction.
80
+ const t = [];
81
+ for (let r = 0; r < e.length; r++) (inFlight(e[r]) ? ambient : t).push(e[r]);
82
+ e.length = 0;
83
+ release(t);
84
+ }, e => {
75
85
  if (ambient.length === 0) return;
76
86
  // The flush parked: the declaration's window is the frame's — the marks
77
87
  // are its transaction's ("nothing async below ⇒ no window").