@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
@@ -12,6 +12,8 @@ import { getOwner, isDisposed } from "../core/owner.js";
12
12
 
13
13
  import { question, setQuestion, insertSubs, schedule } from "../core/scheduler.js";
14
14
 
15
+ import { optHooks } from "./hooks.js";
16
+
15
17
  import { reconcileState } from "./reconcile.js";
16
18
 
17
19
  import { wrap, storeSetter, runDirect } from "./store.js";
@@ -50,22 +52,22 @@ import { $TARGET, markRawIngest, isWrappable, setWriteOverride } from "./types.j
50
52
  * set/getOwnPropertyDescriptor/defineProperty. With the store proxy as
51
53
  * target those checks re-enter the store's traps OUTSIDE the override
52
54
  * bracket, so `Object.keys(state)` in a derive continuation fired the gate.
53
- */ function wrapDraft(e, t, r, n, o, i) {
55
+ */ function wrapDraft(e, t, r, o, n, i) {
54
56
  const mutate = e => {
55
57
  if (!t()) return true;
56
58
  setWriteOverride(true);
57
59
  // A continuation's write is the flight's answer: it asks the flight's
58
60
  // question (A18 provenance — a landing beneath a newer guess is held,
59
61
  // not a correction; lanes.ts `laneWrite`).
60
- const n = question;
62
+ const o = question;
61
63
  if (i !== undefined) setQuestion(i());
62
64
  try {
63
65
  r ? r(e) : e();
64
66
  } finally {
65
67
  setWriteOverride(false);
66
- setQuestion(n);
68
+ setQuestion(o);
67
69
  }
68
- if (o) o();
70
+ if (n) n();
69
71
  return true;
70
72
  };
71
73
  const read = e => {
@@ -79,23 +81,23 @@ import { $TARGET, markRawIngest, isWrappable, setWriteOverride } from "./types.j
79
81
  const s = {
80
82
  get(s, c) {
81
83
  const u = read(() => e[c]);
82
- return !n && typeof u === "object" && u !== null && c !== $TARGET ? wrapDraft(u, t, r, false, o, i) : u;
84
+ return !o && typeof u === "object" && u !== null && c !== $TARGET ? wrapDraft(u, t, r, false, n, i) : u;
83
85
  },
84
86
  has: (t, r) => read(() => r in e),
85
- set: (t, r, n) => mutate(() => {
86
- e[r] = n;
87
+ set: (t, r, o) => mutate(() => {
88
+ e[r] = o;
87
89
  }),
88
90
  deleteProperty: (t, r) => mutate(() => {
89
91
  delete e[r];
90
92
  }),
91
93
  ownKeys: () => read(() => Reflect.ownKeys(e)),
92
94
  getOwnPropertyDescriptor(t, r) {
93
- const n = read(() => Reflect.getOwnPropertyDescriptor(e, r));
94
- if (n) n.configurable = true;
95
- return n;
95
+ const o = read(() => Reflect.getOwnPropertyDescriptor(e, r));
96
+ if (o) o.configurable = true;
97
+ return o;
96
98
  },
97
- defineProperty: (t, r, n) => mutate(() => {
98
- Reflect.defineProperty(e, r, n);
99
+ defineProperty: (t, r, o) => mutate(() => {
100
+ Reflect.defineProperty(e, r, o);
99
101
  })
100
102
  };
101
103
  const c = new Proxy(Array.isArray(e) ? [] : {}, s);
@@ -127,6 +129,7 @@ function cloneState(e, t) {
127
129
  // reader the landing leaves unchanged does not re-run to clear it (#4305).
128
130
  const r = new NotReadyError(t);
129
131
  e.woke = true;
132
+ optHooks?.ask(e);
130
133
  forEachFamilyNode(e, e => {
131
134
  if (e.o === null) return;
132
135
  // The leaf carries the derive's pending as a propagated copy, as a memo
@@ -162,6 +165,10 @@ function cloneState(e, t) {
162
165
  forEachFamilyNode(e, e => {
163
166
  if (e.o !== null) settlePendingSource(e, t);
164
167
  });
168
+ // The landing is the derive's change for a reader parked on the derive
169
+ // itself, whatever status the landing pass began from (an errored derive
170
+ // re-asked: a reader that met the error has no leaf to learn it from).
171
+ insertSubs(t);
165
172
  }
166
173
 
167
174
  /** A source's status reached the derive by propagation (core
@@ -174,8 +181,8 @@ function forEachFamilyNode(e, t) {
174
181
  for (const r of e.live) {
175
182
  const e = r.n;
176
183
  if (e !== null) for (const r of Reflect.ownKeys(e)) t(e[r]);
177
- const n = r.h;
178
- if (n !== null) for (const e of Reflect.ownKeys(n)) t(n[e]);
184
+ const o = r.h;
185
+ if (o !== null) for (const e of Reflect.ownKeys(o)) t(o[e]);
179
186
  if (r.k !== null) t(r.k);
180
187
  if (r.dk !== null) t(r.dk);
181
188
  }
@@ -185,7 +192,7 @@ function forEachFamilyNode(e, t) {
185
192
  * result (sync, or each async landing) through the reconcile channel.
186
193
  * Draft validity is per run (R37): live from here until the NEXT run starts
187
194
  * — a sync derive that subscribes to an external source and pushes into
188
- * the draft from the callback (#3585) is the model use. */ function runProjectionComputed(e, t, r, n, o) {
195
+ * the draft from the callback (#3585) is the model use. */ function runProjectionComputed(e, t, r, o, n) {
189
196
  const i = getOwner();
190
197
  const s = e[$TARGET];
191
198
  const c = s.fam;
@@ -199,8 +206,8 @@ function forEachFamilyNode(e, t) {
199
206
  // The question a write of this run answers: inside a slice, the slice's
200
207
  // (the sync pass); in a continuation — the flight still registered — the
201
208
  // flight's (`handleAsync` stamped it with the action that asked).
202
- const asking = () => question || (i.T !== null && i.T.Ee !== null ? i.T.Te : 0);
203
- const f = wrapDraft(e, () => c.run === u && !isDisposed(i), o, s.s,
209
+ const asking = () => question || (i.T !== null && i.T.Ee !== null ? i.T.Ie : 0);
210
+ const f = wrapDraft(e, () => c.run === u && !isDisposed(i), n, s.s,
204
211
  // A write after the run returned (a continuation, a callback) arms the
205
212
  // drain itself when no landing will.
206
213
  () => {
@@ -210,24 +217,24 @@ function forEachFamilyNode(e, t) {
210
217
  // `_value`); every later run — a re-derive in a flush, an async landing
211
218
  // — stages and commits with the flush like a memo's recompute.
212
219
  const d = u === 1;
213
- const pass = () => storeSetter(f, o => {
214
- l = t(a ?? o);
215
- const commit = t => {
220
+ const pass = () => storeSetter(f, n => {
221
+ l = t(a ?? n);
222
+ const commit = (t, u = 1) => {
216
223
  // Shadow run: commit a detached snapshot, never the shadow itself
217
224
  // (adoption takes the value by identity — handing it the live
218
225
  // shadow would fuse the draft to the observable store).
219
226
  if (a && (t === undefined || t === a)) t = cloneState(a, s.s);
220
- if (t !== o && t !== undefined) {
227
+ if (t !== n && t !== undefined) {
221
228
  const write = () => {
222
- const n = question;
229
+ const o = question;
223
230
  setQuestion(asking());
224
231
  try {
225
232
  storeSetter(e, e => reconcileState(t, e, r, true), false);
226
233
  } finally {
227
- setQuestion(n);
234
+ setQuestion(o);
228
235
  }
229
236
  };
230
- n ? n(write, t) : write();
237
+ o ? o(write, t) : write();
231
238
  }
232
239
  // A landing (not the sync pass's own commit): the readers held
233
240
  // pending on the derive settle.
@@ -235,8 +242,11 @@ function forEachFamilyNode(e, t) {
235
242
  // A commit is the derive's change for the readers that observed its
236
243
  // flight (linked by `pullFamily`; a settled pull does not link), as
237
244
  // a memo's landing is for its readers: one that caught the NotReady
238
- // (a fallback value, not pending) re-runs too (#4070).
239
- insertSubs(i);
245
+ // (a fallback value, not pending) re-runs too (#4070). A pass of a
246
+ // settled derive has none (`wake` 0): a link left is a stale one a
247
+ // held frame keeps (A30), from a reader that re-ran since. Woken by
248
+ // every pass, a reader feeding a key the pass peeks at looped (#4342).
249
+ if (u) insertSubs(i);
240
250
  };
241
251
  const u = handleAsync(i, l, commit, (e, t) => {
242
252
  // The flight rejected: the leaves' readers learn it from here —
@@ -248,7 +258,8 @@ function forEachFamilyNode(e, t) {
248
258
  if (d && i.m & STATUS_UNINITIALIZED) return;
249
259
  if (t) wakeFamily(c); else errorFamily(c);
250
260
  });
251
- if (!i.ce) commit(u);
261
+ // (The status is the one before this pass: `recompute` sets it after.)
262
+ if (!i.ce) commit(u, i.m & (STATUS_PENDING | STATUS_ERROR));
252
263
  }, false);
253
264
  try {
254
265
  d ? runDirect(pass) : pass();
@@ -273,8 +284,8 @@ function createProjectionInternal(e, t, r) {
273
284
  // Seedless (`fn` takes no draft): the root starts as an empty record. It is
274
285
  // a draft like any seed, never observable before the first landing (A25).
275
286
  t ||= {};
276
- const n = r?.key === undefined ? "id" : r.key;
277
- const o = {
287
+ const o = r?.key === undefined ? "id" : r.key;
288
+ const n = {
278
289
  map: new WeakMap,
279
290
  node: null,
280
291
  live: new Set,
@@ -282,10 +293,10 @@ function createProjectionInternal(e, t, r) {
282
293
  shallow: !!r?.shallow,
283
294
  // The row key, for a child paired after the adoption (store.ts
284
295
  // `adoptChild`, #3712) as the adoption pairs them.
285
- key: typeof n === "function" ? n : n === null ? null : e => isWrappable(e) ? e[n] : undefined
296
+ key: typeof o === "function" ? o : o === null ? null : e => isWrappable(e) ? e[o] : undefined
286
297
  };
287
- const i = wrap(t, null, null, o);
288
- if (o.shallow) {
298
+ const i = wrap(t, null, null, n);
299
+ if (n.shallow) {
289
300
  i[$TARGET].s = true;
290
301
  markRawIngest(t);
291
302
  }
@@ -294,14 +305,14 @@ function createProjectionInternal(e, t, r) {
294
305
  loadingValue: undefined
295
306
  };
296
307
  const c = computed(() => {
297
- if (!o.node) o.node = getOwner();
298
- runProjectionComputed(i, e, n);
308
+ if (!n.node) n.node = getOwner();
309
+ runProjectionComputed(i, e, o);
299
310
  }, s);
300
311
  c.l &= ~CONFIG_AUTO_DISPOSE;
301
- o.node = c;
312
+ n.node = c;
302
313
  // The derive's leaves are not its dependents: a mark's repoll
303
314
  // (affects-marks.ts) reaches their readers through this.
304
- c.fam = o;
315
+ c.fam = n;
305
316
  return {
306
317
  store: i,
307
318
  node: c
@@ -313,8 +324,8 @@ function createProjection(e, t, r) {
313
324
  }
314
325
 
315
326
  /** `createStore(fn, seed, options)`: a projection with a user setter. */ function createStoreDerived(e, t, r) {
316
- const {store: n} = createProjectionInternal(e, t, r);
317
- return [ n, e => storeSetter(n, e) ];
327
+ const {store: o} = createProjectionInternal(e, t, r);
328
+ return [ o, e => storeSetter(o, e) ];
318
329
  }
319
330
 
320
331
  export { createProjection, createStoreDerived, runProjectionComputed };
@@ -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,10 +1,10 @@
1
- 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";
1
+ 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";
2
2
 
3
- import { writeHook, setSignal, isEqual, read, verdict, snapshotCaptureActive, captureWriteSnapshot, context, notePromotedWrite, untrack, slotSignal, stagedScreen, stagedRead } from "../core/core.js";
3
+ import { writeHook, setSignal, isEqual, read, verdict, snapshotCaptureActive, captureWriteSnapshot, context, notePromotedWrite, untrack, slotSignal, stagedScreen, stagedRead, tracking } from "../core/core.js";
4
4
 
5
5
  import { signalHook } from "../core/identities.js";
6
6
 
7
- import { setSlotUnobserved } from "../core/graph.js";
7
+ import { link, setSlotUnobserved } from "../core/graph.js";
8
8
 
9
9
  import { getObserver } from "../core/owner.js";
10
10
 
@@ -16,7 +16,7 @@ import { lookupTarget, setLookup, $OWNER, isOwned, markDescendants } from "./tar
16
16
 
17
17
  import { optHooks, affectsHooks } from "./hooks.js";
18
18
 
19
- import { $TARGET, rawValuesUsed, isRawValue, isWrappable, markRawIngest, writeOverride, markRawOne, $RECORD, $PROXY, $TRACK } from "./types.js";
19
+ import { $TARGET, rawValuesUsed, isRawValue, isWrappable, markRawIngest, writeOverride, markRawOne, $RECORD, $PROXY, $TRACK, installShallowView } from "./types.js";
20
20
 
21
21
  export { markRaw } from "./types.js";
22
22
 
@@ -220,7 +220,7 @@ function sameLogicalSlot(e, n, t) {
220
220
 
221
221
  function releaseSlot(e) {
222
222
  const n = e.O;
223
- const t = e.ze;
223
+ const t = e.Xe;
224
224
  if (t === $TRACK) {
225
225
  if (n.k === e) n.k = null;
226
226
  } else if (t === $DEEP) {
@@ -236,7 +236,7 @@ function releaseSlot(e) {
236
236
  /** A node was born on `target`: a family target enters the live index. */ function noteNode(e, n, t) {
237
237
  if (e.fam !== null) e.fam.live.add(e);
238
238
  markDescendants(e);
239
- if (affectsHooks !== null) affectsHooks.fo(e, n, t);
239
+ if (affectsHooks !== null) affectsHooks.ro(e, n, t);
240
240
  }
241
241
 
242
242
  // Shared slot-node release handler: registered once; the core sweep
@@ -247,7 +247,7 @@ function releaseSlot(e) {
247
247
  // holds: its release waits for the commit that resolves it. So does a leaf of
248
248
  // a held container (#4146): a mainline write it carried is in the held
249
249
  // staging too, where it would read as the hold's until the landing.
250
- const slotKept = e => e.ie !== NOT_PENDING || e.l & CONFIG_OVERRIDE || e.O.k?.l & CONFIG_HELD;
250
+ const slotKept = e => e.ue !== NOT_PENDING || e.l & CONFIG_OVERRIDE || e.O.k?.l & CONFIG_HELD;
251
251
 
252
252
  setSlotUnobserved(e => {
253
253
  if (slotKept(e)) deferredReleases.add(e); else releaseSlot(e);
@@ -258,9 +258,9 @@ setSlotUnobserved(e => {
258
258
  * pending one — and held by the container's transaction when the container
259
259
  * is held (#3706: the unit of the hold is the key — a key the batch left
260
260
  * unchanged stages nothing and holds no one). */ function bornStaged(e, n, t, r) {
261
- if (n.xe && n.xe(n.le, t)) return;
261
+ if (n.ye && n.ye(n.le, t)) return;
262
262
  queuePendingNode(n);
263
- n.ie = t;
263
+ n.ue = t;
264
264
  const o = e.k;
265
265
  if (o === null) return;
266
266
  // a node-less staging holds nothing (`stageOn`)
@@ -326,7 +326,7 @@ function getHasNode(e, n) {
326
326
  // would have read had the node been there from the write.
327
327
  if (e.pb !== null && !directCommit) {
328
328
  n.le = preBatch(e);
329
- n.ie = e.pb;
329
+ n.ue = e.pb;
330
330
  queuePendingNode(n);
331
331
  }
332
332
  }
@@ -343,10 +343,10 @@ function getHasNode(e, n) {
343
343
  * re-created, queued, swept and dropped ~7000 container nodes). If the
344
344
  * flush parks, the seam materializes and holds the node then
345
345
  * (`storeParkHook`) — the one case the staging home exists for. */ function stageOn(e, n) {
346
- if (e.k === null && passLane === null && (context === null || globalQueue.q) && !deriveHolds(e)) return;
346
+ if (e.k === null && passLane === null && (context === null || globalQueue.Y) && !deriveHolds(e)) return;
347
347
  const t = getContainerNode(e);
348
- if (t.ie === NOT_PENDING) queuePendingNode(t);
349
- t.ie = n;
348
+ if (t.ue === NOT_PENDING) queuePendingNode(t);
349
+ t.ue = n;
350
350
  // A28 (4): a write issued inside a pass outside a flush (a creation-time
351
351
  // derive) is promoted at the pass's end — the batch with it.
352
352
  if (context !== null) notePromotedWrite(t);
@@ -364,6 +364,7 @@ const storePark = e => {
364
364
  const t = foldList[n];
365
365
  if (t.k === null && t.pb !== null) holdNode(getContainerNode(t), e);
366
366
  }
367
+ optHooks?.park(e);
367
368
  };
368
369
 
369
370
  function getDeepNode(e) {
@@ -419,16 +420,23 @@ function isOwnAccessor(e, n) {
419
420
  t[$OWNER] = n;
420
421
  return t;
421
422
  }
422
- const t = Object.getOwnPropertyDescriptors(e);
423
- for (const r of Reflect.ownKeys(t)) {
424
- const o = t[r];
425
- if (r === "length" && Array.isArray(e)) continue;
426
- o.configurable = true;
427
- if (!o.get && !o.set) o.writable = true; else n.a = true;
423
+ // A plain array with no accessors and no named keys beyond `length`
424
+ // (`kc` counts own keys) clones by `slice()`: the descriptor path
425
+ // allocates one descriptor per element, O(n) per first draft write (10k
426
+ // rows: ~10 ms per push).
427
+ let t;
428
+ if (!n.a && n.kc <= e.length + 1 && Object.getPrototypeOf(e) === Array.prototype) t = e.slice(); else {
429
+ const r = Object.getOwnPropertyDescriptors(e);
430
+ for (const t of Reflect.ownKeys(r)) {
431
+ const o = r[t];
432
+ if (t === "length" && Array.isArray(e)) continue;
433
+ o.configurable = true;
434
+ if (!o.get && !o.set) o.writable = true; else n.a = true;
435
+ }
436
+ t = Array.isArray(e) ? Object.defineProperties([], r) : Object.create(Object.getPrototypeOf(e), r);
428
437
  }
429
- const r = Array.isArray(e) ? Object.defineProperties([], t) : Object.create(Object.getPrototypeOf(e), t);
430
- r[$OWNER] = n;
431
- return r;
438
+ t[$OWNER] = n;
439
+ return t;
432
440
  }
433
441
 
434
442
  /** Wide plain clone (many keys): null-prototype build, then reset. */ function wideClone(e, n) {
@@ -494,7 +502,7 @@ const plainProto = e => {
494
502
  const t = overlayOnto(e, e.sc === 2 ? wideClone(e.v, e) : cloneRaw(e.v, e), n);
495
503
  e.pb = t;
496
504
  // The staging IS the pending backing: re-point the container node.
497
- if (e.k?.ie === n) e.k.ie = t;
505
+ if (e.k?.ue === n) e.k.ue = t;
498
506
  return t;
499
507
  }
500
508
 
@@ -515,7 +523,7 @@ const plainProto = e => {
515
523
  // mainline setter mid-hold publishes mainline; the held keys stay held).
516
524
  n = e.pb = cloneRaw(n, e);
517
525
  const t = e.k;
518
- if (t !== null && !(t.l & CONFIG_HELD)) t.ie = n;
526
+ if (t !== null && !(t.l & CONFIG_HELD)) t.ue = n;
519
527
  } else if (n === null) {
520
528
  const t = e.v;
521
529
  if (!directCommit) queueFold(e);
@@ -692,7 +700,7 @@ let foldIndex = null;
692
700
  if (r.pb === null) continue;
693
701
  // folded already (a direct commit)
694
702
  const o = r.k;
695
- if (o !== null && o.ie !== NOT_PENDING) {
703
+ if (o !== null && o.ue !== NOT_PENDING) {
696
704
  // held: commits with its hold
697
705
  foldList.push(r);
698
706
  foldOlds.push(n[t]);
@@ -722,10 +730,10 @@ let foldIndex = null;
722
730
  /** Core asks (lanes.ts `covered`): the value a slot node's guess covered.
723
731
  * A chained link's committed value is the inner store's at the last
724
732
  * read-through — refreshed here, since a guess served the reads since
725
- * (#3672: the revert compares against the base's live value). */ GlobalQueue.fn = e => {
733
+ * (#3672: the revert compares against the base's live value). */ GlobalQueue.an = e => {
726
734
  const n = e.O;
727
735
  if (n.ch) {
728
- const t = e.ze;
736
+ const t = e.Xe;
729
737
  const r = n.v;
730
738
  if (n.h?.[t] === e) e.le = untrack(() => t in r); else if (n.n?.[t] === e) e.le = untrack(() => unwrapValue(r[t]));
731
739
  }
@@ -738,9 +746,9 @@ installStoreHooks(storePark, () => {
738
746
  });
739
747
 
740
748
  /** The store half of `affects()` (store/affects.ts) makes its marks' nodes
741
- * and walks records through these: it never imports the engine. */ GlobalQueue.no = getNode;
749
+ * and walks records through these: it never imports the engine. */ GlobalQueue.so = getNode;
742
750
 
743
- GlobalQueue.so = isWrappable;
751
+ GlobalQueue.fo = isWrappable;
744
752
 
745
753
  /** A projection's creation run commits directly (a memo's first value is
746
754
  * its `_value`, not a staging): the draft still writes a clone of the seed,
@@ -840,13 +848,18 @@ function heldDerivation(e, n, t) {
840
848
  // Optimistic channel: a user's writes on an optimistic family become
841
849
  // guesses on the nodes (lanes); the draft is discarded, the committed
842
850
  // backing never touched.
843
- if (optHooks && userWrite && e.fam?.opt === true) {
844
- // The draft is discarded first: the diff reads the target (its nodes
845
- // are born from the committed frame, not the draft).
846
- e.pb = null;
847
- e.wk = null;
848
- e.pb = optHooks.writes(e, n);
849
- return;
851
+ if (optHooks && e.fam?.opt === true) {
852
+ if (userWrite) {
853
+ // The draft is discarded first: the diff reads the target (its nodes
854
+ // are born from the committed frame, not the draft).
855
+ e.pb = null;
856
+ e.wk = null;
857
+ e.pb = optHooks.writes(e, n);
858
+ return;
859
+ }
860
+ // The derive's draft: new rows under guessed rows are paired with them
861
+ // first (D-34, #4329).
862
+ optHooks.pair(e, n);
850
863
  }
851
864
  const t = e.v;
852
865
  const r = e.n;
@@ -990,7 +1003,10 @@ function membershipChanged(e, n) {
990
1003
  } else {
991
1004
  const o = t[n];
992
1005
  const i = r[n];
993
- if (!isEqual(o, i) && !targetsEqual(o, i)) setSignal(e, typeof i === "function" ? () => i : i);
1006
+ // A live chained `old` (a store proxy, §7b) already reads the inner
1007
+ // store's next frame, not what the node was last told: written, the
1008
+ // node's own equality judging it (#4361).
1009
+ if (t[$TARGET] || !isEqual(o, i) && !targetsEqual(o, i)) setSignal(e, typeof i === "function" ? () => i : i);
994
1010
  }
995
1011
  }
996
1012
 
@@ -1016,7 +1032,8 @@ function hasAccessorFlag(e) {
1016
1032
  * unrelated `a.value` stayed stale until the action settled). A real
1017
1033
  * change still writes, and on a held node still proposes. A live chained
1018
1034
  * `old` (a store proxy, §7b) reflects the inner store, not what the node
1019
- * was last told — written unconditionally, as before.
1035
+ * was last told — written unconditionally, as before; its container too
1036
+ * (#4361).
1020
1037
  *
1021
1038
  * The container is told of an arrangement change when it has structural
1022
1039
  * subscribers OR carries an arrangement guess (S4): for a guessed
@@ -1027,14 +1044,14 @@ function hasAccessorFlag(e) {
1027
1044
  * answers the arrangement — before, only the presence write reached the
1028
1045
  * lane, by accident of being unconditional. */ function notifyFoldTail(e, n, t) {
1029
1046
  const r = e.h;
1047
+ const o = n[$TARGET];
1030
1048
  if (r !== null) {
1031
- const e = n[$TARGET];
1032
- for (const o of Reflect.ownKeys(r)) if (e || o in n !== o in t) setSignal(r[o], o in t);
1049
+ for (const e of Reflect.ownKeys(r)) if (o || e in n !== e in t) setSignal(r[e], e in t);
1033
1050
  }
1034
- const o = e.k;
1035
- if (o !== null && (o.o !== null || o.l & CONFIG_OVERRIDE)) {
1036
- const e = Array.isArray(t) && Array.isArray(n) ? arrayStructureChanged(n, t) : membershipChanged(n, t);
1037
- if (e) notifyContainer(o, t);
1051
+ const i = e.k;
1052
+ if (i !== null && (i.o !== null || i.l & CONFIG_OVERRIDE)) {
1053
+ const e = o || (Array.isArray(t) && Array.isArray(n) ? arrayStructureChanged(n, t) : membershipChanged(n, t));
1054
+ if (e) notifyContainer(i, t);
1038
1055
  }
1039
1056
  }
1040
1057
 
@@ -1089,7 +1106,7 @@ function inDraft(e) {
1089
1106
  * "adoption is eager by contract" — and the node keeps the frame committed
1090
1107
  * readers under a hold still see), `t.v` otherwise. */ function committed(e) {
1091
1108
  const n = e.k;
1092
- if (n !== null) return n.ie !== NOT_PENDING ? asBacking(n.le) : e.v;
1109
+ if (n !== null) return n.ue !== NOT_PENDING ? asBacking(n.le) : e.v;
1093
1110
  // A node-less staging (`stageOn`): the fold queue remembers the frame.
1094
1111
  return e.pb !== null ? preBatch(e) : e.v;
1095
1112
  }
@@ -1101,7 +1118,7 @@ function inDraft(e) {
1101
1118
  * committed frame. */ function stagingReader() {
1102
1119
  let e = context;
1103
1120
  if (e === null) return null;
1104
- if (e.Qe) e = e.Ye ?? null;
1121
+ if (e.Qe) e = e.Ke ?? null;
1105
1122
  return e === null || e.l & CONFIG_CHILDREN_FORBIDDEN ? null : e;
1106
1123
  }
1107
1124
 
@@ -1208,7 +1225,7 @@ function inDraft(e) {
1208
1225
 
1209
1226
  /** Did the HELD staging (the container node's, not a mainline layer above
1210
1227
  * it) change `key`? */ function heldKeyChanged(e, n, t = true) {
1211
- return changedBetween(e, e.k.le, e.k.ie, n, t) && !adoptChild(e, n);
1228
+ return changedBetween(e, e.k.le, e.k.ue, n, t) && !adoptChild(e, n);
1212
1229
  }
1213
1230
 
1214
1231
  /** The values of a held frame, built once per frame: neither the committed
@@ -1237,12 +1254,12 @@ function rawsOf(e) {
1237
1254
  const t = e.k;
1238
1255
  const r = asBacking(t.le);
1239
1256
  const o = r[n];
1240
- const i = t.ie[n];
1257
+ const i = t.ue[n];
1241
1258
  const l = Array.isArray(r) ? e.fam?.key : null;
1242
1259
  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 ||
1243
1260
  // A raw in both frames (the same objects swapped or reordered) is its
1244
1261
  // own row: pairing it with another would alias the two after the commit.
1245
- rawsOf(r).has(i) || rawsOf(t.ie).has(o)) return false;
1262
+ rawsOf(r).has(i) || rawsOf(t.ue).has(o)) return false;
1246
1263
  // A raw-marked value wraps to itself, a proxy to its own target: neither
1247
1264
  // is a fresh target around `ov`.
1248
1265
  const u = wrap(o, e, n)[$TARGET];
@@ -1285,9 +1302,16 @@ function changedBetween(e, n, t, r, o) {
1285
1302
 
1286
1303
  /** Value of a key with a leaf: core's — `read()` links a tracked reader and,
1287
1304
  * tracked or not, applies the hold and A28 rules a signal's read does. A
1288
- * projection's unheld staging is ahead for a handler (`familyAhead`). */ function nodeValue(e, n, t) {
1289
- let r = read(e);
1290
- if (e.ie !== NOT_PENDING && !(e.l & CONFIG_HELD) && context === null && t.fam !== null) r = e.ie;
1305
+ * projection's unheld staging is ahead for a handler (`familyAhead`), and is
1306
+ * the only value while its derive has never committed: the committed side
1307
+ * is the seed, never observable (A25) — a reader core serves the screen (a
1308
+ * verdict reader, #4355) is served the staging, as for a memo born staged
1309
+ * (core `serve`). */ function nodeValue(e, n, t) {
1310
+ let r;
1311
+ if (e.ue !== NOT_PENDING && !(e.l & CONFIG_HELD) && t.fam !== null && (context === null || t.fam.node?.m & STATUS_UNINITIALIZED)) {
1312
+ if (context !== null && tracking) link(e, context);
1313
+ r = e.ue;
1314
+ } else r = read(e);
1291
1315
  return r === FORCE ? n : r;
1292
1316
  }
1293
1317
 
@@ -1365,9 +1389,9 @@ function changedBetween(e, n, t, r, o) {
1365
1389
  // (committed plus the guesses) and does not suspend on the derive; a
1366
1390
  // verdict window still asks it (`isPending` is true while the flight is
1367
1391
  // up). The family's, not the target's: the edit stands in for the one
1368
- // flight the family has.
1369
- const r = e.fam.overlaid;
1370
- if (verdict === null && n.m & STATUS_PENDING && r !== undefined && r.size !== 0) return;
1392
+ // flight the family has — not over a new question they do not answer
1393
+ // (optimistic.ts `stands`, #4336).
1394
+ if (verdict === null && n.m & STATUS_PENDING && optHooks?.stands(e)) return;
1371
1395
  read(n);
1372
1396
  } else untrack(() => read(n));
1373
1397
  }
@@ -1380,7 +1404,7 @@ function changedBetween(e, n, t, r, o) {
1380
1404
  * classification, as core reads it — `heldNotFinal`). */ function holdWithDerive(e, n) {
1381
1405
  const t = e.fam?.node;
1382
1406
  if (t !== undefined && t !== null && !userWrite && // the derive's own write (its draft or landing), not a user setter's
1383
- t.l & CONFIG_HELD && n.ie !== NOT_PENDING &&
1407
+ t.l & CONFIG_HELD && n.ue !== NOT_PENDING &&
1384
1408
  // A node carrying a guess is the lane's: its staging is the truth beneath
1385
1409
  // the guess (`setSignal` → `laneWrite`), never re-homed by the store.
1386
1410
  !(n.l & (CONFIG_HELD | CONFIG_OVERRIDE))) {
@@ -1472,7 +1496,7 @@ const traps = {
1472
1496
  }
1473
1497
  // (The witness before the pull: a mark on an uninitialized derived
1474
1498
  // store is witnessed, then the pull throws — loading, declared pending.)
1475
- if (verdict !== null && affectsHooks !== null && getObserver() === null) affectsHooks.ro(e, n);
1499
+ if (verdict !== null && affectsHooks !== null && getObserver() === null) affectsHooks.io(e, n);
1476
1500
  if (e.fam !== null) pullFamily(e);
1477
1501
  // Hot inline case: existing PLAIN node (non-accessor) read outside any
1478
1502
  // draft — the dbmon/uibench effect re-read shape. Core's `read()` serves
@@ -1549,7 +1573,8 @@ const traps = {
1549
1573
  // Reading a currently-absent own key subscribes to it (R12) — for any
1550
1574
  // target OUTSIDE its own draft scope, even mid-setter (#3037, above).
1551
1575
  if (u === undefined && !inDraft(e)) {
1552
- if (getObserver() !== null) read(getNode(e, n, i));
1576
+ // (`nodeValue` reads — and links — the node it creates.)
1577
+ if (getObserver() !== null) getNode(e, n, i);
1553
1578
  const t = e.n?.[n];
1554
1579
  if (t) {
1555
1580
  const r = nodeValue(t, undefined, e);
@@ -1569,7 +1594,7 @@ const traps = {
1569
1594
  if (n === $OWNER || n === $RECORD) return false;
1570
1595
  // (The witness before the pull: a mark on an uninitialized derived
1571
1596
  // store is witnessed, then the pull throws — loading, declared pending.)
1572
- if (verdict !== null && affectsHooks !== null && getObserver() === null) affectsHooks.ro(e, n);
1597
+ if (verdict !== null && affectsHooks !== null && getObserver() === null) affectsHooks.io(e, n);
1573
1598
  if (e.fam !== null) pullFamily(e);
1574
1599
  const t = readSource(e, n);
1575
1600
  // A tracked reader's presence node answers (born from the two frames;
@@ -1594,14 +1619,14 @@ const traps = {
1594
1619
  return n in t && !(t === e.pb && e.del?.has(n));
1595
1620
  },
1596
1621
  ownKeys(e) {
1597
- if (verdict !== null && affectsHooks !== null && getObserver() === null) affectsHooks.ro(e, undefined);
1622
+ if (verdict !== null && affectsHooks !== null && getObserver() === null) affectsHooks.io(e, undefined);
1598
1623
  if (e.fam !== null) pullFamily(e);
1599
1624
  return visibleKeys(e, enumerationSource(e));
1600
1625
  },
1601
1626
  getOwnPropertyDescriptor(e, n) {
1602
1627
  if (n === $OWNER || n === $RECORD) return undefined;
1603
1628
  const t = getObserver();
1604
- if (verdict !== null && affectsHooks !== null && t === null) affectsHooks.ro(e, n);
1629
+ if (verdict !== null && affectsHooks !== null && t === null) affectsHooks.io(e, n);
1605
1630
  if (e.fam !== null) pullFamily(e);
1606
1631
  // An enumerator (spread, Object.entries) already holds the container
1607
1632
  // node and reads its frame; a descriptor read on its own tracks
@@ -1728,8 +1753,8 @@ const traps = {
1728
1753
  * to the key's presence node or a later add never re-runs it. */ function observerHoldsContainer(e, n) {
1729
1754
  const t = e.k;
1730
1755
  if (t === null) return false;
1731
- const r = t.ve;
1732
- return r !== null && r.u === n && (!(n.R & REACTIVE_RECOMPUTING_DEPS) || r.de === n.Ne);
1756
+ const r = t.Ze;
1757
+ return r !== null && r.u === n && (!(n.R & REACTIVE_RECOMPUTING_DEPS) || r.Ne === n.de);
1733
1758
  }
1734
1759
 
1735
1760
  function storeSetter(e, n, t = true) {
@@ -1797,6 +1822,17 @@ function createStore(e, n) {
1797
1822
  return [ r, setter ];
1798
1823
  }
1799
1824
 
1825
+ /** The list driver's slot view of a shallow store (types.ts `shallowView`):
1826
+ * one `readSource` for the container instead of a trap per slot. A shallow
1827
+ * store's slots hold raw records, so the backing the container read serves
1828
+ * is what each slot read would — except where a slot's own leaf can answer
1829
+ * differently: an optimistic family's guesses, a chained backing, accessor
1830
+ * keys, or a read inside a draft or write scope. Those keep the trap. */ installShallowView(e => {
1831
+ // Any open write scope keeps the trap (it covers `inDraft`).
1832
+ if (e?.s && !(e.ch || e.a || writeScopes || writeOverride || e.fam?.opt)) return e.fam && pullFamily(e),
1833
+ readSource(e);
1834
+ });
1835
+
1800
1836
  /** True when `proxy` is a SHALLOW store (children served verbatim, slots
1801
1837
  * replaced by reference — #2932). The list driver uses this to choose the
1802
1838
  * slot-patch channel (collected row bodies) over per-record registration. */ function storeIsShallow(e) {