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

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 (66) hide show
  1. package/dist/dev-shared.js +248 -189
  2. package/dist/dev.attribution.js +124 -41
  3. package/dist/dev.js +553 -322
  4. package/dist/observe/affects.js +34 -31
  5. package/dist/observe/boundaries.js +166 -132
  6. package/dist/observe/core/async.js +90 -83
  7. package/dist/observe/core/attribution-queries.js +1 -1
  8. package/dist/observe/core/attribution.js +281 -197
  9. package/dist/observe/core/constants.js +7 -2
  10. package/dist/observe/core/context.js +3 -3
  11. package/dist/observe/core/core.js +277 -245
  12. package/dist/observe/core/dev.js +3 -3
  13. package/dist/observe/core/effect.js +21 -21
  14. package/dist/observe/core/external.js +2 -2
  15. package/dist/observe/core/graph.js +25 -25
  16. package/dist/observe/core/heap.js +9 -9
  17. package/dist/observe/core/lanes.js +206 -160
  18. package/dist/observe/core/owner.js +27 -27
  19. package/dist/observe/core/scheduler.js +92 -86
  20. package/dist/observe/core/verdict.js +66 -54
  21. package/dist/observe/map.js +35 -35
  22. package/dist/observe/reveal.js +24 -24
  23. package/dist/observe/signals.js +38 -28
  24. package/dist/observe/store/affects.js +75 -68
  25. package/dist/observe/store/hooks.js +13 -0
  26. package/dist/observe/store/index.js +0 -2
  27. package/dist/observe/store/optimistic.js +67 -34
  28. package/dist/observe/store/projection.js +37 -31
  29. package/dist/observe/store/reconcile.js +32 -30
  30. package/dist/observe/store/store.js +128 -85
  31. package/dist/prod/affects.js +34 -31
  32. package/dist/prod/boundaries.js +143 -109
  33. package/dist/prod/core/async.js +64 -67
  34. package/dist/prod/core/constants.js +7 -2
  35. package/dist/prod/core/context.js +3 -3
  36. package/dist/prod/core/core.js +302 -270
  37. package/dist/prod/core/effect.js +38 -38
  38. package/dist/prod/core/external.js +4 -4
  39. package/dist/prod/core/graph.js +27 -27
  40. package/dist/prod/core/heap.js +30 -30
  41. package/dist/prod/core/lanes.js +257 -211
  42. package/dist/prod/core/owner.js +39 -39
  43. package/dist/prod/core/scheduler.js +125 -119
  44. package/dist/prod/core/verdict.js +70 -58
  45. package/dist/prod/map.js +73 -73
  46. package/dist/prod/reveal.js +38 -38
  47. package/dist/prod/signals.js +30 -20
  48. package/dist/prod/store/affects.js +75 -68
  49. package/dist/prod/store/hooks.js +13 -0
  50. package/dist/prod/store/index.js +0 -2
  51. package/dist/prod/store/optimistic.js +114 -81
  52. package/dist/prod/store/projection.js +30 -24
  53. package/dist/prod/store/reconcile.js +32 -30
  54. package/dist/prod/store/store.js +136 -93
  55. package/dist/types/boundaries.d.ts +4 -0
  56. package/dist/types/core/attribution.d.ts +6 -2
  57. package/dist/types/core/constants.d.ts +5 -0
  58. package/dist/types/core/core.d.ts +33 -3
  59. package/dist/types/core/lanes.d.ts +10 -0
  60. package/dist/types/core/scheduler.d.ts +12 -6
  61. package/dist/types/core/types.d.ts +0 -6
  62. package/dist/types/store/affects.d.ts +11 -0
  63. package/dist/types/store/hooks.d.ts +48 -0
  64. package/dist/types/store/index.d.ts +0 -1
  65. package/dist/types/store/store.d.ts +3 -38
  66. package/package.json +1 -1
@@ -2,7 +2,7 @@ import { attrHooks } from "../core/attribution-hooks.js";
2
2
 
3
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";
4
4
 
5
- import { setSignal, isEqual, read, verdict, context, notePromotedWrite, untrack, slotSignal, stagedScreen, stagedRead } from "../core/core.js";
5
+ import { setSignal, isEqual, read, verdict, snapshotCaptureActive, captureWriteSnapshot, context, notePromotedWrite, untrack, slotSignal, stagedScreen, stagedRead } from "../core/core.js";
6
6
 
7
7
  import { registerGraph } from "../core/dev.js";
8
8
 
