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

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 (90) hide show
  1. package/dist/dev-shared.js +2268 -1734
  2. package/dist/dev.attribution.js +75 -52
  3. package/dist/dev.js +1348 -658
  4. package/dist/observe/affects-marks.js +86 -0
  5. package/dist/observe/affects.js +22 -73
  6. package/dist/observe/boundaries.js +192 -190
  7. package/dist/observe/core/async.js +177 -131
  8. package/dist/observe/core/attribution-queries.js +1 -1
  9. package/dist/observe/core/attribution.js +176 -159
  10. package/dist/observe/core/constants.js +17 -3
  11. package/dist/observe/core/context.js +5 -3
  12. package/dist/observe/core/core.js +520 -364
  13. package/dist/observe/core/dev.js +66 -38
  14. package/dist/observe/core/effect.js +50 -48
  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 +60 -44
  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 +238 -260
  21. package/dist/observe/core/owner.js +117 -79
  22. package/dist/observe/core/scheduler.js +381 -217
  23. package/dist/observe/core/truth.js +45 -0
  24. package/dist/observe/core/verdict.js +91 -50
  25. package/dist/observe/deferred.js +184 -0
  26. package/dist/observe/index.js +7 -1
  27. package/dist/observe/map.js +345 -260
  28. package/dist/observe/reveal.js +76 -76
  29. package/dist/observe/signals.js +47 -31
  30. package/dist/observe/store/affects.js +18 -18
  31. package/dist/observe/store/edges.js +39 -0
  32. package/dist/observe/store/optimistic.js +146 -100
  33. package/dist/observe/store/projection.js +68 -55
  34. package/dist/observe/store/store.js +180 -118
  35. package/dist/prod/affects-marks.js +86 -0
  36. package/dist/prod/affects.js +23 -72
  37. package/dist/prod/boundaries.js +156 -154
  38. package/dist/prod/core/async.js +173 -127
  39. package/dist/prod/core/constants.js +17 -3
  40. package/dist/prod/core/context.js +5 -3
  41. package/dist/prod/core/core.js +495 -343
  42. package/dist/prod/core/effect.js +70 -68
  43. package/dist/prod/core/error.js +5 -3
  44. package/dist/prod/core/external.js +5 -6
  45. package/dist/prod/core/graph.js +60 -44
  46. package/dist/prod/core/heap.js +38 -38
  47. package/dist/prod/core/identities.js +62 -0
  48. package/dist/prod/core/lanes.js +230 -252
  49. package/dist/prod/core/owner.js +114 -77
  50. package/dist/prod/core/scheduler.js +327 -163
  51. package/dist/prod/core/truth.js +45 -0
  52. package/dist/prod/core/verdict.js +90 -49
  53. package/dist/prod/deferred.js +182 -0
  54. package/dist/prod/index.js +7 -1
  55. package/dist/prod/map.js +360 -275
  56. package/dist/prod/reveal.js +76 -76
  57. package/dist/prod/signals.js +42 -26
  58. package/dist/prod/store/affects.js +6 -6
  59. package/dist/prod/store/edges.js +39 -0
  60. package/dist/prod/store/optimistic.js +143 -98
  61. package/dist/prod/store/projection.js +25 -19
  62. package/dist/prod/store/store.js +157 -100
  63. package/dist/types/affects-marks.d.ts +14 -0
  64. package/dist/types/boundaries.d.ts +1 -1
  65. package/dist/types/core/async.d.ts +10 -0
  66. package/dist/types/core/constants.d.ts +13 -1
  67. package/dist/types/core/core.d.ts +54 -5
  68. package/dist/types/core/dev.d.ts +51 -13
  69. package/dist/types/core/error.d.ts +9 -1
  70. package/dist/types/core/graph.d.ts +11 -0
  71. package/dist/types/core/identities.d.ts +17 -0
  72. package/dist/types/core/index.d.ts +2 -1
  73. package/dist/types/core/lanes.d.ts +2 -14
  74. package/dist/types/core/owner.d.ts +9 -3
  75. package/dist/types/core/scheduler.d.ts +70 -32
  76. package/dist/types/core/truth.d.ts +16 -0
  77. package/dist/types/core/types.d.ts +21 -0
  78. package/dist/types/core/verdict.d.ts +32 -0
  79. package/dist/types/deferred.d.ts +28 -0
  80. package/dist/types/index.d.ts +6 -0
  81. package/dist/types/map.d.ts +2 -2
  82. package/dist/types/signals.d.ts +28 -7
  83. package/dist/types/store/edges.d.ts +9 -0
  84. package/dist/types/store/index.d.ts +10 -3
  85. package/dist/types/store/optimistic.d.ts +3 -2
  86. package/dist/types/store/projection.d.ts +4 -3
  87. package/dist/types/store/store.d.ts +1 -1
  88. package/dist/types/store/target.d.ts +16 -8
  89. package/dist/types/store/types.d.ts +14 -0
  90. package/package.json +1 -1
@@ -2,17 +2,19 @@ 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, snapshotCaptureActive, captureWriteSnapshot, context, notePromotedWrite, untrack, slotSignal, stagedScreen, stagedRead } from "../core/core.js";
5
+ import { writeHook, setSignal, isEqual, read, verdict, snapshotCaptureActive, captureWriteSnapshot, context, notePromotedWrite, untrack, slotSignal, stagedScreen, stagedRead } 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, resolveTx, 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
 
