@solidjs/signals 2.0.0-rc.5 → 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 (66) hide show
  1. package/dist/dev.js +2467 -1322
  2. package/dist/node.cjs +2037 -2428
  3. package/dist/node.dev.cjs +13724 -0
  4. package/dist/prod/boundaries.js +3 -1
  5. package/dist/prod/core/async.js +47 -20
  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 +260 -188
  10. package/dist/prod/core/dev.js +2 -0
  11. package/dist/prod/core/effect.js +41 -32
  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 +44 -30
  16. package/dist/prod/core/optimistic.js +55 -45
  17. package/dist/prod/core/owner.js +35 -35
  18. package/dist/prod/core/scheduler.js +176 -177
  19. package/dist/prod/core/verdict.js +53 -52
  20. package/dist/prod/index.js +0 -2
  21. package/dist/prod/map.js +104 -94
  22. package/dist/prod/signals.js +119 -35
  23. package/dist/prod/store/next/optimistic.js +217 -163
  24. package/dist/prod/store/next/projection.js +2 -2
  25. package/dist/prod/store/next/reconcile.js +140 -288
  26. package/dist/prod/store/next/store.js +338 -252
  27. package/dist/prod/store/store.js +2 -2
  28. package/dist/types/core/attribution-hooks.d.ts +75 -0
  29. package/dist/types/core/attribution.d.ts +313 -9
  30. package/dist/types/core/constants.d.ts +8 -0
  31. package/dist/types/core/core.d.ts +12 -3
  32. package/dist/types/core/dev.d.ts +56 -9
  33. package/dist/types/core/heap.d.ts +5 -3
  34. package/dist/types/core/invariants.d.ts +1 -1
  35. package/dist/types/core/lanes.d.ts +10 -0
  36. package/dist/types/core/scheduler.d.ts +14 -4
  37. package/dist/types/signals.d.ts +10 -11
  38. package/dist/types/store/index.d.ts +3 -6
  39. package/dist/types/store/next/optimistic.d.ts +4 -2
  40. package/dist/types/store/next/reconcile.d.ts +5 -12
  41. package/dist/types/store/next/store.d.ts +3 -9
  42. package/dist/types/store/next/target.d.ts +30 -46
  43. package/dist/types/store/store.d.ts +14 -9
  44. package/dist/types-cjs/core/attribution-hooks.d.cts +75 -0
  45. package/dist/types-cjs/core/attribution.d.cts +313 -9
  46. package/dist/types-cjs/core/constants.d.cts +8 -0
  47. package/dist/types-cjs/core/core.d.cts +12 -3
  48. package/dist/types-cjs/core/dev.d.cts +56 -9
  49. package/dist/types-cjs/core/heap.d.cts +5 -3
  50. package/dist/types-cjs/core/invariants.d.cts +1 -1
  51. package/dist/types-cjs/core/lanes.d.cts +10 -0
  52. package/dist/types-cjs/core/scheduler.d.cts +14 -4
  53. package/dist/types-cjs/signals.d.cts +10 -11
  54. package/dist/types-cjs/store/index.d.cts +3 -6
  55. package/dist/types-cjs/store/next/optimistic.d.cts +4 -2
  56. package/dist/types-cjs/store/next/reconcile.d.cts +5 -12
  57. package/dist/types-cjs/store/next/store.d.cts +3 -9
  58. package/dist/types-cjs/store/next/target.d.cts +30 -46
  59. package/dist/types-cjs/store/store.d.cts +14 -9
  60. package/package.json +3 -2
  61. package/dist/prod/store/next/patch-hooks.js +0 -13
  62. package/dist/prod/store/next/patch.js +0 -614
  63. package/dist/types/store/next/patch-hooks.d.ts +0 -41
  64. package/dist/types/store/next/patch.d.ts +0 -91
  65. package/dist/types-cjs/store/next/patch-hooks.d.cts +0 -41
  66. package/dist/types-cjs/store/next/patch.d.cts +0 -91
