@solidjs/signals 2.0.0-rc.6 → 2.0.0-rc.7

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 (65) hide show
  1. package/dist/dev.js +2033 -1291
  2. package/dist/node.cjs +1821 -2401
  3. package/dist/node.dev.cjs +13724 -0
  4. package/dist/prod/boundaries.js +3 -1
  5. package/dist/prod/core/async.js +6 -1
  6. package/dist/prod/core/attribution-hooks.js +3 -0
  7. package/dist/prod/core/constants.js +9 -1
  8. package/dist/prod/core/context.js +10 -16
  9. package/dist/prod/core/core.js +215 -139
  10. package/dist/prod/core/dev.js +2 -0
  11. package/dist/prod/core/effect.js +37 -28
  12. package/dist/prod/core/external.js +2 -2
  13. package/dist/prod/core/graph.js +35 -31
  14. package/dist/prod/core/heap.js +34 -40
  15. package/dist/prod/core/lanes.js +41 -27
  16. package/dist/prod/core/optimistic.js +42 -32
  17. package/dist/prod/core/owner.js +32 -32
  18. package/dist/prod/core/scheduler.js +140 -154
  19. package/dist/prod/core/verdict.js +31 -34
  20. package/dist/prod/index.js +0 -2
  21. package/dist/prod/map.js +104 -94
  22. package/dist/prod/signals.js +51 -34
  23. package/dist/prod/store/next/optimistic.js +153 -172
  24. package/dist/prod/store/next/reconcile.js +140 -288
  25. package/dist/prod/store/next/store.js +307 -237
  26. package/dist/prod/store/store.js +2 -2
  27. package/dist/types/core/attribution-hooks.d.ts +64 -0
  28. package/dist/types/core/attribution.d.ts +265 -9
  29. package/dist/types/core/constants.d.ts +8 -0
  30. package/dist/types/core/core.d.ts +12 -3
  31. package/dist/types/core/dev.d.ts +49 -8
  32. package/dist/types/core/heap.d.ts +5 -3
  33. package/dist/types/core/invariants.d.ts +1 -1
  34. package/dist/types/core/lanes.d.ts +10 -0
  35. package/dist/types/core/scheduler.d.ts +9 -4
  36. package/dist/types/signals.d.ts +10 -0
  37. package/dist/types/store/index.d.ts +3 -6
  38. package/dist/types/store/next/optimistic.d.ts +4 -2
  39. package/dist/types/store/next/reconcile.d.ts +5 -12
  40. package/dist/types/store/next/store.d.ts +3 -9
  41. package/dist/types/store/next/target.d.ts +20 -46
  42. package/dist/types/store/store.d.ts +14 -9
  43. package/dist/types-cjs/core/attribution-hooks.d.cts +64 -0
  44. package/dist/types-cjs/core/attribution.d.cts +265 -9
  45. package/dist/types-cjs/core/constants.d.cts +8 -0
  46. package/dist/types-cjs/core/core.d.cts +12 -3
  47. package/dist/types-cjs/core/dev.d.cts +49 -8
  48. package/dist/types-cjs/core/heap.d.cts +5 -3
  49. package/dist/types-cjs/core/invariants.d.cts +1 -1
  50. package/dist/types-cjs/core/lanes.d.cts +10 -0
  51. package/dist/types-cjs/core/scheduler.d.cts +9 -4
  52. package/dist/types-cjs/signals.d.cts +10 -0
  53. package/dist/types-cjs/store/index.d.cts +3 -6
  54. package/dist/types-cjs/store/next/optimistic.d.cts +4 -2
  55. package/dist/types-cjs/store/next/reconcile.d.cts +5 -12
  56. package/dist/types-cjs/store/next/store.d.cts +3 -9
  57. package/dist/types-cjs/store/next/target.d.cts +20 -46
  58. package/dist/types-cjs/store/store.d.cts +14 -9
  59. package/package.json +3 -2
  60. package/dist/prod/store/next/patch-hooks.js +0 -13
  61. package/dist/prod/store/next/patch.js +0 -614
  62. package/dist/types/store/next/patch-hooks.d.ts +0 -41
  63. package/dist/types/store/next/patch.d.ts +0 -91
  64. package/dist/types-cjs/store/next/patch-hooks.d.cts +0 -41
  65. package/dist/types-cjs/store/next/patch.d.cts +0 -91
@@ -1,6 +1,6 @@
1
1
  import { unwrapOverride, $REFRESH, NOT_PENDING, CONFIG_OWNED_WRITE, CONFIG_OPTIMISTIC, CONFIG_HELD_TRUTH, STATUS_UNINITIALIZED, STATUS_ERROR, CONFIG_CHILDREN_FORBIDDEN, CONFIG_AUTHORITATIVE_READ } from "../../core/constants.js";
2
2
 
3
- import { setSignal, isEqual, read, pendingCheckActive, latestReadActive, readNodeFast, READ_SLOW, signal, ext, setLatestReadActive, prepareComputed, context } from "../../core/core.js";
3
+ import { setSlotUnobserved, setSignal, isEqual, read, pendingCheckActive, latestReadActive, readNodeFast, READ_SLOW, signal, ext, setLatestReadActive, prepareComputed, slotSignal, context } from "../../core/core.js";
4
4
 
5
5
  import { projectionWriteActive, setProjectionWriteActive, activeTransition, schedule, currentTransition, setStoreCommitHook } from "../../core/scheduler.js";
6
6
 