@@ -170,7 +172,7 @@ function wrap(e, n = null, t = null, r = n?.fam ?? null) {
170
172
  // adoption swap raw identity without changing the logical value — only
171
173
  // changed leaves notify, R9).
172
174
  const slotNodeEquals = function(e, n) {
173
- return isEqual(e, n) || sameLogicalSlot(this.yt, e, n);
175
+ return isEqual(e, n) || sameLogicalSlot(this.O, e, n);
174
176
  };
175
177
 
176
178
  /** The deep witness: the store decides when it notifies. */ const never = false;
@@ -208,7 +210,7 @@ function sameKey(e, n) {
208
210
  /** The container node's comparator: a staging is a backing (identity — a
209
211
  * pending backing is never "equal" to the committed one); a lane value is
210
212
  * an arrangement (`LaneView`), judged by the optimistic machinery. */ const containerEquals = function(e, n) {
211
- return e === n || optHooks !== null && optHooks.arrangement(this.yt, e, n);
213
+ return e === n || optHooks !== null && optHooks.arrangement(this.O, e, n);
212
214
  };
213
215
 
214
216
  function sameLogicalSlot(e, n, t) {
@@ -221,8 +223,8 @@ function sameLogicalSlot(e, n, t) {
221
223
  * released at the commit that resolves it (`drainFolds`). */ const deferredReleases = new Set;
222
224
 
223
225
  function releaseSlot(e) {
224
- const n = e.yt;
225
- const t = e.bt;
226
+ const n = e.O;
227
+ const t = e.Vt;
226
228
  if (t === $TRACK) {
227
229
  if (n.k === e) n.k = null;
228
230
  } else if (t === $DEEP) {
@@ -238,17 +240,21 @@ function releaseSlot(e) {
238
240
  /** A node was born on `target`: a family target enters the live index. */ function noteNode(e, n, t) {
239
241
  if (e.fam !== null) e.fam.live.add(e);
240
242
  markDescendants(e);
241
- if (affectsHooks !== null) affectsHooks.fo(e, n, t);
243
+ if (affectsHooks !== null) affectsHooks.ro(e, n, t);
242
244
  }
243
245
 
244
246
  // Shared slot-node release handler: registered once; the core sweep
245
247
  // dispatches CONFIG_SLOT_NODE nodes here (graph.ts) instead of holding a
246
248
  // per-node closure in a per-node NodeExtension. A dropped node is
247
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
+
248
256
  setSlotUnobserved(e => {
249
- // A staged write — or a lane's value (a guess, S4) — is state only the
250
- // node holds: defer the release to the commit that resolves it.
251
- if (e.Oe !== NOT_PENDING || e.i & CONFIG_OVERRIDE) deferredReleases.add(e); else releaseSlot(e);
257
+ if (slotKept(e)) deferredReleases.add(e); else releaseSlot(e);
252
258
  });
253
259
 
254
260
  /** A node born while its container has a staging is born from the two
@@ -256,19 +262,24 @@ setSlotUnobserved(e => {
256
262
  * pending one — and held by the container's transaction when the container
257
263
  * is held (#3706: the unit of the hold is the key — a key the batch left
258
264
  * unchanged stages nothing and holds no one). */ function bornStaged(e, n, t, r) {
259
- if (n.en && n.en(n.Ae, t)) return;
265
+ if (n.Ye && n.Ye(n.Ie, t)) return;
260
266
  queuePendingNode(n);
261
- n.Oe = t;
267
+ n.ue = t;
262
268
  const o = e.k;
263
269
  if (o === null) return;
264
270
  // a node-less staging holds nothing (`stageOn`)
265
271
  // Held by the container's transaction only for a key the HELD staging
266
272
  // changed — a mainline layer's change above it stages mainline. (A
267
- // container carrying a guess is a lane's: the flush's park decides.)
268
- if ((o.i & (CONFIG_HELD | CONFIG_OVERRIDE)) === CONFIG_HELD && heldKeyChanged(e, r)) holdNode(n, txOf(o));
273
+ // container carrying a guess is a lane's: the flush's park decides —
274
+ // unless a correction folded under the guess, D-34: the key is that
275
+ // correction's hold's.)
276
+ if (o.l & CONFIG_HELD && heldKeyChanged(e, r)) {
277
+ const e = txOf(o);
278
+ if (!(o.l & CONFIG_OVERRIDE) || e.folded) holdNode(n, resolveTx(e._parent ?? e));
279
+ }
269
280
  // A28 (4): a batch written inside a creation-time pass is promoted — its
270
281
  // late-materialized leaf with it.
271
- if (o.i & CONFIG_PROMOTED) notePromotedWrite(n);
282
+ if (o.l & CONFIG_PROMOTED) notePromotedWrite(n);
272
283
  }
273
284
 
274
285
  function getNode(e, n,
@@ -293,7 +304,9 @@ t = -1) {
293
304
  // the hottest store path, and the disabled cost must stay one null
294
305
  // check (nodes created before enable() stay generically named).
295
306
  if (attrHooks !== null) {
296
- l._name = storeLabel(e) + "." + String(n);
307
+ const t = storeRoot(e);
308
+ l._name = (storeNames.get(t) ?? "store") + "." + String(n);
309
+ l._loc = storeLocs.get(t);
297
310
  stampNodeOwner(l, e);
298
311
  }
299
312
  r[n] = o;
@@ -330,8 +343,8 @@ function getHasNode(e, n) {
330
343
  // pending backing staged — so whoever asked for it reads the frame it
331
344
  // would have read had the node been there from the write.
332
345
  if (e.pb !== null && !directCommit) {
333
- n.Ae = preBatch(e);
334
- n.Oe = e.pb;
346
+ n.Ie = preBatch(e);
347
+ n.ue = e.pb;
335
348
  queuePendingNode(n);
336
349
  }
337
350
  }
@@ -347,11 +360,11 @@ function getHasNode(e, n) {
347
360
  * staging: nothing allocates, nothing is swept at the commit (dbmon's tick
348
361
  * re-created, queued, swept and dropped ~7000 container nodes). If the
349
362
  * flush parks, the seam materializes and holds the node then
350
- * (`_storePark`) — the one case the staging home exists for. */ function stageOn(e, n) {
351
- if (e.k === null && passLane === null && (context === null || globalQueue.X) && !deriveHolds(e)) return;
363
+ * (`storeParkHook`) — the one case the staging home exists for. */ function stageOn(e, n) {
364
+ if (e.k === null && passLane === null && (context === null || globalQueue.q) && !deriveHolds(e)) return;
352
365
  const t = getContainerNode(e);
353
- if (t.Oe === NOT_PENDING) queuePendingNode(t);
354
- t.Oe = n;
366
+ if (t.ue === NOT_PENDING) queuePendingNode(t);
367
+ t.ue = n;
355
368
  // A28 (4): a write issued inside a pass outside a flush (a creation-time
356
369
  // derive) is promoted at the pass's end — the batch with it.
357
370
  if (context !== null) notePromotedWrite(t);
@@ -361,10 +374,10 @@ function getHasNode(e, n) {
361
374
  /** The projection's derive is held by a transaction and this is its own
362
375
  * write (not a user setter's): the staging is the derive's (`holdWithDerive`). */ function deriveHolds(e) {
363
376
  const n = e.fam?.node;
364
- return n != null && !userWrite && (n.i & CONFIG_HELD) !== 0;
377
+ return n != null && !userWrite && (n.l & CONFIG_HELD) !== 0;
365
378
  }
366
379
 
367
- GlobalQueue.An = e => {
380
+ const storePark = e => {
368
381
  for (let n = 0; n < foldList.length; n++) {
369
382
  const t = foldList[n];
370
383
  if (t.k === null && t.pb !== null) holdNode(getContainerNode(t), e);
@@ -393,9 +406,9 @@ function bumpDeep(e) {
393
406
  // on the guess (`laneWrite` → `supersede`, judged by `containerEquals`);
394
407
  // otherwise the structural readers are walked (the staging is already on
395
408
  // the node; the comparator is the store's, done).
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);
409
+ if (e.l & CONFIG_OVERRIDE) setSignal(e, n); else {
410
+ if (e.l & CONFIG_HELD) joinFuture(txOf(e));
411
+ if (snapshotCaptureActive) captureWriteSnapshot(e, e.Ie);
399
412
  insertSubs(e);
400
413
  schedule();
401
414
  }
@@ -413,17 +426,22 @@ function storeRootOwner(e) {
413
426
  }
414
427
 
415
428
  function stampNodeOwner(e, n) {
416
- e.L = storeRootOwner(n) ?? null;
429
+ e.H = storeRootOwner(n) ?? null;
417
430
  }
418
431
 
419
432
  const storeNames = new WeakMap;
420
433
 
421
- function nameStore(e, n) {
422
- if (attrHooks !== null && n) storeNames.set(e[$TARGET], n);
423
- }
434
+ // The store's `loc` option, carried onto every property node beside its
435
+ // label (the same gate: nodes created before the engine is installed carry
436
+ // neither).
437
+ const storeLocs = new WeakMap;
424
438
 
425
- function storeLabel(e) {
426
- return storeNames.get(storeRoot(e)) ?? "store";
439
+ function nameStore(e, n, t) {
440
+ if (attrHooks !== null) {
441
+ const r = e[$TARGET];
442
+ if (n) storeNames.set(r, n);
443
+ if (t) storeLocs.set(r, t);
444
+ }
427
445
  }
428
446
 
429
447
  // ---------------------------------------------------------------------------
@@ -512,7 +530,7 @@ const plainProto = e => {
512
530
  if (e.kc > OVERLAY_REBUILD_MAX_KEYS) return false;
513
531
  if (e.del !== null && e.del.size !== 0) return true;
514
532
  const t = e.wk;
515
- if (t !== null && t !== WK_ALL && t.size <= OVERLAY_REBUILD_MIN_ADDS) return false;
533
+ if (t !== null && t !== WK_ALL && t.u === undefined && t.size <= OVERLAY_REBUILD_MIN_ADDS) return false;
516
534
  const r = e.v;
517
535
  let o = 0;
518
536
  for (const e of Reflect.ownKeys(n)) if (!hasOwn.call(r, e) && ++o > OVERLAY_REBUILD_MIN_ADDS) return true;
@@ -532,7 +550,7 @@ const plainProto = e => {
532
550
  e.pb = t;
533
551
  e.ovl = false;
534
552
  // The staging IS the pending backing: re-point the container node.
535
- if (e.k !== null && e.k.Oe === n) e.k.Oe = t;
553
+ if (e.k !== null && e.k.ue === n) e.k.ue = t;
536
554
  return t;
537
555
  }
538
556
 
@@ -553,7 +571,7 @@ const plainProto = e => {
553
571
  // mainline setter mid-hold publishes mainline; the held keys stay held).
554
572
  n = e.pb = cloneRaw(n, e);
555
573
  const t = e.k;
556
- if (t !== null && !(t.i & CONFIG_HELD)) t.Oe = n;
574
+ if (t !== null && !(t.l & CONFIG_HELD)) t.ue = n;
557
575
  } else if (n === null) {
558
576
  const t = e.v;
559
577
  if (!directCommit) queueFold(e);
@@ -632,7 +650,7 @@ const plainProto = e => {
632
650
  } else r = e.v;
633
651
  if (directCommit) {
634
652
  e.pb = null;
635
- if (e.k !== null) e.k.Ae = n;
653
+ if (e.k !== null) e.k.Ie = n;
636
654
  } else {
637
655
  // Eager (INTERNALS §3): the backing swaps now — handlers read the
638
656
  // adopted object before the flush — while the container node, when
@@ -728,7 +746,7 @@ let foldIndex = null;
728
746
  if (r.pb === null) continue;
729
747
  // folded already (a direct commit)
730
748
  const o = r.k;
731
- if (o !== null && o.Oe !== NOT_PENDING) {
749
+ if (o !== null && o.ue !== NOT_PENDING) {
732
750
  // held: commits with its hold
733
751
  foldList.push(r);
734
752
  foldOlds.push(n[t]);
@@ -739,7 +757,7 @@ let foldIndex = null;
739
757
  }
740
758
  if (deferredReleases.size !== 0) {
741
759
  for (const e of deferredReleases) {
742
- if (e.Oe === NOT_PENDING && !(e.i & CONFIG_OVERRIDE) && e.t === null) {
760
+ if (e.o === null && !slotKept(e)) {
743
761
  deferredReleases.delete(e);
744
762
  releaseSlot(e);
745
763
  }
@@ -750,33 +768,33 @@ let foldIndex = null;
750
768
  /** Core asks: is a slot node's truth in flight? Its family's derive is —
751
769
  * lanes (A17, #2864: a guess mid-refetch stands in for the flight) and
752
770
  * marks (an ambient `affects()` over a declared reload lives to the
753
- * landing) read it. */ GlobalQueue.C = e => {
754
- const n = e.yt?.fam;
755
- return n != null && n.node !== null && (n.node.S & STATUS_PENDING) !== 0;
771
+ * landing) read it. */ GlobalQueue.k = e => {
772
+ const n = e.O?.fam;
773
+ return n != null && n.node !== null && (n.node.m & STATUS_PENDING) !== 0;
756
774
  };
757
775
 
758
776
  /** Core asks (lanes.ts `covered`): the value a slot node's guess covered.
759
777
  * A chained link's committed value is the inner store's at the last
760
778
  * read-through — refreshed here, since a guess served the reads since
761
- * (#3672: the revert compares against the base's live value). */ GlobalQueue.In = e => {
762
- const n = e.yt;
779
+ * (#3672: the revert compares against the base's live value). */ GlobalQueue.fn = e => {
780
+ const n = e.O;
763
781
  if (n.ch) {
764
- const t = e.bt;
782
+ const t = e.Vt;
765
783
  const r = n.v;
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]));
784
+ 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]));
767
785
  }
768
- return e.Ae;
786
+ return e.Ie;
769
787
  };
770
788
 
771
- GlobalQueue.Rn = () => {
789
+ installStoreHooks(storePark, () => {
772
790
  drainFolds();
773
791
  optHooks?.sweep();
774
- };
792
+ });
775
793
 
776
794
  /** 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;
795
+ * and walks records through these: it never imports the engine. */ GlobalQueue.so = getNode;
778
796
 
779
- GlobalQueue.so = isWrappable;
797
+ GlobalQueue.fo = isWrappable;
780
798
 
781
799
  /** A projection's creation run commits directly (a memo's first value is
782
800
  * its `_value`, not a staging): the draft still writes a clone of the seed,
@@ -804,7 +822,7 @@ function runDirect(e) {
804
822
  // the adopted object (shared ownership — never cloned).
805
823
  e.pb = null;
806
824
  e.wk = null;
807
- } else if (e.ovl && (e.v !== n || !overlayRebuilds(e, r) && t?.m?.ot !== n)) {
825
+ } else if (e.ovl && (e.v !== n || !overlayRebuilds(e, r) && t?.T?.lt !== n)) {
808
826
  // Overlay flatten (#3044): the backing keeps its identity, so the
809
827
  // `t.v === old` gate below skips path copying (the parent slot
810
828
  // already points here) and the adopted-notify (setter
@@ -818,8 +836,8 @@ function runDirect(e) {
818
836
  const t = e.v;
819
837
  const o = e.wk;
820
838
  if (o !== null && o !== WK_ALL) {
821
- for (const e of o) {
822
- if (hasOwn.call(r, e)) copyOwn(t, r, e); else delete t[e];
839
+ for (let e = o; e !== undefined; e = e.u) for (const n of e) {
840
+ if (hasOwn.call(r, n)) copyOwn(t, r, n); else delete t[n];
823
841
  }
824
842
  } else {
825
843
  for (const e of Reflect.ownKeys(r)) {
@@ -846,7 +864,7 @@ function runDirect(e) {
846
864
  // subscribes to was a transient staging home (INTERNALS §5: an
847
865
  // observer-less write holds a transient record until the fold,
848
866
  // discarded with it) — gone with the batch.
849
- if (t.t === null) e.k = null; else if (t.Ae instanceof LaneView) t.Ae.base = e.v; else t.Ae = e.v;
867
+ if (t.o === null) e.k = null; else if (t.Ie instanceof LaneView) t.Ie.base = e.v; else t.Ie = e.v;
850
868
  }
851
869
  if (e.v !== n && e.u) {
852
870
  const t = parentSlotKey(e, n);
@@ -919,9 +937,9 @@ function reportReplacedContainers(e, n, t, r) {
919
937
 
920
938
  function heldDerivation(e, n, t) {
921
939
  if (e.fam === null || !userWrite) return false;
922
- if (n !== undefined) return (n.i & (CONFIG_HELD | CONFIG_MANUAL_WRITE)) === CONFIG_HELD && txOf(n) !== flushTransaction;
940
+ if (n !== undefined) return (n.l & (CONFIG_HELD | CONFIG_MANUAL_WRITE)) === CONFIG_HELD && txOf(n) !== flushTransaction;
923
941
  const r = e.k;
924
- return r !== null && (r.i & CONFIG_HELD) !== 0 && txOf(r) !== flushTransaction && heldKeyChanged(e, t);
942
+ return r !== null && (r.l & CONFIG_HELD) !== 0 && txOf(r) !== flushTransaction && heldKeyChanged(e, t);
925
943
  }
926
944
 
927
945
  /** Setter exit: one node write per changed observed key (the leaves stage
@@ -951,7 +969,10 @@ function heldDerivation(e, n, t) {
951
969
  // value can change when ANY key is written), or a non-plain prototype
952
970
  // (class instances: prototype getters derive from arbitrary fields).
953
971
  // Overlay pbs chain to the COMMITTED object (#3044) — plainness is the
954
- // committed container's prototype, not the overlay's.
972
+ // committed container's prototype, not the overlay's. THIS setter's keys
973
+ // only (D-8, #3743): a key an earlier setter of the batch wrote and this
974
+ // one did not is no second proposal — its node was told then, and telling
975
+ // a held node its unchanged value again would join this tick to the hold.
955
976
  const o = e.wk;
956
977
  const i = o === WK_ALL || e.a === true || !plainProto(e.ovl ? e.v : n) ? null : o;
957
978
  if (attrHooks !== null) reportReplacedContainers(e, t, n, i);
@@ -987,9 +1008,9 @@ function heldDerivation(e, n, t) {
987
1008
  // The derive's write is a derivation; a user setter's is a proposal
988
1009
  // (A34 (3)'s discriminator).
989
1010
  if (!userWrite) {
990
- o.i &= ~CONFIG_MANUAL_WRITE;
1011
+ o.l &= ~CONFIG_MANUAL_WRITE;
991
1012
  holdWithDerive(e, o);
992
- } else o.i |= CONFIG_MANUAL_WRITE;
1013
+ } else o.l |= CONFIG_MANUAL_WRITE;
993
1014
  }
994
1015
  }
995
1016
  }
@@ -1020,7 +1041,7 @@ function heldDerivation(e, n, t) {
1020
1041
  // container carrying an arrangement guess is told whether or not anyone
1021
1042
  // subscribes: the write is a landing on the guess (`notifyContainer`).
1022
1043
  const u = e.k;
1023
- if (u !== null && (u.t !== null || u.i & CONFIG_OVERRIDE)) {
1044
+ if (u !== null && (u.o !== null || u.l & CONFIG_OVERRIDE)) {
1024
1045
  let r;
1025
1046
  if (e.ovl) {
1026
1047
  r = e.del !== null && e.del.size !== 0;
@@ -1122,7 +1143,7 @@ function hasAccessorFlag(e) {
1122
1143
  for (const o of Reflect.ownKeys(r)) if (e || o in n !== o in t) setSignal(r[o], o in t);
1123
1144
  }
1124
1145
  const o = e.k;
1125
- if (o !== null && (o.t !== null || o.i & CONFIG_OVERRIDE)) {
1146
+ if (o !== null && (o.o !== null || o.l & CONFIG_OVERRIDE)) {
1126
1147
  const e = Array.isArray(t) && Array.isArray(n) ? arrayStructureChanged(n, t) : membershipChanged(n, t);
1127
1148
  if (e) notifyContainer(o, t);
1128
1149
  }
@@ -1179,7 +1200,7 @@ function inDraft(e) {
1179
1200
  * "adoption is eager by contract" — and the node keeps the frame committed
1180
1201
  * readers under a hold still see), `t.v` otherwise. */ function committed(e) {
1181
1202
  const n = e.k;
1182
- if (n !== null) return n.Oe !== NOT_PENDING ? asBacking(n.Ae) : e.v;
1203
+ if (n !== null) return n.ue !== NOT_PENDING ? asBacking(n.Ie) : e.v;
1183
1204
  // A node-less staging (`stageOn`): the fold queue remembers the frame.
1184
1205
  return e.pb !== null ? preBatch(e) : e.v;
1185
1206
  }
@@ -1191,8 +1212,8 @@ function inDraft(e) {
1191
1212
  * committed frame. */ function stagingReader() {
1192
1213
  let e = context;
1193
1214
  if (e === null) return null;
1194
- if (e.Ht) e = e.gt ?? null;
1195
- return e === null || e.i & CONFIG_CHILDREN_FORBIDDEN ? null : e;
1215
+ if (e.Pt) e = e.Ht ?? null;
1216
+ return e === null || e.l & CONFIG_CHILDREN_FORBIDDEN ? null : e;
1196
1217
  }
1197
1218
 
1198
1219
  /** The backing a read serves (RUL-1, signal parity): the draft and the write
@@ -1230,10 +1251,10 @@ function inDraft(e) {
1230
1251
  if (writeOverride && !userWrite) {
1231
1252
  const t = n !== undefined ? e.n?.[n] : undefined;
1232
1253
  if (t !== undefined) {
1233
- if ((t.i & (CONFIG_HELD | CONFIG_OVERRIDE)) === CONFIG_HELD) joinPassTx(txOf(t));
1254
+ if ((t.l & (CONFIG_HELD | CONFIG_OVERRIDE)) === CONFIG_HELD) joinPassTx(txOf(t));
1234
1255
  } else {
1235
1256
  const t = e.k;
1236
- if (t !== null && (t.i & (CONFIG_HELD | CONFIG_OVERRIDE)) === CONFIG_HELD && (n === undefined || heldKeyChanged(e, n))) joinPassTx(txOf(t));
1257
+ if (t !== null && (t.l & (CONFIG_HELD | CONFIG_OVERRIDE)) === CONFIG_HELD && (n === undefined || heldKeyChanged(e, n))) joinPassTx(txOf(t));
1237
1258
  }
1238
1259
  }
1239
1260
  return r;
@@ -1245,9 +1266,9 @@ function inDraft(e) {
1245
1266
  // it).
1246
1267
  if (n !== undefined) {
1247
1268
  const t = e.n?.[n];
1248
- if (t !== undefined && t.i & CONFIG_OVERRIDE) return committed(e);
1269
+ if (t !== undefined && t.l & CONFIG_OVERRIDE) return committed(e);
1249
1270
  }
1250
- const i = o !== null && (o.i & CONFIG_HELD) !== 0;
1271
+ const i = o !== null && (o.l & CONFIG_HELD) !== 0;
1251
1272
  // A held container: the hold's unit is the key (#3706). A key the HELD
1252
1273
  // staging left unchanged reads the ambient view — a mainline layer's
1253
1274
  // write above the hold publishes mainline (#3688, #3612); a key it
@@ -1265,7 +1286,7 @@ function inDraft(e) {
1265
1286
  // probe) judges the container like any node — for a key the batch
1266
1287
  // changed; an unchanged key is committed and final (A22: pending is per
1267
1288
  // key). A held container's committed frame is the node's.
1268
- if (verdict === null || !i && n !== undefined && !keyChanged(e, n, t)) return i ? asBacking(o.Ae) : e.v;
1289
+ if (verdict === null || !i && n !== undefined && !keyChanged(e, n, t)) return i ? asBacking(o.Ie) : e.v;
1269
1290
  return o === null ? committed(e) : asBacking(read(o));
1270
1291
  }
1271
1292
  // A pass reading a key the batch left unchanged: the same value in both
@@ -1282,13 +1303,13 @@ function inDraft(e) {
1282
1303
  // frames, a changed one is what the mark already stands for. The
1283
1304
  // per-key question is asked once per pass, not once per key (a
1284
1305
  // `mapArray` over 1k shifted rows asked it 1k times).
1285
- if (l.R & REACTIVE_STAGED_READ && !(l.i & (CONFIG_VERDICT | CONFIG_CHILDREN_FORBIDDEN))) return r;
1306
+ if (l.R & REACTIVE_STAGED_READ && !(l.l & (CONFIG_VERDICT | CONFIG_CHILDREN_FORBIDDEN))) return r;
1286
1307
  if (n !== undefined && !keyChanged(e, n, t)) return committed(e);
1287
1308
  // A node-less staging (`stageOn`): what `read()` of its node would do
1288
1309
  // for this pass — a verdict reader sees the screen, any other derives
1289
1310
  // from the staging and is marked as having read it.
1290
1311
  if (o === null) {
1291
- if (l.i & CONFIG_VERDICT && stagedScreen(l)) return committed(e);
1312
+ if (l.l & CONFIG_VERDICT && stagedScreen(l)) return committed(e);
1292
1313
  stagedRead(l);
1293
1314
  return r;
1294
1315
  }
@@ -1298,7 +1319,7 @@ function inDraft(e) {
1298
1319
 
1299
1320
  /** Did the HELD staging (the container node's, not a mainline layer above
1300
1321
  * it) change `key`? */ function heldKeyChanged(e, n, t = true) {
1301
- return changedBetween(e, e.k.Ae, e.k.Oe, n, t) && !adoptChild(e, n);
1322
+ return changedBetween(e, e.k.Ie, e.k.ue, n, t) && !adoptChild(e, n);
1302
1323
  }
1303
1324
 
1304
1325
  /** The values of a held frame, built once per frame: neither the committed
@@ -1325,14 +1346,14 @@ function rawsOf(e) {
1325
1346
  * `version` holds. A record's keys pair by name; an array's by the family's
1326
1347
  * key — without a family the pairing is not known here and the hold stands. */ function adoptChild(e, n) {
1327
1348
  const t = e.k;
1328
- const r = asBacking(t.Ae);
1349
+ const r = asBacking(t.Ie);
1329
1350
  const o = r[n];
1330
- const i = t.Oe[n];
1351
+ const i = t.ue[n];
1331
1352
  const l = Array.isArray(r) ? e.fam?.key : null;
1332
1353
  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
1354
  // A raw in both frames (the same objects swapped or reordered) is its
1334
1355
  // 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;
1356
+ rawsOf(r).has(i) || rawsOf(t.ue).has(o)) return false;
1336
1357
  // A raw-marked value wraps to itself, a proxy to its own target: neither
1337
1358
  // is a fresh target around `ov`.
1338
1359
  const u = wrap(o, e, n)[$TARGET];
@@ -1343,7 +1364,7 @@ function rawsOf(e) {
1343
1364
  }
1344
1365
 
1345
1366
  /** A family target's unheld staging, read with no pass (a handler). */ function familyAhead(e) {
1346
- return e.fam !== null && context === null && !(e.k !== null && e.k.i & CONFIG_HELD);
1367
+ return e.fam !== null && context === null && !(e.k !== null && e.k.l & CONFIG_HELD);
1347
1368
  }
1348
1369
 
1349
1370
  /** Did this batch change `key` — value (slot equality), presence, and (for
@@ -1377,7 +1398,7 @@ function changedBetween(e, n, t, r, o) {
1377
1398
  * tracked or not, applies the hold and A28 rules a signal's read does. A
1378
1399
  * projection's unheld staging is ahead for a handler (`familyAhead`). */ function nodeValue(e, n, t) {
1379
1400
  let r = read(e);
1380
- if (e.Oe !== NOT_PENDING && !(e.i & CONFIG_HELD) && context === null && t.fam !== null) r = e.Oe;
1401
+ if (e.ue !== NOT_PENDING && !(e.l & CONFIG_HELD) && context === null && t.fam !== null) r = e.ue;
1381
1402
  return r === FORCE ? n : r;
1382
1403
  }
1383
1404
 
@@ -1397,8 +1418,8 @@ function changedBetween(e, n, t, r, o) {
1397
1418
  if (l) {
1398
1419
  // The link's committed value follows the inner store's (what a lane
1399
1420
  // over it covers — its reveal and revert compare against it, S4).
1400
- o.Ae = t;
1401
- if (o.i & CONFIG_OVERRIDE) u = n;
1421
+ o.Ie = t;
1422
+ if (o.l & CONFIG_OVERRIDE) u = n;
1402
1423
  } else u = n;
1403
1424
  }
1404
1425
  }
@@ -1419,7 +1440,7 @@ function changedBetween(e, n, t, r, o) {
1419
1440
  }
1420
1441
 
1421
1442
  /** Is `n` among `c`'s dependencies (any pass's — a kept tail included)? */ function linkedTo(e, n) {
1422
- for (let t = e.G; t !== null; t = t._) if (t.I === n) return true;
1443
+ for (let t = e.C; t !== null; t = t.G) if (t.S === n) return true;
1423
1444
  return false;
1424
1445
  }
1425
1446
 
@@ -1438,7 +1459,7 @@ function changedBetween(e, n, t, r, o) {
1438
1459
  // reader of a memo would — linked, so it re-runs at the landing (the
1439
1460
  // next settled pass pulls without linking and the stale link trims), a
1440
1461
  // verdict reader registered, a tracked pass suspended.
1441
- if (n.S & (STATUS_PENDING | STATUS_ERROR)) {
1462
+ if (n.m & (STATUS_PENDING | STATUS_ERROR)) {
1442
1463
  // A render effect outside the flight's own flush is the frame, not a
1443
1464
  // derivation: it keeps what it shows and learns of the landing from the
1444
1465
  // leaves the landing changes (unchanged leaves say nothing — it is not a
@@ -1449,7 +1470,7 @@ function changedBetween(e, n, t, r, o) {
1449
1470
  // release the hold with the flight still up. An errored derive has no
1450
1471
  // landing to learn of: the error is what the frame shows.
1451
1472
  const t = context;
1452
- if (t !== null && t.u === EFFECT_RENDER && !(n.S & STATUS_ERROR) && n.i & CONFIG_HELD && flushTransaction !== txOf(n) && !linkedTo(t, n)) return;
1473
+ if (t !== null && t.i === EFFECT_RENDER && !(n.m & STATUS_ERROR) && n.l & CONFIG_HELD && flushTransaction !== txOf(n) && !linkedTo(t, n)) return;
1453
1474
  // An optimistic family carrying guesses that stand in for the flight
1454
1475
  // (A17): the guesses are the display — a reader is served the leaves
1455
1476
  // (committed plus the guesses) and does not suspend on the derive; a
@@ -1457,7 +1478,7 @@ function changedBetween(e, n, t, r, o) {
1457
1478
  // up). The family's, not the target's: the edit stands in for the one
1458
1479
  // flight the family has.
1459
1480
  const r = e.fam.overlaid;
1460
- if (verdict === null && n.S & STATUS_PENDING && r !== undefined && r.size !== 0) return;
1481
+ if (verdict === null && n.m & STATUS_PENDING && r !== undefined && r.size !== 0) return;
1461
1482
  read(n);
1462
1483
  } else untrack(() => read(n));
1463
1484
  }
@@ -1470,12 +1491,12 @@ function changedBetween(e, n, t, r, o) {
1470
1491
  * classification, as core reads it — `heldNotFinal`). */ function holdWithDerive(e, n) {
1471
1492
  const t = e.fam?.node;
1472
1493
  if (t !== undefined && t !== null && !userWrite && // the derive's own write (its draft or landing), not a user setter's
1473
- t.i & CONFIG_HELD && n.Oe !== NOT_PENDING &&
1494
+ t.l & CONFIG_HELD && n.ue !== NOT_PENDING &&
1474
1495
  // A node carrying a guess is the lane's: its staging is the truth beneath
1475
1496
  // the guess (`setSignal` → `laneWrite`), never re-homed by the store.
1476
- !(n.i & (CONFIG_HELD | CONFIG_OVERRIDE))) {
1497
+ !(n.l & (CONFIG_HELD | CONFIG_OVERRIDE))) {
1477
1498
  holdNode(n, txOf(t));
1478
- n.m.It = t.m.It;
1499
+ n.T.ut = t.T.ut;
1479
1500
  }
1480
1501
  }
1481
1502
 
@@ -1518,6 +1539,22 @@ function changedBetween(e, n, t, r, o) {
1518
1539
 
1519
1540
  // ---------------------------------------------------------------------------
1520
1541
  // traps
1542
+ /** A trap write: the target is told at the setter's exit, the key recorded
1543
+ * in this setter's `wk`. A setter's first write to a target still staged by
1544
+ * an earlier setter of the batch opens a set of its own (the notify visits
1545
+ * it, D-8) and chains the earlier one behind for the fold's union. */ function touch(e, n) {
1546
+ let t = e.wk;
1547
+ if (!pendingNotify.has(e)) {
1548
+ pendingNotify.add(e);
1549
+ if (t !== null && t !== WK_ALL) {
1550
+ t = new Set;
1551
+ t.u = e.wk;
1552
+ e.wk = t;
1553
+ }
1554
+ }
1555
+ if (t !== WK_ALL) (e.wk ??= new Set).add(n);
1556
+ }
1557
+
1521
1558
  const traps = {
1522
1559
  get(e, n, t) {
1523
1560
  // One typeof gates every brand-symbol compare off the hot string path.
@@ -1546,7 +1583,7 @@ const traps = {
1546
1583
  }
1547
1584
  // (The witness before the pull: a mark on an uninitialized derived
1548
1585
  // store is witnessed, then the pull throws — loading, declared pending.)
1549
- if (verdict !== null && affectsHooks !== null && getObserver() === null) affectsHooks.ro(e, n);
1586
+ if (verdict !== null && affectsHooks !== null && getObserver() === null) affectsHooks.io(e, n);
1550
1587
  if (e.fam !== null) pullFamily(e);
1551
1588
  // Hot inline case: existing PLAIN node (non-accessor) read outside any
1552
1589
  // draft — the dbmon/uibench effect re-read shape. Core's `read()` serves
@@ -1561,7 +1598,7 @@ const traps = {
1561
1598
  const r = e.n?.[n];
1562
1599
  if (writeScopes === null && !writeOverride) {
1563
1600
  const t = r;
1564
- if (t !== undefined && t.acc !== true && (!e.ch || t.i & CONFIG_OVERRIDE)) {
1601
+ if (t !== undefined && t.acc !== true && (!e.ch || t.l & CONFIG_OVERRIDE)) {
1565
1602
  const r = nodeValue(t, undefined, e);
1566
1603
  if (r === null || typeof r !== "object") return r;
1567
1604
  if (e.s) return serveShallow(e, n, r);
@@ -1643,7 +1680,7 @@ const traps = {
1643
1680
  if (n === $OWNER || n === $RECORD) return false;
1644
1681
  // (The witness before the pull: a mark on an uninitialized derived
1645
1682
  // store is witnessed, then the pull throws — loading, declared pending.)
1646
- if (verdict !== null && affectsHooks !== null && getObserver() === null) affectsHooks.ro(e, n);
1683
+ if (verdict !== null && affectsHooks !== null && getObserver() === null) affectsHooks.io(e, n);
1647
1684
  if (e.fam !== null) pullFamily(e);
1648
1685
  const t = readSource(e, n);
1649
1686
  // A tracked reader's presence node answers (born from the two frames;
@@ -1656,9 +1693,9 @@ const traps = {
1656
1693
  const o = read(r);
1657
1694
  if (!e.ch || t !== e.v) return !!o;
1658
1695
  const i = n in t;
1659
- r.Ae = i;
1696
+ r.Ie = i;
1660
1697
  // the link follows the inner store (S4)
1661
- if (r.i & CONFIG_OVERRIDE) return !!o;
1698
+ if (r.l & CONFIG_OVERRIDE) return !!o;
1662
1699
  return i;
1663
1700
  } else if (optRead(e)) {
1664
1701
  const t = optHooks.has(e, n);
@@ -1668,14 +1705,14 @@ const traps = {
1668
1705
  return n in t && !(e.del !== null && t === e.pb && e.del.has(n));
1669
1706
  },
1670
1707
  ownKeys(e) {
1671
- if (verdict !== null && affectsHooks !== null && getObserver() === null) affectsHooks.ro(e, undefined);
1708
+ if (verdict !== null && affectsHooks !== null && getObserver() === null) affectsHooks.io(e, undefined);
1672
1709
  if (e.fam !== null) pullFamily(e);
1673
1710
  return visibleKeys(e, enumerationSource(e));
1674
1711
  },
1675
1712
  getOwnPropertyDescriptor(e, n) {
1676
1713
  if (n === $OWNER || n === $RECORD) return undefined;
1677
1714
  const t = getObserver();
1678
- if (verdict !== null && affectsHooks !== null && t === null) affectsHooks.ro(e, n);
1715
+ if (verdict !== null && affectsHooks !== null && t === null) affectsHooks.io(e, n);
1679
1716
  if (e.fam !== null) pullFamily(e);
1680
1717
  // An enumerator (spread, Object.entries) already holds the container
1681
1718
  // node and reads its frame; a descriptor read on its own tracks
@@ -1693,7 +1730,7 @@ const traps = {
1693
1730
  const t = getHasNode(e, n);
1694
1731
  const r = read(t);
1695
1732
  i = readSource(e, n, true);
1696
- if (!r && (!e.ch || t.i & CONFIG_OVERRIDE || !(n in i))) return undefined;
1733
+ if (!r && (!e.ch || t.l & CONFIG_OVERRIDE || !(n in i))) return undefined;
1697
1734
  } else i = readSource(e, n, true);
1698
1735
  let l;
1699
1736
  if (!o && optRead(e)) {
@@ -1709,20 +1746,29 @@ const traps = {
1709
1746
  set(e, n, t) {
1710
1747
  const r = inDraft(e);
1711
1748
  const o = !r && writeOverride;
1712
- if (!r && !o) return true;
1749
+ if (!r && !o) {
1750
+ return true;
1751
+ }
1713
1752
  if (n === "__proto__") return true;
1714
1753
  // pollution guard (core R30)
1715
1754
  const i = e.s ? t : unwrapValue(t);
1716
1755
  const l = ensurePB(e);
1717
- pendingNotify.add(e);
1756
+ // #4146 (R1: node existence is unobservable; #3706: the hold's unit is
1757
+ // the key): a user setter writing a key a HELD container's staging left
1758
+ // unchanged would write it into the held staging — read as the hold's
1759
+ // until its landing, while the same key with a leaf (a live reader)
1760
+ // publishes at the flush. The key gets its leaf first, born from the two
1761
+ // frames (`bornStaged`: a key the hold left unchanged — or added — is
1762
+ // unstaged and held by no one; a key the hold changed is born held, A34
1763
+ // (1)), and the write stages on it (A28; a write by the holder joins it,
1764
+ // A34) — as it would with a reader's leaf. Kept while the container is
1765
+ // held (`deferredReleases`).
1766
+ if (userWrite && e.k?.l & CONFIG_HELD) deferredReleases.add(getNode(e, n));
1718
1767
  if (Array.isArray(l)) {
1719
- if (n === "length") e.wk = WK_ALL; else if (e.wk !== WK_ALL) {
1720
- const t = e.wk ??= new Set;
1721
- t.add(n);
1722
- t.add("length");
1723
- }
1768
+ touch(e, n);
1769
+ if (n === "length") e.wk = WK_ALL; else touch(e, "length");
1724
1770
  } else {
1725
- if (e.wk !== WK_ALL) (e.wk ??= new Set).add(n);
1771
+ touch(e, n);
1726
1772
  if (!(n in l)) e.kc++;
1727
1773
  }
1728
1774
  if (UNSAFE_KEYS.has(n)) {
@@ -1751,7 +1797,9 @@ const traps = {
1751
1797
  defineProperty(e, n, t) {
1752
1798
  const r = inDraft(e);
1753
1799
  const o = !r && writeOverride;
1754
- if (!r && !o) return true;
1800
+ if (!r && !o) {
1801
+ return true;
1802
+ }
1755
1803
  if (n === "__proto__") return true;
1756
1804
  if (t.get || t.set) e.a = true;
1757
1805
  if ("value" in t) t = {
@@ -1760,8 +1808,7 @@ const traps = {
1760
1808
  };
1761
1809
  const i = ensurePB(e);
1762
1810
  if (e.a || !(t.enumerable && t.writable && t.configurable)) e.sc = 1;
1763
- pendingNotify.add(e);
1764
- if (e.wk !== WK_ALL) (e.wk ??= new Set).add(n);
1811
+ touch(e, n);
1765
1812
  Object.defineProperty(i, n, t);
1766
1813
  if (e.del !== null) e.del.delete(n);
1767
1814
  if (o) notifyWrites(e);
@@ -1770,12 +1817,26 @@ const traps = {
1770
1817
  deleteProperty(e, n) {
1771
1818
  const t = inDraft(e);
1772
1819
  const r = !t && writeOverride;
1773
- if (!t && !r) return true;
1820
+ if (!t && !r) {
1821
+ return true;
1822
+ }
1774
1823
  const o = ensurePB(e);
1775
- pendingNotify.add(e);
1776
- if (e.wk !== WK_ALL) (e.wk ??= new Set).add(n);
1777
- delete o[n];
1778
- if (e.ovl && hasOwn.call(e.v, n)) (e.del ??= new Set).add(n);
1824
+ touch(e, n);
1825
+ // The key count (`kc`, the overlay gate and the rebuild cap) follows a
1826
+ // delete the way it follows an add: an own key of the draft leaving — a
1827
+ // clone's key, or an overlay's own add — is one fewer now. An overlay's
1828
+ // override of a committed key is the exception: the committed key's
1829
+ // deletion is recorded in `del` and counted at the rebuild, as before.
1830
+ // Without the decrement the natural "delete the old key, set the new
1831
+ // one" shape drifted the count up by one per write until a three-key
1832
+ // object crossed the overlay threshold and paid a rebuild on every
1833
+ // commit after.
1834
+ const i = e.ovl && hasOwn.call(e.v, n);
1835
+ if (hasOwn.call(o, n)) {
1836
+ delete o[n];
1837
+ i || e.kc--;
1838
+ }
1839
+ if (i) (e.del ??= new Set).add(n);
1779
1840
  if (r) notifyWrites(e);
1780
1841
  return true;
1781
1842
  }
@@ -1786,11 +1847,12 @@ const traps = {
1786
1847
  * to the key's presence node or a later add never re-runs it. */ function observerHoldsContainer(e, n) {
1787
1848
  const t = e.k;
1788
1849
  if (t === null) return false;
1789
- const r = t.Dt;
1790
- return r !== null && r.l === n && (!(n.R & REACTIVE_RECOMPUTING_DEPS) || r.Pe === n.pe);
1850
+ const r = t.dt;
1851
+ return r !== null && r.u === n && (!(n.R & REACTIVE_RECOMPUTING_DEPS) || r.Te === n.Ne);
1791
1852
  }
1792
1853
 
1793
1854
  function storeSetter(e, n, t = true) {
1855
+ if (writeHook !== null) writeHook(e);
1794
1856
  const r = e[$TARGET];
1795
1857
  const o = writeScopes;
1796
1858
  writeScopes = new Set;
@@ -1844,21 +1906,21 @@ function storeSetter(e, n, t = true) {
1844
1906
  function createStore(e, n) {
1845
1907
  const t = n?.shallow === true;
1846
1908
  const r = wrap(e);
1909
+ // Dependency edges (store/edges.ts): the root is named like a plain signal.
1910
+ if (signalHook !== null) signalHook(r[$TARGET]);
1847
1911
  if (t) {
1848
1912
  r[$TARGET].s = true;
1849
1913
  markRawIngest(e);
1850
1914
  }
1851
1915
  {
1852
1916
  const e = getOwner();
1853
- // Dev-tier graph registration (owner signal lists, onGraph); the
1854
- // `_owner` write itself never reaches the proxy, see storeOwners.
1855
- registerGraph(r, e);
1917
+ registerGraph(r, e);
1856
1918
  // Only once the engine is installed, like the node stamping it feeds: a
1857
1919
  // WeakMap.set per fresh store is a growing ephemeron table and,
1858
1920
  // disabled, buys nothing.
1859
1921
  if (attrHooks !== null) {
1860
1922
  storeOwners.set(r[$TARGET], e);
1861
- nameStore(r, n?.name);
1923
+ nameStore(r, n?.name, n?.loc);
1862
1924
  }
1863
1925
  }
1864
1926
  const setter = e => storeSetter(r, e);
@@ -1987,7 +2049,7 @@ function snapshotWalk(e, n, t) {
1987
2049
  // R27: a snapshot sees the batch's pending backing — a held one (another
1988
2050
  // transaction's future) only from its own draft.
1989
2051
  let i = o.v;
1990
- if (o.pb !== null && (inDraft(o) || writeOverride || o.k === null || !(o.k.i & CONFIG_HELD))) {
2052
+ if (o.pb !== null && (inDraft(o) || writeOverride || o.k === null || !(o.k.l & CONFIG_HELD))) {
1991
2053
  if (o.ovl) materializePB(o);
1992
2054
  i = o.pb;
1993
2055
  }