@@ -16,6 +16,8 @@ import { txOf, flushTransaction, holdNode, joinFuture, insertSubs, schedule, pas
16
16
 
17
17
  import { lookupTarget, storeOwners, setLookup, $OWNER, isOwned, markDescendants, devAssertNeverUserMutation } from "./target.js";
18
18
 
19
+ import { optHooks, affectsHooks } from "./hooks.js";
20
+
19
21
  import { $TARGET, rawValuesUsed, isRawValue, isWrappable, markRawIngest, writeOverride, markRawOne, $RECORD, $PROXY, $TRACK } from "./types.js";
20
22
 
21
23
  export { markRaw } from "./types.js";
@@ -49,18 +51,7 @@ export { markRaw } from "./types.js";
49
51
  * Perf contracts (plan sec. 31.4): lazy nodes; one literal per node; O(written) per
50
52
  * setter (`wk`); overlay drafts for large plain records; no new named
51
53
  * fields on array targets; zero allocation for adopted-but-unread objects.
52
- */ let optHooks = null;
53
-
54
- function installOptHooks(e) {
55
- optHooks = e;
56
- }
57
-
58
- let affectsHooks = null;
59
-
60
- function installAffectsHooks(e) {
61
- affectsHooks = e;
62
- }
63
-
54
+ */
64
55
  /** Key of the deep-witness slot node (never a user key). */ const $DEEP = Symbol(0);
65
56
 
66
57
  // ---------------------------------------------------------------------------
@@ -247,7 +238,7 @@ function releaseSlot(e) {
247
238
  /** A node was born on `target`: a family target enters the live index. */ function noteNode(e, n, t) {
248
239
  if (e.fam !== null) e.fam.live.add(e);
249
240
  markDescendants(e);
250
- if (affectsHooks !== null) affectsHooks.born(e, n, t);
241
+ if (affectsHooks !== null) affectsHooks.fo(e, n, t);
251
242
  }
252
243
 
253
244
  // Shared slot-node release handler: registered once; the core sweep
@@ -257,7 +248,7 @@ function releaseSlot(e) {
257
248
  setSlotUnobserved(e => {
258
249
  // A staged write — or a lane's value (a guess, S4) — is state only the
259
250
  // node holds: defer the release to the commit that resolves it.
260
- if (e.Ae !== NOT_PENDING || e.u & CONFIG_OVERRIDE) deferredReleases.add(e); else releaseSlot(e);
251
+ if (e.Oe !== NOT_PENDING || e.i & CONFIG_OVERRIDE) deferredReleases.add(e); else releaseSlot(e);
261
252
  });
262
253
 
263
254
  /** A node born while its container has a staging is born from the two
@@ -265,19 +256,19 @@ setSlotUnobserved(e => {
265
256
  * pending one — and held by the container's transaction when the container
266
257
  * is held (#3706: the unit of the hold is the key — a key the batch left
267
258
  * unchanged stages nothing and holds no one). */ function bornStaged(e, n, t, r) {
268
- if (n.tn && n.tn(n.pe, t)) return;
259
+ if (n.en && n.en(n.Ae, t)) return;
269
260
  queuePendingNode(n);
270
- n.Ae = t;
261
+ n.Oe = t;
271
262
  const o = e.k;
272
263
  if (o === null) return;
273
264
  // a node-less staging holds nothing (`stageOn`)
274
265
  // Held by the container's transaction only for a key the HELD staging
275
266
  // changed — a mainline layer's change above it stages mainline. (A
276
267
  // container carrying a guess is a lane's: the flush's park decides.)
277
- if ((o.u & (CONFIG_HELD | CONFIG_OVERRIDE)) === CONFIG_HELD && heldKeyChanged(e, r)) holdNode(n, txOf(o));
268
+ if ((o.i & (CONFIG_HELD | CONFIG_OVERRIDE)) === CONFIG_HELD && heldKeyChanged(e, r)) holdNode(n, txOf(o));
278
269
  // A28 (4): a batch written inside a creation-time pass is promoted — its
279
270
  // late-materialized leaf with it.
280
- if (o.u & CONFIG_PROMOTED) notePromotedWrite(n);
271
+ if (o.i & CONFIG_PROMOTED) notePromotedWrite(n);
281
272
  }
282
273
 
283
274
  function getNode(e, n,
@@ -339,8 +330,8 @@ function getHasNode(e, n) {
339
330
  // pending backing staged — so whoever asked for it reads the frame it
340
331
  // would have read had the node been there from the write.
341
332
  if (e.pb !== null && !directCommit) {
342
- n.pe = preBatch(e);
343
- n.Ae = e.pb;
333
+ n.Ae = preBatch(e);
334
+ n.Oe = e.pb;
344
335
  queuePendingNode(n);
345
336
  }
346
337
  }
@@ -357,10 +348,10 @@ function getHasNode(e, n) {
357
348
  * re-created, queued, swept and dropped ~7000 container nodes). If the
358
349
  * flush parks, the seam materializes and holds the node then
359
350
  * (`_storePark`) — the one case the staging home exists for. */ function stageOn(e, n) {
360
- if (e.k === null && passLane === null && (context === null || globalQueue.ee) && !deriveHolds(e)) return;
351
+ if (e.k === null && passLane === null && (context === null || globalQueue.X) && !deriveHolds(e)) return;
361
352
  const t = getContainerNode(e);
362
- if (t.Ae === NOT_PENDING) queuePendingNode(t);
363
- t.Ae = n;
353
+ if (t.Oe === NOT_PENDING) queuePendingNode(t);
354
+ t.Oe = n;
364
355
  // A28 (4): a write issued inside a pass outside a flush (a creation-time
365
356
  // derive) is promoted at the pass's end — the batch with it.
366
357
  if (context !== null) notePromotedWrite(t);
@@ -370,10 +361,10 @@ function getHasNode(e, n) {
370
361
  /** The projection's derive is held by a transaction and this is its own
371
362
  * write (not a user setter's): the staging is the derive's (`holdWithDerive`). */ function deriveHolds(e) {
372
363
  const n = e.fam?.node;
373
- return n != null && !userWrite && (n.u & CONFIG_HELD) !== 0;
364
+ return n != null && !userWrite && (n.i & CONFIG_HELD) !== 0;
374
365
  }
375
366
 
376
- GlobalQueue.hn = e => {
367
+ GlobalQueue.An = e => {
377
368
  for (let n = 0; n < foldList.length; n++) {
378
369
  const t = foldList[n];
379
370
  if (t.k === null && t.pb !== null) holdNode(getContainerNode(t), e);
@@ -402,8 +393,9 @@ function bumpDeep(e) {
402
393
  // on the guess (`laneWrite` → `supersede`, judged by `containerEquals`);
403
394
  // otherwise the structural readers are walked (the staging is already on
404
395
  // the node; the comparator is the store's, done).
405
- if (e.u & CONFIG_OVERRIDE) setSignal(e, n); else {
406
- if (e.u & CONFIG_HELD) joinFuture(txOf(e));
396
+ if (e.i & CONFIG_OVERRIDE) setSignal(e, n); else {
397
+ if (e.i & CONFIG_HELD) joinFuture(txOf(e));
398
+ if (snapshotCaptureActive) captureWriteSnapshot(e, e.Ae);
407
399
  insertSubs(e);
408
400
  schedule();
409
401
  }
@@ -421,7 +413,7 @@ function storeRootOwner(e) {
421
413
  }
422
414
 
423
415
  function stampNodeOwner(e, n) {
424
- e.H = storeRootOwner(n) ?? null;
416
+ e.L = storeRootOwner(n) ?? null;
425
417
  }
426
418
 
427
419
  const storeNames = new WeakMap;
@@ -540,7 +532,7 @@ const plainProto = e => {
540
532
  e.pb = t;
541
533
  e.ovl = false;
542
534
  // The staging IS the pending backing: re-point the container node.
543
- if (e.k !== null && e.k.Ae === n) e.k.Ae = t;
535
+ if (e.k !== null && e.k.Oe === n) e.k.Oe = t;
544
536
  return t;
545
537
  }
546
538
 
@@ -561,7 +553,7 @@ const plainProto = e => {
561
553
  // mainline setter mid-hold publishes mainline; the held keys stay held).
562
554
  n = e.pb = cloneRaw(n, e);
563
555
  const t = e.k;
564
- if (t !== null && !(t.u & CONFIG_HELD)) t.Ae = n;
556
+ if (t !== null && !(t.i & CONFIG_HELD)) t.Oe = n;
565
557
  } else if (n === null) {
566
558
  const t = e.v;
567
559
  if (!directCommit) queueFold(e);
@@ -640,7 +632,7 @@ const plainProto = e => {
640
632
  } else r = e.v;
641
633
  if (directCommit) {
642
634
  e.pb = null;
643
- if (e.k !== null) e.k.pe = n;
635
+ if (e.k !== null) e.k.Ae = n;
644
636
  } else {
645
637
  // Eager (INTERNALS §3): the backing swaps now — handlers read the
646
638
  // adopted object before the flush — while the container node, when
@@ -736,7 +728,7 @@ let foldIndex = null;
736
728
  if (r.pb === null) continue;
737
729
  // folded already (a direct commit)
738
730
  const o = r.k;
739
- if (o !== null && o.Ae !== NOT_PENDING) {
731
+ if (o !== null && o.Oe !== NOT_PENDING) {
740
732
  // held: commits with its hold
741
733
  foldList.push(r);
742
734
  foldOlds.push(n[t]);
@@ -747,7 +739,7 @@ let foldIndex = null;
747
739
  }
748
740
  if (deferredReleases.size !== 0) {
749
741
  for (const e of deferredReleases) {
750
- if (e.Ae === NOT_PENDING && !(e.u & CONFIG_OVERRIDE) && e.t === null) {
742
+ if (e.Oe === NOT_PENDING && !(e.i & CONFIG_OVERRIDE) && e.t === null) {
751
743
  deferredReleases.delete(e);
752
744
  releaseSlot(e);
753
745
  }
@@ -758,29 +750,34 @@ let foldIndex = null;
758
750
  /** Core asks: is a slot node's truth in flight? Its family's derive is —
759
751
  * lanes (A17, #2864: a guess mid-refetch stands in for the flight) and
760
752
  * marks (an ambient `affects()` over a declared reload lives to the
761
- * landing) read it. */ GlobalQueue.F = e => {
753
+ * landing) read it. */ GlobalQueue.C = e => {
762
754
  const n = e.yt?.fam;
763
- return n != null && n.node !== null && (n.node.G & STATUS_PENDING) !== 0;
755
+ return n != null && n.node !== null && (n.node.S & STATUS_PENDING) !== 0;
764
756
  };
765
757
 
766
758
  /** Core asks (lanes.ts `covered`): the value a slot node's guess covered.
767
759
  * A chained link's committed value is the inner store's at the last
768
760
  * read-through — refreshed here, since a guess served the reads since
769
- * (#3672: the revert compares against the base's live value). */ GlobalQueue.Nn = e => {
761
+ * (#3672: the revert compares against the base's live value). */ GlobalQueue.In = e => {
770
762
  const n = e.yt;
771
763
  if (n.ch) {
772
764
  const t = e.bt;
773
765
  const r = n.v;
774
- if (n.h !== null && n.h[t] === e) e.pe = untrack(() => t in r); else if (n.n !== null && n.n[t] === e) e.pe = untrack(() => unwrapValue(r[t]));
766
+ if (n.h !== null && n.h[t] === e) e.Ae = untrack(() => t in r); else if (n.n !== null && n.n[t] === e) e.Ae = untrack(() => unwrapValue(r[t]));
775
767
  }
776
- return e.pe;
768
+ return e.Ae;
777
769
  };
778
770
 
779
- GlobalQueue.Sn = () => {
771
+ GlobalQueue.Rn = () => {
780
772
  drainFolds();
781
773
  optHooks?.sweep();
782
774
  };
783
775
 
776
+ /** The store half of `affects()` (store/affects.ts) makes its marks' nodes
777
+ * and walks records through these: it never imports the engine. */ GlobalQueue.no = getNode;
778
+
779
+ GlobalQueue.so = isWrappable;
780
+
784
781
  /** A projection's creation run commits directly (a memo's first value is
785
782
  * its `_value`, not a staging): the draft still writes a clone of the seed,
786
783
  * nothing is staged on the nodes, and the setter's exit folds the touched
@@ -807,7 +804,7 @@ function runDirect(e) {
807
804
  // the adopted object (shared ownership — never cloned).
808
805
  e.pb = null;
809
806
  e.wk = null;
810
- } else if (e.ovl && (e.v !== n || !overlayRebuilds(e, r))) {
807
+ } else if (e.ovl && (e.v !== n || !overlayRebuilds(e, r) && t?.m?.ot !== n)) {
811
808
  // Overlay flatten (#3044): the backing keeps its identity, so the
812
809
  // `t.v === old` gate below skips path copying (the parent slot
813
810
  // already points here) and the adopted-notify (setter
@@ -849,7 +846,7 @@ function runDirect(e) {
849
846
  // subscribes to was a transient staging home (INTERNALS §5: an
850
847
  // observer-less write holds a transient record until the fold,
851
848
  // discarded with it) — gone with the batch.
852
- if (t.t === null) e.k = null; else if (t.pe instanceof LaneView) t.pe.base = e.v; else t.pe = e.v;
849
+ if (t.t === null) e.k = null; else if (t.Ae instanceof LaneView) t.Ae.base = e.v; else t.Ae = e.v;
853
850
  }
854
851
  if (e.v !== n && e.u) {
855
852
  const t = parentSlotKey(e, n);
@@ -922,9 +919,9 @@ function reportReplacedContainers(e, n, t, r) {
922
919
 
923
920
  function heldDerivation(e, n, t) {
924
921
  if (e.fam === null || !userWrite) return false;
925
- if (n !== undefined) return (n.u & (CONFIG_HELD | CONFIG_MANUAL_WRITE)) === CONFIG_HELD && txOf(n) !== flushTransaction;
922
+ if (n !== undefined) return (n.i & (CONFIG_HELD | CONFIG_MANUAL_WRITE)) === CONFIG_HELD && txOf(n) !== flushTransaction;
926
923
  const r = e.k;
927
- return r !== null && (r.u & CONFIG_HELD) !== 0 && txOf(r) !== flushTransaction && heldKeyChanged(e, t);
924
+ return r !== null && (r.i & CONFIG_HELD) !== 0 && txOf(r) !== flushTransaction && heldKeyChanged(e, t);
928
925
  }
929
926
 
930
927
  /** Setter exit: one node write per changed observed key (the leaves stage
@@ -970,15 +967,18 @@ function heldDerivation(e, n, t) {
970
967
  if (o.acc === true || hasOwn.call(n, i) && lookupGetter.call(n, i) !== undefined) {
971
968
  // Accessor keys: the node is linked for shape-change notification,
972
969
  // its value is never served (the getter runs with the proxy
973
- // receiver on read) — FORCE wakes the readers.
970
+ // receiver on read) — FORCE wakes the readers. A data descriptor
971
+ // falls through so the cleared-acc hot read serves it (#3949);
972
+ // an own getter's descriptor has no `value`, so the compare below
973
+ // would otherwise keep the getter's result (or FORCE) for `undefined`.
974
974
  o.acc = isOwnAccessor(n, i);
975
975
  const e = Object.getOwnPropertyDescriptor(t, i);
976
976
  const r = Object.getOwnPropertyDescriptor(n, i);
977
- if (e && (e.get || e.set) || r && (r.get || r.set)) {
977
+ if (!r || r.get || r.set) {
978
978
  if (e?.get !== r?.get || e?.set !== r?.set || e?.value !== r?.value) setSignal(o, () => FORCE);
979
979
  continue;
980
980
  }
981
- if (!isEqual(e?.value, r?.value)) setSignal(o, () => r?.value);
981
+ if (e?.get || e?.set || !isEqual(e?.value, r.value)) setSignal(o, () => r.value);
982
982
  continue;
983
983
  }
984
984
  const l = e.del !== null && e.del.has(i) ? undefined : n[i];
@@ -987,9 +987,9 @@ function heldDerivation(e, n, t) {
987
987
  // The derive's write is a derivation; a user setter's is a proposal
988
988
  // (A34 (3)'s discriminator).
989
989
  if (!userWrite) {
990
- o.u &= ~CONFIG_MANUAL_WRITE;
990
+ o.i &= ~CONFIG_MANUAL_WRITE;
991
991
  holdWithDerive(e, o);
992
- } else o.u |= CONFIG_MANUAL_WRITE;
992
+ } else o.i |= CONFIG_MANUAL_WRITE;
993
993
  }
994
994
  }
995
995
  }
@@ -1020,7 +1020,7 @@ function heldDerivation(e, n, t) {
1020
1020
  // container carrying an arrangement guess is told whether or not anyone
1021
1021
  // subscribes: the write is a landing on the guess (`notifyContainer`).
1022
1022
  const u = e.k;
1023
- if (u !== null && (u.t !== null || u.u & CONFIG_OVERRIDE)) {
1023
+ if (u !== null && (u.t !== null || u.i & CONFIG_OVERRIDE)) {
1024
1024
  let r;
1025
1025
  if (e.ovl) {
1026
1026
  r = e.del !== null && e.del.size !== 0;
@@ -1069,13 +1069,14 @@ function membershipChanged(e, n) {
1069
1069
  e.acc = isOwnAccessor(r, n);
1070
1070
  const o = Object.getOwnPropertyDescriptor(t, n);
1071
1071
  const i = Object.getOwnPropertyDescriptor(r, n);
1072
- if (o && (o.get || o.set) || i && (i.get || i.set)) {
1072
+ // Data descriptors fall through, as in notifyWrites (#3949).
1073
+ if (!i || i.get || i.set) {
1073
1074
  if (o?.get !== i?.get || o?.set !== i?.set || o?.value !== i?.value) setSignal(e, () => FORCE);
1074
1075
  return;
1075
1076
  }
1076
1077
  const l = o?.value;
1077
1078
  const u = i?.value;
1078
- if (!isEqual(l, u) && !targetsEqual(l, u)) setSignal(e, typeof u === "function" ? () => u : u);
1079
+ if (o?.get || o?.set || !isEqual(l, u) && !targetsEqual(l, u)) setSignal(e, typeof u === "function" ? () => u : u);
1079
1080
  } else {
1080
1081
  const o = t[n];
1081
1082
  const i = r[n];
@@ -1121,7 +1122,7 @@ function hasAccessorFlag(e) {
1121
1122
  for (const o of Reflect.ownKeys(r)) if (e || o in n !== o in t) setSignal(r[o], o in t);
1122
1123
  }
1123
1124
  const o = e.k;
1124
- if (o !== null && (o.t !== null || o.u & CONFIG_OVERRIDE)) {
1125
+ if (o !== null && (o.t !== null || o.i & CONFIG_OVERRIDE)) {
1125
1126
  const e = Array.isArray(t) && Array.isArray(n) ? arrayStructureChanged(n, t) : membershipChanged(n, t);
1126
1127
  if (e) notifyContainer(o, t);
1127
1128
  }
@@ -1178,7 +1179,7 @@ function inDraft(e) {
1178
1179
  * "adoption is eager by contract" — and the node keeps the frame committed
1179
1180
  * readers under a hold still see), `t.v` otherwise. */ function committed(e) {
1180
1181
  const n = e.k;
1181
- if (n !== null) return n.Ae !== NOT_PENDING ? asBacking(n.pe) : e.v;
1182
+ if (n !== null) return n.Oe !== NOT_PENDING ? asBacking(n.Ae) : e.v;
1182
1183
  // A node-less staging (`stageOn`): the fold queue remembers the frame.
1183
1184
  return e.pb !== null ? preBatch(e) : e.v;
1184
1185
  }
@@ -1191,7 +1192,7 @@ function inDraft(e) {
1191
1192
  let e = context;
1192
1193
  if (e === null) return null;
1193
1194
  if (e.Ht) e = e.gt ?? null;
1194
- return e === null || e.u & CONFIG_CHILDREN_FORBIDDEN ? null : e;
1195
+ return e === null || e.i & CONFIG_CHILDREN_FORBIDDEN ? null : e;
1195
1196
  }
1196
1197
 
1197
1198
  /** The backing a read serves (RUL-1, signal parity): the draft and the write
@@ -1229,10 +1230,10 @@ function inDraft(e) {
1229
1230
  if (writeOverride && !userWrite) {
1230
1231
  const t = n !== undefined ? e.n?.[n] : undefined;
1231
1232
  if (t !== undefined) {
1232
- if ((t.u & (CONFIG_HELD | CONFIG_OVERRIDE)) === CONFIG_HELD) joinPassTx(txOf(t));
1233
+ if ((t.i & (CONFIG_HELD | CONFIG_OVERRIDE)) === CONFIG_HELD) joinPassTx(txOf(t));
1233
1234
  } else {
1234
1235
  const t = e.k;
1235
- if (t !== null && (t.u & (CONFIG_HELD | CONFIG_OVERRIDE)) === CONFIG_HELD && (n === undefined || heldKeyChanged(e, n))) joinPassTx(txOf(t));
1236
+ if (t !== null && (t.i & (CONFIG_HELD | CONFIG_OVERRIDE)) === CONFIG_HELD && (n === undefined || heldKeyChanged(e, n))) joinPassTx(txOf(t));
1236
1237
  }
1237
1238
  }
1238
1239
  return r;
@@ -1244,9 +1245,9 @@ function inDraft(e) {
1244
1245
  // it).
1245
1246
  if (n !== undefined) {
1246
1247
  const t = e.n?.[n];
1247
- if (t !== undefined && t.u & CONFIG_OVERRIDE) return committed(e);
1248
+ if (t !== undefined && t.i & CONFIG_OVERRIDE) return committed(e);
1248
1249
  }
1249
- const i = o !== null && (o.u & CONFIG_HELD) !== 0;
1250
+ const i = o !== null && (o.i & CONFIG_HELD) !== 0;
1250
1251
  // A held container: the hold's unit is the key (#3706). A key the HELD
1251
1252
  // staging left unchanged reads the ambient view — a mainline layer's
1252
1253
  // write above the hold publishes mainline (#3688, #3612); a key it
@@ -1264,7 +1265,7 @@ function inDraft(e) {
1264
1265
  // probe) judges the container like any node — for a key the batch
1265
1266
  // changed; an unchanged key is committed and final (A22: pending is per
1266
1267
  // key). A held container's committed frame is the node's.
1267
- if (verdict === null || !i && n !== undefined && !keyChanged(e, n, t)) return i ? asBacking(o.pe) : e.v;
1268
+ if (verdict === null || !i && n !== undefined && !keyChanged(e, n, t)) return i ? asBacking(o.Ae) : e.v;
1268
1269
  return o === null ? committed(e) : asBacking(read(o));
1269
1270
  }
1270
1271
  // A pass reading a key the batch left unchanged: the same value in both
@@ -1281,13 +1282,13 @@ function inDraft(e) {
1281
1282
  // frames, a changed one is what the mark already stands for. The
1282
1283
  // per-key question is asked once per pass, not once per key (a
1283
1284
  // `mapArray` over 1k shifted rows asked it 1k times).
1284
- if (l.R & REACTIVE_STAGED_READ && !(l.u & (CONFIG_VERDICT | CONFIG_CHILDREN_FORBIDDEN))) return r;
1285
+ if (l.R & REACTIVE_STAGED_READ && !(l.i & (CONFIG_VERDICT | CONFIG_CHILDREN_FORBIDDEN))) return r;
1285
1286
  if (n !== undefined && !keyChanged(e, n, t)) return committed(e);
1286
1287
  // A node-less staging (`stageOn`): what `read()` of its node would do
1287
1288
  // for this pass — a verdict reader sees the screen, any other derives
1288
1289
  // from the staging and is marked as having read it.
1289
1290
  if (o === null) {
1290
- if (l.u & CONFIG_VERDICT && stagedScreen(l)) return committed(e);
1291
+ if (l.i & CONFIG_VERDICT && stagedScreen(l)) return committed(e);
1291
1292
  stagedRead(l);
1292
1293
  return r;
1293
1294
  }
@@ -1297,11 +1298,52 @@ function inDraft(e) {
1297
1298
 
1298
1299
  /** Did the HELD staging (the container node's, not a mainline layer above
1299
1300
  * it) change `key`? */ function heldKeyChanged(e, n, t = true) {
1300
- return changedBetween(e, e.k.pe, e.k.Ae, n, t);
1301
+ return changedBetween(e, e.k.Ae, e.k.Oe, n, t) && !adoptChild(e, n);
1302
+ }
1303
+
1304
+ /** The values of a held frame, built once per frame: neither the committed
1305
+ * frame nor the held staging changes while the hold stands (a draft writes a
1306
+ * clone), so the set stays true for every first read of the hold. */ const frameRaws = new WeakMap;
1307
+
1308
+ function rawsOf(e) {
1309
+ let n = frameRaws.get(e);
1310
+ if (n === undefined) frameRaws.set(e, n = new Set(Object.values(e)));
1311
+ return n;
1312
+ }
1313
+
1314
+ /** The same row at a slot, as the adoption pairs rows (reconcile.ts): two
1315
+ * objects whose keys agree — positionally for an unkeyed family. */ function sameRow(e, n, t) {
1316
+ return e !== null && typeof e === "object" && n !== null && typeof n === "object" && (t === null || sameKey(t(e), t(n)));
1317
+ }
1318
+
1319
+ /** A key of a held adoption whose old and new values are the same row that
1320
+ * nothing had proxied when the adoption ran, so `descend` never paired
1321
+ * them: paired now, as it would have been (#3712) — one target, the old
1322
+ * raw its committed frame, the new raw its staging, held by the same
1323
+ * transaction. Both raws resolve to it, so the slot reads as unchanged and
1324
+ * the row's own leaves decide: `id` the same in both frames holds no one,
1325
+ * `version` holds. A record's keys pair by name; an array's by the family's
1326
+ * key — without a family the pairing is not known here and the hold stands. */ function adoptChild(e, n) {
1327
+ const t = e.k;
1328
+ const r = asBacking(t.Ae);
1329
+ const o = r[n];
1330
+ const i = t.Oe[n];
1331
+ const l = Array.isArray(r) ? e.fam?.key : null;
1332
+ 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 ||
1333
+ // A raw in both frames (the same objects swapped or reordered) is its
1334
+ // own row: pairing it with another would alias the two after the commit.
1335
+ rawsOf(r).has(i) || rawsOf(t.Oe).has(o)) return false;
1336
+ // A raw-marked value wraps to itself, a proxy to its own target: neither
1337
+ // is a fresh target around `ov`.
1338
+ const u = wrap(o, e, n)[$TARGET];
1339
+ if (u === undefined || u.v !== o) return false;
1340
+ adoptPB(u, i, false);
1341
+ holdNode(getContainerNode(u), txOf(t));
1342
+ return true;
1301
1343
  }
1302
1344
 
1303
1345
  /** A family target's unheld staging, read with no pass (a handler). */ function familyAhead(e) {
1304
- return e.fam !== null && context === null && !(e.k !== null && e.k.u & CONFIG_HELD);
1346
+ return e.fam !== null && context === null && !(e.k !== null && e.k.i & CONFIG_HELD);
1305
1347
  }
1306
1348
 
1307
1349
  /** Did this batch change `key` — value (slot equality), presence, and (for
@@ -1335,7 +1377,7 @@ function changedBetween(e, n, t, r, o) {
1335
1377
  * tracked or not, applies the hold and A28 rules a signal's read does. A
1336
1378
  * projection's unheld staging is ahead for a handler (`familyAhead`). */ function nodeValue(e, n, t) {
1337
1379
  let r = read(e);
1338
- if (e.Ae !== NOT_PENDING && !(e.u & CONFIG_HELD) && context === null && t.fam !== null) r = e.Ae;
1380
+ if (e.Oe !== NOT_PENDING && !(e.i & CONFIG_HELD) && context === null && t.fam !== null) r = e.Oe;
1339
1381
  return r === FORCE ? n : r;
1340
1382
  }
1341
1383
 
@@ -1355,8 +1397,8 @@ function changedBetween(e, n, t, r, o) {
1355
1397
  if (l) {
1356
1398
  // The link's committed value follows the inner store's (what a lane
1357
1399
  // over it covers — its reveal and revert compare against it, S4).
1358
- o.pe = t;
1359
- if (o.u & CONFIG_OVERRIDE) u = n;
1400
+ o.Ae = t;
1401
+ if (o.i & CONFIG_OVERRIDE) u = n;
1360
1402
  } else u = n;
1361
1403
  }
1362
1404
  }
@@ -1377,7 +1419,7 @@ function changedBetween(e, n, t, r, o) {
1377
1419
  }
1378
1420
 
1379
1421
  /** Is `n` among `c`'s dependencies (any pass's — a kept tail included)? */ function linkedTo(e, n) {
1380
- for (let t = e._; t !== null; t = t.p) if (t.I === n) return true;
1422
+ for (let t = e.G; t !== null; t = t._) if (t.I === n) return true;
1381
1423
  return false;
1382
1424
  }
1383
1425
 
@@ -1396,7 +1438,7 @@ function changedBetween(e, n, t, r, o) {
1396
1438
  // reader of a memo would — linked, so it re-runs at the landing (the
1397
1439
  // next settled pass pulls without linking and the stale link trims), a
1398
1440
  // verdict reader registered, a tracked pass suspended.
1399
- if (n.G & (STATUS_PENDING | STATUS_ERROR)) {
1441
+ if (n.S & (STATUS_PENDING | STATUS_ERROR)) {
1400
1442
  // A render effect outside the flight's own flush is the frame, not a
1401
1443
  // derivation: it keeps what it shows and learns of the landing from the
1402
1444
  // leaves the landing changes (unchanged leaves say nothing — it is not a
@@ -1404,9 +1446,10 @@ function changedBetween(e, n, t, r, o) {
1404
1446
  // One that already observes the flight (linked by the pass that saw it
1405
1447
  // go up — the frame's hold, `blocked`) keeps observing it: a re-run for
1406
1448
  // another reason (a guess it read, the seam) that dropped the link would
1407
- // release the hold with the flight still up.
1449
+ // release the hold with the flight still up. An errored derive has no
1450
+ // landing to learn of: the error is what the frame shows.
1408
1451
  const t = context;
1409
- if (t !== null && t.i === EFFECT_RENDER && n.u & CONFIG_HELD && flushTransaction !== txOf(n) && !linkedTo(t, n)) return;
1452
+ if (t !== null && t.u === EFFECT_RENDER && !(n.S & STATUS_ERROR) && n.i & CONFIG_HELD && flushTransaction !== txOf(n) && !linkedTo(t, n)) return;
1410
1453
  // An optimistic family carrying guesses that stand in for the flight
1411
1454
  // (A17): the guesses are the display — a reader is served the leaves
1412
1455
  // (committed plus the guesses) and does not suspend on the derive; a
@@ -1414,7 +1457,7 @@ function changedBetween(e, n, t, r, o) {
1414
1457
  // up). The family's, not the target's: the edit stands in for the one
1415
1458
  // flight the family has.
1416
1459
  const r = e.fam.overlaid;
1417
- if (verdict === null && n.G & STATUS_PENDING && r !== undefined && r.size !== 0) return;
1460
+ if (verdict === null && n.S & STATUS_PENDING && r !== undefined && r.size !== 0) return;
1418
1461
  read(n);
1419
1462
  } else untrack(() => read(n));
1420
1463
  }
@@ -1427,10 +1470,10 @@ function changedBetween(e, n, t, r, o) {
1427
1470
  * classification, as core reads it — `heldNotFinal`). */ function holdWithDerive(e, n) {
1428
1471
  const t = e.fam?.node;
1429
1472
  if (t !== undefined && t !== null && !userWrite && // the derive's own write (its draft or landing), not a user setter's
1430
- t.u & CONFIG_HELD && n.Ae !== NOT_PENDING &&
1473
+ t.i & CONFIG_HELD && n.Oe !== NOT_PENDING &&
1431
1474
  // A node carrying a guess is the lane's: its staging is the truth beneath
1432
1475
  // the guess (`setSignal` → `laneWrite`), never re-homed by the store.
1433
- !(n.u & (CONFIG_HELD | CONFIG_OVERRIDE))) {
1476
+ !(n.i & (CONFIG_HELD | CONFIG_OVERRIDE))) {
1434
1477
  holdNode(n, txOf(t));
1435
1478
  n.m.It = t.m.It;
1436
1479
  }
@@ -1503,7 +1546,7 @@ const traps = {
1503
1546
  }
1504
1547
  // (The witness before the pull: a mark on an uninitialized derived
1505
1548
  // store is witnessed, then the pull throws — loading, declared pending.)
1506
- if (verdict !== null && affectsHooks !== null && getObserver() === null) affectsHooks.witness(e, n);
1549
+ if (verdict !== null && affectsHooks !== null && getObserver() === null) affectsHooks.ro(e, n);
1507
1550
  if (e.fam !== null) pullFamily(e);
1508
1551
  // Hot inline case: existing PLAIN node (non-accessor) read outside any
1509
1552
  // draft — the dbmon/uibench effect re-read shape. Core's `read()` serves
@@ -1518,7 +1561,7 @@ const traps = {
1518
1561
  const r = e.n?.[n];
1519
1562
  if (writeScopes === null && !writeOverride) {
1520
1563
  const t = r;
1521
- if (t !== undefined && t.acc !== true && (!e.ch || t.u & CONFIG_OVERRIDE)) {
1564
+ if (t !== undefined && t.acc !== true && (!e.ch || t.i & CONFIG_OVERRIDE)) {
1522
1565
  const r = nodeValue(t, undefined, e);
1523
1566
  if (r === null || typeof r !== "object") return r;
1524
1567
  if (e.s) return serveShallow(e, n, r);
@@ -1600,7 +1643,7 @@ const traps = {
1600
1643
  if (n === $OWNER || n === $RECORD) return false;
1601
1644
  // (The witness before the pull: a mark on an uninitialized derived
1602
1645
  // store is witnessed, then the pull throws — loading, declared pending.)
1603
- if (verdict !== null && affectsHooks !== null && getObserver() === null) affectsHooks.witness(e, n);
1646
+ if (verdict !== null && affectsHooks !== null && getObserver() === null) affectsHooks.ro(e, n);
1604
1647
  if (e.fam !== null) pullFamily(e);
1605
1648
  const t = readSource(e, n);
1606
1649
  // A tracked reader's presence node answers (born from the two frames;
@@ -1613,9 +1656,9 @@ const traps = {
1613
1656
  const o = read(r);
1614
1657
  if (!e.ch || t !== e.v) return !!o;
1615
1658
  const i = n in t;
1616
- r.pe = i;
1659
+ r.Ae = i;
1617
1660
  // the link follows the inner store (S4)
1618
- if (r.u & CONFIG_OVERRIDE) return !!o;
1661
+ if (r.i & CONFIG_OVERRIDE) return !!o;
1619
1662
  return i;
1620
1663
  } else if (optRead(e)) {
1621
1664
  const t = optHooks.has(e, n);
@@ -1625,14 +1668,14 @@ const traps = {
1625
1668
  return n in t && !(e.del !== null && t === e.pb && e.del.has(n));
1626
1669
  },
1627
1670
  ownKeys(e) {
1628
- if (verdict !== null && affectsHooks !== null && getObserver() === null) affectsHooks.witness(e, undefined);
1671
+ if (verdict !== null && affectsHooks !== null && getObserver() === null) affectsHooks.ro(e, undefined);
1629
1672
  if (e.fam !== null) pullFamily(e);
1630
1673
  return visibleKeys(e, enumerationSource(e));
1631
1674
  },
1632
1675
  getOwnPropertyDescriptor(e, n) {
1633
1676
  if (n === $OWNER || n === $RECORD) return undefined;
1634
1677
  const t = getObserver();
1635
- if (verdict !== null && affectsHooks !== null && t === null) affectsHooks.witness(e, n);
1678
+ if (verdict !== null && affectsHooks !== null && t === null) affectsHooks.ro(e, n);
1636
1679
  if (e.fam !== null) pullFamily(e);
1637
1680
  // An enumerator (spread, Object.entries) already holds the container
1638
1681
  // node and reads its frame; a descriptor read on its own tracks
@@ -1650,7 +1693,7 @@ const traps = {
1650
1693
  const t = getHasNode(e, n);
1651
1694
  const r = read(t);
1652
1695
  i = readSource(e, n, true);
1653
- if (!r && (!e.ch || t.u & CONFIG_OVERRIDE || !(n in i))) return undefined;
1696
+ if (!r && (!e.ch || t.i & CONFIG_OVERRIDE || !(n in i))) return undefined;
1654
1697
  } else i = readSource(e, n, true);
1655
1698
  let l;
1656
1699
  if (!o && optRead(e)) {
@@ -1744,7 +1787,7 @@ const traps = {
1744
1787
  const t = e.k;
1745
1788
  if (t === null) return false;
1746
1789
  const r = t.Dt;
1747
- return r !== null && r.o === n && (!(n.R & REACTIVE_RECOMPUTING_DEPS) || r.Ue === n.he);
1790
+ return r !== null && r.l === n && (!(n.R & REACTIVE_RECOMPUTING_DEPS) || r.Pe === n.pe);
1748
1791
  }
1749
1792
 
1750
1793
  function storeSetter(e, n, t = true) {
@@ -1944,7 +1987,7 @@ function snapshotWalk(e, n, t) {
1944
1987
  // R27: a snapshot sees the batch's pending backing — a held one (another
1945
1988
  // transaction's future) only from its own draft.
1946
1989
  let i = o.v;
1947
- if (o.pb !== null && (inDraft(o) || writeOverride || o.k === null || !(o.k.u & CONFIG_HELD))) {
1990
+ if (o.pb !== null && (inDraft(o) || writeOverride || o.k === null || !(o.k.i & CONFIG_HELD))) {
1948
1991
  if (o.ovl) materializePB(o);
1949
1992
  i = o.pb;
1950
1993
  }
@@ -1996,4 +2039,4 @@ function snapshotWalk(e, n, t) {
1996
2039
  return i ?? r;
1997
2040
  }
1998
2041
 
1999
- export { $PROXY, $TARGET, $TRACK, LaneView, adoptPB, affectsHooks, arrayStructureChanged, asBacking, bumpDeep, cloneRaw, committed, containerEquals, createStore, deep, getContainerNode, getHasNode, getNode, hasAccessorFlag, installAffectsHooks, installOptHooks, materializePB, membershipChanged, nameStore, notifyFold, notifyFoldTail, notifyKeyDiff, notifyKeyValue, optHooks, resolveChainedRaw, runDirect, sameKey, snapshot, storeHasFamily, storeHasOptimisticFamily, storeIsShallow, storeSetter, targetsEqual, unwrapValue, userWriting, wrap };
2042
+ export { $PROXY, $TARGET, $TRACK, LaneView, adoptPB, arrayStructureChanged, asBacking, bumpDeep, cloneRaw, committed, containerEquals, createStore, deep, getContainerNode, getHasNode, getNode, hasAccessorFlag, materializePB, membershipChanged, nameStore, notifyFold, notifyFoldTail, notifyKeyDiff, notifyKeyValue, resolveChainedRaw, runDirect, sameKey, sameRow, snapshot, storeHasFamily, storeHasOptimisticFamily, storeIsShallow, storeSetter, targetsEqual, unwrapValue, userWriting, wrap };