@@ -1,6 +1,6 @@
1
- import { unwrapOverride, $REFRESH, CONFIG_OWNED_WRITE, NOT_PENDING, CONFIG_OPTIMISTIC, CONFIG_HELD_TRUTH, STATUS_UNINITIALIZED, STATUS_ERROR, CONFIG_CHILDREN_FORBIDDEN, CONFIG_AUTHORITATIVE_READ } from "../../core/constants.js";
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).Oe = 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) {
@@ -226,7 +226,7 @@ function getHasNode(e, t, n) {
226
226
  }, e.fam?.node ?? undefined);
227
227
  o.T |= CONFIG_OWNED_WRITE;
228
228
  if (e.fam?.opt) {
229
- ext(o).Oe = NOT_PENDING;
229
+ ext(o).Pe = NOT_PENDING;
230
230
  o.T |= CONFIG_OPTIMISTIC;
231
231
  }
232
232
  if (affectsScopesLive()) inheritAffectsMarks(o, e.v, t);
@@ -247,7 +247,7 @@ function getKeySetNode(e) {
247
247
  }, e.fam?.node ?? undefined);
248
248
  n.T |= CONFIG_OWNED_WRITE;
249
249
  if (e.fam?.opt) {
250
- ext(n).Oe = NOT_PENDING;
250
+ ext(n).Pe = NOT_PENDING;
251
251
  n.T |= CONFIG_OPTIMISTIC;
252
252
  }
253
253
  e.k = t;
@@ -267,7 +267,7 @@ function getDeepNode(e) {
267
267
  }, e.fam?.node ?? undefined);
268
268
  n.T |= CONFIG_OWNED_WRITE;
269
269
  if (e.fam?.opt) {
270
- ext(n).Oe = NOT_PENDING;
270
+ ext(n).Pe = NOT_PENDING;
271
271
  n.T |= CONFIG_OPTIMISTIC;
272
272
  }
273
273
  if (affectsScopesLive()) inheritAffectsMarks(n, e.v, $TRACK);
@@ -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;
@@ -388,14 +387,14 @@ function ensurePB(e) {
388
387
  if (n !== null) {
389
388
  for (const e of Reflect.ownKeys(n)) {
390
389
  const r = n[e];
391
- if (hasActiveOverride(r)) t[e] = unwrapOverride(r.o?.Oe);
390
+ if (hasActiveOverride(r)) t[e] = unwrapOverride(r.o?.Pe);
392
391
  }
393
392
  }
394
393
  const r = e.h;
395
394
  if (r !== null) {
396
395
  for (const e of Reflect.ownKeys(r)) {
397
396
  const n = r[e];
398
- if (hasActiveOverride(n) && !unwrapOverride(n.o?.Oe)) delete t[e];
397
+ if (hasActiveOverride(n) && !unwrapOverride(n.o?.Pe)) delete t[e];
399
398
  }
400
399
  }
401
400
  }
@@ -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,14 +609,14 @@ 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.
575
616
  !plainProto(t.ovl ? t.v : i) ? Reflect.ownKeys(o) : e;
576
617
  for (const e of n) {
577
618
  const t = o[e];
578
- if (t !== undefined && t.Pe !== NOT_PENDING) {
619
+ if (t !== undefined && t.Re !== NOT_PENDING) {
579
620
  r = true;
580
621
  break;
581
622
  }
@@ -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
@@ -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
  }
@@ -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
 
@@ -1030,7 +1077,7 @@ const UNSAFE_KEYS = new Set([ "__proto__", "prototype", "constructor" ]);
1030
1077
  if (t === null) return false;
1031
1078
  for (const e of Reflect.ownKeys(t)) {
1032
1079
  const n = t[e];
1033
- if (n.Pe !== NOT_PENDING && n.Ae != null && n.Ae.sn !== true) return true;
1080
+ if (n.Re !== NOT_PENDING && n.Ae != null && n.Ae.sn !== true) return true;
1034
1081
  }
1035
1082
  return false;
1036
1083
  }
