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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/dist/dev-shared.js +882 -405
  2. package/dist/dev.attribution.js +172 -66
  3. package/dist/dev.js +1529 -745
  4. package/dist/observe/affects-marks.js +86 -0
  5. package/dist/observe/affects.js +32 -80
  6. package/dist/observe/boundaries.js +202 -166
  7. package/dist/observe/core/async.js +192 -141
  8. package/dist/observe/core/attribution-queries.js +1 -1
  9. package/dist/observe/core/attribution.js +315 -214
  10. package/dist/observe/core/constants.js +23 -4
  11. package/dist/observe/core/context.js +3 -3
  12. package/dist/observe/core/core.js +532 -360
  13. package/dist/observe/core/dev.js +63 -35
  14. package/dist/observe/core/effect.js +47 -47
  15. package/dist/observe/core/error.js +5 -3
  16. package/dist/observe/core/external.js +5 -6
  17. package/dist/observe/core/graph.js +56 -42
  18. package/dist/observe/core/heap.js +33 -33
  19. package/dist/observe/core/identities.js +62 -0
  20. package/dist/observe/core/lanes.js +242 -229
  21. package/dist/observe/core/owner.js +62 -50
  22. package/dist/observe/core/scheduler.js +322 -168
  23. package/dist/observe/core/truth.js +45 -0
  24. package/dist/observe/core/verdict.js +114 -64
  25. package/dist/observe/deferred.js +184 -0
  26. package/dist/observe/index.js +7 -1
  27. package/dist/observe/map.js +339 -254
  28. package/dist/observe/reveal.js +77 -77
  29. package/dist/observe/signals.js +54 -33
  30. package/dist/observe/store/affects.js +76 -69
  31. package/dist/observe/store/edges.js +39 -0
  32. package/dist/observe/store/hooks.js +13 -0
  33. package/dist/observe/store/index.js +0 -2
  34. package/dist/observe/store/optimistic.js +82 -44
  35. package/dist/observe/store/projection.js +83 -64
  36. package/dist/observe/store/reconcile.js +32 -30
  37. package/dist/observe/store/store.js +219 -123
  38. package/dist/prod/affects-marks.js +86 -0
  39. package/dist/prod/affects.js +33 -79
  40. package/dist/prod/boundaries.js +185 -149
  41. package/dist/prod/core/async.js +176 -135
  42. package/dist/prod/core/constants.js +23 -4
  43. package/dist/prod/core/context.js +3 -3
  44. package/dist/prod/core/core.js +521 -353
  45. package/dist/prod/core/effect.js +66 -66
  46. package/dist/prod/core/error.js +5 -3
  47. package/dist/prod/core/external.js +5 -6
  48. package/dist/prod/core/graph.js +56 -42
  49. package/dist/prod/core/heap.js +38 -38
  50. package/dist/prod/core/identities.js +62 -0
  51. package/dist/prod/core/lanes.js +237 -224
  52. package/dist/prod/core/owner.js +62 -51
  53. package/dist/prod/core/scheduler.js +312 -158
  54. package/dist/prod/core/truth.js +45 -0
  55. package/dist/prod/core/verdict.js +113 -63
  56. package/dist/prod/deferred.js +182 -0
  57. package/dist/prod/index.js +7 -1
  58. package/dist/prod/map.js +366 -281
  59. package/dist/prod/reveal.js +77 -77
  60. package/dist/prod/signals.js +59 -38
  61. package/dist/prod/store/affects.js +76 -69
  62. package/dist/prod/store/edges.js +39 -0
  63. package/dist/prod/store/hooks.js +13 -0
  64. package/dist/prod/store/index.js +0 -2
  65. package/dist/prod/store/optimistic.js +121 -84
  66. package/dist/prod/store/projection.js +46 -34
  67. package/dist/prod/store/reconcile.js +32 -30
  68. package/dist/prod/store/store.js +205 -116
  69. package/dist/types/affects-marks.d.ts +14 -0
  70. package/dist/types/boundaries.d.ts +5 -1
  71. package/dist/types/core/async.d.ts +10 -0
  72. package/dist/types/core/attribution.d.ts +6 -2
  73. package/dist/types/core/constants.d.ts +18 -1
  74. package/dist/types/core/core.d.ts +77 -5
  75. package/dist/types/core/dev.d.ts +43 -13
  76. package/dist/types/core/graph.d.ts +11 -0
  77. package/dist/types/core/identities.d.ts +17 -0
  78. package/dist/types/core/index.d.ts +2 -1
  79. package/dist/types/core/lanes.d.ts +2 -4
  80. package/dist/types/core/owner.d.ts +5 -3
  81. package/dist/types/core/scheduler.d.ts +59 -34
  82. package/dist/types/core/truth.d.ts +16 -0
  83. package/dist/types/core/types.d.ts +21 -6
  84. package/dist/types/core/verdict.d.ts +32 -0
  85. package/dist/types/deferred.d.ts +28 -0
  86. package/dist/types/index.d.ts +6 -0
  87. package/dist/types/map.d.ts +2 -2
  88. package/dist/types/signals.d.ts +28 -7
  89. package/dist/types/store/affects.d.ts +11 -0
  90. package/dist/types/store/edges.d.ts +9 -0
  91. package/dist/types/store/hooks.d.ts +48 -0
  92. package/dist/types/store/index.d.ts +10 -4
  93. package/dist/types/store/optimistic.d.ts +3 -2
  94. package/dist/types/store/projection.d.ts +4 -3
  95. package/dist/types/store/store.d.ts +4 -39
  96. package/dist/types/store/target.d.ts +16 -8
  97. package/dist/types/store/types.d.ts +14 -0
  98. package/package.json +1 -1