@@ -10,8 +10,6 @@ import { $TARGET, isWrappable, markRawIngest, $PROXY, getWriteOverride, markRawO
10
10
 
11
11
  import { storeNextLookup, optHooks, ownedRaw, markDescendants, devAssertNeverUserMutation } from "./target.js";
12
12
 
13
- import { patchHooks, rowHooks } from "./patch-hooks.js";
14
-
15
13
  /**
16
14
  * Store rewrite — increment 2: plain deep stores with pending-backing writes.
17
15
  * Contract: INTERNALS-STORE-STATE.md.
@@ -44,9 +42,9 @@ import { patchHooks, rowHooks } from "./patch-hooks.js";
44
42
  *
45
43
  * ARRAY SHAPE RULE: arrays normalize their named properties to dictionary
46
44
  * mode as the count grows (V8 13.x: counts ≡ 0 mod 3 from 18 up), so the
47
- * target's named field count is capped at 20 — write-side patch-channel
48
- * state lives inside the single `pc` extension (see target.ts), never as
49
- * new named fields here. */
45
+ * target's named field count is capped at 20 — any future write-side state
46
+ * beyond `wk` must ride an extension object (see target.ts), never new
47
+ * named fields here. */
50
48
  function TargetShape() {
51
49
  this.v = undefined;
52
50
  this.ch = undefined;
@@ -62,29 +60,18 @@ function TargetShape() {
62
60
  this.a = undefined;
63
61
  this.sc = undefined;
64
62
  this.nc = undefined;
65
- this.adopted = undefined;
63
+ this.ab = undefined;
66
64
  this.fam = undefined;
67
65
  this.s = undefined;
68
66
  this.ovl = undefined;
69
67
  this.del = undefined;
70
- this.pc = undefined;
68
+ this.wk = undefined;
71
69
  this.hv = undefined;
72
70
  this.ht = undefined;
73
71
  }
74
72
 
75
73
  TargetShape.prototype = Object.prototype;
76
74
 
77
- /** Lazily allocate the patch-channel extension (one literal shape). */ function pcOf(e) {
78
- return e.pc ?? (e.pc = {
79
- sp: null,
80
- p: null,
81
- ro: null,
82
- wk: null,
83
- qa: null,
84
- qe: null
85
- });
86
- }
87
-
88
75
  function createTarget(e, t, n, r = t?.fam ?? null) {
89
76
  // The proxy target carries the array exotic class when the value is an
90
77
  // array, so Array.isArray(proxy) is true; the fields live on it directly.
@@ -101,7 +88,7 @@ function createTarget(e, t, n, r = t?.fam ?? null) {
101
88
  i.h = null;
102
89
  i.k = null;
103
90
  i.dk = null;
104
- i.pc = null;
91
+ i.wk = null;
105
92
  i.u = t;
106
93
  i.pk = n;
107
94
  i.px = null;
@@ -109,7 +96,7 @@ function createTarget(e, t, n, r = t?.fam ?? null) {
109
96
  i.a = false;
110
97
  i.sc = false;
111
98
  i.nc = 0;
112
- i.adopted = false;
99
+ i.ab = null;
113
100
  i.fam = r;
114
101
  i.s = false;
115
102
  i.ovl = false;
@@ -155,55 +142,68 @@ function wrapNext(e, t = null, n = null, r = t?.fam ?? null) {
155
142
 
156
143
  // ---------------------------------------------------------------------------
157
144
  // nodes: pure subscription points (values used only for equality gating)
158
- function getNode(e, t, n) {
159
- const r = e.n ??= Object.create(null);
160
- let i = r[t];
161
- if (i === undefined) {
162
- const o = i = signal(n, {
163
- // Logical-slot equality: values resolving to the same child target
164
- // are the same slot (privatization/adoption swap raw identity without
165
- // changing the logical value — only changed leaves notify, R9).
166
- equals: (t, n) => isEqual(t, n) || sameLogicalSlot(e, t, n),
167
- unobserved() {
168
- // A live affects() mark keeps the node addressable (sweep parity).
169
- if (o.o?.t) return;
170
- if (e.n && e.n[t] === o) {
171
- delete e.n[t];
172
- e.nc--;
173
- }
174
- }
175
- },
176
- // Projection nodes carry the projection computed as their firewall:
177
- // reads through them link the derive's status/lifecycle (§7b).
178
- e.fam?.node ?? undefined);
179
- // Store nodes are ownedWrite: the setter carries the owned-scope write
180
- // guard; node-level setSignals are internal notification machinery.
181
- o.T |= CONFIG_OWNED_WRITE;
182
- // Accessor-ness resolved ONCE per node (no per-object descriptor scan):
183
- // accessor keys serve through Reflect.get with the proxy receiver.
184
- o.acc = isOwnAccessor(e.pb ?? e.v, t);
185
- // Wrap cache: the proxy last served for this key and the raw it wrapped.
186
- // Raw-as-truth stores raw in nodes, so every object read needs a wrapper;
187
- // one pointer compare (pxv === value) replaces the per-read WeakMap
188
- // lookup in wrapNext the dominant read-path cost vs legacy, whose
189
- // nodes stored pre-wrapped values. A replaced child fails the compare
190
- // and re-wraps; at most one stale proxy is pinned until the next read.
191
- o.px = undefined;
192
- o.pxv = undefined;
145
+ // Shared slot-node equality (create-floor diet): ONE function for every
146
+ // store node `this` is the node (method-call convention at every _equals
147
+ // site), `_host` is the baked-in target backref. Logical-slot equality:
148
+ // values resolving to the same child target are the same slot
149
+ // (privatization/adoption swap raw identity without changing the logical
150
+ // value only changed leaves notify, R9).
151
+ const slotNodeEquals = function(e, t) {
152
+ return isEqual(e, t) || sameLogicalSlot(this.ht, e, t);
153
+ };
154
+
155
+ // Shared slot-node unobserved handler (create-floor diet): registered once;
156
+ // the core sweep dispatches CONFIG_SLOT_NODE nodes here instead of holding a
157
+ // per-node closure in a per-node NodeExtension.
158
+ setSlotUnobserved(e => {
159
+ // A live affects() mark keeps the node addressable (sweep parity).
160
+ if (e.o?.t) return;
161
+ const t = e.ht;
162
+ const n = e.Ht;
163
+ if (t.n && t.n[n] === e) {
164
+ delete t.n[n];
165
+ t.nc--;
166
+ }
167
+ });
168
+
169
+ function getNode(e, t, n,
170
+ // First-read dedupe (create-floor slice 2): the get trap probes
171
+ // accessor-ness right before creating the node — pass the verdict through
172
+ // so creation skips the second descriptor scan. -1 = unknown (other
173
+ // callers), 0/1 = probed.
174
+ r = -1) {
175
+ const i = e.n ??= Object.create(null);
176
+ let o = i[t];
177
+ if (o === undefined) {
178
+ // Create-floor diet: slotSignal bakes the whole node into one literal —
179
+ // no options object, no equals/unobserved closures, no NodeExtension,
180
+ // no post-construction expandos (acc + the wrap cache px/pxv are
181
+ // pre-shaped fields: the proxy last served for this key and the raw it
182
+ // wrapped — one pointer compare replaces the per-read WeakMap lookup in
183
+ // wrapNext). ownedWrite rides the literal's config: the setter carries
184
+ // the owned-scope write guard; node-level setSignals are internal
185
+ // notification machinery. Projection nodes carry the projection
186
+ // computed as their firewall: reads through them link the derive's
187
+ // status/lifecycle (§7b).
188
+ const f = o = slotSignal(n, slotNodeEquals, e, t,
189
+ // Accessor-ness resolved ONCE per node (no per-object descriptor
190
+ // scan on reads): accessor keys serve through Reflect.get with the
191
+ // proxy receiver.
192
+ r === -1 ? isOwnAccessor(e.pb ?? e.v, t) : r === 1, e.fam?.node ?? undefined);
193
193
  // Optimistic families: arm the override slot — setSignal routes armed
194
194
  // nodes through the core engine (lanes, ownership, reverts all native).
195
195
  if (e.fam?.opt) {
196
- ext(o).Pe = NOT_PENDING;
197
- o.T |= CONFIG_OPTIMISTIC;
196
+ ext(f).Pe = NOT_PENDING;
197
+ f.T |= CONFIG_OPTIMISTIC;
198
198
  }
199
199
  // A node born inside a live mark's identity scope inherits the mark
200
200
  // (the declaration walk could only cover nodes existing then).
201
- if (t !== $AFFECTS && affectsScopesLive()) inheritAffectsMarks(o, e.v, t);
202
- r[t] = i;
201
+ if (t !== $AFFECTS && affectsScopesLive()) inheritAffectsMarks(f, e.v, t);
202
+ i[t] = o;
203
203
  e.nc++;
204
204
  markDescendants(e);
205
205
  }
206
- return i;
206
+ return o;
207
207
  }
208
208
 
209
209
  function sameLogicalSlot(e, t, n) {
@@ -305,10 +305,12 @@ function cloneRaw(e, t) {
305
305
  return Array.isArray(e) ? Object.defineProperties([], n) : Object.create(Object.getPrototypeOf(e), n);
306
306
  }
307
307
 
308
- /** Scanned plainness for patch admission (patchableRaw): runs the one-time
309
- * accessor scan if it hasn't happened yet — the sticky `a` flag alone is not
310
- * trustworthy before a scan (it starts false and is discovered lazily). */ function targetIsPlain(e) {
311
- return e.sc ? !e.a : scanAccessorsOnce(e);
308
+ /** Copy own `key` from `from` onto `to`. A plain data slot (enumerable,
309
+ * writable, configurable, no accessor) is a bare assignment — the common case
310
+ * and the cheap one; anything else goes through defineProperty so accessors
311
+ * and attribute flags survive the copy. */ function copyOwn(e, t, n) {
312
+ const r = Object.getOwnPropertyDescriptor(t, n);
313
+ if (r.get || r.set || !r.enumerable || !r.writable || !r.configurable) Object.defineProperty(e, n, r); else e[n] = r.value;
312
314
  }
313
315
 
314
316
  /** One-time own-accessor scan (Annex-B probes, no descriptor allocation);
@@ -333,10 +335,7 @@ function cloneRaw(e, t) {
333
335
  if (!e.ovl) return;
334
336
  const t = e.pb;
335
337
  const n = cloneRaw(e.v, e);
336
- for (const e of Reflect.ownKeys(t)) {
337
- const r = Object.getOwnPropertyDescriptor(t, e);
338
- if (r.get || r.set || !r.enumerable || !r.writable || !r.configurable) Object.defineProperty(n, e, r); else n[e] = r.value;
339
- }
338
+ for (const e of Reflect.ownKeys(t)) copyOwn(n, t, e);
340
339
  if (e.del !== null) {
341
340
  for (const t of e.del) delete n[t];
342
341
  e.del = null;
@@ -439,7 +438,18 @@ function ensurePB(e) {
439
438
  if (!n) {
440
439
  queueFold(e);
441
440
  // records the pre-batch old before we swap
442
- e.adopted = true;
441
+ // Diff base = the view the nodes were last told (#3296). A draft's
442
+ // setter-exit notifications already moved them to its pending backing,
443
+ // so a later adoption diffs against THAT — against committed, a key the
444
+ // draft changed and the adoption restores would never re-notify. An
445
+ // adoption with no draft leaves nodes where they were: keep an existing
446
+ // base, else the pre-batch committed (foldOlds' entry itself stays the
447
+ // committed identity for the path-copy CAS). Eager callers read t.pb
448
+ // directly; this hand-off exists because pb is gone by drain time.
449
+ if (e.pb !== null) {
450
+ if (e.ovl) materializePB(e);
451
+ e.ab = e.pb;
452
+ } else e.ab ??= foldOlds.get(e);
443
453
  // #3074/#3075: a projection recompute deriving from uncommitted inputs
444
454
  // swaps the backing SPECULATIVELY — committed-visibility readers must
445
455
  // keep the pre-hold view until the hold resolves (a source held by a
@@ -467,7 +477,7 @@ function ensurePB(e) {
467
477
  e.del = null;
468
478
  e.sc = false;
469
479
  e.a = false;
470
- if (e.pc !== null) e.pc.wk = null;
480
+ e.wk = null;
471
481
  // adoption supersedes staged trap writes
472
482
  e.v = t;
473
483
  e.ch = t[$TARGET] !== undefined;
@@ -517,20 +527,56 @@ function queueFold(e) {
517
527
  * transition is ambient). Entries die with their draft — tentative backings
518
528
  * are consumed at setter exit. */ const tentativePBs = new WeakSet;
519
529
 
530
+ /** A draft read composes the live optimistic view until the draft has opened
531
+ * its OWN view-seeded backing (ensurePB seeds that clone from the view and
532
+ * registers it in tentativePBs; from then on reads must see the draft's
533
+ * writes, not the overrides they superseded). A pending backing that exists
534
+ * for any other reason is not that clone — a truth landing staged into a
535
+ * retaining transaction (#3164 fold) is authoritative truth WITHOUT the
536
+ * live overrides. ensurePB parks such a backing on the draft's first WRITE
537
+ * and reseeds from the view, but the reads that precede that write went to
538
+ * the staged truth: `votes++` read base, wrote base+1, and the override it
539
+ * emitted landed on the value already displayed — a second in-flight
540
+ * increment made after a sibling's landing was invisible (#2951's compose
541
+ * half, one landing later). */ function draftSeesOverrides(e) {
542
+ return e.pb === null || !tentativePBs.has(e.pb);
543
+ }
544
+
520
545
  /** Committed-time privatization for parent-chain slot updates (path copying). */ function privatizeCommitted(e) {
521
546
  if (ownedRaw.has(e.v)) return;
522
- const t = cloneRaw(e.v, e);
523
- ownedRaw.add(t);
524
- storeNextLookup.set(t, e);
525
- e.v = t;
547
+ const t = e.v;
548
+ const n = cloneRaw(t, e);
549
+ ownedRaw.add(n);
550
+ // Register in the target's OWN registration map (#3284): family targets
551
+ // (derived stores, projections, optimistic) resolve children through
552
+ // fam.map — a clone parked only in the global lookup makes the next parent
553
+ // read miss, wrap a fresh target, and orphan every node (subscribers) on
554
+ // this one.
555
+ (e.fam?.map ?? storeNextLookup).set(n, e);
556
+ e.v = n;
526
557
  e.ch = false;
527
558
  if (e.u) {
528
559
  privatizeCommitted(e.u);
529
560
  devAssertNeverUserMutation(e.u.v);
530
- e.u.v[e.pk] = e.v;
561
+ e.u.v[parentSlotKey(e, t)] = e.v;
531
562
  }
532
563
  }
533
564
 
565
+ /** Resolve the slot this child currently occupies in its parent's committed
566
+ * backing (#3282). `pk` is stamped at wrap time and arrays MOVE: a reverse/
567
+ * unshift/splice relocates the raw, and a fold that re-points the wrap-time
568
+ * slot writes the clone over whichever row lives there now. Objects never
569
+ * move keys, so the stamp is authoritative; for arrays, verify and re-locate
570
+ * by identity when stale (fold-time only — never on a read path). */ function parentSlotKey(e, t) {
571
+ const n = e.pk;
572
+ const r = e.u.v;
573
+ if (r[n] === t || !Array.isArray(r)) return n;
574
+ const i = r.indexOf(t);
575
+ if (i === -1) return n;
576
+ e.pk = i;
577
+ return i;
578
+ }
579
+
534
580
  function drainFolds() {
535
581
  if (foldOlds.size === 0) return;
536
582
  const e = [ ...foldOlds ];
@@ -540,11 +586,6 @@ function drainFolds() {
540
586
  // is committing the batch the pull ran ahead of. Transition holds stay —
541
587
  // they clear when their transition is done (heldMaskView).
542
588
  if (t.ht === PLAIN_HOLD) t.ht = t.hv = null;
543
- // Eager (write-override) family folds swap pb -> v at notifyWrites'
544
- // tail: by the time this drain runs they carry no pb, and their
545
- // structural ops must emit at the fold-commit site below (the clone
546
- // branch never sees them). Re-audit blocker 4.
547
- const e = t.pb === null;
548
589
  if (t.pb !== null) {
549
590
  // #3089: a fold written under a still-running transition defers to
550
591
  // that transition's settle (the write-time stamp covers unobserved
@@ -568,7 +609,7 @@ function drainFolds() {
568
609
  // Only written keys can hold (their nodes took the setSignal); the
569
610
  // wk bound keeps this O(written) — see notifyWrites. Same fallback
570
611
  // rules as the notify (WK_ALL / accessors / non-plain prototypes).
571
- const e = t.pc !== null ? t.pc.wk : null;
612
+ const e = t.wk;
572
613
  const n = e === null || e === WK_ALL || t.a === true ||
573
614
  // Overlay pbs chain to the COMMITTED object (#3044) — plainness is
574
615
  // the committed container's prototype, not the overlay's.
@@ -596,10 +637,7 @@ function drainFolds() {
596
637
  // — the never-mutate-user-data contract holds.
597
638
  privatizeCommitted(t);
598
639
  const e = t.v;
599
- for (const t of Reflect.ownKeys(i)) {
600
- const n = Object.getOwnPropertyDescriptor(i, t);
601
- if (n.get || n.set || !n.enumerable || !n.writable || !n.configurable) Object.defineProperty(e, t, n); else e[t] = n.value;
602
- }
640
+ for (const t of Reflect.ownKeys(i)) copyOwn(e, i, t);
603
641
  if (t.del !== null) {
604
642
  for (const n of t.del) delete e[n];
605
643
  t.del = null;
@@ -607,67 +645,78 @@ function drainFolds() {
607
645
  (t.fam?.map ?? storeNextLookup).delete(i);
608
646
  t.pb = null;
609
647
  t.ovl = false;
610
- if (t.pc !== null) t.pc.wk = null;
648
+ t.wk = null;
649
+ // written-keys window closes with the fold commit
650
+ } else if (t.v !== n) {
651
+ // Privatized mid-batch (#3271): an earlier fold in this drain
652
+ // path-copied THROUGH this target — privatizeCommitted cloned the
653
+ // committed backing, re-pointed the parent slot at the clone, and
654
+ // stitched the descendant's fold into it. The draft's pb predates
655
+ // that: swapping it in would clobber the descendant's fold, and the
656
+ // parent CAS below (still comparing against `old`) would fail and
657
+ // orphan this fold entirely — a projection draft writing descendant-
658
+ // then-ancestor silently lost the ancestor write. Merge the batch's
659
+ // writes onto the current container instead (the parent slot already
660
+ // points at it). privatize first: an adopt-then-write batch can land
661
+ // here with an unowned adoptee as t.v.
662
+ privatizeCommitted(t);
663
+ const e = t.v;
664
+ const r = t.wk;
665
+ if (r !== null && r !== WK_ALL) {
666
+ // The trap records every write/delete key — apply exactly those.
667
+ for (const t of r) {
668
+ if (hasOwn.call(i, t)) copyOwn(e, i, t); else delete e[t];
669
+ }
670
+ } else {
671
+ // Array length write poisoned the bound (WK_ALL) — value-diff
672
+ // against the pre-batch old. Slots the draft never touched hold
673
+ // the same raw reference in both, so descendant folds stay put.
674
+ // Not copyOwn: the plain-value write is GATED on "the draft changed
675
+ // this slot" — an untouched slot in pb holds the pre-batch reference,
676
+ // and writing it back would clobber a descendant fold stitched into v.
677
+ for (const t of Reflect.ownKeys(i)) {
678
+ const r = Object.getOwnPropertyDescriptor(i, t);
679
+ if (r.get || r.set || !r.enumerable || !r.writable || !r.configurable) Object.defineProperty(e, t, r); else if (r.value !== n[t] || !hasOwn.call(n, t)) e[t] = r.value;
680
+ }
681
+ for (const t of Reflect.ownKeys(n)) {
682
+ if (!hasOwn.call(i, t)) delete e[t];
683
+ }
684
+ }
685
+ (t.fam?.map ?? storeNextLookup).delete(i);
686
+ t.pb = null;
687
+ t.wk = null;
611
688
  // written-keys window closes with the fold commit
612
689
  } else {
613
- // Setter-channel structural ops: a fold that changes an array's shape
614
- // (push/splice/permutation through the setter — the reconcile walk
615
- // never queues here) is a structural visibility transition for any
616
- // registered list driver. Identity-keyed; aligned folds emit nothing.
617
- // Family targets defer to their own adoption emission (fam reconcile).
618
- // Arrays always fold on this clone branch (overlay is non-array only).
619
- // Family setter drafts (writable projection push/splice through the
620
- // masked setter) fold on this branch too and the fold IS their
621
- // visibility moment — emit unless the structure already rode another
622
- // channel: adoption folds (reconcile walk emitted ops) and
623
- // optimistic families (lane-timed override channel). Re-audit
624
- // blocker 4.
625
- if (t.pc !== null && t.pc.ro !== null && !t.adopted && t.fam?.opt !== true && Array.isArray(i) && Array.isArray(t.v)) rowHooks.emitSetterRowOps(t, t.v, i);
626
690
  t.v = i;
627
691
  t.ch = false;
628
692
  // pb is always a plain clone
629
693
  t.pb = null;
630
- if (t.pc !== null) t.pc.wk = null;
694
+ t.wk = null;
631
695
  // written-keys window closes with the fold commit
632
696
  }
633
697
  }
634
- if (t.v === n) {
635
- // A no-op adoption (A -> B -> A before flush) still consumed its walk:
636
- // clear the flag or every later setter row-op gate (!t.adopted) stays
637
- // failed and a driven family list freezes (re-audit 5, P1-1).
638
- t.adopted = false;
639
- continue;
640
- }
641
- // Patch channel (fold-commit site): family targets emit HERE — the fold
642
- // IS their visibility moment (held folds re-queued above emit when they
643
- // actually commit) and so do PLAIN fold-adopted targets (setter-
644
- // returned root replacements, chained-store swaps: adoptions WITHOUT a
645
- // reconcile walk, so no walk-site emission ever happened — re-audit 2,
646
- // P1-2). Plain eager targets emitted at their walk/setter sites already.
647
- if (t.pc !== null && (t.fam !== null || t.adopted)) {
648
- // Structural ops for folds whose structure rode no other channel:
649
- // eager-folded family SETTER drafts (write-override swaps pb -> v at
650
- // notifyWrites' tail — the clone branch never sees them; adoption
651
- // folds re-emitting would double the walk's ops) and PLAIN fold
652
- // adoptions (no walk at all). Optimistic families ride the override
653
- // channel (lane-timed ops + revert RESYNC) — never re-emit here.
654
- if (t.pc.ro !== null && t.fam?.opt !== true && (t.fam !== null ? e && !t.adopted : t.adopted) && Array.isArray(t.v) && Array.isArray(n)) rowHooks.emitSetterRowOps(t, n, t.v);
655
- if (t.pc.p !== null) {
656
- // Accessor demotion at the fold-commit seam is DEV-ONLY (see the
657
- // reconcile seam note: prod never pays per-adoption scans).
658
- patchHooks.emitPatchLocal(t, t.v, n);
698
+ const e = t.ab;
699
+ t.ab = null;
700
+ if (t.v !== n) {
701
+ // Path copying (CAS: see the eager-fold twin above). Slot resolved by
702
+ // identity (#3282): an array move relocated the raw, so the wrap-time
703
+ // pk may point at a sibling — a raw-slot CAS there both failed to
704
+ // re-point AND (via privatizeCommitted's unguarded write) clobbered
705
+ // the sibling.
706
+ if (t.u) {
707
+ const e = parentSlotKey(t, n);
708
+ if (t.u.v[e] === n) {
709
+ privatizeCommitted(t.u);
710
+ devAssertNeverUserMutation(t.u.v);
711
+ t.u.v[e] = t.v;
712
+ }
659
713
  }
660
714
  }
661
- // Path copying (CAS: see the eager-fold twin above).
662
- if (t.u && t.u.v[t.pk] === n) {
663
- privatizeCommitted(t.u);
664
- devAssertNeverUserMutation(t.u.v);
665
- t.u.v[t.pk] = t.v;
666
- }
667
- if (t.adopted) {
668
- t.adopted = false;
669
- notifyFold(t, n, t.v);
670
- }
715
+ // Adoption notify against the base the nodes were last told (#3296). A
716
+ // no-op adoption (A -> B -> A before flush, no draft) has base === v and
717
+ // nothing to say; a draft superseded by an adoption back to the SAME raw
718
+ // still has (base = pending backing) !== v and must notify.
719
+ if (e !== null && e !== t.v) notifyFold(t, e, t.v);
671
720
  }
672
721
  }
673
722
 
@@ -717,7 +766,7 @@ function drainFolds() {
717
766
  // implicit index deletes), accessors on the record (t.a — a getter node's
718
767
  // value can change when ANY key is written), or a non-plain prototype
719
768
  // (class instances: prototype getters derive from arbitrary fields).
720
- const i = e.pc !== null ? e.pc.wk : null;
769
+ const i = e.wk;
721
770
  // Overlay pbs chain to the COMMITTED object (#3044): a prototype-overlay
722
771
  // draft is plain data on its own layer, but its getPrototypeOf is the
723
772
  // committed container — judge plainness by the COMMITTED prototype or the
@@ -749,15 +798,15 @@ function drainFolds() {
749
798
  // projection recompute can run before the prior fold commits) — the
750
799
  // node's OWN current value is the true old side, and setSignal's
751
800
  // internal equality already checks exactly that.
752
- const l = e.del !== null && e.del.has(o) ? undefined : t[o];
753
- setSignal(i, () => l);
801
+ const f = e.del !== null && e.del.has(o) ? undefined : t[o];
802
+ setSignal(i, () => f);
754
803
  }
755
804
  }
756
- const l = e.h;
757
- if (l !== null) {
758
- const n = o ?? Reflect.ownKeys(l);
805
+ const f = e.h;
806
+ if (f !== null) {
807
+ const n = o ?? Reflect.ownKeys(f);
759
808
  for (const r of n) {
760
- const n = l[r];
809
+ const n = f[r];
761
810
  if (n !== undefined) setSignal(n, r in t && !(e.del !== null && e.del.has(r)));
762
811
  }
763
812
  }
@@ -791,12 +840,6 @@ function drainFolds() {
791
840
  }
792
841
  if (r) setSignal(e.k, e => e + 1);
793
842
  }
794
- // Patch channel (setter site): a committed write transitions this record —
795
- // queue its patches and bubble to ancestors (targeted nested writes must
796
- // reach the row patch, §4b). One number compare when no patches exist.
797
- // Family targets skip this site: their visibility moment is the FOLD
798
- // commit (drainFolds emits), not the recompute/draft write.
799
- if (e.fam === null && patchHooks !== null && patchHooks.hasPatches()) patchHooks.emitPatch(e, t, n);
800
843
  // Projection backing folds split by channel (two pinned contracts):
801
844
  // - sync-derive drafts (recompute body): NEVER eager — a downstream async
802
845
  // hold can form LATER in the same flush and the leaf must stay at stale
@@ -821,10 +864,14 @@ function drainFolds() {
821
864
  e.pb = null;
822
865
  e.v = t;
823
866
  e.ch = false;
824
- if (e.u && e.u.v[e.pk] === n) {
825
- privatizeCommitted(e.u);
826
- devAssertNeverUserMutation(e.u.v);
827
- e.u.v[e.pk] = t;
867
+ if (e.u) {
868
+ // Identity-resolved slot (#3282) — see drainFolds' path-copy twin.
869
+ const r = parentSlotKey(e, n);
870
+ if (e.u.v[r] === n) {
871
+ privatizeCommitted(e.u);
872
+ devAssertNeverUserMutation(e.u.v);
873
+ e.u.v[r] = t;
874
+ }
828
875
  }
829
876
  }
830
877
  }
@@ -881,9 +928,9 @@ i = true) {
881
928
  if (i?.get !== o?.get || i?.set !== o?.set || i?.value !== o?.value) setSignal(e, () => FORCE);
882
929
  return;
883
930
  }
884
- const l = i?.value;
885
- const f = o?.value;
886
- if (!isEqual(l, f) && !targetsEqual(l, f)) setSignal(e, typeof f === "function" ? () => f : f);
931
+ const f = i?.value;
932
+ const l = o?.value;
933
+ if (!isEqual(f, l) && !targetsEqual(f, l)) setSignal(e, typeof l === "function" ? () => l : l);
887
934
  } else {
888
935
  const i = n[t];
889
936
  const o = r[t];
@@ -1008,7 +1055,7 @@ const UNSAFE_KEYS = new Set([ "__proto__", "prototype", "constructor" ]);
1008
1055
  * / onSettled callbacks) get COMMITTED visibility (#3006), same as core. */ function inOwnerContext() {
1009
1056
  const e = getOwner();
1010
1057
  if (e === null) return false;
1011
- const t = e.Gt ? e.Dt : e;
1058
+ const t = e.Dt ? e.Pt : e;
1012
1059
  return t != null && !(t.T & CONFIG_CHILDREN_FORBIDDEN);
1013
1060
  }
1014
1061
 
@@ -1019,7 +1066,7 @@ const UNSAFE_KEYS = new Set([ "__proto__", "prototype", "constructor" ]);
1019
1066
  * committed to them regardless of staged writes. */ function inForbiddenScope() {
1020
1067
  const e = getOwner();
1021
1068
  if (e === null) return false;
1022
- const t = e.Gt ? e.Dt : e;
1069
+ const t = e.Dt ? e.Pt : e;
1023
1070
  return t != null && !!(t.T & CONFIG_CHILDREN_FORBIDDEN);
1024
1071
  }
1025
1072
 
@@ -1171,15 +1218,15 @@ function isOwnAccessor(e, t) {
1171
1218
  * holds, lanes ride the node); backing otherwise. Chained backings (§7b: the
1172
1219
  * backing IS another store's proxy) serve the read-through value — the outer
1173
1220
  * node is linked only for adoption-swap notification, its value never
1174
- * shadows the live chain. */ function serveDataKey(e, t, n, r, i) {
1175
- const o = e.ch && r === e.v;
1221
+ * shadows the live chain. */ function serveDataKey(e, t, n, r, i, o = -1) {
1222
+ const f = e.ch && r === e.v;
1176
1223
  let l = n;
1177
1224
  // §6: on optimistic arrays LENGTH IS A VIEW, not a node value — one home
1178
1225
  // (backing ± presence overrides) for both length and indices makes torn
1179
1226
  // iteration impossible (a length node's value rides different visibility
1180
1227
  // rails than index overrides mid-settle). The node still carries
1181
1228
  // subscriptions; its value is never served here.
1182
- if (t === "length" && e.fam?.opt === true && !o && Array.isArray(r)) {
1229
+ if (t === "length" && e.fam?.opt === true && !f && Array.isArray(r)) {
1183
1230
  if (!inDraft(e)) {
1184
1231
  const r = e.n?.length;
1185
1232
  if (r !== undefined) {
@@ -1199,7 +1246,7 @@ function isOwnAccessor(e, t) {
1199
1246
  // #2951). Once ensurePB runs, the seeded clone carries the view.
1200
1247
  // AUTHORITATIVE drafts (projection derive) never overlay — ensurePB's
1201
1248
  // seeding rule, applied to the read side (#3108).
1202
- if (e.fam?.opt && e.pb === null && !authoritativeServe()) {
1249
+ if (e.fam?.opt && draftSeesOverrides(e) && !authoritativeServe()) {
1203
1250
  const n = e.n?.[t];
1204
1251
  if (n !== undefined && hasActiveOverride(n)) l = unwrapOverride(n.o?.Pe);
1205
1252
  }
@@ -1213,12 +1260,14 @@ function isOwnAccessor(e, t) {
1213
1260
  // parity): READ_SLOW = a global read window or non-plain node.
1214
1261
  let e = readNodeFast(i);
1215
1262
  if (e === READ_SLOW) e = read(i);
1216
- if (!o || hasActiveOverride(i)) l = e === FORCE ? n : e;
1217
- } else if (!o || hasActiveOverride(i)) {
1263
+ if (!f || hasActiveOverride(i)) l = e === FORCE ? n : e;
1264
+ } else if (!f || hasActiveOverride(i)) {
1218
1265
  l = nodeValue(i, n);
1219
1266
  }
1220
1267
  } else if (getObserver() !== null) {
1221
- read(getNode(e, t, n));
1268
+ // First tracked read: create + link, and let the wrap-cache branch
1269
+ // below populate px/pxv so read #2 skips wrapNext (slice 2).
1270
+ read(i = getNode(e, t, n, o));
1222
1271
  }
1223
1272
  }
1224
1273
  // Shallow stores serve data raw; store-proxy slots get boundary wrappers.
@@ -1320,8 +1369,11 @@ const traps = {
1320
1369
  // tracked read of a present data key — the dbmon/uibench effect re-read
1321
1370
  // shape. Skips serveDataKey's frame, the FORCE compare (only accessor
1322
1371
  // keys ever hold the sentinel), and isWrappable for primitives.
1323
- if (e.ch === false && writeScopes === null) {
1324
- const n = e.n?.[t];
1372
+ // ONE node-map lookup serves this block and the accessor probe below
1373
+ // (nothing between them creates nodes).
1374
+ const i = e.n?.[t];
1375
+ if (e.ch === false && writeScopes === null) {
1376
+ const n = i;
1325
1377
  if (n !== undefined && n.acc !== true && getObserver() !== null) {
1326
1378
  let r = readNodeFast(n);
1327
1379
  if (r === READ_SLOW) r = read(n);
@@ -1349,14 +1401,22 @@ const traps = {
1349
1401
  // absent-key/accessor subscriptions for every store read during any
1350
1402
  // derive, leaving nested projections permanently dependency-less when
1351
1403
  // their sources hadn't materialized yet (#3037).
1352
- const i = e.n?.[t];
1404
+ // First-read dedupe (create-floor slice 2): remember the probe verdict —
1405
+ // node creation downstream reuses it instead of re-scanning the
1406
+ // descriptor, but only when the probed object IS the one getNode would
1407
+ // scan (pb ?? v).
1408
+ let o = -1;
1353
1409
  {
1354
- const o = i !== undefined ? i.acc === true : !inDraft(e) && getObserver() !== null && isOwnAccessor(r, t);
1355
- if (o) {
1356
- if (!inDraft(e) && getObserver() !== null) read(i ?? getNode(e, t, undefined));
1357
- const o = Reflect.get(r, t, n);
1358
- if (e.s) return serveShallow(e, t, o);
1359
- return isWrappable(o) ? draftServe(e, wrapNext(o, e, t)) : o;
1410
+ let f;
1411
+ if (i !== undefined) f = i.acc === true; else if (!inDraft(e) && getObserver() !== null) {
1412
+ f = isOwnAccessor(r, t);
1413
+ if (r === (e.pb ?? e.v)) o = f ? 1 : 0;
1414
+ } else f = false;
1415
+ if (f) {
1416
+ if (!inDraft(e) && getObserver() !== null) read(i ?? getNode(e, t, undefined, o));
1417
+ const f = Reflect.get(r, t, n);
1418
+ if (e.s) return serveShallow(e, t, f);
1419
+ return isWrappable(f) ? draftServe(e, wrapNext(f, e, t)) : f;
1360
1420
  }
1361
1421
  }
1362
1422
  // Plain-data fast path: no descriptor allocation per read.
@@ -1366,10 +1426,10 @@ const traps = {
1366
1426
  // backings chain to the committed backing, so "own in the view" means
1367
1427
  // own on either layer (ownInView) — a genuine prototype method is one
1368
1428
  // that is own on NEITHER.
1369
- const o = e.ovl && r === e.pb;
1370
- if ((t === "constructor" || t === "__proto__" || t === "prototype") && !hasOwn.call(r, t) && !(o && hasOwn.call(e.v, t))) return undefined;
1429
+ const f = e.ovl && r === e.pb;
1430
+ if ((t === "constructor" || t === "__proto__" || t === "prototype") && !hasOwn.call(r, t) && !(f && hasOwn.call(e.v, t))) return undefined;
1371
1431
  let l = r[t];
1372
- if (l === undefined ? !hasOwn.call(r, t) && !(o && hasOwn.call(e.v, t)) : false) {
1432
+ if (l === undefined ? !hasOwn.call(r, t) && !(f && hasOwn.call(e.v, t)) : false) {
1373
1433
  // Inherited: prototype getters/methods run with the proxy receiver.
1374
1434
  l = Reflect.get(r, t, n);
1375
1435
  if (typeof l === "function") return l;
@@ -1377,14 +1437,14 @@ const traps = {
1377
1437
  // Reading a currently-absent own key subscribes to it (R12) — for any
1378
1438
  // target OUTSIDE its own draft scope, even mid-setter (#3037, above).
1379
1439
  if (l === undefined && !inDraft(e)) {
1380
- if (getObserver() !== null) read(getNode(e, t, undefined));
1440
+ if (getObserver() !== null) read(getNode(e, t, undefined, o));
1381
1441
  const n = e.n?.[t];
1382
1442
  if (n) {
1383
1443
  const r = nodeValue(n, undefined);
1384
1444
  if (e.s) return serveShallow(e, t, r);
1385
1445
  return isWrappable(r) ? draftServe(e, wrapNext(r, e, t)) : r;
1386
1446
  }
1387
- } else if (l === undefined && inDraft(e) && e.fam?.opt && e.pb === null &&
1447
+ } else if (l === undefined && inDraft(e) && e.fam?.opt && draftSeesOverrides(e) &&
1388
1448
  // AUTHORITATIVE drafts (landing folds) never seed from overrides —
1389
1449
  // the caller's optimism is not truth (has-trap twin below).
1390
1450
  !authoritativeServe()) {
@@ -1394,9 +1454,9 @@ const traps = {
1394
1454
  if (e.s) return serveShallow(e, t, l);
1395
1455
  return isWrappable(l) ? draftServe(e, wrapNext(l, e, t)) : l;
1396
1456
  }
1397
- if (typeof l === "function" && !hasOwn.call(r, t) && !(o && hasOwn.call(e.v, t))) return l;
1457
+ if (typeof l === "function" && !hasOwn.call(r, t) && !(f && hasOwn.call(e.v, t))) return l;
1398
1458
  // proto method
1399
- return serveDataKey(e, t, l, r, i);
1459
+ return serveDataKey(e, t, l, r, i, o);
1400
1460
  },
1401
1461
  has(e, t) {
1402
1462
  if (t === $TARGET || t === $PROXY || t === $TRACK) return true;
@@ -1417,7 +1477,7 @@ const traps = {
1417
1477
  const n = e.h?.[t];
1418
1478
  if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.Pe);
1419
1479
  }
1420
- } else if (e.fam?.opt && e.pb === null && !authoritativeServe()) {
1480
+ } else if (e.fam?.opt && draftSeesOverrides(e) && !authoritativeServe()) {
1421
1481
  const n = e.h?.[t];
1422
1482
  if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.Pe);
1423
1483
  }
@@ -1444,7 +1504,7 @@ const traps = {
1444
1504
  // Draft reads before the first write overlay too (pb, once created, is
1445
1505
  // seeded with the view). Authoritative-view reads (until()'s predicate,
1446
1506
  // truth-author drafts) skip the overlay.
1447
- if (!authoritativeServe() && e.fam?.opt && e.h !== null && (!inDraft(e) || e.pb === null)) {
1507
+ if (!authoritativeServe() && e.fam?.opt && e.h !== null && (!inDraft(e) || draftSeesOverrides(e))) {
1448
1508
  let t = null;
1449
1509
  for (const r of Reflect.ownKeys(e.h)) {
1450
1510
  const i = e.h[r];
@@ -1504,23 +1564,22 @@ const traps = {
1504
1564
  // passes through by reference (#2932; markRawOne skips proxies), while
1505
1565
  // deep stores unwrap to raw backings.
1506
1566
  const o = e.s ? n : unwrapValue(n);
1507
- const l = ensurePB(e);
1567
+ const f = ensurePB(e);
1508
1568
  pendingNotify.add(e);
1509
1569
  // Array length writes implicitly delete indices — the written-keys bound
1510
1570
  // can't see them, so poison to the full scan for this batch. Index
1511
1571
  // writes implicitly GROW length, so arrays always record it alongside.
1512
- const f = pcOf(e);
1513
- if (Array.isArray(l)) {
1514
- if (t === "length") f.wk = WK_ALL; else if (f.wk !== WK_ALL) {
1515
- const e = f.wk ??= new Set;
1516
- e.add(t);
1517
- e.add("length");
1572
+ if (Array.isArray(f)) {
1573
+ if (t === "length") e.wk = WK_ALL; else if (e.wk !== WK_ALL) {
1574
+ const n = e.wk ??= new Set;
1575
+ n.add(t);
1576
+ n.add("length");
1518
1577
  }
1519
- } else if (f.wk !== WK_ALL) (f.wk ??= new Set).add(t);
1578
+ } else if (e.wk !== WK_ALL) (e.wk ??= new Set).add(t);
1520
1579
  // Own data keys literally named "prototype"/"constructor" land as data —
1521
1580
  // defineProperty sidesteps a proto-chain setter named the same.
1522
1581
  if (UNSAFE_KEYS.has(t)) {
1523
- Object.defineProperty(l, t, {
1582
+ Object.defineProperty(f, t, {
1524
1583
  value: o,
1525
1584
  writable: true,
1526
1585
  enumerable: true,
@@ -1532,14 +1591,14 @@ const traps = {
1532
1591
  // Overlay first-write DEFINES the own key: assignment through the proto
1533
1592
  // chain would reject on a non-writable committed property (the clone
1534
1593
  // path normalized descriptors for exactly this — R51 parity).
1535
- if (e.ovl && !hasOwn.call(l, t)) {
1536
- Object.defineProperty(l, t, {
1594
+ if (e.ovl && !hasOwn.call(f, t)) {
1595
+ Object.defineProperty(f, t, {
1537
1596
  value: o,
1538
1597
  writable: true,
1539
1598
  enumerable: true,
1540
1599
  configurable: true
1541
1600
  });
1542
- } else l[t] = o;
1601
+ } else f[t] = o;
1543
1602
  if (e.del !== null) e.del.delete(t);
1544
1603
  // Shallow ingest: written records are sticky raw-marked (one entity is
1545
1604
  // never both deep-wrapped and raw — R41/#2932, shared invariant).
@@ -1554,14 +1613,7 @@ const traps = {
1554
1613
  const i = !r && getWriteOverride();
1555
1614
  if (!r && !i) return true;
1556
1615
  if (t === "__proto__") return true;
1557
- if (n.get || n.set) {
1558
- e.a = true;
1559
- // Accessor demotion (re-audit blocker 3): a record that acquires an
1560
- // accessor after patch registration stops being patchable — pull its
1561
- // patches and re-drive them as tracked effect fallbacks. Hooks are
1562
- // installed whenever pc.p exists (registration installs them).
1563
- if (e.pc !== null && e.pc.p !== null) patchHooks.demoteToEffects(e);
1564
- }
1616
+ if (n.get || n.set) e.a = true;
1565
1617
  // Unwrap before ensurePB (see the set trap: self-reference materializes).
1566
1618
  if ("value" in n) n = {
1567
1619
  ...n,
@@ -1569,8 +1621,7 @@ const traps = {
1569
1621
  };
1570
1622
  const o = ensurePB(e);
1571
1623
  pendingNotify.add(e);
1572
- const l = pcOf(e);
1573
- if (l.wk !== WK_ALL) (l.wk ??= new Set).add(t);
1624
+ if (e.wk !== WK_ALL) (e.wk ??= new Set).add(t);
1574
1625
  Object.defineProperty(o, t, n);
1575
1626
  if (e.del !== null) e.del.delete(t);
1576
1627
  if (i) notifyWrites(e);
@@ -1582,8 +1633,7 @@ const traps = {
1582
1633
  if (!n && !r) return true;
1583
1634
  const i = ensurePB(e);
1584
1635
  pendingNotify.add(e);
1585
- const o = pcOf(e);
1586
- if (o.wk !== WK_ALL) (o.wk ??= new Set).add(t);
1636
+ if (e.wk !== WK_ALL) (e.wk ??= new Set).add(t);
1587
1637
  delete i[t];
1588
1638
  // A prototype overlay cannot shadow a delete of a committed key —
1589
1639
  // record it aside (#3044); reads/has/ownKeys/commit consult the set.
@@ -1647,6 +1697,10 @@ function createStoreNext(e, t = false) {
1647
1697
  return [ n, setter ];
1648
1698
  }
1649
1699
 
1700
+ // ---------------------------------------------------------------------------
1701
+ // snapshot (next targets): the backing IS the plain raw graph — zero copy.
1702
+ // Sees pending (R27) by reading pb. Chained/owned-copy caching lands with the
1703
+ // utilities increment; this covers the createStore-suite contract.
1650
1704
  /** True when `proxy` is a SHALLOW store (children served verbatim, slots
1651
1705
  * replaced by reference — #2932). The list driver uses this to choose the
1652
1706
  * slot-patch channel (collected row bodies) over per-record registration. */ function storeIsShallow(e) {
@@ -1696,8 +1750,24 @@ function createStoreNext(e, t = false) {
1696
1750
  read(getKeySetNode(e));
1697
1751
  read(getDeepNode(e));
1698
1752
  const r = e.fam?.map ?? storeNextLookup;
1699
- for (const n of Reflect.ownKeys(t)) {
1700
- const i = Object.getOwnPropertyDescriptor(t, n);
1753
+ // Overlay pending backings chain to the committed object (#3044): their
1754
+ // OWN keys are only this batch's writes. A bare ownKeys walk mid-flush
1755
+ // (effects recompute before the fold commits) missed every untouched
1756
+ // child, so the re-subscribing effect dropped those records from its
1757
+ // dependency set — later child edits never notified it (#3283). Merge
1758
+ // committed keys, minus deletes, exactly as the ownKeys trap does.
1759
+ let i = Reflect.ownKeys(t);
1760
+ if (e.ovl && t === e.pb) {
1761
+ const t = Reflect.ownKeys(e.v);
1762
+ const n = e.del;
1763
+ const r = n !== null && n.size !== 0 ? t.filter(e => !n.has(e)) : t;
1764
+ for (const t of i) {
1765
+ if (!hasOwn.call(e.v, t)) r.push(t);
1766
+ }
1767
+ i = r;
1768
+ }
1769
+ for (const n of i) {
1770
+ const i = Object.getOwnPropertyDescriptor(t, n) ?? Object.getOwnPropertyDescriptor(e.v, n);
1701
1771
  if (i === undefined) continue;
1702
1772
  if (i.get || i.set) {
1703
1773
  e.a = true;
@@ -1708,15 +1778,15 @@ function createStoreNext(e, t = false) {
1708
1778
  if (o === null || typeof o !== "object") continue;
1709
1779
  // Stored proxies (chained slots) resolve through their own target;
1710
1780
  // raw children through the family map, created on first visit.
1711
- let l = o[$TARGET] ?? r.get(o);
1712
- if (l === undefined) {
1781
+ let f = o[$TARGET] ?? r.get(o);
1782
+ if (f === undefined) {
1713
1783
  if (!isWrappable(o)) continue;
1714
1784
  wrapNext(o, e, n);
1715
- l = r.get(o);
1716
- if (l === undefined) continue;
1785
+ f = r.get(o);
1786
+ if (f === undefined) continue;
1717
1787
  // raw-marked: leaf by contract
1718
1788
  }
1719
- walkT(l);
1789
+ walkT(f);
1720
1790
  }
1721
1791
  };
1722
1792
  walkT(t);
@@ -1775,15 +1845,15 @@ function snapshotWalk(e, t, n) {
1775
1845
  const i = t.get(r);
1776
1846
  if (i !== undefined) return i;
1777
1847
  const o = Array.isArray(e);
1778
- const l = o ? [] : Object.create(Object.getPrototypeOf(e));
1779
- t.set(r, l);
1848
+ const f = o ? [] : Object.create(Object.getPrototypeOf(e));
1849
+ t.set(r, f);
1780
1850
  for (const r of Reflect.ownKeys(e)) {
1781
1851
  if (o && r === "length") continue;
1782
1852
  const i = e[r];
1783
- l[r] = i !== null && typeof i === "object" ? snapshotWalk(i, t, n) : i;
1853
+ f[r] = i !== null && typeof i === "object" ? snapshotWalk(i, t, n) : i;
1784
1854
  }
1785
- if (o) l.length = e.length;
1786
- return l;
1855
+ if (o) f.length = e.length;
1856
+ return f;
1787
1857
  }
1788
1858
  }
1789
1859
  const o = t.get(r);
@@ -1798,17 +1868,17 @@ function snapshotWalk(e, t, n) {
1798
1868
  t.set(r, i);
1799
1869
  for (const o of Reflect.ownKeys(r)) {
1800
1870
  if (e && o === "length") continue;
1801
- const l = Object.getOwnPropertyDescriptor(r, o);
1802
- if (typeof o === "symbol" && !l.enumerable) continue;
1803
- if (l.get || l.set) {
1804
- Object.defineProperty(i, o, l);
1871
+ const f = Object.getOwnPropertyDescriptor(r, o);
1872
+ if (typeof o === "symbol" && !f.enumerable) continue;
1873
+ if (f.get || f.set) {
1874
+ Object.defineProperty(i, o, f);
1805
1875
  continue;
1806
1876
  }
1807
- const f = l.value;
1808
- const u = f !== null && typeof f === "object" ? snapshotWalk(f, t, n) : f;
1809
- if (l.enumerable && l.writable && l.configurable) i[o] = u; else Object.defineProperty(i, o, {
1810
- ...l,
1811
- value: u
1877
+ const l = f.value;
1878
+ const s = l !== null && typeof l === "object" ? snapshotWalk(l, t, n) : l;
1879
+ if (f.enumerable && f.writable && f.configurable) i[o] = s; else Object.defineProperty(i, o, {
1880
+ ...f,
1881
+ value: s
1812
1882
  });
1813
1883
  }
1814
1884
  if (e && i.length !== r.length) i.length = r.length;
@@ -1817,22 +1887,22 @@ function snapshotWalk(e, t, n) {
1817
1887
  // UNOWNED (shared/user) subtrees keep identity unless a descendant
1818
1888
  // substituted; copy-on-substitution preserves the documented CoW contract.
1819
1889
  t.set(r, r);
1820
- let l = null;
1890
+ let f = null;
1821
1891
  for (const e of Reflect.ownKeys(r)) {
1822
1892
  const i = Object.getOwnPropertyDescriptor(r, e);
1823
1893
  if (!i || i.get || i.set) continue;
1824
1894
  const o = i.value;
1825
1895
  if (o === null || typeof o !== "object") continue;
1826
- const f = snapshotWalk(o, t, n);
1827
- if (f !== o) {
1828
- if (l === null) {
1829
- l = Array.isArray(r) ? [ ...r ] : Object.create(Object.getPrototypeOf(r), Object.getOwnPropertyDescriptors(r));
1830
- t.set(r, l);
1896
+ const l = snapshotWalk(o, t, n);
1897
+ if (l !== o) {
1898
+ if (f === null) {
1899
+ f = Array.isArray(r) ? [ ...r ] : Object.create(Object.getPrototypeOf(r), Object.getOwnPropertyDescriptors(r));
1900
+ t.set(r, f);
1831
1901
  }
1832
- l[e] = f;
1902
+ f[e] = l;
1833
1903
  }
1834
1904
  }
1835
- return l ?? r;
1905
+ return f ?? r;
1836
1906
  }
1837
1907
 
1838
- export { adoptPB, arrayStructureChanged, authoritativeRead, authoritativeServe, bumpDeep, createStoreNext, deepNext, getHasNode, getKeySetNode, getNode, hasAccessorFlag, hasActiveOverride, materializePB, membershipChanged, notifyFold, notifyFoldTail, notifyKeyDiff, notifyKeyValue, pcOf, runAuthoritative, snapshotNext, stagedTruthPB, storeHasFamily, storeHasOptimisticFamily, storeIsShallow, storeSetterNext, targetIsPlain, targetsEqual, unwrapValue, wrapNext };
1908
+ export { adoptPB, arrayStructureChanged, authoritativeRead, authoritativeServe, bumpDeep, createStoreNext, deepNext, getHasNode, getKeySetNode, getNode, hasAccessorFlag, hasActiveOverride, materializePB, membershipChanged, notifyFold, notifyFoldTail, notifyKeyDiff, notifyKeyValue, runAuthoritative, snapshotNext, stagedTruthPB, storeHasFamily, storeHasOptimisticFamily, storeIsShallow, storeSetterNext, targetsEqual, unwrapValue, wrapNext };