@@ -1044,28 +1091,39 @@ function readSource(e) {
1044
1091
  const t = heldMaskView(e);
1045
1092
  if (t !== null) return t;
1046
1093
  }
1047
- // Signal-parity visibility (core read(): owner-context reads serve
1048
- // _pendingValue, context-free reads serve committed — effects recompute
1049
- // BEFORE commitPendingNodes in the flush, so the pending view must be
1050
- // servable). Drafts (setter window OR projection write-override) and
1051
- // owner-context reads see the pending backing; context-free reads see
1052
- // committed. Node reads apply the same rule, so both homes agree.
1053
- if (e.pb !== null && (inDraft(e) || getWriteOverride() ||
1054
- // Owner-context readers see the pending backing — EXCEPT held truth
1055
- // on an optimistic family (#3164 fold): a live pb on an opt family
1056
- // outside the draft/write-override windows is a staged landing
1057
- // (tentative drafts never outlive their setter), and only the
1058
- // authoritative postures and latest() see it (the backing-level twin
1059
- // of core read()'s A17-for-held-truth arm; ordinary readers keep
1060
- // committed until the transaction's reveal).
1061
- inOwnerContext() && !heldTruthMasked(e) ||
1094
+ return pendingBackingVisible(e, false) ? e.pb : e.v;
1095
+ }
1096
+
1097
+ /** The single pb-vs-committed visibility decision (#3147), shared by per-key
1098
+ * backing reads (readSource) and deep()/snapshot composition (snapshotWalk)
1099
+ * so the two reader families can never disagree about a HELD landing.
1100
+ *
1101
+ * Signal-parity visibility (core read(): owner-context reads serve
1102
+ * _pendingValue, context-free reads serve committed effects recompute
1103
+ * BEFORE commitPendingNodes in the flush, so the pending view must be
1104
+ * servable). Drafts (setter window OR projection write-override) and
1105
+ * owner-context reads see the pending backing; context-free reads see
1106
+ * committed. Node reads apply the same rule, so all homes agree.
1107
+ *
1108
+ * `speculative` is deep()/snapshot's posture: an untrack/deep PEEK that sees
1109
+ * ordinary pending staging regardless of owner context (the documented
1110
+ * divergence from context-free per-key reads) — but never through a hold:
1111
+ * held truth stays masked exactly as it is for per-key readers. */ function pendingBackingVisible(e, t) {
1112
+ return e.pb !== null && (inDraft(e) || getWriteOverride() ||
1113
+ // Owner-context (and speculative-peek) readers see the pending
1114
+ // backing — EXCEPT held truth on an optimistic family (#3164 fold):
1115
+ // a live pb on an opt family outside the draft/write-override windows
1116
+ // is a staged landing (tentative drafts never outlive their setter),
1117
+ // and only the authoritative postures and latest() see it (the
1118
+ // backing-level twin of core read()'s A17-for-held-truth arm;
1119
+ // ordinary readers keep committed until the transaction's reveal).
1120
+ (t || inOwnerContext()) && !heldTruthMasked(e) ||
1062
1121
  // A projection's pending backing is authoritative-elect: serve it to
1063
1122
  // context-free readers too UNLESS a transition is holding the node
1064
1123
  // commits (downstream async hold — stale committed is the contract)
1065
1124
  // or the reader is a CHILDREN_FORBIDDEN scope, which never observes
1066
1125
  // its own unsettled write (#3082, signal parity per #3006).
1067
- e.fam !== null && !heldTruthMasked(e) && !foldHeld(e) && !inForbiddenScope())) return e.pb;
1068
- return e.v;
1126
+ e.fam !== null && !heldTruthMasked(e) && !foldHeld(e) && !inForbiddenScope());
1069
1127
  }
1070
1128
 