@@ -2,20 +2,24 @@ 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 { writeHook, 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
 
9
+ import { signalHook } from "../core/identities.js";
10
+
9
11
  import { setSlotUnobserved } from "../core/graph.js";
10
12
 
11
13
  import { getOwner, getObserver } from "../core/owner.js";
12
14
 
13
15
  import { insertIntoHeap } from "../core/heap.js";
14
16
 
15
- import { txOf, flushTransaction, holdNode, joinFuture, insertSubs, schedule, passLane, globalQueue, queuePendingNode, GlobalQueue, joinPassTx, dirtyQueue } from "../core/scheduler.js";
17
+ import { txOf, flushTransaction, holdNode, joinFuture, insertSubs, schedule, passLane, globalQueue, queuePendingNode, GlobalQueue, installStoreHooks, joinPassTx, dirtyQueue } from "../core/scheduler.js";
16
18
 
17
19
  import { lookupTarget, storeOwners, setLookup, $OWNER, isOwned, markDescendants, devAssertNeverUserMutation } from "./target.js";
18
20
 
21
+ import { optHooks, affectsHooks } from "./hooks.js";
22
+
19
23
  import { $TARGET, rawValuesUsed, isRawValue, isWrappable, markRawIngest, writeOverride, markRawOne, $RECORD, $PROXY, $TRACK } from "./types.js";
20
24
 
21
25
  export { markRaw } from "./types.js";
@@ -49,18 +53,7 @@ export { markRaw } from "./types.js";
49
53
  * Perf contracts (plan sec. 31.4): lazy nodes; one literal per node; O(written) per
50
54
  * setter (`wk`); overlay drafts for large plain records; no new named
51
55
  * 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
-
56
+ */
64
57
  /** Key of the deep-witness slot node (never a user key). */ const $DEEP = Symbol(0);
65
58
 
66
59
  // ---------------------------------------------------------------------------
@@ -179,7 +172,7 @@ function wrap(e, n = null, t = null, r = n?.fam ?? null) {
179
172
  // adoption swap raw identity without changing the logical value — only
180
173
  // changed leaves notify, R9).
181
174
  const slotNodeEquals = function(e, n) {
182
- return isEqual(e, n) || sameLogicalSlot(this.yt, e, n);
175
+ return isEqual(e, n) || sameLogicalSlot(this.O, e, n);
183
176
  };
184
177
 
185
178
  /** The deep witness: the store decides when it notifies. */ const never = false;
@@ -217,7 +210,7 @@ function sameKey(e, n) {
217
210
  /** The container node's comparator: a staging is a backing (identity — a
218
211
  * pending backing is never "equal" to the committed one); a lane value is
219
212
  * an arrangement (`LaneView`), judged by the optimistic machinery. */ const containerEquals = function(e, n) {
220
- return e === n || optHooks !== null && optHooks.arrangement(this.yt, e, n);
213
+ return e === n || optHooks !== null && optHooks.arrangement(this.O, e, n);
221
214
  };
222
215
 
223
216
  function sameLogicalSlot(e, n, t) {
@@ -230,8 +223,8 @@ function sameLogicalSlot(e, n, t) {
230
223
  * released at the commit that resolves it (`drainFolds`). */ const deferredReleases = new Set;
231
224
 
232
225
  function releaseSlot(e) {
233
- const n = e.yt;
234
- const t = e.bt;
226
+ const n = e.O;
227
+ const t = e.Vt;
235
228
  if (t === $TRACK) {
236
229
  if (n.k === e) n.k = null;
237
230
  } else if (t === $DEEP) {
@@ -247,17 +240,21 @@ function releaseSlot(e) {
247
240
  /** A node was born on `target`: a family target enters the live index. */ function noteNode(e, n, t) {
248
241
  if (e.fam !== null) e.fam.live.add(e);
249
242
  markDescendants(e);
250
- if (affectsHooks !== null) affectsHooks.born(e, n, t);
243
+ if (affectsHooks !== null) affectsHooks.fo(e, n, t);
251
244
  }
252
245
 
253
246
  // Shared slot-node release handler: registered once; the core sweep
254
247
  // dispatches CONFIG_SLOT_NODE nodes here (graph.ts) instead of holding a
255
248
  // per-node closure in a per-node NodeExtension. A dropped node is
256
249
  // unreachable through the store — a fresh read makes a fresh node.
250
+ // A staged write — or a lane's value (a guess, S4) — is state only the node
251
+ // holds: its release waits for the commit that resolves it. So does a leaf of
252
+ // a held container (#4146): a mainline write it carried is in the held
253
+ // staging too, where it would read as the hold's until the landing.
254
+ const slotKept = e => e.ue !== NOT_PENDING || e.l & CONFIG_OVERRIDE || e.O.k?.l & CONFIG_HELD;
255
+
257
256
  setSlotUnobserved(e => {
258
- // A staged write — or a lane's value (a guess, S4) — is state only the
259
- // 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);
257
+ if (slotKept(e)) deferredReleases.add(e); else releaseSlot(e);
261
258
  });
262
259
 
263
260
  /** A node born while its container has a staging is born from the two
@@ -265,19 +262,19 @@ setSlotUnobserved(e => {
265
262
  * pending one — and held by the container's transaction when the container
266
263
  * is held (#3706: the unit of the hold is the key — a key the batch left
267
264
  * unchanged stages nothing and holds no one). */ function bornStaged(e, n, t, r) {
268
- if (n.tn && n.tn(n.pe, t)) return;
265
+ if (n.Ye && n.Ye(n.Ie, t)) return;
269
266
  queuePendingNode(n);
270
- n.Ae = t;
267
+ n.ue = t;
271
268
  const o = e.k;
272
269
  if (o === null) return;
273
270
  // a node-less staging holds nothing (`stageOn`)
274
271
  // Held by the container's transaction only for a key the HELD staging
275
272
  // changed — a mainline layer's change above it stages mainline. (A
276
273
  // 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));
274
+ if ((o.l & (CONFIG_HELD | CONFIG_OVERRIDE)) === CONFIG_HELD && heldKeyChanged(e, r)) holdNode(n, txOf(o));
278
275
  // A28 (4): a batch written inside a creation-time pass is promoted — its
279
276
  // late-materialized leaf with it.
280
- if (o.u & CONFIG_PROMOTED) notePromotedWrite(n);
277
+ if (o.l & CONFIG_PROMOTED) notePromotedWrite(n);
281
278
  }
282
279
 
283
280
  function getNode(e, n,
@@ -302,7 +299,9 @@ t = -1) {
302
299
  // the hottest store path, and the disabled cost must stay one null
303
300
  // check (nodes created before enable() stay generically named).
304
301
  if (attrHooks !== null) {
305
- l._name = storeLabel(e) + "." + String(n);
302
+ const t = storeRoot(e);
303
+ l._name = (storeNames.get(t) ?? "store") + "." + String(n);
304
+ l._loc = storeLocs.get(t);
306
305
  stampNodeOwner(l, e);
307
306
  }
308
307
  r[n] = o;
@@ -339,8 +338,8 @@ function getHasNode(e, n) {
339
338
  // pending backing staged — so whoever asked for it reads the frame it
340
339
  // would have read had the node been there from the write.
341
340
  if (e.pb !== null && !directCommit) {
342
- n.pe = preBatch(e);
343
- n.Ae = e.pb;
341
+ n.Ie = preBatch(e);
342
+ n.ue = e.pb;
344
343
  queuePendingNode(n);
345
344
  }
346
345
  }
@@ -356,11 +355,11 @@ function getHasNode(e, n) {
356
355
  * staging: nothing allocates, nothing is swept at the commit (dbmon's tick
357
356
  * re-created, queued, swept and dropped ~7000 container nodes). If the
358
357
  * flush parks, the seam materializes and holds the node then
359
- * (`_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;
358
+ * (`storeParkHook`) — the one case the staging home exists for. */ function stageOn(e, n) {
359
+ if (e.k === null && passLane === null && (context === null || globalQueue.q) && !deriveHolds(e)) return;
361
360
  const t = getContainerNode(e);
362
- if (t.Ae === NOT_PENDING) queuePendingNode(t);
363
- t.Ae = n;
361
+ if (t.ue === NOT_PENDING) queuePendingNode(t);
362
+ t.ue = n;
364
363
  // A28 (4): a write issued inside a pass outside a flush (a creation-time
365
364
  // derive) is promoted at the pass's end — the batch with it.
366
365
  if (context !== null) notePromotedWrite(t);
@@ -370,10 +369,10 @@ function getHasNode(e, n) {
370
369
  /** The projection's derive is held by a transaction and this is its own
371
370
  * write (not a user setter's): the staging is the derive's (`holdWithDerive`). */ function deriveHolds(e) {
372
371
  const n = e.fam?.node;
373
- return n != null && !userWrite && (n.u & CONFIG_HELD) !== 0;
372
+ return n != null && !userWrite && (n.l & CONFIG_HELD) !== 0;
374
373
  }
375
374
 
376
- GlobalQueue.hn = e => {
375
+ const storePark = e => {
377
376
  for (let n = 0; n < foldList.length; n++) {
378
377
  const t = foldList[n];
379
378
  if (t.k === null && t.pb !== null) holdNode(getContainerNode(t), e);
@@ -402,8 +401,9 @@ function bumpDeep(e) {
402
401
  // on the guess (`laneWrite` → `supersede`, judged by `containerEquals`);
403
402
  // otherwise the structural readers are walked (the staging is already on
404
403
  // 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));
404
+ if (e.l & CONFIG_OVERRIDE) setSignal(e, n); else {
405
+ if (e.l & CONFIG_HELD) joinFuture(txOf(e));
406
+ if (snapshotCaptureActive) captureWriteSnapshot(e, e.Ie);
407
407
  insertSubs(e);
408
408
  schedule();
409
409
  }
@@ -426,12 +426,17 @@ function stampNodeOwner(e, n) {
426
426
 
427
427
  const storeNames = new WeakMap;
428
428
 
429
- function nameStore(e, n) {
430
- if (attrHooks !== null && n) storeNames.set(e[$TARGET], n);
431
- }
429
+ // The store's `loc` option, carried onto every property node beside its
430
+ // label (the same gate: nodes created before the engine is installed carry
431
+ // neither).
432
+ const storeLocs = new WeakMap;
432
433
 
433
- function storeLabel(e) {
434
- return storeNames.get(storeRoot(e)) ?? "store";
434
+ function nameStore(e, n, t) {
435
+ if (attrHooks !== null) {
436
+ const r = e[$TARGET];
437
+ if (n) storeNames.set(r, n);
438
+ if (t) storeLocs.set(r, t);
439
+ }
435
440
  }
436
441
 
437
442
  // ---------------------------------------------------------------------------
@@ -520,7 +525,7 @@ const plainProto = e => {
520
525
  if (e.kc > OVERLAY_REBUILD_MAX_KEYS) return false;
521
526
  if (e.del !== null && e.del.size !== 0) return true;
522
527
  const t = e.wk;
523
- if (t !== null && t !== WK_ALL && t.size <= OVERLAY_REBUILD_MIN_ADDS) return false;
528
+ if (t !== null && t !== WK_ALL && t.u === undefined && t.size <= OVERLAY_REBUILD_MIN_ADDS) return false;
524
529
  const r = e.v;
525
530
  let o = 0;
526
531
  for (const e of Reflect.ownKeys(n)) if (!hasOwn.call(r, e) && ++o > OVERLAY_REBUILD_MIN_ADDS) return true;
@@ -540,7 +545,7 @@ const plainProto = e => {
540
545
  e.pb = t;
541
546
  e.ovl = false;
542
547
  // The staging IS the pending backing: re-point the container node.
543
- if (e.k !== null && e.k.Ae === n) e.k.Ae = t;
548
+ if (e.k !== null && e.k.ue === n) e.k.ue = t;
544
549
  return t;
545
550
  }
546
551
 
@@ -561,7 +566,7 @@ const plainProto = e => {
561
566
  // mainline setter mid-hold publishes mainline; the held keys stay held).
562
567
  n = e.pb = cloneRaw(n, e);
563
568
  const t = e.k;
564
- if (t !== null && !(t.u & CONFIG_HELD)) t.Ae = n;
569
+ if (t !== null && !(t.l & CONFIG_HELD)) t.ue = n;
565
570
  } else if (n === null) {
566
571
  const t = e.v;
567
572
  if (!directCommit) queueFold(e);
@@ -640,7 +645,7 @@ const plainProto = e => {
640
645
  } else r = e.v;
641
646
  if (directCommit) {
642
647
  e.pb = null;
643
- if (e.k !== null) e.k.pe = n;
648
+ if (e.k !== null) e.k.Ie = n;
644
649
  } else {
645
650
  // Eager (INTERNALS §3): the backing swaps now — handlers read the
646
651
  // adopted object before the flush — while the container node, when
@@ -736,7 +741,7 @@ let foldIndex = null;
736
741
  if (r.pb === null) continue;
737
742
  // folded already (a direct commit)
738
743
  const o = r.k;
739
- if (o !== null && o.Ae !== NOT_PENDING) {
744
+ if (o !== null && o.ue !== NOT_PENDING) {
740
745
  // held: commits with its hold
741
746
  foldList.push(r);
742
747
  foldOlds.push(n[t]);
@@ -747,7 +752,7 @@ let foldIndex = null;
747
752
  }
748
753
  if (deferredReleases.size !== 0) {
749
754
  for (const e of deferredReleases) {
750
- if (e.Ae === NOT_PENDING && !(e.u & CONFIG_OVERRIDE) && e.t === null) {
755
+ if (e.o === null && !slotKept(e)) {
751
756
  deferredReleases.delete(e);
752
757
  releaseSlot(e);
753
758
  }
@@ -758,28 +763,33 @@ let foldIndex = null;
758
763
  /** Core asks: is a slot node's truth in flight? Its family's derive is —
759
764
  * lanes (A17, #2864: a guess mid-refetch stands in for the flight) and
760
765
  * marks (an ambient `affects()` over a declared reload lives to the
761
- * landing) read it. */ GlobalQueue.F = e => {
762
- const n = e.yt?.fam;
763
- return n != null && n.node !== null && (n.node.G & STATUS_PENDING) !== 0;
766
+ * landing) read it. */ GlobalQueue.k = e => {
767
+ const n = e.O?.fam;
768
+ return n != null && n.node !== null && (n.node.m & STATUS_PENDING) !== 0;
764
769
  };
765
770
 
766
771
  /** Core asks (lanes.ts `covered`): the value a slot node's guess covered.
767
772
  * A chained link's committed value is the inner store's at the last
768
773
  * 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 => {
770
- const n = e.yt;
774
+ * (#3672: the revert compares against the base's live value). */ GlobalQueue.fn = e => {
775
+ const n = e.O;
771
776
  if (n.ch) {
772
- const t = e.bt;
777
+ const t = e.Vt;
773
778
  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]));
779
+ if (n.h !== null && n.h[t] === e) e.Ie = untrack(() => t in r); else if (n.n !== null && n.n[t] === e) e.Ie = untrack(() => unwrapValue(r[t]));
775
780
  }
776
- return e.pe;
781
+ return e.Ie;
777
782
  };
778
783
 
779
- GlobalQueue.Sn = () => {
784
+ installStoreHooks(storePark, () => {
780
785
  drainFolds();
781
786
  optHooks?.sweep();
782
- };
787
+ });
788
+
789
+ /** The store half of `affects()` (store/affects.ts) makes its marks' nodes
790
+ * and walks records through these: it never imports the engine. */ GlobalQueue.no = getNode;
791
+
792
+ GlobalQueue.so = isWrappable;
783
793
 
784
794
  /** A projection's creation run commits directly (a memo's first value is
785
795
  * its `_value`, not a staging): the draft still writes a clone of the seed,
@@ -807,7 +817,7 @@ function runDirect(e) {
807
817
  // the adopted object (shared ownership — never cloned).
808
818
  e.pb = null;
809
819
  e.wk = null;
810
- } else if (e.ovl && (e.v !== n || !overlayRebuilds(e, r))) {
820
+ } else if (e.ovl && (e.v !== n || !overlayRebuilds(e, r) && t?.T?.lt !== n)) {
811
821
  // Overlay flatten (#3044): the backing keeps its identity, so the
812
822
  // `t.v === old` gate below skips path copying (the parent slot
813
823
  // already points here) and the adopted-notify (setter
@@ -821,8 +831,8 @@ function runDirect(e) {
821
831
  const t = e.v;
822
832
  const o = e.wk;
823
833
  if (o !== null && o !== WK_ALL) {
824
- for (const e of o) {
825
- if (hasOwn.call(r, e)) copyOwn(t, r, e); else delete t[e];
834
+ for (let e = o; e !== undefined; e = e.u) for (const n of e) {
835
+ if (hasOwn.call(r, n)) copyOwn(t, r, n); else delete t[n];
826
836
  }
827
837
  } else {
828
838
  for (const e of Reflect.ownKeys(r)) {
@@ -849,7 +859,7 @@ function runDirect(e) {
849
859
  // subscribes to was a transient staging home (INTERNALS §5: an
850
860
  // observer-less write holds a transient record until the fold,
851
861
  // 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;
862
+ if (t.o === null) e.k = null; else if (t.Ie instanceof LaneView) t.Ie.base = e.v; else t.Ie = e.v;
853
863
  }
854
864
  if (e.v !== n && e.u) {
855
865
  const t = parentSlotKey(e, n);
@@ -922,9 +932,9 @@ function reportReplacedContainers(e, n, t, r) {
922
932
 
923
933
  function heldDerivation(e, n, t) {
924
934
  if (e.fam === null || !userWrite) return false;
925
- if (n !== undefined) return (n.u & (CONFIG_HELD | CONFIG_MANUAL_WRITE)) === CONFIG_HELD && txOf(n) !== flushTransaction;
935
+ if (n !== undefined) return (n.l & (CONFIG_HELD | CONFIG_MANUAL_WRITE)) === CONFIG_HELD && txOf(n) !== flushTransaction;
926
936
  const r = e.k;
927
- return r !== null && (r.u & CONFIG_HELD) !== 0 && txOf(r) !== flushTransaction && heldKeyChanged(e, t);
937
+ return r !== null && (r.l & CONFIG_HELD) !== 0 && txOf(r) !== flushTransaction && heldKeyChanged(e, t);
928
938
  }
929
939
 
930
940
  /** Setter exit: one node write per changed observed key (the leaves stage
@@ -954,7 +964,10 @@ function heldDerivation(e, n, t) {
954
964
  // value can change when ANY key is written), or a non-plain prototype
955
965
  // (class instances: prototype getters derive from arbitrary fields).
956
966
  // Overlay pbs chain to the COMMITTED object (#3044) — plainness is the
957
- // committed container's prototype, not the overlay's.
967
+ // committed container's prototype, not the overlay's. THIS setter's keys
968
+ // only (D-8, #3743): a key an earlier setter of the batch wrote and this
969
+ // one did not is no second proposal — its node was told then, and telling
970
+ // a held node its unchanged value again would join this tick to the hold.
958
971
  const o = e.wk;
959
972
  const i = o === WK_ALL || e.a === true || !plainProto(e.ovl ? e.v : n) ? null : o;
960
973
  if (attrHooks !== null) reportReplacedContainers(e, t, n, i);
@@ -970,15 +983,18 @@ function heldDerivation(e, n, t) {
970
983
  if (o.acc === true || hasOwn.call(n, i) && lookupGetter.call(n, i) !== undefined) {
971
984
  // Accessor keys: the node is linked for shape-change notification,
972
985
  // its value is never served (the getter runs with the proxy
973
- // receiver on read) — FORCE wakes the readers.
986
+ // receiver on read) — FORCE wakes the readers. A data descriptor
987
+ // falls through so the cleared-acc hot read serves it (#3949);
988
+ // an own getter's descriptor has no `value`, so the compare below
989
+ // would otherwise keep the getter's result (or FORCE) for `undefined`.
974
990
  o.acc = isOwnAccessor(n, i);
975
991
  const e = Object.getOwnPropertyDescriptor(t, i);
976
992
  const r = Object.getOwnPropertyDescriptor(n, i);
977
- if (e && (e.get || e.set) || r && (r.get || r.set)) {
993
+ if (!r || r.get || r.set) {
978
994
  if (e?.get !== r?.get || e?.set !== r?.set || e?.value !== r?.value) setSignal(o, () => FORCE);
979
995
  continue;
980
996
  }
981
- if (!isEqual(e?.value, r?.value)) setSignal(o, () => r?.value);
997
+ if (e?.get || e?.set || !isEqual(e?.value, r.value)) setSignal(o, () => r.value);
982
998
  continue;
983
999
  }
984
1000
  const l = e.del !== null && e.del.has(i) ? undefined : n[i];
@@ -987,9 +1003,9 @@ function heldDerivation(e, n, t) {
987
1003
  // The derive's write is a derivation; a user setter's is a proposal
988
1004
  // (A34 (3)'s discriminator).
989
1005
  if (!userWrite) {
990
- o.u &= ~CONFIG_MANUAL_WRITE;
1006
+ o.l &= ~CONFIG_MANUAL_WRITE;
991
1007
  holdWithDerive(e, o);
992
- } else o.u |= CONFIG_MANUAL_WRITE;
1008
+ } else o.l |= CONFIG_MANUAL_WRITE;
993
1009
  }
994
1010
  }
995
1011
  }
@@ -1020,7 +1036,7 @@ function heldDerivation(e, n, t) {
1020
1036
  // container carrying an arrangement guess is told whether or not anyone
1021
1037
  // subscribes: the write is a landing on the guess (`notifyContainer`).
1022
1038
  const u = e.k;
1023
- if (u !== null && (u.t !== null || u.u & CONFIG_OVERRIDE)) {
1039
+ if (u !== null && (u.o !== null || u.l & CONFIG_OVERRIDE)) {
1024
1040
  let r;
1025
1041
  if (e.ovl) {
1026
1042
  r = e.del !== null && e.del.size !== 0;
@@ -1069,13 +1085,14 @@ function membershipChanged(e, n) {
1069
1085
  e.acc = isOwnAccessor(r, n);
1070
1086
  const o = Object.getOwnPropertyDescriptor(t, n);
1071
1087
  const i = Object.getOwnPropertyDescriptor(r, n);
1072
- if (o && (o.get || o.set) || i && (i.get || i.set)) {
1088
+ // Data descriptors fall through, as in notifyWrites (#3949).
1089
+ if (!i || i.get || i.set) {
1073
1090
  if (o?.get !== i?.get || o?.set !== i?.set || o?.value !== i?.value) setSignal(e, () => FORCE);
1074
1091
  return;
1075
1092
  }
1076
1093
  const l = o?.value;
1077
1094
  const u = i?.value;
1078
- if (!isEqual(l, u) && !targetsEqual(l, u)) setSignal(e, typeof u === "function" ? () => u : u);
1095
+ if (o?.get || o?.set || !isEqual(l, u) && !targetsEqual(l, u)) setSignal(e, typeof u === "function" ? () => u : u);
1079
1096
  } else {
1080
1097
  const o = t[n];
1081
1098
  const i = r[n];
@@ -1121,7 +1138,7 @@ function hasAccessorFlag(e) {
1121
1138
  for (const o of Reflect.ownKeys(r)) if (e || o in n !== o in t) setSignal(r[o], o in t);
1122
1139
  }
1123
1140
  const o = e.k;
1124
- if (o !== null && (o.t !== null || o.u & CONFIG_OVERRIDE)) {
1141
+ if (o !== null && (o.o !== null || o.l & CONFIG_OVERRIDE)) {
1125
1142
  const e = Array.isArray(t) && Array.isArray(n) ? arrayStructureChanged(n, t) : membershipChanged(n, t);
1126
1143
  if (e) notifyContainer(o, t);
1127
1144
  }
@@ -1178,7 +1195,7 @@ function inDraft(e) {
1178
1195
  * "adoption is eager by contract" — and the node keeps the frame committed
1179
1196
  * readers under a hold still see), `t.v` otherwise. */ function committed(e) {
1180
1197
  const n = e.k;
1181
- if (n !== null) return n.Ae !== NOT_PENDING ? asBacking(n.pe) : e.v;
1198
+ if (n !== null) return n.ue !== NOT_PENDING ? asBacking(n.Ie) : e.v;
1182
1199
  // A node-less staging (`stageOn`): the fold queue remembers the frame.
1183
1200
  return e.pb !== null ? preBatch(e) : e.v;
1184
1201
  }
@@ -1190,8 +1207,8 @@ function inDraft(e) {
1190
1207
  * committed frame. */ function stagingReader() {
1191
1208
  let e = context;
1192
1209
  if (e === null) return null;
1193
- if (e.Ht) e = e.gt ?? null;
1194
- return e === null || e.u & CONFIG_CHILDREN_FORBIDDEN ? null : e;
1210
+ if (e.Pt) e = e.Ht ?? null;
1211
+ return e === null || e.l & CONFIG_CHILDREN_FORBIDDEN ? null : e;
1195
1212
  }
1196
1213
 
1197
1214
  /** The backing a read serves (RUL-1, signal parity): the draft and the write
@@ -1229,10 +1246,10 @@ function inDraft(e) {
1229
1246
  if (writeOverride && !userWrite) {
1230
1247
  const t = n !== undefined ? e.n?.[n] : undefined;
1231
1248
  if (t !== undefined) {
1232
- if ((t.u & (CONFIG_HELD | CONFIG_OVERRIDE)) === CONFIG_HELD) joinPassTx(txOf(t));
1249
+ if ((t.l & (CONFIG_HELD | CONFIG_OVERRIDE)) === CONFIG_HELD) joinPassTx(txOf(t));
1233
1250
  } else {
1234
1251
  const t = e.k;
1235
- if (t !== null && (t.u & (CONFIG_HELD | CONFIG_OVERRIDE)) === CONFIG_HELD && (n === undefined || heldKeyChanged(e, n))) joinPassTx(txOf(t));
1252
+ if (t !== null && (t.l & (CONFIG_HELD | CONFIG_OVERRIDE)) === CONFIG_HELD && (n === undefined || heldKeyChanged(e, n))) joinPassTx(txOf(t));
1236
1253
  }
1237
1254
  }
1238
1255
  return r;
@@ -1244,9 +1261,9 @@ function inDraft(e) {
1244
1261
  // it).
1245
1262
  if (n !== undefined) {
1246
1263
  const t = e.n?.[n];
1247
- if (t !== undefined && t.u & CONFIG_OVERRIDE) return committed(e);
1264
+ if (t !== undefined && t.l & CONFIG_OVERRIDE) return committed(e);
1248
1265
  }
1249
- const i = o !== null && (o.u & CONFIG_HELD) !== 0;
1266
+ const i = o !== null && (o.l & CONFIG_HELD) !== 0;
1250
1267
  // A held container: the hold's unit is the key (#3706). A key the HELD
1251
1268
  // staging left unchanged reads the ambient view — a mainline layer's
1252
1269
  // write above the hold publishes mainline (#3688, #3612); a key it
@@ -1264,7 +1281,7 @@ function inDraft(e) {
1264
1281
  // probe) judges the container like any node — for a key the batch
1265
1282
  // changed; an unchanged key is committed and final (A22: pending is per
1266
1283
  // 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;
1284
+ if (verdict === null || !i && n !== undefined && !keyChanged(e, n, t)) return i ? asBacking(o.Ie) : e.v;
1268
1285
  return o === null ? committed(e) : asBacking(read(o));
1269
1286
  }
1270
1287
  // A pass reading a key the batch left unchanged: the same value in both
@@ -1281,13 +1298,13 @@ function inDraft(e) {
1281
1298
  // frames, a changed one is what the mark already stands for. The
1282
1299
  // per-key question is asked once per pass, not once per key (a
1283
1300
  // `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;
1301
+ if (l.R & REACTIVE_STAGED_READ && !(l.l & (CONFIG_VERDICT | CONFIG_CHILDREN_FORBIDDEN))) return r;
1285
1302
  if (n !== undefined && !keyChanged(e, n, t)) return committed(e);
1286
1303
  // A node-less staging (`stageOn`): what `read()` of its node would do
1287
1304
  // for this pass — a verdict reader sees the screen, any other derives
1288
1305
  // from the staging and is marked as having read it.
1289
1306
  if (o === null) {
1290
- if (l.u & CONFIG_VERDICT && stagedScreen(l)) return committed(e);
1307
+ if (l.l & CONFIG_VERDICT && stagedScreen(l)) return committed(e);
1291
1308
  stagedRead(l);
1292
1309
  return r;
1293
1310
  }
@@ -1297,11 +1314,52 @@ function inDraft(e) {
1297
1314
 
1298
1315
  /** Did the HELD staging (the container node's, not a mainline layer above
1299
1316
  * it) change `key`? */ function heldKeyChanged(e, n, t = true) {
1300
- return changedBetween(e, e.k.pe, e.k.Ae, n, t);
1317
+ return changedBetween(e, e.k.Ie, e.k.ue, n, t) && !adoptChild(e, n);
1318
+ }
1319
+
1320
+ /** The values of a held frame, built once per frame: neither the committed
1321
+ * frame nor the held staging changes while the hold stands (a draft writes a
1322
+ * clone), so the set stays true for every first read of the hold. */ const frameRaws = new WeakMap;
1323
+
1324
+ function rawsOf(e) {
1325
+ let n = frameRaws.get(e);
1326
+ if (n === undefined) frameRaws.set(e, n = new Set(Object.values(e)));
1327
+ return n;
1328
+ }
1329
+
1330
+ /** The same row at a slot, as the adoption pairs rows (reconcile.ts): two
1331
+ * objects whose keys agree — positionally for an unkeyed family. */ function sameRow(e, n, t) {
1332
+ return e !== null && typeof e === "object" && n !== null && typeof n === "object" && (t === null || sameKey(t(e), t(n)));
1333
+ }
1334
+
1335
+ /** A key of a held adoption whose old and new values are the same row that
1336
+ * nothing had proxied when the adoption ran, so `descend` never paired
1337
+ * them: paired now, as it would have been (#3712) — one target, the old
1338
+ * raw its committed frame, the new raw its staging, held by the same
1339
+ * transaction. Both raws resolve to it, so the slot reads as unchanged and
1340
+ * the row's own leaves decide: `id` the same in both frames holds no one,
1341
+ * `version` holds. A record's keys pair by name; an array's by the family's
1342
+ * key — without a family the pairing is not known here and the hold stands. */ function adoptChild(e, n) {
1343
+ const t = e.k;
1344
+ const r = asBacking(t.Ie);
1345
+ const o = r[n];
1346
+ const i = t.ue[n];
1347
+ const l = Array.isArray(r) ? e.fam?.key : null;
1348
+ 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
+ // A raw in both frames (the same objects swapped or reordered) is its
1350
+ // own row: pairing it with another would alias the two after the commit.
1351
+ rawsOf(r).has(i) || rawsOf(t.ue).has(o)) return false;
1352
+ // A raw-marked value wraps to itself, a proxy to its own target: neither
1353
+ // is a fresh target around `ov`.
1354
+ const u = wrap(o, e, n)[$TARGET];
1355
+ if (u === undefined || u.v !== o) return false;
1356
+ adoptPB(u, i, false);
1357
+ holdNode(getContainerNode(u), txOf(t));
1358
+ return true;
1301
1359
  }
1302
1360
 
1303
1361
  /** 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);
1362
+ return e.fam !== null && context === null && !(e.k !== null && e.k.l & CONFIG_HELD);
1305
1363
  }
1306
1364
 
1307
1365
  /** Did this batch change `key` — value (slot equality), presence, and (for
@@ -1335,7 +1393,7 @@ function changedBetween(e, n, t, r, o) {
1335
1393
  * tracked or not, applies the hold and A28 rules a signal's read does. A
1336
1394
  * projection's unheld staging is ahead for a handler (`familyAhead`). */ function nodeValue(e, n, t) {
1337
1395
  let r = read(e);
1338
- if (e.Ae !== NOT_PENDING && !(e.u & CONFIG_HELD) && context === null && t.fam !== null) r = e.Ae;
1396
+ if (e.ue !== NOT_PENDING && !(e.l & CONFIG_HELD) && context === null && t.fam !== null) r = e.ue;
1339
1397
  return r === FORCE ? n : r;
1340
1398
  }
1341
1399
 
@@ -1355,8 +1413,8 @@ function changedBetween(e, n, t, r, o) {
1355
1413
  if (l) {
1356
1414
  // The link's committed value follows the inner store's (what a lane
1357
1415
  // over it covers — its reveal and revert compare against it, S4).
1358
- o.pe = t;
1359
- if (o.u & CONFIG_OVERRIDE) u = n;
1416
+ o.Ie = t;
1417
+ if (o.l & CONFIG_OVERRIDE) u = n;
1360
1418
  } else u = n;
1361
1419
  }
1362
1420
  }
@@ -1377,7 +1435,7 @@ function changedBetween(e, n, t, r, o) {
1377
1435
  }
1378
1436
 
1379
1437
  /** 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;
1438
+ for (let t = e.C; t !== null; t = t.G) if (t.S === n) return true;
1381
1439
  return false;
1382
1440
  }
1383
1441
 
@@ -1396,7 +1454,7 @@ function changedBetween(e, n, t, r, o) {
1396
1454
  // reader of a memo would — linked, so it re-runs at the landing (the
1397
1455
  // next settled pass pulls without linking and the stale link trims), a
1398
1456
  // verdict reader registered, a tracked pass suspended.
1399
- if (n.G & (STATUS_PENDING | STATUS_ERROR)) {
1457
+ if (n.m & (STATUS_PENDING | STATUS_ERROR)) {
1400
1458
  // A render effect outside the flight's own flush is the frame, not a
1401
1459
  // derivation: it keeps what it shows and learns of the landing from the
1402
1460
  // leaves the landing changes (unchanged leaves say nothing — it is not a
@@ -1404,9 +1462,10 @@ function changedBetween(e, n, t, r, o) {
1404
1462
  // One that already observes the flight (linked by the pass that saw it
1405
1463
  // go up — the frame's hold, `blocked`) keeps observing it: a re-run for
1406
1464
  // another reason (a guess it read, the seam) that dropped the link would
1407
- // release the hold with the flight still up.
1465
+ // release the hold with the flight still up. An errored derive has no
1466
+ // landing to learn of: the error is what the frame shows.
1408
1467
  const t = context;
1409
- if (t !== null && t.i === EFFECT_RENDER && n.u & CONFIG_HELD && flushTransaction !== txOf(n) && !linkedTo(t, n)) return;
1468
+ if (t !== null && t.i === EFFECT_RENDER && !(n.m & STATUS_ERROR) && n.l & CONFIG_HELD && flushTransaction !== txOf(n) && !linkedTo(t, n)) return;
1410
1469
  // An optimistic family carrying guesses that stand in for the flight
1411
1470
  // (A17): the guesses are the display — a reader is served the leaves
1412
1471
  // (committed plus the guesses) and does not suspend on the derive; a
@@ -1414,7 +1473,7 @@ function changedBetween(e, n, t, r, o) {
1414
1473
  // up). The family's, not the target's: the edit stands in for the one
1415
1474
  // flight the family has.
1416
1475
  const r = e.fam.overlaid;
1417
- if (verdict === null && n.G & STATUS_PENDING && r !== undefined && r.size !== 0) return;
1476
+ if (verdict === null && n.m & STATUS_PENDING && r !== undefined && r.size !== 0) return;
1418
1477
  read(n);
1419
1478
  } else untrack(() => read(n));
1420
1479
  }
@@ -1427,12 +1486,12 @@ function changedBetween(e, n, t, r, o) {
1427
1486
  * classification, as core reads it — `heldNotFinal`). */ function holdWithDerive(e, n) {
1428
1487
  const t = e.fam?.node;
1429
1488
  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 &&
1489
+ t.l & CONFIG_HELD && n.ue !== NOT_PENDING &&
1431
1490
  // A node carrying a guess is the lane's: its staging is the truth beneath
1432
1491
  // the guess (`setSignal` → `laneWrite`), never re-homed by the store.
1433
- !(n.u & (CONFIG_HELD | CONFIG_OVERRIDE))) {
1492
+ !(n.l & (CONFIG_HELD | CONFIG_OVERRIDE))) {
1434
1493
  holdNode(n, txOf(t));
1435
- n.m.It = t.m.It;
1494
+ n.T.ut = t.T.ut;
1436
1495
  }
1437
1496
  }
1438
1497
 
@@ -1475,6 +1534,22 @@ function changedBetween(e, n, t, r, o) {
1475
1534
 
1476
1535
  // ---------------------------------------------------------------------------
1477
1536
  // traps
1537
+ /** A trap write: the target is told at the setter's exit, the key recorded
1538
+ * in this setter's `wk`. A setter's first write to a target still staged by
1539
+ * an earlier setter of the batch opens a set of its own (the notify visits
1540
+ * it, D-8) and chains the earlier one behind for the fold's union. */ function touch(e, n) {
1541
+ let t = e.wk;
1542
+ if (!pendingNotify.has(e)) {
1543
+ pendingNotify.add(e);
1544
+ if (t !== null && t !== WK_ALL) {
1545
+ t = new Set;
1546
+ t.u = e.wk;
1547
+ e.wk = t;
1548
+ }
1549
+ }
1550
+ if (t !== WK_ALL) (e.wk ??= new Set).add(n);
1551
+ }
1552
+
1478
1553
  const traps = {
1479
1554
  get(e, n, t) {
1480
1555
  // One typeof gates every brand-symbol compare off the hot string path.
@@ -1503,7 +1578,7 @@ const traps = {
1503
1578
  }
1504
1579
  // (The witness before the pull: a mark on an uninitialized derived
1505
1580
  // store is witnessed, then the pull throws — loading, declared pending.)
1506
- if (verdict !== null && affectsHooks !== null && getObserver() === null) affectsHooks.witness(e, n);
1581
+ if (verdict !== null && affectsHooks !== null && getObserver() === null) affectsHooks.ro(e, n);
1507
1582
  if (e.fam !== null) pullFamily(e);
1508
1583
  // Hot inline case: existing PLAIN node (non-accessor) read outside any
1509
1584
  // draft — the dbmon/uibench effect re-read shape. Core's `read()` serves
@@ -1518,7 +1593,7 @@ const traps = {
1518
1593
  const r = e.n?.[n];
1519
1594
  if (writeScopes === null && !writeOverride) {
1520
1595
  const t = r;
1521
- if (t !== undefined && t.acc !== true && (!e.ch || t.u & CONFIG_OVERRIDE)) {
1596
+ if (t !== undefined && t.acc !== true && (!e.ch || t.l & CONFIG_OVERRIDE)) {
1522
1597
  const r = nodeValue(t, undefined, e);
1523
1598
  if (r === null || typeof r !== "object") return r;
1524
1599
  if (e.s) return serveShallow(e, n, r);
@@ -1600,7 +1675,7 @@ const traps = {
1600
1675
  if (n === $OWNER || n === $RECORD) return false;
1601
1676
  // (The witness before the pull: a mark on an uninitialized derived
1602
1677
  // store is witnessed, then the pull throws — loading, declared pending.)
1603
- if (verdict !== null && affectsHooks !== null && getObserver() === null) affectsHooks.witness(e, n);
1678
+ if (verdict !== null && affectsHooks !== null && getObserver() === null) affectsHooks.ro(e, n);
1604
1679
  if (e.fam !== null) pullFamily(e);
1605
1680
  const t = readSource(e, n);
1606
1681
  // A tracked reader's presence node answers (born from the two frames;
@@ -1613,9 +1688,9 @@ const traps = {
1613
1688
  const o = read(r);
1614
1689
  if (!e.ch || t !== e.v) return !!o;
1615
1690
  const i = n in t;
1616
- r.pe = i;
1691
+ r.Ie = i;
1617
1692
  // the link follows the inner store (S4)
1618
- if (r.u & CONFIG_OVERRIDE) return !!o;
1693
+ if (r.l & CONFIG_OVERRIDE) return !!o;
1619
1694
  return i;
1620
1695
  } else if (optRead(e)) {
1621
1696
  const t = optHooks.has(e, n);
@@ -1625,14 +1700,14 @@ const traps = {
1625
1700
  return n in t && !(e.del !== null && t === e.pb && e.del.has(n));
1626
1701
  },
1627
1702
  ownKeys(e) {
1628
- if (verdict !== null && affectsHooks !== null && getObserver() === null) affectsHooks.witness(e, undefined);
1703
+ if (verdict !== null && affectsHooks !== null && getObserver() === null) affectsHooks.ro(e, undefined);
1629
1704
  if (e.fam !== null) pullFamily(e);
1630
1705
  return visibleKeys(e, enumerationSource(e));
1631
1706
  },
1632
1707
  getOwnPropertyDescriptor(e, n) {
1633
1708
  if (n === $OWNER || n === $RECORD) return undefined;
1634
1709
  const t = getObserver();
1635
- if (verdict !== null && affectsHooks !== null && t === null) affectsHooks.witness(e, n);
1710
+ if (verdict !== null && affectsHooks !== null && t === null) affectsHooks.ro(e, n);
1636
1711
  if (e.fam !== null) pullFamily(e);
1637
1712
  // An enumerator (spread, Object.entries) already holds the container
1638
1713
  // node and reads its frame; a descriptor read on its own tracks
@@ -1650,7 +1725,7 @@ const traps = {
1650
1725
  const t = getHasNode(e, n);
1651
1726
  const r = read(t);
1652
1727
  i = readSource(e, n, true);
1653
- if (!r && (!e.ch || t.u & CONFIG_OVERRIDE || !(n in i))) return undefined;
1728
+ if (!r && (!e.ch || t.l & CONFIG_OVERRIDE || !(n in i))) return undefined;
1654
1729
  } else i = readSource(e, n, true);
1655
1730
  let l;
1656
1731
  if (!o && optRead(e)) {
@@ -1671,15 +1746,22 @@ const traps = {
1671
1746
  // pollution guard (core R30)
1672
1747
  const i = e.s ? t : unwrapValue(t);
1673
1748
  const l = ensurePB(e);
1674
- pendingNotify.add(e);
1749
+ // #4146 (R1: node existence is unobservable; #3706: the hold's unit is
1750
+ // the key): a user setter writing a key a HELD container's staging left
1751
+ // unchanged would write it into the held staging — read as the hold's
1752
+ // until its landing, while the same key with a leaf (a live reader)
1753
+ // publishes at the flush. The key gets its leaf first, born from the two
1754
+ // frames (`bornStaged`: a key the hold left unchanged — or added — is
1755
+ // unstaged and held by no one; a key the hold changed is born held, A34
1756
+ // (1)), and the write stages on it (A28; a write by the holder joins it,
1757
+ // A34) — as it would with a reader's leaf. Kept while the container is
1758
+ // held (`deferredReleases`).
1759
+ if (userWrite && e.k?.l & CONFIG_HELD) deferredReleases.add(getNode(e, n));
1675
1760
  if (Array.isArray(l)) {
1676
- if (n === "length") e.wk = WK_ALL; else if (e.wk !== WK_ALL) {
1677
- const t = e.wk ??= new Set;
1678
- t.add(n);
1679
- t.add("length");
1680
- }
1761
+ touch(e, n);
1762
+ if (n === "length") e.wk = WK_ALL; else touch(e, "length");
1681
1763
  } else {
1682
- if (e.wk !== WK_ALL) (e.wk ??= new Set).add(n);
1764
+ touch(e, n);
1683
1765
  if (!(n in l)) e.kc++;
1684
1766
  }
1685
1767
  if (UNSAFE_KEYS.has(n)) {
@@ -1717,8 +1799,7 @@ const traps = {
1717
1799
  };
1718
1800
  const i = ensurePB(e);
1719
1801
  if (e.a || !(t.enumerable && t.writable && t.configurable)) e.sc = 1;
1720
- pendingNotify.add(e);
1721
- if (e.wk !== WK_ALL) (e.wk ??= new Set).add(n);
1802
+ touch(e, n);
1722
1803
  Object.defineProperty(i, n, t);
1723
1804
  if (e.del !== null) e.del.delete(n);
1724
1805
  if (o) notifyWrites(e);
@@ -1729,10 +1810,22 @@ const traps = {
1729
1810
  const r = !t && writeOverride;
1730
1811
  if (!t && !r) return true;
1731
1812
  const o = ensurePB(e);
1732
- pendingNotify.add(e);
1733
- if (e.wk !== WK_ALL) (e.wk ??= new Set).add(n);
1734
- delete o[n];
1735
- if (e.ovl && hasOwn.call(e.v, n)) (e.del ??= new Set).add(n);
1813
+ touch(e, n);
1814
+ // The key count (`kc`, the overlay gate and the rebuild cap) follows a
1815
+ // delete the way it follows an add: an own key of the draft leaving — a
1816
+ // clone's key, or an overlay's own add — is one fewer now. An overlay's
1817
+ // override of a committed key is the exception: the committed key's
1818
+ // deletion is recorded in `del` and counted at the rebuild, as before.
1819
+ // Without the decrement the natural "delete the old key, set the new
1820
+ // one" shape drifted the count up by one per write until a three-key
1821
+ // object crossed the overlay threshold and paid a rebuild on every
1822
+ // commit after.
1823
+ const i = e.ovl && hasOwn.call(e.v, n);
1824
+ if (hasOwn.call(o, n)) {
1825
+ delete o[n];
1826
+ i || e.kc--;
1827
+ }
1828
+ if (i) (e.del ??= new Set).add(n);
1736
1829
  if (r) notifyWrites(e);
1737
1830
  return true;
1738
1831
  }
@@ -1743,11 +1836,12 @@ const traps = {
1743
1836
  * to the key's presence node or a later add never re-runs it. */ function observerHoldsContainer(e, n) {
1744
1837
  const t = e.k;
1745
1838
  if (t === null) return false;
1746
- const r = t.Dt;
1747
- return r !== null && r.o === n && (!(n.R & REACTIVE_RECOMPUTING_DEPS) || r.Ue === n.he);
1839
+ const r = t.dt;
1840
+ return r !== null && r.u === n && (!(n.R & REACTIVE_RECOMPUTING_DEPS) || r.Te === n.Ne);
1748
1841
  }
1749
1842
 
1750
1843
  function storeSetter(e, n, t = true) {
1844
+ if (writeHook !== null) writeHook(e);
1751
1845
  const r = e[$TARGET];
1752
1846
  const o = writeScopes;
1753
1847
  writeScopes = new Set;
@@ -1801,6 +1895,8 @@ function storeSetter(e, n, t = true) {
1801
1895
  function createStore(e, n) {
1802
1896
  const t = n?.shallow === true;
1803
1897
  const r = wrap(e);
1898
+ // Dependency edges (store/edges.ts): the root is named like a plain signal.
1899
+ if (signalHook !== null) signalHook(r[$TARGET]);
1804
1900
  if (t) {
1805
1901
  r[$TARGET].s = true;
1806
1902
  markRawIngest(e);
@@ -1815,7 +1911,7 @@ function createStore(e, n) {
1815
1911
  // disabled, buys nothing.
1816
1912
  if (attrHooks !== null) {
1817
1913
  storeOwners.set(r[$TARGET], e);
1818
- nameStore(r, n?.name);
1914
+ nameStore(r, n?.name, n?.loc);
1819
1915
  }
1820
1916
  }
1821
1917
  const setter = e => storeSetter(r, e);
@@ -1944,7 +2040,7 @@ function snapshotWalk(e, n, t) {
1944
2040
  // R27: a snapshot sees the batch's pending backing — a held one (another
1945
2041
  // transaction's future) only from its own draft.
1946
2042
  let i = o.v;
1947
- if (o.pb !== null && (inDraft(o) || writeOverride || o.k === null || !(o.k.u & CONFIG_HELD))) {
2043
+ if (o.pb !== null && (inDraft(o) || writeOverride || o.k === null || !(o.k.l & CONFIG_HELD))) {
1948
2044
  if (o.ovl) materializePB(o);
1949
2045
  i = o.pb;
1950
2046
  }
@@ -1996,4 +2092,4 @@ function snapshotWalk(e, n, t) {
1996
2092
  return i ?? r;
1997
2093
  }
1998
2094
 
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 };
2095
+ 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 };