1071
1129
  /** #3164 fold: HELD truth on an optimistic family — a pending backing
@@ -1110,7 +1168,7 @@ function isOwnAccessor(e, t) {
1110
1168
 
1111
1169
  /** Active optimistic override on an armed node (armed slot idles at
1112
1170
  * NOT_PENDING; undefined = unarmed plain node). */ function hasActiveOverride(e) {
1113
- return e.o?.Oe !== undefined && e.o?.Oe !== NOT_PENDING;
1171
+ return e.o?.Pe !== undefined && e.o?.Pe !== NOT_PENDING;
1114
1172
  }
1115
1173
 
1116
1174
  /** The reading computation is until()'s authoritative-view predicate — same
@@ -1145,14 +1203,14 @@ function isOwnAccessor(e, t) {
1145
1203
  // Authoritative-view reads (until()'s predicate) skip the override arm
1146
1204
  // only: staged pending values are authoritative, overrides are the
1147
1205
  // caller's optimism.
1148
- const n = !authoritativeServe() && hasActiveOverride(e) ? unwrapOverride(e.o?.Oe) : e.Pe !== NOT_PENDING && (latestReadActive ||
1206
+ const n = !authoritativeServe() && hasActiveOverride(e) ? unwrapOverride(e.o?.Pe) : e.Re !== NOT_PENDING && (latestReadActive ||
1149
1207
  // Owner-context pending visibility — except HELD truth (#3164,
1150
1208
  // see CONFIG_HELD_TRUTH: fold-staged or entangle-stolen
1151
1209
  // confirming truth), which only authoritative/latest readers
1152
1210
  // see (core read()'s A17-for-held-truth twin; ordinary readers
1153
1211
  // keep committed until the transaction's reveal — latest() is
1154
1212
  // exempted by the leading arm above).
1155
- (inOwnerContext() || authoritativeServe()) && !(e.T & CONFIG_HELD_TRUTH && !authoritativeServe())) ? e.Pe : t;
1213
+ (inOwnerContext() || authoritativeServe()) && !(e.T & CONFIG_HELD_TRUTH && !authoritativeServe())) ? e.Re : t;
1156
1214
  return n === FORCE ? t : n;
1157
1215
  }
1158
1216
 
@@ -1160,15 +1218,15 @@ function isOwnAccessor(e, t) {
1160
1218
  * holds, lanes ride the node); backing otherwise. Chained backings (§7b: the
1161
1219
  * backing IS another store's proxy) serve the read-through value — the outer
1162
1220
  * node is linked only for adoption-swap notification, its value never
1163
- * shadows the live chain. */ function serveDataKey(e, t, n, r, i) {
1164
- const o = e.ch && r === e.v;
1165
- let f = n;
1221
+ * shadows the live chain. */ function serveDataKey(e, t, n, r, i, o = -1) {
1222
+ const f = e.ch && r === e.v;
1223
+ let l = n;
1166
1224
  // §6: on optimistic arrays LENGTH IS A VIEW, not a node value — one home
1167
1225
  // (backing ± presence overrides) for both length and indices makes torn
1168
1226
  // iteration impossible (a length node's value rides different visibility
1169
1227
  // rails than index overrides mid-settle). The node still carries
1170
1228
  // subscriptions; its value is never served here.
1171
- if (t === "length" && e.fam?.opt === true && !o && Array.isArray(r)) {
1229
+ if (t === "length" && e.fam?.opt === true && !f && Array.isArray(r)) {
1172
1230
  if (!inDraft(e)) {
1173
1231
  const r = e.n?.length;
1174
1232
  if (r !== undefined) {
@@ -1188,9 +1246,9 @@ function isOwnAccessor(e, t) {
1188
1246
  // #2951). Once ensurePB runs, the seeded clone carries the view.
1189
1247
  // AUTHORITATIVE drafts (projection derive) never overlay — ensurePB's
1190
1248
  // seeding rule, applied to the read side (#3108).
1191
- if (e.fam?.opt && e.pb === null && !authoritativeServe()) {
1249
+ if (e.fam?.opt && draftSeesOverrides(e) && !authoritativeServe()) {
1192
1250
  const n = e.n?.[t];
1193
- if (n !== undefined && hasActiveOverride(n)) f = unwrapOverride(n.o?.Oe);
1251
+ if (n !== undefined && hasActiveOverride(n)) l = unwrapOverride(n.o?.Pe);
1194
1252
  }
1195
1253
  } else {
1196
1254
  if (i !== undefined) {
@@ -1202,28 +1260,30 @@ function isOwnAccessor(e, t) {
1202
1260
  // parity): READ_SLOW = a global read window or non-plain node.
1203
1261
  let e = readNodeFast(i);
1204
1262
  if (e === READ_SLOW) e = read(i);
1205
- if (!o || hasActiveOverride(i)) f = e === FORCE ? n : e;
1206
- } else if (!o || hasActiveOverride(i)) {
1207
- f = nodeValue(i, n);
1263
+ if (!f || hasActiveOverride(i)) l = e === FORCE ? n : e;
1264
+ } else if (!f || hasActiveOverride(i)) {
1265
+ l = nodeValue(i, n);
1208
1266
  }
1209
1267
  } else if (getObserver() !== null) {
1210
- 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));
1211
1271
  }
1212
1272
  }
1213
1273
  // Shallow stores serve data raw; store-proxy slots get boundary wrappers.
1214
- if (e.s) return serveShallow(e, t, f);
1274
+ if (e.s) return serveShallow(e, t, l);
1215
1275
  if (i !== undefined) {
1216
1276
  // Wrap cache (see getNode): only wrappables are ever cached, so a hit
1217
1277
  // skips isWrappable too — pointer-compare replaces both checks.
1218
- if (i.pxv === f && f !== undefined) return draftServe(e, i.px);
1219
- if (!isWrappable(f)) return f;
1220
- const n = wrapNext(f, e, t);
1278
+ if (i.pxv === l && l !== undefined) return draftServe(e, i.px);
1279
+ if (!isWrappable(l)) return l;
1280
+ const n = wrapNext(l, e, t);
1221
1281
  i.px = n;
1222
- i.pxv = f;
1282
+ i.pxv = l;
1223
1283
  return draftServe(e, n);
1224
1284
  }
1225
- if (!isWrappable(f)) return f;
1226
- return draftServe(e, wrapNext(f, e, t));
1285
+ if (!isWrappable(l)) return l;
1286
+ return draftServe(e, wrapNext(l, e, t));
1227
1287
  }
1228
1288
 
1229
1289
  /** §6c store-wide status gate for reads that DON'T flow through a node:
@@ -1309,8 +1369,11 @@ const traps = {
1309
1369
  // tracked read of a present data key — the dbmon/uibench effect re-read
1310
1370
  // shape. Skips serveDataKey's frame, the FORCE compare (only accessor
1311
1371
  // keys ever hold the sentinel), and isWrappable for primitives.
1312
- if (e.ch === false && writeScopes === null) {
1313
- 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;
1314
1377
  if (n !== undefined && n.acc !== true && getObserver() !== null) {
1315
1378
  let r = readNodeFast(n);
1316
1379
  if (r === READ_SLOW) r = read(n);
@@ -1338,14 +1401,22 @@ const traps = {
1338
1401
  // absent-key/accessor subscriptions for every store read during any
1339
1402
  // derive, leaving nested projections permanently dependency-less when
1340
1403
  // their sources hadn't materialized yet (#3037).
1341
- 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;
1342
1409
  {
1343
- const o = i !== undefined ? i.acc === true : !inDraft(e) && getObserver() !== null && isOwnAccessor(r, t);
1344
- if (o) {
1345
- if (!inDraft(e) && getObserver() !== null) read(i ?? getNode(e, t, undefined));
1346
- const o = Reflect.get(r, t, n);
1347
- if (e.s) return serveShallow(e, t, o);
1348
- 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;
1349
1420
  }
1350
1421
  }
1351
1422
  // Plain-data fast path: no descriptor allocation per read.
@@ -1355,37 +1426,37 @@ const traps = {
1355
1426
  // backings chain to the committed backing, so "own in the view" means
1356
1427
  // own on either layer (ownInView) — a genuine prototype method is one
1357
1428
  // that is own on NEITHER.
1358
- const o = e.ovl && r === e.pb;
1359
- if ((t === "constructor" || t === "__proto__" || t === "prototype") && !hasOwn.call(r, t) && !(o && hasOwn.call(e.v, t))) return undefined;
1360
- let f = r[t];
1361
- if (f === undefined ? !hasOwn.call(r, t) && !(o && hasOwn.call(e.v, t)) : false) {
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;
1431
+ let l = r[t];
1432
+ if (l === undefined ? !hasOwn.call(r, t) && !(f && hasOwn.call(e.v, t)) : false) {
1362
1433
  // Inherited: prototype getters/methods run with the proxy receiver.
1363
- f = Reflect.get(r, t, n);
1364
- if (typeof f === "function") return f;
1434
+ l = Reflect.get(r, t, n);
1435
+ if (typeof l === "function") return l;
1365
1436
  // proto methods untracked
1366
1437
  // Reading a currently-absent own key subscribes to it (R12) — for any
1367
1438
  // target OUTSIDE its own draft scope, even mid-setter (#3037, above).
1368
- if (f === undefined && !inDraft(e)) {
1369
- if (getObserver() !== null) read(getNode(e, t, undefined));
1439
+ if (l === undefined && !inDraft(e)) {
1440
+ if (getObserver() !== null) read(getNode(e, t, undefined, o));
1370
1441
  const n = e.n?.[t];
1371
1442
  if (n) {
1372
1443
  const r = nodeValue(n, undefined);
1373
1444
  if (e.s) return serveShallow(e, t, r);
1374
1445
  return isWrappable(r) ? draftServe(e, wrapNext(r, e, t)) : r;
1375
1446
  }
1376
- } else if (f === undefined && inDraft(e) && e.fam?.opt && e.pb === null &&
1447
+ } else if (l === undefined && inDraft(e) && e.fam?.opt && draftSeesOverrides(e) &&
1377
1448
  // AUTHORITATIVE drafts (landing folds) never seed from overrides —
1378
1449
  // the caller's optimism is not truth (has-trap twin below).
1379
1450
  !authoritativeServe()) {
1380
1451
  const n = e.n?.[t];
1381
- if (n !== undefined && hasActiveOverride(n)) f = unwrapOverride(n.o?.Oe);
1452
+ if (n !== undefined && hasActiveOverride(n)) l = unwrapOverride(n.o?.Pe);
1382
1453
  }
1383
- if (e.s) return serveShallow(e, t, f);
1384
- return isWrappable(f) ? draftServe(e, wrapNext(f, e, t)) : f;
1454
+ if (e.s) return serveShallow(e, t, l);
1455
+ return isWrappable(l) ? draftServe(e, wrapNext(l, e, t)) : l;
1385
1456
  }
1386
- if (typeof f === "function" && !hasOwn.call(r, t) && !(o && hasOwn.call(e.v, t))) return f;
1457
+ if (typeof l === "function" && !hasOwn.call(r, t) && !(f && hasOwn.call(e.v, t))) return l;
1387
1458
  // proto method
1388
- return serveDataKey(e, t, f, r, i);
1459
+ return serveDataKey(e, t, l, r, i, o);
1389
1460
  },
1390
1461
  has(e, t) {
1391
1462
  if (t === $TARGET || t === $PROXY || t === $TRACK) return true;
@@ -1404,11 +1475,11 @@ const traps = {
1404
1475
  if (hasActiveOverride(n)) r = !!i;
1405
1476
  } else if (!authoritativeServe()) {
1406
1477
  const n = e.h?.[t];
1407
- if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.Oe);
1478
+ if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.Pe);
1408
1479
  }
1409
- } else if (e.fam?.opt && e.pb === null && !authoritativeServe()) {
1480
+ } else if (e.fam?.opt && draftSeesOverrides(e) && !authoritativeServe()) {
1410
1481
  const n = e.h?.[t];
1411
- if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.Oe);
1482
+ if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.Pe);
1412
1483
  }
1413
1484
  return r;
1414
1485
  },
@@ -1433,13 +1504,13 @@ const traps = {
1433
1504
  // Draft reads before the first write overlay too (pb, once created, is
1434
1505
  // seeded with the view). Authoritative-view reads (until()'s predicate,
1435
1506
  // truth-author drafts) skip the overlay.
1436
- 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))) {
1437
1508
  let t = null;
1438
1509
  for (const r of Reflect.ownKeys(e.h)) {
1439
1510
  const i = e.h[r];
1440
1511
  if (!hasActiveOverride(i)) continue;
1441
1512
  t ??= new Set(n);
1442
- if (unwrapOverride(i.o?.Oe)) t.add(r); else t.delete(r);
1513
+ if (unwrapOverride(i.o?.Pe)) t.add(r); else t.delete(r);
1443
1514
  }
1444
1515
  if (t !== null) return [ ...t ];
1445
1516
  }
@@ -1457,7 +1528,7 @@ const traps = {
1457
1528
  if (!authoritativeServe() && e.fam?.opt && !inDraft(e)) {
1458
1529
  const n = e.h?.[t];
1459
1530
  if (n !== undefined && hasActiveOverride(n)) {
1460
- if (!unwrapOverride(n.o?.Oe)) return undefined;
1531
+ if (!unwrapOverride(n.o?.Pe)) return undefined;
1461
1532
  // opt delete
1462
1533
  if (r === undefined) {
1463
1534
  const n = e.n?.[t];
@@ -1498,14 +1569,13 @@ const traps = {
1498
1569
  // Array length writes implicitly delete indices — the written-keys bound
1499
1570
  // can't see them, so poison to the full scan for this batch. Index
1500
1571
  // writes implicitly GROW length, so arrays always record it alongside.
1501
- const l = pcOf(e);
1502
- if (Array.isArray(f)) {
1503
- if (t === "length") l.wk = WK_ALL; else if (l.wk !== WK_ALL) {
1504
- const e = l.wk ??= new Set;
1505
- e.add(t);
1506
- 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");
1507
1577
  }
1508
- } else if (l.wk !== WK_ALL) (l.wk ??= new Set).add(t);
1578
+ } else if (e.wk !== WK_ALL) (e.wk ??= new Set).add(t);
1509
1579
  // Own data keys literally named "prototype"/"constructor" land as data —
1510
1580
  // defineProperty sidesteps a proto-chain setter named the same.
1511
1581
  if (UNSAFE_KEYS.has(t)) {
@@ -1543,14 +1613,7 @@ const traps = {
1543
1613
  const i = !r && getWriteOverride();
1544
1614
  if (!r && !i) return true;
1545
1615
  if (t === "__proto__") return true;
1546
- if (n.get || n.set) {
1547
- e.a = true;
1548
- // Accessor demotion (re-audit blocker 3): a record that acquires an
1549
- // accessor after patch registration stops being patchable — pull its
1550
- // patches and re-drive them as tracked effect fallbacks. Hooks are
1551
- // installed whenever pc.p exists (registration installs them).
1552
- if (e.pc !== null && e.pc.p !== null) patchHooks.demoteToEffects(e);
1553
- }
1616
+ if (n.get || n.set) e.a = true;
1554
1617
  // Unwrap before ensurePB (see the set trap: self-reference materializes).
1555
1618
  if ("value" in n) n = {
1556
1619
  ...n,
@@ -1558,8 +1621,7 @@ const traps = {
1558
1621
  };
1559
1622
  const o = ensurePB(e);
1560
1623
  pendingNotify.add(e);
1561
- const f = pcOf(e);
1562
- if (f.wk !== WK_ALL) (f.wk ??= new Set).add(t);
1624
+ if (e.wk !== WK_ALL) (e.wk ??= new Set).add(t);
1563
1625
  Object.defineProperty(o, t, n);
1564
1626
  if (e.del !== null) e.del.delete(t);
1565
1627
  if (i) notifyWrites(e);
@@ -1571,8 +1633,7 @@ const traps = {
1571
1633
  if (!n && !r) return true;
1572
1634
  const i = ensurePB(e);
1573
1635
  pendingNotify.add(e);
1574
- const o = pcOf(e);
1575
- if (o.wk !== WK_ALL) (o.wk ??= new Set).add(t);
1636
+ if (e.wk !== WK_ALL) (e.wk ??= new Set).add(t);
1576
1637
  delete i[t];
1577
1638
  // A prototype overlay cannot shadow a delete of a committed key —
1578
1639
  // record it aside (#3044); reads/has/ownKeys/commit consult the set.
@@ -1636,6 +1697,10 @@ function createStoreNext(e, t = false) {
1636
1697
  return [ n, setter ];
1637
1698
  }
1638
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.
1639
1704
  /** True when `proxy` is a SHALLOW store (children served verbatim, slots
1640
1705
  * replaced by reference — #2932). The list driver uses this to choose the
1641
1706
  * slot-patch channel (collected row bodies) over per-record registration. */ function storeIsShallow(e) {
@@ -1685,8 +1750,24 @@ function createStoreNext(e, t = false) {
1685
1750
  read(getKeySetNode(e));
1686
1751
  read(getDeepNode(e));
1687
1752
  const r = e.fam?.map ?? storeNextLookup;
1688
- for (const n of Reflect.ownKeys(t)) {
1689
- 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);
1690
1771
  if (i === undefined) continue;
1691
1772
  if (i.get || i.set) {
1692
1773
  e.a = true;
@@ -1742,12 +1823,17 @@ function snapshotWalk(e, t, n) {
1742
1823
  if (e === undefined) break;
1743
1824
  if (e.fam !== null) n = e.fam;
1744
1825
  if (e.fam?.opt === true) (i ??= []).push(e);
1826
+ // The shared visibility decision (#3147): the speculative peek serves
1827
+ // pending staging, but a HELD landing is masked to committed exactly as
1828
+ // it is for per-key readers — the two families must answer alike while
1829
+ // a transaction holds store landings.
1830
+ const t = pendingBackingVisible(e, true);
1745
1831
  // Snapshot runs mid-flush (tracked memos execute before commit), so a
1746
1832
  // pending prototype overlay must present as a REAL merged container.
1747
- if (e.ovl) materializePB(e);
1748
- const t = e.pb ?? e.v;
1749
- if (t === r) break;
1750
- r = t;
1833
+ if (t && e.ovl) materializePB(e);
1834
+ const o = t ? e.pb : e.v;
1835
+ if (o === r) break;
1836
+ r = o;
1751
1837
  }
1752
1838
  if (!isWrappable(r)) return r;
1753
1839
  // Optimistic families: compose the visible view; a composed view is a fresh
@@ -1789,10 +1875,10 @@ function snapshotWalk(e, t, n) {
1789
1875
  continue;
1790
1876
  }
1791
1877
  const l = f.value;
1792
- const u = l !== null && typeof l === "object" ? snapshotWalk(l, t, n) : l;
1793
- if (f.enumerable && f.writable && f.configurable) i[o] = u; else Object.defineProperty(i, o, {
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, {
1794
1880
  ...f,
1795
- value: u
1881
+ value: s
1796
1882
  });
1797
1883
  }
1798
1884
  if (e && i.length !== r.length) i.length = r.length;
@@ -1819,4 +1905,4 @@ function snapshotWalk(e, t, n) {
1819
1905
  return f ?? r;
1820
1906
  }
1821
1907
 
1822
- 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 };