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

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 (134) hide show
  1. package/dist/dev-shared.js +6357 -0
  2. package/dist/dev.attribution.js +2485 -0
  3. package/dist/dev.js +2851 -7027
  4. package/dist/observe/affects.js +129 -0
  5. package/dist/observe/attribution.js +7 -0
  6. package/dist/observe/boundaries.js +641 -0
  7. package/dist/observe/core/action.js +177 -0
  8. package/dist/observe/core/async.js +846 -0
  9. package/dist/observe/core/attribution-costs.js +66 -0
  10. package/dist/observe/core/attribution-feedback.js +282 -0
  11. package/dist/observe/core/attribution-hooks.js +82 -0
  12. package/dist/observe/core/attribution-queries.js +28 -0
  13. package/dist/observe/core/attribution.js +2095 -0
  14. package/dist/observe/core/constants.js +248 -0
  15. package/dist/{types-cjs/core/context.d.cts → observe/core/context.js} +37 -12
  16. package/dist/observe/core/core.js +1789 -0
  17. package/dist/observe/core/dev.js +293 -0
  18. package/dist/observe/core/effect.js +195 -0
  19. package/dist/observe/core/error-hooks.js +71 -0
  20. package/dist/observe/core/error.js +80 -0
  21. package/dist/observe/core/external.js +98 -0
  22. package/dist/observe/core/graph.js +155 -0
  23. package/dist/observe/core/heap.js +147 -0
  24. package/dist/observe/core/invariants.js +45 -0
  25. package/dist/observe/core/lanes.js +205 -0
  26. package/dist/observe/core/optimistic.js +585 -0
  27. package/dist/observe/core/owner.js +322 -0
  28. package/dist/observe/core/scheduler.js +1428 -0
  29. package/dist/observe/core/verdict.js +669 -0
  30. package/dist/observe/index.js +59 -0
  31. package/dist/observe/map.js +484 -0
  32. package/dist/{types-cjs/signals.d.cts → observe/signals.js} +311 -317
  33. package/dist/observe/store/index.js +40 -0
  34. package/dist/observe/store/next/optimistic.js +660 -0
  35. package/dist/observe/store/next/projection.js +255 -0
  36. package/dist/observe/store/next/reconcile.js +375 -0
  37. package/dist/observe/store/next/store.js +2327 -0
  38. package/dist/observe/store/next/target.js +51 -0
  39. package/dist/observe/store/store.js +325 -0
  40. package/dist/observe/store/storePath.js +103 -0
  41. package/dist/observe/store/utils.js +1023 -0
  42. package/dist/prod/attribution.js +41 -0
  43. package/dist/prod/boundaries.js +128 -76
  44. package/dist/prod/core/action.js +65 -37
  45. package/dist/prod/core/async.js +324 -172
  46. package/dist/prod/core/constants.js +57 -1
  47. package/dist/prod/core/context.js +3 -3
  48. package/dist/prod/core/core.js +1003 -364
  49. package/dist/prod/core/dev.js +17 -1
  50. package/dist/prod/core/effect.js +76 -42
  51. package/dist/prod/core/error-hooks.js +71 -0
  52. package/dist/prod/core/external.js +4 -4
  53. package/dist/prod/core/graph.js +37 -37
  54. package/dist/prod/core/heap.js +61 -56
  55. package/dist/prod/core/lanes.js +107 -63
  56. package/dist/prod/core/optimistic.js +374 -93
  57. package/dist/prod/core/owner.js +62 -46
  58. package/dist/prod/core/scheduler.js +653 -286
  59. package/dist/prod/core/verdict.js +245 -127
  60. package/dist/prod/index.js +15 -5
  61. package/dist/prod/map.js +292 -141
  62. package/dist/prod/signals.js +29 -16
  63. package/dist/prod/store/next/optimistic.js +77 -55
  64. package/dist/prod/store/next/projection.js +31 -20
  65. package/dist/prod/store/next/reconcile.js +87 -84
  66. package/dist/prod/store/next/store.js +718 -401
  67. package/dist/prod/store/next/target.js +32 -10
  68. package/dist/prod/store/store.js +12 -14
  69. package/dist/prod/store/utils.js +950 -130
  70. package/dist/types/attribution.d.ts +20 -0
  71. package/dist/types/attribution.prod.d.ts +22 -0
  72. package/dist/types/boundaries.d.ts +10 -1
  73. package/dist/types/core/action.d.ts +12 -5
  74. package/dist/types/core/async.d.ts +1 -1
  75. package/dist/types/core/attribution-costs.d.ts +35 -0
  76. package/dist/types/core/attribution-feedback.d.ts +133 -0
  77. package/dist/types/core/attribution-hooks.d.ts +147 -9
  78. package/dist/types/core/attribution-queries.d.ts +10 -0
  79. package/dist/types/core/attribution.d.ts +216 -182
  80. package/dist/types/core/constants.d.ts +56 -0
  81. package/dist/types/core/core.d.ts +190 -2
  82. package/dist/types/core/dev.d.ts +246 -28
  83. package/dist/types/core/effect.d.ts +8 -1
  84. package/dist/types/core/error-hooks.d.ts +71 -0
  85. package/dist/types/core/index.d.ts +4 -1
  86. package/dist/types/core/invariants.d.ts +4 -0
  87. package/dist/types/core/lanes.d.ts +39 -5
  88. package/dist/types/core/scheduler.d.ts +153 -0
  89. package/dist/types/core/types.d.ts +32 -9
  90. package/dist/types/index.d.ts +10 -3
  91. package/dist/types/map.d.ts +2 -0
  92. package/dist/types/signals.d.ts +8 -0
  93. package/dist/types/store/index.d.ts +2 -0
  94. package/dist/types/store/next/optimistic.d.ts +1 -1
  95. package/dist/types/store/next/store.d.ts +11 -5
  96. package/dist/types/store/next/target.d.ts +37 -10
  97. package/dist/types/store/utils.d.ts +178 -0
  98. package/package.json +21 -17
  99. package/dist/node.cjs +0 -10541
  100. package/dist/node.dev.cjs +0 -13724
  101. package/dist/types-cjs/affects.d.cts +0 -47
  102. package/dist/types-cjs/boundaries.d.cts +0 -175
  103. package/dist/types-cjs/core/action.d.cts +0 -64
  104. package/dist/types-cjs/core/async.d.cts +0 -23
  105. package/dist/types-cjs/core/attribution-hooks.d.cts +0 -127
  106. package/dist/types-cjs/core/attribution.d.cts +0 -495
  107. package/dist/types-cjs/core/constants.d.cts +0 -159
  108. package/dist/types-cjs/core/core.d.cts +0 -185
  109. package/dist/types-cjs/core/dev.d.cts +0 -136
  110. package/dist/types-cjs/core/effect.d.cts +0 -30
  111. package/dist/types-cjs/core/error.d.cts +0 -56
  112. package/dist/types-cjs/core/external.d.cts +0 -15
  113. package/dist/types-cjs/core/graph.d.cts +0 -28
  114. package/dist/types-cjs/core/heap.d.cts +0 -24
  115. package/dist/types-cjs/core/index.d.cts +0 -13
  116. package/dist/types-cjs/core/invariants.d.cts +0 -59
  117. package/dist/types-cjs/core/lanes.d.cts +0 -54
  118. package/dist/types-cjs/core/optimistic.d.cts +0 -6
  119. package/dist/types-cjs/core/owner.d.cts +0 -124
  120. package/dist/types-cjs/core/scheduler.d.cts +0 -236
  121. package/dist/types-cjs/core/types.d.cts +0 -204
  122. package/dist/types-cjs/core/verdict.d.cts +0 -2
  123. package/dist/types-cjs/index.d.cts +0 -10
  124. package/dist/types-cjs/map.d.cts +0 -69
  125. package/dist/types-cjs/package.json +0 -3
  126. package/dist/types-cjs/store/index.d.cts +0 -18
  127. package/dist/types-cjs/store/next/optimistic.d.cts +0 -25
  128. package/dist/types-cjs/store/next/projection.d.cts +0 -8
  129. package/dist/types-cjs/store/next/reconcile.d.cts +0 -10
  130. package/dist/types-cjs/store/next/store.d.cts +0 -121
  131. package/dist/types-cjs/store/next/target.d.cts +0 -166
  132. package/dist/types-cjs/store/store.d.cts +0 -143
  133. package/dist/types-cjs/store/storePath.d.cts +0 -58
  134. package/dist/types-cjs/store/utils.d.cts +0 -74
@@ -1,14 +1,16 @@
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";
1
+ import { unwrapOverride, $REFRESH, CONFIG_CHILDREN_FORBIDDEN, CONFIG_OWNED_WRITE, NOT_PENDING, CONFIG_OPTIMISTIC, STATUS_UNINITIALIZED, STATUS_ERROR, CONFIG_AUTHORITATIVE_READ } from "../../core/constants.js";
2
2
 
3
- import { setSlotUnobserved, setSignal, isEqual, read, pendingCheckActive, latestReadActive, readNodeFast, READ_SLOW, signal, ext, setLatestReadActive, prepareComputed, slotSignal, context } from "../../core/core.js";
3
+ import { setSlotUnobserved, setSignal, isEqual, read, hasActiveOverride, pendingCheckActive, visibleOverride, latestReadActive, readNodeFast, READ_SLOW, signal, unlinkFirewallChild, ext, ownsHold, setLatestReadActive, prepareComputed, slotSignal, serve, stale, recordStaleReplay, enterStagedRead, context } from "../../core/core.js";
4
4
 
5
- import { projectionWriteActive, setProjectionWriteActive, activeTransition, schedule, currentTransition, setStoreCommitHook } from "../../core/scheduler.js";
5
+ import { projectionWriteActive, setProjectionWriteActive, activeTransition, schedule, currentTransition, deferSlotRelease, setStoreCommitHook } from "../../core/scheduler.js";
6
+
7
+ import "../../core/invariants.js";
6
8
 
7
9
  import { getObserver, getOwner } from "../../core/owner.js";
8
10
 
9
11
  import { $TARGET, isWrappable, markRawIngest, $PROXY, getWriteOverride, markRawOne, witnessAffectsMark, $TRACK, affectsScopesLive, inheritAffectsMarks, $AFFECTS, rawValuesUsed, isRawValue, setNextAffectsNodeResolver } from "../store.js";
10
12
 
11
- import { storeNextLookup, optHooks, ownedRaw, markDescendants, devAssertNeverUserMutation } from "./target.js";
13
+ import { lookupTarget, optHooks, storeNextLookup, $OWNER, isOwned, markDescendants, devAssertNeverUserMutation } from "./target.js";
12
14
 
13
15
  /**
14
16
  * Store rewrite — increment 2: plain deep stores with pending-backing writes.
@@ -59,6 +61,7 @@ function TargetShape() {
59
61
  this.d = undefined;
60
62
  this.a = undefined;
61
63
  this.sc = undefined;
64
+ this.kc = undefined;
62
65
  this.nc = undefined;
63
66
  this.ab = undefined;
64
67
  this.fam = undefined;
@@ -94,7 +97,8 @@ function createTarget(e, t, n, r = t?.fam ?? null) {
94
97
  i.px = null;
95
98
  i.d = false;
96
99
  i.a = false;
97
- i.sc = false;
100
+ i.sc = 0;
101
+ i.kc = 0;
98
102
  i.nc = 0;
99
103
  i.ab = null;
100
104
  i.fam = r;
@@ -115,13 +119,13 @@ function wrapNext(e, t = null, n = null, r = t?.fam ?? null) {
115
119
  // markRaw'd values never wrap through ANY store (R42; sticky raw-marking
116
120
  // is one half of the never-both-wrapped-and-raw invariant, RUL-12).
117
121
  if (rawValuesUsed && isRawValue(e)) return e;
118
- const i = (r?.map ?? storeNextLookup).get(e);
122
+ const i = lookupTarget(e, r);
119
123
  if (i !== undefined) return i.px;
120
- const o = e[$TARGET];
121
- if (o !== undefined && o.px === e) {
124
+ const l = e[$TARGET];
125
+ if (l !== undefined && l.px === e) {
122
126
  // Foreign-family proxies re-wrap into THIS family (writes stay isolated);
123
127
  // same-family and plain-store proxies pass through.
124
- if (r === null || o.fam === r) return e;
128
+ if (r === null || l.fam === r) return e;
125
129
  return createTarget(e, t, n, r).px;
126
130
  }
127
131
  return createTarget(e, t, n, r).px;
@@ -149,7 +153,7 @@ function wrapNext(e, t = null, n = null, r = t?.fam ?? null) {
149
153
  // (privatization/adoption swap raw identity without changing the logical
150
154
  // value — only changed leaves notify, R9).
151
155
  const slotNodeEquals = function(e, t) {
152
- return isEqual(e, t) || sameLogicalSlot(this.ht, e, t);
156
+ return isEqual(e, t) || sameLogicalSlot(this.Wt, e, t);
153
157
  };
154
158
 
155
159
  // Shared slot-node unobserved handler (create-floor diet): registered once;
@@ -158,11 +162,20 @@ const slotNodeEquals = function(e, t) {
158
162
  setSlotUnobserved(e => {
159
163
  // A live affects() mark keeps the node addressable (sweep parity).
160
164
  if (e.o?.t) return;
161
- const t = e.ht;
162
- const n = e.Ht;
165
+ // An active override or a staged write is state only the node holds (an
166
+ // optimistic signal keeps its override whether or not anything reads it —
167
+ // store parity, posture-store-parity S7): defer the release to the flush
168
+ // that resolves it (the scheduler's transient-node sweep).
169
+ if (hasActiveOverride(e) || e.ve !== NOT_PENDING) return deferSlotRelease(e);
170
+ const t = e.Wt;
171
+ const n = e.qt;
163
172
  if (t.n && t.n[n] === e) {
164
173
  delete t.n[n];
165
174
  t.nc--;
175
+ // Projection leaves also leave the firewall child chain (#3351): a
176
+ // dropped node is unreachable through the store — a fresh read makes a
177
+ // fresh node — so the chain would only retain it and its last value.
178
+ unlinkFirewallChild(e);
166
179
  }
167
180
  });
168
181
 
@@ -173,8 +186,25 @@ function getNode(e, t, n,
173
186
  // callers), 0/1 = probed.
174
187
  r = -1) {
175
188
  const i = e.n ??= Object.create(null);
176
- let o = i[t];
177
- if (o === undefined) {
189
+ let l = i[t];
190
+ if (l === undefined) {
191
+ // Born holding (#3330 store twin): a key first read while a live
192
+ // transaction holds an ADOPTION on this target (adoptPB's `ht`) is
193
+ // created as if it had existed when the hold was notified — the adopted
194
+ // value already swapped into `v`, so committed is the held view
195
+ // `hv[key]` and the adopted `v[key]` is staged under the transaction
196
+ // (stageHeldKey). Its held-adoption notification (stageHeldAdoptions)
197
+ // ran before the node existed and the drain has nothing left to say.
198
+ // Without this the node was born from whichever view its first reader
199
+ // saw and never learned the other. Two kinds of hold, one rule (#3336):
200
+ // - a held FOLD (pb): a setter's write to an unobserved key landed only
201
+ // in the pending backing; committed is `v[key]`, staged is `pb[key]`
202
+ // (undefined for a deleted key);
203
+ // - a held ADOPTION (ht): the adopted value already swapped into `v`;
204
+ // committed is the held view `hv[key]`, staged is `v[key]`.
205
+ const o = heldFoldTransition(e);
206
+ let f = heldAdoptionTransition(e);
207
+ if (f !== null) n = e.hv[t]; else if ((f = o) !== null) n = e.v[t];
178
208
  // Create-floor diet: slotSignal bakes the whole node into one literal —
179
209
  // no options object, no equals/unobserved closures, no NodeExtension,
180
210
  // no post-construction expandos (acc + the wrap cache px/pxv are
@@ -185,7 +215,7 @@ r = -1) {
185
215
  // notification machinery. Projection nodes carry the projection
186
216
  // computed as their firewall: reads through them link the derive's
187
217
  // status/lifecycle (§7b).
188
- const f = o = slotSignal(n, slotNodeEquals, e, t,
218
+ const s = l = slotSignal(n, slotNodeEquals, e, t,
189
219
  // Accessor-ness resolved ONCE per node (no per-object descriptor
190
220
  // scan on reads): accessor keys serve through Reflect.get with the
191
221
  // proxy receiver.
@@ -193,43 +223,122 @@ r = -1) {
193
223
  // Optimistic families: arm the override slot — setSignal routes armed
194
224
  // nodes through the core engine (lanes, ownership, reverts all native).
195
225
  if (e.fam?.opt) {
196
- ext(f).Pe = NOT_PENDING;
197
- f.T |= CONFIG_OPTIMISTIC;
226
+ ext(s).Ce = NOT_PENDING;
227
+ s.T |= CONFIG_OPTIMISTIC;
198
228
  }
199
229
  // A node born inside a live mark's identity scope inherits the mark
200
230
  // (the declaration walk could only cover nodes existing then).
201
- if (t !== $AFFECTS && affectsScopesLive()) inheritAffectsMarks(f, e.v, t);
202
- i[t] = o;
231
+ if (t !== $AFFECTS && affectsScopesLive()) inheritAffectsMarks(s, e.v, t);
232
+ if (f !== null) stageHeldKey(s, o !== null ? e.del !== null && e.del.has(t) ? undefined : e.pb[t] : e.v[t], f);
233
+ i[t] = l;
203
234
  e.nc++;
204
235
  markDescendants(e);
205
236
  }
206
- return o;
237
+ return l;
238
+ }
239
+
240
+ /** The live transaction holding an adoption on `target` (adoptPB's `ht`;
241
+ * a latest()-pull PLAIN_HOLD is not a transaction), else null. */ function heldAdoptionTransition(e) {
242
+ if (e.ht === null || e.ht === PLAIN_HOLD || heldMaskView(e) === null) return null;
243
+ const t = currentTransition(e.ht);
244
+ return t.Tt === false ? t : null;
245
+ }
246
+
247
+ /**
248
+ * Materialization under a hold. A node created by a first tracked read while
249
+ * a transaction holds this target must carry the same state a node that
250
+ * existed when the hold was notified carries — a transaction-stamped
251
+ * `_pendingValue` that core read() serves by ITS rules (a stale render reader
252
+ * of a foreign transaction's write sees committed, an owner-context reader
253
+ * inside the transaction sees the write) — or the reader's answer depends on
254
+ * whether some OTHER reader had materialized the key before the hold. Stage
255
+ * `nv` (the held value for the key — see getNode for which view it comes
256
+ * from) as the holding transaction's write — directly, not through
257
+ * setSignal: it is not a new write (it staged with the adoption's batch) and
258
+ * it walks no subscriber (the node has none yet). Transition-stamped now, as
259
+ * `runFolded` does — no parked-flush pass will stamp it.
260
+ */
261
+ /** The live transaction holding `target`'s pending backing (the #3089
262
+ * write-time stamp, resolved through merges), else null. */ function liveFoldTransition(e) {
263
+ if (e.pb === null) return null;
264
+ const t = foldBatches.get(e);
265
+ if (t === undefined) return null;
266
+ const n = currentTransition(t);
267
+ return n.Tt === false ? n : null;
268
+ }
269
+
270
+ /** liveFoldTransition for node materialization (stageHeldKey). Inside the
271
+ * draft the backing is the setter's working copy, not a flushed hold — its
272
+ * nodes take their writes at setter exit (notifyWrites). Optimistic families
273
+ * hold at the backing: tentative writes are node overrides over a discarded
274
+ * clone, and a truth-staged landing is masked from ordinary readers by
275
+ * heldTruthMasked — neither is a plain staged write to mirror. Chained
276
+ * backings serve the inner store's live value, never a node value. */ function heldFoldTransition(e) {
277
+ if (e.ch || e.fam?.opt === true || inDraft(e)) return null;
278
+ return liveFoldTransition(e);
279
+ }
280
+
281
+ /**
282
+ * Rule 1 at the backing, the hold half (core serve()'s stale-of-foreign and
283
+ * A29 arms, for a container instead of a node): given the transaction
284
+ * holding what a reader `c` (a pass; callers serve context-free readers the
285
+ * committed container themselves) is about to be served, is the STAGED
286
+ * container its to see? Its own hold, or none — yes. A foreign hold — a
287
+ * stale pass (render effect) keeps the committed frame and is recorded for
288
+ * replay at the hold's commit (recordStaleReplay, A15 / A26); a deriving
289
+ * pass takes the staged world and enters the transaction (enterStagedRead,
290
+ * A29). Shared by both hold kinds: a setter's fold (committed `v`, staged
291
+ * `pb`, the write-time stamp) and an adoption under a transaction
292
+ * (committed = the held view `hv`, staged = the adopted `v`, #3074).
293
+ */ function holdVisible(e, t) {
294
+ if (e === null || ownsHold(e)) return true;
295
+ if (stale) {
296
+ recordStaleReplay(e, t);
297
+ return false;
298
+ }
299
+ enterStagedRead(null, e);
300
+ return true;
301
+ }
302
+
303
+ function stageHeldKey(e, t, n) {
304
+ if (slotNodeEquals.call(e, e.Qe, t)) return;
305
+ e.ve = t;
306
+ e.Ge = n;
307
+ n.Ot.push(e);
207
308
  }
208
309
 
209
310
  function sameLogicalSlot(e, t, n) {
210
311
  if (t === null || typeof t !== "object" || n === null || typeof n !== "object") return false;
211
- const r = e.fam?.map ?? storeNextLookup;
212
- const i = r.get(t);
213
- return i !== undefined && i === r.get(n);
312
+ const r = lookupTarget(t, e.fam);
313
+ return r !== undefined && r === lookupTarget(n, e.fam);
214
314
  }
215
315
 
216
316
  function getHasNode(e, t, n) {
217
317
  const r = e.h ??= Object.create(null);
218
318
  let i = r[t];
219
319
  if (i === undefined) {
220
- const o = i = signal(n, {
320
+ const l = i = signal(n, {
221
321
  equals: isEqual,
222
322
  unobserved() {
223
- if (o.o?.t) return;
224
- if (e.h && e.h[t] === o) delete e.h[t];
323
+ if (l.o?.t) return;
324
+ // The structural twin of the value slot's rule (setSlotUnobserved,
325
+ // S7): an optimistic add/delete lives on this presence node as its
326
+ // override — releasing it with the override on would let `in`,
327
+ // `Object.keys` and descriptors fall back to committed structure
328
+ // while the action is live. Defer to the flush that resolves it.
329
+ if (hasActiveOverride(l) || l.ve !== NOT_PENDING) return deferSlotRelease(l);
330
+ if (e.h && e.h[t] === l) {
331
+ delete e.h[t];
332
+ unlinkFirewallChild(l);
333
+ }
225
334
  }
226
335
  }, e.fam?.node ?? undefined);
227
- o.T |= CONFIG_OWNED_WRITE;
336
+ l.T |= CONFIG_OWNED_WRITE;
228
337
  if (e.fam?.opt) {
229
- ext(o).Pe = NOT_PENDING;
230
- o.T |= CONFIG_OPTIMISTIC;
338
+ ext(l).Ce = NOT_PENDING;
339
+ l.T |= CONFIG_OPTIMISTIC;
231
340
  }
232
- if (affectsScopesLive()) inheritAffectsMarks(o, e.v, t);
341
+ if (affectsScopesLive()) inheritAffectsMarks(l, e.v, t);
233
342
  r[t] = i;
234
343
  markDescendants(e);
235
344
  }
@@ -242,12 +351,15 @@ function getKeySetNode(e) {
242
351
  const n = t = signal(0, {
243
352
  equals: false,
244
353
  unobserved() {
245
- if (e.k === n) e.k = null;
354
+ if (e.k === n) {
355
+ e.k = null;
356
+ unlinkFirewallChild(n);
357
+ }
246
358
  }
247
359
  }, e.fam?.node ?? undefined);
248
360
  n.T |= CONFIG_OWNED_WRITE;
249
361
  if (e.fam?.opt) {
250
- ext(n).Pe = NOT_PENDING;
362
+ ext(n).Ce = NOT_PENDING;
251
363
  n.T |= CONFIG_OPTIMISTIC;
252
364
  }
253
365
  e.k = t;
@@ -262,12 +374,15 @@ function getDeepNode(e) {
262
374
  const n = t = signal(0, {
263
375
  equals: false,
264
376
  unobserved() {
265
- if (e.dk === n) e.dk = null;
377
+ if (e.dk === n) {
378
+ e.dk = null;
379
+ unlinkFirewallChild(n);
380
+ }
266
381
  }
267
382
  }, e.fam?.node ?? undefined);
268
383
  n.T |= CONFIG_OWNED_WRITE;
269
384
  if (e.fam?.opt) {
270
- ext(n).Pe = NOT_PENDING;
385
+ ext(n).Ce = NOT_PENDING;
271
386
  n.T |= CONFIG_OPTIMISTIC;
272
387
  }
273
388
  if (affectsScopesLive()) inheritAffectsMarks(n, e.v, $TRACK);
@@ -288,21 +403,36 @@ function getDeepNode(e) {
288
403
 
289
404
  let hookInstalled = false;
290
405
 
291
- function cloneRaw(e, t) {
406
+ /** Shallow-clone `source` as a backing OWNED by `t` (stamped `$OWNER`, see
407
+ * target.ts). Callers always pass their own committed backing as `source`. */ function cloneRaw(e, t) {
408
+ // Plain-data fast path (#3360): a scanned `Object.prototype` container
409
+ // whose own keys are all enumerable data clones by spread — the same
410
+ // result as the descriptor walk below (own enumerable string+symbol keys,
411
+ // normalized writable+configurable), at ~1/50th the cost.
412
+ t.sc || scanAccessorsOnce(t);
413
+ if (t.sc === 2) {
414
+ const n = {
415
+ ...e
416
+ };
417
+ n[$OWNER] = t;
418
+ return n;
419
+ }
292
420
  // Descriptor-preserving shallow clone (R29: installed getters stay live;
293
421
  // ruled 2026-08-17: frozen sources clone unfrozen — theirs stays frozen).
294
422
  // Data descriptors normalize to writable+configurable (the clone is OURS to
295
423
  // mutate — R51's "source-non-configurable is writable through the store");
296
424
  // enumerability and accessors are preserved. The scan doubles as the
297
425
  // accessor-flag detector (free — we're enumerating descriptors anyway).
298
- const n = Object.getOwnPropertyDescriptors(e);
426
+ const n = Object.getOwnPropertyDescriptors(e);
299
427
  for (const r of Reflect.ownKeys(n)) {
300
428
  const i = n[r];
301
429
  if (r === "length" && Array.isArray(e)) continue;
302
430
  i.configurable = true;
303
- if (!i.get && !i.set) i.writable = true; else if (t) t.a = true;
431
+ if (!i.get && !i.set) i.writable = true; else t.a = true;
304
432
  }
305
- return Array.isArray(e) ? Object.defineProperties([], n) : Object.create(Object.getPrototypeOf(e), n);
433
+ const r = Array.isArray(e) ? Object.defineProperties([], n) : Object.create(Object.getPrototypeOf(e), n);
434
+ r[$OWNER] = t;
435
+ return r;
306
436
  }
307
437
 
308
438
  /** Copy own `key` from `from` onto `to`. A plain data slot (enumerable,
@@ -314,19 +444,33 @@ function cloneRaw(e, t) {
314
444
  }
315
445
 
316
446
  /** One-time own-accessor scan (Annex-B probes, no descriptor allocation);
317
- * returns true when the container is plain data (overlay-safe). */ function scanAccessorsOnce(e) {
447
+ * returns true when the container is plain data (overlay-safe). Also grades
448
+ * the container for the plain-data fast paths (`sc` = 2): `Object.prototype`
449
+ * and every own key an enumerable data property — what a spread copies
450
+ * exactly and what a bare assignment lands exactly. */ function scanAccessorsOnce(e) {
318
451
  const t = e.v;
319
- for (const n of Reflect.ownKeys(t)) {
452
+ const n = Reflect.ownKeys(t);
453
+ let r = Object.getPrototypeOf(t) === Object.prototype;
454
+ for (const i of n) {
320
455
  // Own keys shadow prototype accessors, so the lookups are exact here.
321
- if (lookupGetter.call(t, n) !== undefined || lookupSetter.call(t, n) !== undefined) {
456
+ if (lookupGetter.call(t, i) !== undefined || lookupSetter.call(t, i) !== undefined) {
322
457
  e.a = true;
458
+ r = false;
323
459
  break;
324
460
  }
461
+ if (r && !propertyIsEnumerable.call(t, i)) r = false;
325
462
  }
326
- e.sc = true;
463
+ e.sc = r ? 2 : 1;
464
+ e.kc = n.length;
327
465
  return !e.a;
328
466
  }
329
467
 
468
+ /** Own-key count above which a draft opens as a prototype overlay rather
469
+ * than a clone (#3360). Below it a spread clone of a plain container is
470
+ * cheaper than the overlay's `Object.create` (V8 converts the committed
471
+ * backing into a prototype, and every later flatten writes into that
472
+ * prototype); above it the clone's O(keys) copy dominates (#3044). */ const OVERLAY_MIN_KEYS = 32;
473
+
330
474
  /** Downgrade a prototype-overlay pending backing to the clone path: builds
331
475
  * the real container (committed + overlay writes − deletes) that fold will
332
476
  * SWAP in as the committed backing, exactly as if the draft had started on
@@ -340,10 +484,6 @@ function cloneRaw(e, t) {
340
484
  for (const t of e.del) delete n[t];
341
485
  e.del = null;
342
486
  }
343
- const r = e.fam?.map ?? storeNextLookup;
344
- r.delete(t);
345
- ownedRaw.add(n);
346
- r.set(n, e);
347
487
  e.pb = n;
348
488
  e.ovl = false;
349
489
  }
@@ -366,15 +506,32 @@ function ensurePB(e) {
366
506
  }
367
507
  if (activeTransition !== null) foldBatches.set(e, activeTransition);
368
508
  if (t === null) {
369
- // Prototype-chain overlay (#3044): plain-data non-array containers
370
- // outside projection/optimistic families open drafts in O(1) — own keys
371
- // are the writes, reads fall through to committed. Everything else
372
- // (arrays: splice/length semantics; families: seeding/revert machinery;
373
- // accessor containers: live getters) keeps the descriptor clone.
374
- if (e.fam === null && !Array.isArray(e.v) && (e.sc ? !e.a : scanAccessorsOnce(e))) {
375
- t = e.pb = Object.create(e.v);
509
+ // Prototype-chain overlay (#3044): plain-data non-array containers open
510
+ // drafts in O(1) — own keys are the writes, reads fall through to
511
+ // committed. Projection and derived-store families take it too (#3352:
512
+ // a derive touching one root key of a wide keyed record paid an
513
+ // O(keys) clone per recompute). Everything else keeps the descriptor
514
+ // clone: arrays (splice/length semantics), OPTIMISTIC families (the
515
+ // view-seeding below writes and deletes on the draft container, and the
516
+ // tentative discard/truth-park hand whole backings around), chained
517
+ // backings (the committed layer is another store's proxy — an overlay
518
+ // would route every read-through into its traps), accessor containers
519
+ // (live getters).
520
+ // The overlay pays off only for a WIDE container over an OWNED committed
521
+ // backing (#3360). Narrow containers clone cheaper than they overlay (a
522
+ // spread is a fast-path copy; `Object.create` turns the backing into a
523
+ // V8 prototype and every later flatten writes into that prototype). An
524
+ // unowned backing has to be privatized (cloned) at commit anyway, so an
525
+ // overlay there would cost the create PLUS the clone PLUS a per-key
526
+ // copy — the clone path does the one clone and swaps it in. The first
527
+ // write on a fresh store is exactly that case.
528
+ const n = e.v;
529
+ if (!e.fam?.opt && !e.ch && !Array.isArray(n) && (e.sc !== 0 ? !e.a : scanAccessorsOnce(e)) && e.kc > OVERLAY_MIN_KEYS && isOwned(n)) {
530
+ // Inherits `v`'s $OWNER stamp — the overlay resolves and reads as
531
+ // owned without a registration of its own.
532
+ t = e.pb = Object.create(n);
376
533
  e.ovl = true;
377
- } else t = e.pb = cloneRaw(e.v, e);
534
+ } else t = e.pb = cloneRaw(n, e);
378
535
  // Optimistic families: seed USER drafts from the OPTIMISTIC VIEW
379
536
  // (committed + active node overrides), so follow-up writes compose on
380
537
  // optimism instead of clobbering from base (#2951's compose half).
@@ -387,19 +544,17 @@ function ensurePB(e) {
387
544
  if (n !== null) {
388
545
  for (const e of Reflect.ownKeys(n)) {
389
546
  const r = n[e];
390
- if (hasActiveOverride(r)) t[e] = unwrapOverride(r.o?.Pe);
547
+ if (hasActiveOverride(r)) t[e] = unwrapOverride(r.o?.Ce);
391
548
  }
392
549
  }
393
550
  const r = e.h;
394
551
  if (r !== null) {
395
552
  for (const e of Reflect.ownKeys(r)) {
396
553
  const n = r[e];
397
- if (hasActiveOverride(n) && !unwrapOverride(n.o?.Pe)) delete t[e];
554
+ if (hasActiveOverride(n) && !unwrapOverride(n.o?.Ce)) delete t[e];
398
555
  }
399
556
  }
400
557
  }
401
- ownedRaw.add(t);
402
- (e.fam?.map ?? storeNextLookup).set(t, e);
403
558
  queueFold(e);
404
559
  }
405
560
  return t;
@@ -420,7 +575,7 @@ function ensurePB(e) {
420
575
  * foldHeld's node stamps). */ function heldMaskView(e) {
421
576
  const t = e.ht;
422
577
  if (t === null) return null;
423
- if (t !== PLAIN_HOLD && currentTransition(t)?.sn === true) return e.ht = e.hv = null;
578
+ if (t !== PLAIN_HOLD && currentTransition(t)?.Tt === true) return e.ht = e.hv = null;
424
579
  return e.hv;
425
580
  }
426
581
 
@@ -450,20 +605,26 @@ function ensurePB(e) {
450
605
  if (e.ovl) materializePB(e);
451
606
  e.ab = e.pb;
452
607
  } else e.ab ??= foldOlds.get(e);
453
- // #3074/#3075: a projection recompute deriving from uncommitted inputs
454
- // swaps the backing SPECULATIVELY committed-visibility readers must
455
- // keep the pre-hold view until the hold resolves (a source held by a
456
- // live transition, or a latest()-pull ahead of the flush). Post-await
457
- // landings (write-override) stay immediately visible landed truth —
458
- // and clear any hold; optimistic families ride the lane machinery.
459
- if (e.fam?.opt !== true) {
460
- if (getWriteOverride()) {
461
- e.ht = e.hv = null;
462
- } else if (activeTransition !== null || latestPullActive) {
463
- if (heldMaskView(e) === null) e.hv = e.v;
464
- e.ht = activeTransition ?? PLAIN_HOLD;
465
- }
466
- }
608
+ }
609
+ // #3074/#3075: a projection recompute deriving from uncommitted inputs
610
+ // swaps the backing SPECULATIVELY committed-visibility readers must
611
+ // keep the pre-hold view until the hold resolves (a source held by a
612
+ // live transition, or a latest()-pull ahead of the flush). Post-await
613
+ // landings (write-override) stay immediately visible landed truth
614
+ // and clear any hold. Optimistic families hold too (#3330 store twin):
615
+ // their tentative edits ride the lane machinery, but a sync derive
616
+ // adopting under a transaction is held TRUTH like any projection's —
617
+ // unheld, handlers read it early and an optimistic write equal to it
618
+ // compared as a no-op against the swapped-in backing. A plain store's
619
+ // reconcile inside an action holds the same way: its nodes stage under the
620
+ // transaction (the inline notify), and the backing must not show handlers
621
+ // and stale readers what the tracked read masks (signal parity, #3336).
622
+ if (getWriteOverride()) {
623
+ e.ht = e.hv = null;
624
+ } else if (activeTransition !== null || !n && latestPullActive) {
625
+ if (heldMaskView(e) === null) e.hv = e.v;
626
+ e.ht = activeTransition ?? PLAIN_HOLD;
627
+ if (!n && activeTransition !== null) heldAdoptions.add(e);
467
628
  }
468
629
  e.pb = null;
469
630
  // Overlay and accessor-scan state describe the OUTGOING backing — a
@@ -475,13 +636,17 @@ function ensurePB(e) {
475
636
  // draft rescans once (#3044 audit follow-up).
476
637
  e.ovl = false;
477
638
  e.del = null;
478
- e.sc = false;
639
+ e.sc = 0;
479
640
  e.a = false;
480
641
  e.wk = null;
481
642
  // adoption supersedes staged trap writes
482
643
  e.v = t;
483
644
  e.ch = t[$TARGET] !== undefined;
484
- (e.fam?.map ?? storeNextLookup).set(t, e);
645
+ // An adoptee we own within this family (a draft aliasing one of the
646
+ // family's own backings) re-stamps to its new owner — the stamp is the
647
+ // family's registration for it; everything else registers in the map.
648
+ const r = t[$OWNER];
649
+ if (r !== undefined && r.fam === e.fam) t[$OWNER] = e; else (e.fam?.map ?? storeNextLookup).set(t, e);
485
650
  }
486
651
 
487
652
  /** Sentinel for `t.wk`: the written-keys bound is unusable this batch (an
@@ -543,22 +708,22 @@ function queueFold(e) {
543
708
  }
544
709
 
545
710
  /** Committed-time privatization for parent-chain slot updates (path copying). */ function privatizeCommitted(e) {
546
- if (ownedRaw.has(e.v)) return;
711
+ if (isOwned(e.v)) return;
547
712
  const t = e.v;
548
713
  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
714
  e.v = n;
557
715
  e.ch = false;
558
716
  if (e.u) {
559
717
  privatizeCommitted(e.u);
560
718
  devAssertNeverUserMutation(e.u.v);
561
- e.u.v[parentSlotKey(e, t)] = e.v;
719
+ // CAS, same as drainFolds' path copy: re-point the parent slot only
720
+ // while it still holds the raw we cloned. A parent that folded EARLIER
721
+ // in this drain may have replaced or deleted this slot (the same batch
722
+ // wrote the child and then `parent.row = fresh` / `parent.length = 0`)
723
+ // — an unconditional write resurrected the dropped child over it.
724
+ const n = e.u.v;
725
+ const r = parentSlotKey(e, t);
726
+ if (n[r] === t) n[r] = e.v;
562
727
  }
563
728
  }
564
729
 
@@ -577,6 +742,28 @@ function queueFold(e) {
577
742
  return i;
578
743
  }
579
744
 
745
+ /** Overlay commit (#3044): apply the batch's writes onto an OWNED committed
746
+ * backing in place — O(written), not O(container). Unowned backings
747
+ * privatize first (clone once, parents re-slotted) — the never-mutate-user-
748
+ * data contract holds. Shared by the deferred fold (drainFolds) and the
749
+ * projection landing's immediate commit (notifyWrites). */ function flattenOverlay(e, t) {
750
+ privatizeCommitted(e);
751
+ const n = e.v;
752
+ // Plain-data grade (sc 2): every own key on the overlay is an enumerable
753
+ // writable data slot (set-trap writes; a non-plain defineProperty
754
+ // downgrades the grade) — a bare assignment lands it without the
755
+ // descriptor round trip.
756
+ for (const r of Reflect.ownKeys(t)) e.sc === 2 ? n[r] = t[r] : copyOwn(n, t, r);
757
+ if (e.del !== null) {
758
+ for (const t of e.del) delete n[t];
759
+ e.del = null;
760
+ }
761
+ e.pb = null;
762
+ e.ovl = false;
763
+ e.wk = null;
764
+ // written-keys window closes with the commit
765
+ }
766
+
580
767
  function drainFolds() {
581
768
  if (foldOlds.size === 0) return;
582
769
  const e = [ ...foldOlds ];
@@ -592,7 +779,7 @@ function drainFolds() {
592
779
  // keys; observed keys also hit the pending-node held check below).
593
780
  const e = foldBatches.get(t);
594
781
  if (e !== undefined) {
595
- if (currentTransition(e).sn === false) {
782
+ if (currentTransition(e).Tt === false) {
596
783
  foldOlds.set(t, n);
597
784
  continue;
598
785
  }
@@ -604,8 +791,8 @@ function drainFolds() {
604
791
  // node (transition-held) re-queues the target for the settling flush.
605
792
  let r = false;
606
793
  const i = t.pb;
607
- const o = t.n;
608
- if (o !== null) {
794
+ const l = t.n;
795
+ if (l !== null) {
609
796
  // Only written keys can hold (their nodes took the setSignal); the
610
797
  // wk bound keeps this O(written) — see notifyWrites. Same fallback
611
798
  // rules as the notify (WK_ALL / accessors / non-plain prototypes).
@@ -613,10 +800,10 @@ function drainFolds() {
613
800
  const n = e === null || e === WK_ALL || t.a === true ||
614
801
  // Overlay pbs chain to the COMMITTED object (#3044) — plainness is
615
802
  // the committed container's prototype, not the overlay's.
616
- !plainProto(t.ovl ? t.v : i) ? Reflect.ownKeys(o) : e;
803
+ !plainProto(t.ovl ? t.v : i) ? Reflect.ownKeys(l) : e;
617
804
  for (const e of n) {
618
- const t = o[e];
619
- if (t !== undefined && t.Re !== NOT_PENDING) {
805
+ const t = l[e];
806
+ if (t !== undefined && t.ve !== NOT_PENDING) {
620
807
  r = true;
621
808
  break;
622
809
  }
@@ -628,26 +815,12 @@ function drainFolds() {
628
815
  continue;
629
816
  }
630
817
  if (t.ovl) {
631
- // Overlay flatten (#3044): apply this batch's writes onto an OWNED
632
- // committed backing in place O(written), not O(container). The
633
- // backing keeps its identity, so the `t.v === old` gate below skips
634
- // path copying (the parent slot already points here) and the
635
- // adopted-notify (setter notifications happened at write time).
636
- // Unowned backings privatize first (clone once, parents re-slotted)
637
- // — the never-mutate-user-data contract holds.
638
- privatizeCommitted(t);
639
- const e = t.v;
640
- for (const t of Reflect.ownKeys(i)) copyOwn(e, i, t);
641
- if (t.del !== null) {
642
- for (const n of t.del) delete e[n];
643
- t.del = null;
644
- }
645
- (t.fam?.map ?? storeNextLookup).delete(i);
646
- t.pb = null;
647
- t.ovl = false;
648
- t.wk = null;
649
- // written-keys window closes with the fold commit
650
- } else if (t.v !== n) {
818
+ // Overlay flatten (#3044): the backing keeps its identity, so the
819
+ // `t.v === old` gate below skips path copying (the parent slot
820
+ // already points here) and the adopted-notify (setter notifications
821
+ // happened at write time).
822
+ flattenOverlay(t, i);
823
+ } else if (t.v !== n) {
651
824
  // Privatized mid-batch (#3271): an earlier fold in this drain
652
825
  // path-copied THROUGH this target — privatizeCommitted cloned the
653
826
  // committed backing, re-pointed the parent slot at the clone, and
@@ -682,7 +855,6 @@ function drainFolds() {
682
855
  if (!hasOwn.call(i, t)) delete e[t];
683
856
  }
684
857
  }
685
- (t.fam?.map ?? storeNextLookup).delete(i);
686
858
  t.pb = null;
687
859
  t.wk = null;
688
860
  // written-keys window closes with the fold commit
@@ -773,20 +945,20 @@ function drainFolds() {
773
945
  // bound never engages for overlay writes (every plain-object setter batch
774
946
  // would full-scan: the exact selection-map workload wk exists for; jf
775
947
  // `select` regressed 2x on this).
776
- const o = i === WK_ALL || e.a === true || !plainProto(e.ovl ? e.v : t) ? null : i;
948
+ const l = i === WK_ALL || e.a === true || !plainProto(e.ovl ? e.v : t) ? null : i;
777
949
  if (r !== null) {
778
- const i = o ?? Reflect.ownKeys(r);
779
- for (const o of i) {
780
- const i = r[o];
950
+ const i = l ?? Reflect.ownKeys(r);
951
+ for (const l of i) {
952
+ const i = r[l];
781
953
  if (i === undefined) continue;
782
954
  // Per-key accessor handling: the node's cached flag plus ONE getter
783
955
  // probe on the incoming side (getters arriving via merge/adoption).
784
956
  // Setter-only props read as data (value undefined) so lookupSetter is
785
957
  // not consulted on this hot path; prototype getters never own nodes.
786
- if (i.acc === true || hasOwn.call(t, o) && lookupGetter.call(t, o) !== undefined) {
787
- i.acc = isOwnAccessor(t, o);
788
- const e = Object.getOwnPropertyDescriptor(n, o);
789
- const r = Object.getOwnPropertyDescriptor(t, o);
958
+ if (i.acc === true || hasOwn.call(t, l) && lookupGetter.call(t, l) !== undefined) {
959
+ i.acc = isOwnAccessor(t, l);
960
+ const e = Object.getOwnPropertyDescriptor(n, l);
961
+ const r = Object.getOwnPropertyDescriptor(t, l);
790
962
  if (e && (e.get || e.set) || r && (r.get || r.set)) {
791
963
  if (e?.get !== r?.get || e?.set !== r?.set || e?.value !== r?.value) setSignal(i, () => FORCE);
792
964
  continue;
@@ -798,25 +970,26 @@ function drainFolds() {
798
970
  // projection recompute can run before the prior fold commits) — the
799
971
  // node's OWN current value is the true old side, and setSignal's
800
972
  // internal equality already checks exactly that.
801
- const f = e.del !== null && e.del.has(o) ? undefined : t[o];
802
- setSignal(i, () => f);
973
+ const o = e.del !== null && e.del.has(l) ? undefined : t[l];
974
+ setSignal(i, () => o);
803
975
  }
804
976
  }
805
- const f = e.h;
806
- if (f !== null) {
807
- const n = o ?? Reflect.ownKeys(f);
977
+ const o = e.h;
978
+ if (o !== null) {
979
+ const n = l ?? Reflect.ownKeys(o);
808
980
  for (const r of n) {
809
- const n = f[r];
981
+ const n = o[r];
810
982
  if (n !== undefined) setSignal(n, r in t && !(e.del !== null && e.del.has(r)));
811
983
  }
812
984
  }
813
985
  // Deep-witness (dk): setter writes must notify a deep() subscriber even on
814
986
  // keys with no node. O(written/pb keys) equality only when a witness exists.
815
987
  if (e.dk !== null) {
816
- if (e.del !== null && e.del.size !== 0) bumpDeep(e); else for (const r of o ?? Reflect.ownKeys(t)) {
988
+ if (e.del !== null && e.del.size !== 0) bumpDeep(e); else for (const r of l ?? Reflect.ownKeys(t)) {
989
+ if (r === $OWNER) continue;
817
990
  const i = t[r];
818
- const o = n[r];
819
- if (i !== null && typeof i === "object" ? !targetsEqual(o, i) : !isEqual(o, i)) {
991
+ const l = n[r];
992
+ if (i !== null && typeof i === "object" ? !targetsEqual(l, i) : !isEqual(l, i)) {
820
993
  bumpDeep(e);
821
994
  break;
822
995
  }
@@ -860,6 +1033,10 @@ function drainFolds() {
860
1033
  // Landed truth (post-await write-override): immediately visible to every
861
1034
  // reader — any staged held view is superseded.
862
1035
  if (e.ht !== null) e.ht = e.hv = null;
1036
+ // Overlay landing (#3352): flatten in place — identity-stable, and
1037
+ // privatizeCommitted re-slots the parent itself when it has to clone an
1038
+ // unowned seed, so no path copy is needed.
1039
+ if (e.ovl) return flattenOverlay(e, t);
863
1040
  const n = e.v;
864
1041
  e.pb = null;
865
1042
  e.v = t;
@@ -880,9 +1057,9 @@ const FORCE = Symbol();
880
1057
 
881
1058
  /** Same logical slot: both values resolve to one (re-pointed) child target —
882
1059
  * adoption preserved identity, so the slot did not change (R9). */ function targetsEqual(e, t) {
883
- if (e === null || typeof e !== "object") return false;
884
- const n = storeNextLookup.get(e);
885
- return n !== undefined && n === storeNextLookup.get(t);
1060
+ if (e === null || typeof e !== "object" || t === null || typeof t !== "object") return false;
1061
+ const n = lookupTarget(e, null);
1062
+ return n !== undefined && n === lookupTarget(t, null);
886
1063
  }
887
1064
 
888
1065
  function arrayStructureChanged(e, t) {
@@ -897,8 +1074,9 @@ function arrayStructureChanged(e, t) {
897
1074
 
898
1075
  function membershipChanged(e, t) {
899
1076
  const n = Reflect.ownKeys(t);
900
- if (Reflect.ownKeys(e).length !== n.length) return true;
901
- for (const t of n) if (!(t in e)) return true;
1077
+ // The $OWNER stamp is not membership: an owned side counts one key more.
1078
+ if (Reflect.ownKeys(e).length - +isOwned(e) !== n.length - +isOwned(t)) return true;
1079
+ for (const t of n) if (t !== $OWNER && !(t in e)) return true;
902
1080
  return false;
903
1081
  }
904
1082
 
@@ -921,23 +1099,23 @@ i = true) {
921
1099
  if (e.acc === true || i && hasOwn.call(r, t) && lookupGetter.call(r, t) !== undefined) {
922
1100
  e.acc = isOwnAccessor(r, t);
923
1101
  const i = Object.getOwnPropertyDescriptor(n, t);
924
- const o = Object.getOwnPropertyDescriptor(r, t);
925
- if (i && (i.get || i.set) || o && (o.get || o.set)) {
1102
+ const l = Object.getOwnPropertyDescriptor(r, t);
1103
+ if (i && (i.get || i.set) || l && (l.get || l.set)) {
926
1104
  // Accessor involved: never invoke; force-notify on shape change so
927
1105
  // subscribers re-read (and re-track) through the trap.
928
- if (i?.get !== o?.get || i?.set !== o?.set || i?.value !== o?.value) setSignal(e, () => FORCE);
1106
+ if (i?.get !== l?.get || i?.set !== l?.set || i?.value !== l?.value) setSignal(e, () => FORCE);
929
1107
  return;
930
1108
  }
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);
1109
+ const o = i?.value;
1110
+ const f = l?.value;
1111
+ if (!isEqual(o, f) && !targetsEqual(o, f)) setSignal(e, typeof f === "function" ? () => f : f);
934
1112
  } else {
935
1113
  const i = n[t];
936
- const o = r[t];
1114
+ const l = r[t];
937
1115
  // Direct value write when not a function (setSignal treats functions as
938
1116
  // updaters) — saves a closure allocation per changed key on the fold
939
1117
  // hot path.
940
- if (!isEqual(i, o) && !targetsEqual(i, o)) setSignal(e, typeof o === "function" ? () => o : o);
1118
+ if (!isEqual(i, l) && !targetsEqual(i, l)) setSignal(e, typeof l === "function" ? () => l : l);
941
1119
  }
942
1120
  }
943
1121
 
@@ -949,9 +1127,9 @@ i = true) {
949
1127
  /** Fused-walk per-key notification with values already in hand: the caller
950
1128
  * fetched both sides and handled the identity skip; this applies the
951
1129
  * accessor branch (cached flag only — reconcile channel) or the plain
952
- * equality/identity-gated write. */ function notifyKeyValue(e, t, n, r, i, o) {
1130
+ * equality/identity-gated write. */ function notifyKeyValue(e, t, n, r, i, l) {
953
1131
  if (e.acc === true) {
954
- notifyKeyDiff(e, t, i, o, false);
1132
+ notifyKeyDiff(e, t, i, l, false);
955
1133
  return;
956
1134
  }
957
1135
  // The pre-compare is NOT redundant with the node's equals: setSignal parks
@@ -1046,28 +1224,46 @@ function inDraft(e) {
1046
1224
 
1047
1225
  /** Targets written during the current (outermost) setter — notified at exit. */ const pendingNotify = new Set;
1048
1226
 
1227
+ /** Targets adopted under a live transaction this setter (adoptPB set a
1228
+ * transaction hold) — their nodes are notified at the outermost exit. */ const heldAdoptions = new Set;
1229
+
1230
+ /**
1231
+ * Write-time notification for a transaction-held adoption (#3330 store twin;
1232
+ * the setter path's notifyWrites twin for adoptions). A projection's fold
1233
+ * normally notifies its nodes at the drain — and the drain of a batch parked
1234
+ * in a live transaction is the transaction's COMMIT, so the nodes took the
1235
+ * adopted values as fresh writes at commit time: every subscriber was
1236
+ * re-marked and re-ran against a frame the lane had already published (a
1237
+ * third `v=1 d=2`), where a signal's write had staged at write time and
1238
+ * promoted silently. Staging here, inside the transaction's batch, makes the
1239
+ * two paths one: the nodes carry transition-stamped `_pendingValue`s, their
1240
+ * subscribers recompute in this flush and park with the transaction, and the
1241
+ * commit promotes without re-notifying. `ab` moves to the adopted backing —
1242
+ * the view the nodes were last told (#3296) — so the drain has nothing left
1243
+ * to say and only path-copies.
1244
+ */ function stageHeldAdoptions() {
1245
+ const e = [ ...heldAdoptions ];
1246
+ heldAdoptions.clear();
1247
+ for (const t of e) {
1248
+ const e = t.ab;
1249
+ if (e === null || e === t.v) continue;
1250
+ notifyFold(t, e, t.v);
1251
+ t.ab = t.v;
1252
+ }
1253
+ }
1254
+
1049
1255
  const UNSAFE_KEYS = new Set([ "__proto__", "prototype", "constructor" ]);
1050
1256
 
1051
1257
  /** Mirror of core read()'s context rule: the OWNER context (not the tracking
1052
1258
  * observer) decides pending visibility, with Roots resolving to their parent
1053
1259
  * computed (#2687 — untracked reads inside mapArray Roots see in-flight
1054
1260
  * values mid-flush). CHILDREN_FORBIDDEN execution scopes (createTrackedEffect
1055
- * / onSettled callbacks) get COMMITTED visibility (#3006), same as core. */ function inOwnerContext() {
1056
- const e = getOwner();
1057
- if (e === null) return false;
1058
- const t = e.Dt ? e.Pt : e;
1059
- return t != null && !(t.T & CONFIG_CHILDREN_FORBIDDEN);
1060
- }
1061
-
1062
- /** CHILDREN_FORBIDDEN execution scope (createTrackedEffect / onSettled
1063
- * callbacks). Distinct from context-free: these scopes get committed
1064
- * visibility even against a projection's authoritative-elect pending
1065
- * backing (#3082) — parity with signals, where core read() serves
1066
- * committed to them regardless of staged writes. */ function inForbiddenScope() {
1261
+ * / onSettled callbacks) get COMMITTED visibility (#3006), same as core. */
1262
+ /** Core read()'s reader: the current computation, a root reading as its
1263
+ * parent computed (`context` persists under untrack — an untracked read
1264
+ * inside an effect is still that effect's read). */ function readerContext() {
1067
1265
  const e = getOwner();
1068
- if (e === null) return false;
1069
- const t = e.Dt ? e.Pt : e;
1070
- return t != null && !!(t.T & CONFIG_CHILDREN_FORBIDDEN);
1266
+ return e === null ? null : e.xt ? e.Qt ?? null : e;
1071
1267
  }
1072
1268
 
1073
1269
  /** A pending fold is transition-held when any written node's parked value is
@@ -1077,19 +1273,24 @@ const UNSAFE_KEYS = new Set([ "__proto__", "prototype", "constructor" ]);
1077
1273
  if (t === null) return false;
1078
1274
  for (const e of Reflect.ownKeys(t)) {
1079
1275
  const n = t[e];
1080
- if (n.Re !== NOT_PENDING && n.Ae != null && n.Ae.sn !== true) return true;
1276
+ if (n.ve !== NOT_PENDING && n.Ge != null && n.Ge.Tt !== true) return true;
1081
1277
  }
1082
1278
  return false;
1083
1279
  }
1084
1280
 
1085
1281
  function readSource(e) {
1086
- // Held view first (#3074): an adoption staged under a live hold serves the
1087
- // pre-hold committed backing to committed-visibility readers. Speculative
1088
- // readers — drafts, write-override, owner-context computeds recomputing
1089
- // inside the transaction, and latest() reads see the adopted backing.
1090
- if (e.ht !== null && !latestReadActive && !inDraft(e) && !getWriteOverride() && !inOwnerContext()) {
1091
- const t = heldMaskView(e);
1092
- if (t !== null) return t;
1282
+ // Adoption hold first (#3074): an adoption staged under a live transaction
1283
+ // (or a latest()-pull, PLAIN_HOLD) serves the pre-hold committed view to
1284
+ // committed-visibility readers — context-free and children-forbidden ones,
1285
+ // and stale passes off a foreign hold. Drafts, write-override and latest()
1286
+ // see the adopted backing.
1287
+ const t = e.ht;
1288
+ if (t !== null && !latestReadActive && !inDraft(e) && !getWriteOverride()) {
1289
+ const n = heldMaskView(e);
1290
+ if (n !== null) {
1291
+ const e = readerContext();
1292
+ if (e === null || e.T & CONFIG_CHILDREN_FORBIDDEN || !holdVisible(t === PLAIN_HOLD ? null : currentTransition(t), e)) return n;
1293
+ }
1093
1294
  }
1094
1295
  return pendingBackingVisible(e, false) ? e.pb : e.v;
1095
1296
  }
@@ -1109,21 +1310,28 @@ function readSource(e) {
1109
1310
  * ordinary pending staging regardless of owner context (the documented
1110
1311
  * divergence from context-free per-key reads) — but never through a hold:
1111
1312
  * 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) ||
1121
- // A projection's pending backing is authoritative-elect: serve it to
1122
- // context-free readers too UNLESS a transition is holding the node
1123
- // commits (downstream async hold stale committed is the contract)
1124
- // or the reader is a CHILDREN_FORBIDDEN scope, which never observes
1125
- // its own unsettled write (#3082, signal parity per #3006).
1126
- e.fam !== null && !heldTruthMasked(e) && !foldHeld(e) && !inForbiddenScope());
1313
+ if (e.pb === null) return false;
1314
+ // The writer's own channels compose on the pending backing regardless.
1315
+ if (inDraft(e) || getWriteOverride()) return true;
1316
+ // HELD truth on an optimistic family (#3164 fold) is masked from ordinary
1317
+ // readers until the transaction's reveal (the backing-level twin of core
1318
+ // serve()'s CONFIG_HELD_TRUTH arm; authoritative postures and latest()
1319
+ // tunnel through inside heldTruthMasked).
1320
+ if (heldTruthMasked(e)) return false;
1321
+ const n = readerContext();
1322
+ if (n === null || n.T & CONFIG_CHILDREN_FORBIDDEN) {
1323
+ // No pass, or a children-forbidden one: the committed frame (A32) —
1324
+ // except the speculative peek (deep()/snapshot()), which sees ordinary
1325
+ // pending staging but never through a live foreign hold, and a
1326
+ // projection's pending backing, authoritative-elect for context-free
1327
+ // readers UNLESS a transition holds the node commits (downstream async
1328
+ // hold — stale committed is the contract; the write-time stamp covers
1329
+ // keys with no node, #3336) or the scope is children-forbidden (#3082).
1330
+ const r = liveFoldTransition(e);
1331
+ if (t) return r === null || ownsHold(r);
1332
+ return e.fam !== null && n === null && !foldHeld(e) && r === null;
1333
+ }
1334
+ return holdVisible(liveFoldTransition(e), n);
1127
1335
  }
1128
1336
 
1129
1337
  /** #3164 fold: HELD truth on an optimistic family — a pending backing
@@ -1149,6 +1357,8 @@ const lookupGetter = Object.prototype.__lookupGetter__;
1149
1357
 
1150
1358
  const lookupSetter = Object.prototype.__lookupSetter__;
1151
1359
 
1360
+ const propertyIsEnumerable = Object.prototype.propertyIsEnumerable;
1361
+
1152
1362
  function isOwnAccessor(e, t) {
1153
1363
  return hasOwn.call(e, t) && (lookupGetter.call(e, t) !== undefined || lookupSetter.call(e, t) !== undefined);
1154
1364
  }
@@ -1166,11 +1376,6 @@ function isOwnAccessor(e, t) {
1166
1376
  }
1167
1377
  }
1168
1378
 
1169
- /** Active optimistic override on an armed node (armed slot idles at
1170
- * NOT_PENDING; undefined = unarmed plain node). */ function hasActiveOverride(e) {
1171
- return e.o?.Pe !== undefined && e.o?.Pe !== NOT_PENDING;
1172
- }
1173
-
1174
1379
  /** The reading computation is until()'s authoritative-view predicate — same
1175
1380
  * source of truth as core read()'s A17 carve-out (`context`, which persists
1176
1381
  * under untrack). optimisticView()'s composition gate consults exactly this:
@@ -1198,35 +1403,56 @@ function isOwnAccessor(e, t) {
1198
1403
  * reader that forced it (backing commits eagerly; node values fold at flush).
1199
1404
  * FORCE sentinels never surface (they only bump subscribers of accessor
1200
1405
  * keys, which are served by the trap, not the node). */ function nodeValue(e, t) {
1201
- // latest() sees the in-flight parked value like an owner-context reader
1202
- // does (#3075) signal/memo parity for store-node-backed keys.
1203
- // Authoritative-view reads (until()'s predicate) skip the override arm
1204
- // only: staged pending values are authoritative, overrides are the
1205
- // caller's optimism.
1206
- const n = !authoritativeServe() && hasActiveOverride(e) ? unwrapOverride(e.o?.Pe) : e.Re !== NOT_PENDING && (latestReadActive ||
1207
- // Owner-context pending visibility except HELD truth (#3164,
1208
- // see CONFIG_HELD_TRUTH: fold-staged or entangle-stolen
1209
- // confirming truth), which only authoritative/latest readers
1210
- // see (core read()'s A17-for-held-truth twin; ordinary readers
1211
- // keep committed until the transaction's reveal latest() is
1212
- // exempted by the leading arm above).
1213
- (inOwnerContext() || authoritativeServe()) && !(e.T & CONFIG_HELD_TRUTH && !authoritativeServe())) ? e.Re : t;
1406
+ // Store-only tunnels, ahead of Rule 1: truth authors (authoritativeServe
1407
+ // the projection derive's draft, the write-override continuation) see
1408
+ // staged truth and never an override; latest() reaching this untracked
1409
+ // path for a store key sees the in-flight parked value like an
1410
+ // owner-context reader does (#3075), the visible override first.
1411
+ let n;
1412
+ if (authoritativeServe()) n = e.ve !== NOT_PENDING ? e.ve : t; else if (latestReadActive) n = visibleOverride(e) ? unwrapOverride(e.o?.Ce) : e.ve !== NOT_PENDING ? e.ve : t;
1413
+ // Otherwise the one slow selection core read() uses (serve): override,
1414
+ // lane gate, A28, readerSeesCommitted / A29 — with the BACKING as the
1415
+ // committed value (single-home rule, O6).
1416
+ else n = serve(e, readerContext(), e.Te || e, t);
1214
1417
  return n === FORCE ? t : n;
1215
1418
  }
1216
1419
 
1420
+ /** §7b: a chained target's child found as a RAW — from its pending backing
1421
+ * (a cloneRaw of the inner proxy, whose descriptors yield the inner store's
1422
+ * raws) or from the deep() walk's descriptor read through the chain — must
1423
+ * resolve to the inner family's proxy for that object before this family
1424
+ * wraps it, so the overlay serves the same chained targets as the settled
1425
+ * state. Otherwise every row re-wrapped as a fresh non-chained target keyed
1426
+ * by the raw: identities churned for the life of an optimistic action and
1427
+ * snapped back at settle, and writes landed on those orphans while deep()'s
1428
+ * witnesses sat on the chained targets (#3323). The inner family owns the
1429
+ * raw iff it has served it or its backing holds that exact object at `key`;
1430
+ * anything else is a draft's own replacement object — view-owned, correctly
1431
+ * non-chained — and is returned as is. Recurses down further chains. */ function resolveChainedRaw(e, t, n) {
1432
+ const r = e.v[$TARGET];
1433
+ if (r.ch) {
1434
+ const e = resolveChainedRaw(r, t, n);
1435
+ return e === n ? n : wrapNext(e, r, t);
1436
+ }
1437
+ const i = lookupTarget(n, r.fam);
1438
+ if (i !== undefined) return i.px;
1439
+ if ((r.v[t] === n || r.pb?.[t] === n) && isWrappable(n)) return wrapNext(n, r, t);
1440
+ return n;
1441
+ }
1442
+
1217
1443
  /** Serve an own data key: node-first when a node exists (pending visibility,
1218
1444
  * holds, lanes ride the node); backing otherwise. Chained backings (§7b: the
1219
1445
  * backing IS another store's proxy) serve the read-through value — the outer
1220
1446
  * node is linked only for adoption-swap notification, its value never
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;
1447
+ * shadows the live chain. */ function serveDataKey(e, t, n, r, i, l = -1) {
1448
+ const o = e.ch && r === e.v;
1449
+ let f = n;
1224
1450
  // §6: on optimistic arrays LENGTH IS A VIEW, not a node value — one home
1225
1451
  // (backing ± presence overrides) for both length and indices makes torn
1226
1452
  // iteration impossible (a length node's value rides different visibility
1227
1453
  // rails than index overrides mid-settle). The node still carries
1228
1454
  // subscriptions; its value is never served here.
1229
- if (t === "length" && e.fam?.opt === true && !f && Array.isArray(r)) {
1455
+ if (t === "length" && e.fam?.opt === true && !o && Array.isArray(r)) {
1230
1456
  if (!inDraft(e)) {
1231
1457
  const r = e.n?.length;
1232
1458
  if (r !== undefined) {
@@ -1238,7 +1464,7 @@ function isOwnAccessor(e, t) {
1238
1464
  // Truth authors read the backing's own length — an optimistic row from
1239
1465
  // the caller's transaction must not shift where the author's next write
1240
1466
  // lands (#3108).
1241
- return (authoritativeServe() ? r : optHooks.optimisticView(e, r)).length;
1467
+ return (authoritativeServe() ? r : optHooks.optimisticView(e, r, inDraft(e))).length;
1242
1468
  }
1243
1469
  if (inDraft(e)) {
1244
1470
  // Optimistic drafts before their first write have no pending backing yet;
@@ -1248,42 +1474,43 @@ function isOwnAccessor(e, t) {
1248
1474
  // seeding rule, applied to the read side (#3108).
1249
1475
  if (e.fam?.opt && draftSeesOverrides(e) && !authoritativeServe()) {
1250
1476
  const n = e.n?.[t];
1251
- if (n !== undefined && hasActiveOverride(n)) l = unwrapOverride(n.o?.Pe);
1477
+ if (n !== undefined && hasActiveOverride(n)) f = unwrapOverride(n.o?.Ce);
1252
1478
  }
1253
1479
  } else {
1254
- if (i !== undefined) {
1255
- // §7b: a lane value on the outer node SHADOWS read-through an active
1256
- // override pierces the chained gate; otherwise chained backings always
1257
- // serve the live inner value.
1258
- if (getObserver() !== null) {
1259
- // read()'s plain-signal fast path hoisted over the call (legacy trap
1260
- // parity): READ_SLOW = a global read window or non-plain node.
1261
- let e = readNodeFast(i);
1262
- if (e === READ_SLOW) e = read(i);
1263
- if (!f || hasActiveOverride(i)) l = e === FORCE ? n : e;
1264
- } else if (!f || hasActiveOverride(i)) {
1265
- l = nodeValue(i, n);
1266
- }
1267
- } else if (getObserver() !== null) {
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));
1480
+ // §7b: a lane value on the outer node SHADOWS read-through — an active
1481
+ // override pierces the chained gate; otherwise chained backings always
1482
+ // serve the live inner value.
1483
+ if (getObserver() !== null) {
1484
+ // First tracked read: create + link (the wrap-cache branch below
1485
+ // populates px/pxv so read #2 skips wrapNext, slice 2). The value is
1486
+ // served THROUGH the node from this read on a node born under a held
1487
+ // fold carries the hold (getNode, #3336), and the backing it was read
1488
+ // from does not.
1489
+ if (i === undefined) i = getNode(e, t, n, l);
1490
+ // read()'s plain-signal fast path hoisted over the call (legacy trap
1491
+ // parity): READ_SLOW = a global read window or non-plain node.
1492
+ let r = readNodeFast(i);
1493
+ if (r === READ_SLOW) r = read(i);
1494
+ if (!o || hasActiveOverride(i)) f = r === FORCE ? n : r;
1495
+ } else if (i !== undefined && (!o || hasActiveOverride(i))) {
1496
+ f = nodeValue(i, n);
1271
1497
  }
1272
1498
  }
1273
1499
  // Shallow stores serve data raw; store-proxy slots get boundary wrappers.
1274
- if (e.s) return serveShallow(e, t, l);
1500
+ if (e.s) return serveShallow(e, t, f);
1501
+ if (e.ch && !o && f !== null && typeof f === "object" && f[$TARGET] === undefined) f = resolveChainedRaw(e, t, f);
1275
1502
  if (i !== undefined) {
1276
1503
  // Wrap cache (see getNode): only wrappables are ever cached, so a hit
1277
1504
  // skips isWrappable too — pointer-compare replaces both checks.
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);
1505
+ if (i.pxv === f && f !== undefined) return draftServe(e, i.px);
1506
+ if (!isWrappable(f)) return f;
1507
+ const n = wrapNext(f, e, t);
1281
1508
  i.px = n;
1282
- i.pxv = l;
1509
+ i.pxv = f;
1283
1510
  return draftServe(e, n);
1284
1511
  }
1285
- if (!isWrappable(l)) return l;
1286
- return draftServe(e, wrapNext(l, e, t));
1512
+ if (!isWrappable(f)) return f;
1513
+ return draftServe(e, wrapNext(f, e, t));
1287
1514
  }
1288
1515
 
1289
1516
  /** §6c store-wide status gate for reads that DON'T flow through a node:
@@ -1334,6 +1561,8 @@ const traps = {
1334
1561
  if (typeof t !== "string") {
1335
1562
  if (t === $TARGET) return e;
1336
1563
  if (t === $PROXY) return n;
1564
+ if (t === $OWNER) return undefined;
1565
+ // ownership stamp: never a user key
1337
1566
  // refresh()/isPending resolve the projection computed through $REFRESH.
1338
1567
  if (t === $REFRESH) return e.fam?.node ?? undefined;
1339
1568
  if (t === $TRACK) {
@@ -1405,18 +1634,18 @@ const traps = {
1405
1634
  // node creation downstream reuses it instead of re-scanning the
1406
1635
  // descriptor, but only when the probed object IS the one getNode would
1407
1636
  // scan (pb ?? v).
1408
- let o = -1;
1637
+ let l = -1;
1409
1638
  {
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;
1639
+ let o;
1640
+ if (i !== undefined) o = i.acc === true; else if (!inDraft(e) && getObserver() !== null) {
1641
+ o = isOwnAccessor(r, t);
1642
+ if (r === (e.pb ?? e.v)) l = o ? 1 : 0;
1643
+ } else o = false;
1644
+ if (o) {
1645
+ if (!inDraft(e) && getObserver() !== null) read(i ?? getNode(e, t, undefined, l));
1646
+ const o = Reflect.get(r, t, n);
1647
+ if (e.s) return serveShallow(e, t, o);
1648
+ return isWrappable(o) ? draftServe(e, wrapNext(o, e, t)) : o;
1420
1649
  }
1421
1650
  }
1422
1651
  // Plain-data fast path: no descriptor allocation per read.
@@ -1426,40 +1655,41 @@ const traps = {
1426
1655
  // backings chain to the committed backing, so "own in the view" means
1427
1656
  // own on either layer (ownInView) — a genuine prototype method is one
1428
1657
  // that is own on NEITHER.
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) {
1658
+ const o = e.ovl && r === e.pb;
1659
+ if ((t === "constructor" || t === "__proto__" || t === "prototype") && !hasOwn.call(r, t) && !(o && hasOwn.call(e.v, t))) return undefined;
1660
+ let f = r[t];
1661
+ if (f === undefined ? !hasOwn.call(r, t) && !(o && hasOwn.call(e.v, t)) : false) {
1433
1662
  // Inherited: prototype getters/methods run with the proxy receiver.
1434
- l = Reflect.get(r, t, n);
1435
- if (typeof l === "function") return l;
1663
+ f = Reflect.get(r, t, n);
1664
+ if (typeof f === "function") return f;
1436
1665
  // proto methods untracked
1437
1666
  // Reading a currently-absent own key subscribes to it (R12) — for any
1438
1667
  // target OUTSIDE its own draft scope, even mid-setter (#3037, above).
1439
- if (l === undefined && !inDraft(e)) {
1440
- if (getObserver() !== null) read(getNode(e, t, undefined, o));
1668
+ if (f === undefined && !inDraft(e)) {
1669
+ if (getObserver() !== null) read(getNode(e, t, undefined, l));
1441
1670
  const n = e.n?.[t];
1442
1671
  if (n) {
1443
1672
  const r = nodeValue(n, undefined);
1444
1673
  if (e.s) return serveShallow(e, t, r);
1445
1674
  return isWrappable(r) ? draftServe(e, wrapNext(r, e, t)) : r;
1446
1675
  }
1447
- } else if (l === undefined && inDraft(e) && e.fam?.opt && draftSeesOverrides(e) &&
1676
+ } else if (f === undefined && inDraft(e) && e.fam?.opt && draftSeesOverrides(e) &&
1448
1677
  // AUTHORITATIVE drafts (landing folds) never seed from overrides —
1449
1678
  // the caller's optimism is not truth (has-trap twin below).
1450
1679
  !authoritativeServe()) {
1451
1680
  const n = e.n?.[t];
1452
- if (n !== undefined && hasActiveOverride(n)) l = unwrapOverride(n.o?.Pe);
1681
+ if (n !== undefined && visibleOverride(n)) f = unwrapOverride(n.o?.Ce);
1453
1682
  }
1454
- if (e.s) return serveShallow(e, t, l);
1455
- return isWrappable(l) ? draftServe(e, wrapNext(l, e, t)) : l;
1683
+ if (e.s) return serveShallow(e, t, f);
1684
+ return isWrappable(f) ? draftServe(e, wrapNext(f, e, t)) : f;
1456
1685
  }
1457
- if (typeof l === "function" && !hasOwn.call(r, t) && !(f && hasOwn.call(e.v, t))) return l;
1686
+ if (typeof f === "function" && !hasOwn.call(r, t) && !(o && hasOwn.call(e.v, t))) return f;
1458
1687
  // proto method
1459
- return serveDataKey(e, t, l, r, i, o);
1688
+ return serveDataKey(e, t, f, r, i, l);
1460
1689
  },
1461
1690
  has(e, t) {
1462
1691
  if (t === $TARGET || t === $PROXY || t === $TRACK) return true;
1692
+ if (t === $OWNER) return false;
1463
1693
  if (pendingCheckActive) witnessAffectsMark(e, t);
1464
1694
  if (e.fam !== null && getObserver() === null && !inDraft(e)) firewallGate(e);
1465
1695
  const n = readSource(e);
@@ -1475,11 +1705,11 @@ const traps = {
1475
1705
  if (hasActiveOverride(n)) r = !!i;
1476
1706
  } else if (!authoritativeServe()) {
1477
1707
  const n = e.h?.[t];
1478
- if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.Pe);
1708
+ if (n !== undefined && visibleOverride(n)) r = !!unwrapOverride(n.o?.Ce);
1479
1709
  }
1480
1710
  } else if (e.fam?.opt && draftSeesOverrides(e) && !authoritativeServe()) {
1481
1711
  const n = e.h?.[t];
1482
- if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.Pe);
1712
+ if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.Ce);
1483
1713
  }
1484
1714
  return r;
1485
1715
  },
@@ -1487,59 +1717,27 @@ const traps = {
1487
1717
  if (pendingCheckActive) witnessAffectsMark(e);
1488
1718
  if (e.fam !== null && getObserver() === null && !inDraft(e)) firewallGate(e);
1489
1719
  if (!inDraft(e) && getObserver() !== null) read(getKeySetNode(e));
1490
- const t = readSource(e);
1491
- let n;
1492
- if (e.ovl && t === e.pb) {
1493
- // Overlay merge (#3044): committed keys in their order, then this
1494
- // batch's NEW keys, minus deletes.
1495
- n = Reflect.ownKeys(e.v);
1496
- const r = e.del;
1497
- if (r !== null && r.size !== 0) n = n.filter(e => !r.has(e));
1498
- for (const r of Reflect.ownKeys(t)) {
1499
- if (!hasOwn.call(e.v, r)) n.push(r);
1500
- }
1501
- } else n = Reflect.ownKeys(t);
1502
- // Optimistic membership overlay: presence-node overrides add/remove keys
1503
- // (per-transaction lifecycle rides the nodes — §6, FINDING-2's fix).
1504
- // Draft reads before the first write overlay too (pb, once created, is
1505
- // seeded with the view). Authoritative-view reads (until()'s predicate,
1506
- // truth-author drafts) skip the overlay.
1507
- if (!authoritativeServe() && e.fam?.opt && e.h !== null && (!inDraft(e) || draftSeesOverrides(e))) {
1508
- let t = null;
1509
- for (const r of Reflect.ownKeys(e.h)) {
1510
- const i = e.h[r];
1511
- if (!hasActiveOverride(i)) continue;
1512
- t ??= new Set(n);
1513
- if (unwrapOverride(i.o?.Pe)) t.add(r); else t.delete(r);
1514
- }
1515
- if (t !== null) return [ ...t ];
1516
- }
1517
- return n;
1720
+ return visibleKeys(e, readSource(e));
1518
1721
  },
1519
1722
  getOwnPropertyDescriptor(e, t) {
1723
+ if (t === $OWNER) return undefined;
1724
+ // A descriptor read is a PRESENCE read: it subscribes to the key's
1725
+ // presence node and witnesses affects()/isPending() exactly as `in` does
1726
+ // (structural oracle, 2026-09-17 — the trap read no node before, so a
1727
+ // render effect inspecting a key through getOwnPropertyDescriptor never
1728
+ // re-ran for an optimistic add or delete, and an isPending() probe over
1729
+ // it witnessed nothing). The value it reports rides the value node's
1730
+ // view through visibleDescriptor.
1731
+ if (pendingCheckActive) witnessAffectsMark(e, t);
1732
+ if (e.fam !== null && getObserver() === null && !inDraft(e)) firewallGate(e);
1520
1733
  const n = readSource(e);
1521
- let r = Object.getOwnPropertyDescriptor(n, t);
1522
- // Overlay (#3044): unwritten keys live on the committed backing;
1523
- // deleted keys are absent from the pending view.
1524
- if (e.ovl && n === e.pb) {
1525
- if (e.del !== null && e.del.has(t)) return undefined;
1526
- if (r === undefined) r = Object.getOwnPropertyDescriptor(e.v, t);
1527
- }
1528
- if (!authoritativeServe() && e.fam?.opt && !inDraft(e)) {
1529
- const n = e.h?.[t];
1530
- if (n !== undefined && hasActiveOverride(n)) {
1531
- if (!unwrapOverride(n.o?.Pe)) return undefined;
1532
- // opt delete
1533
- if (r === undefined) {
1534
- const n = e.n?.[t];
1535
- return {
1536
- value: n !== undefined ? nodeValue(n, undefined) : undefined,
1537
- writable: true,
1538
- enumerable: true,
1539
- configurable: true
1540
- };
1541
- }
1542
- }
1734
+ const r = visibleDescriptor(e, n, t);
1735
+ if (!inDraft(e) && getObserver() !== null) {
1736
+ // The node is born from the source's presence (as `has` births it),
1737
+ // not the override-adjusted answer.
1738
+ let r = t in n;
1739
+ if (r && e.del !== null && n === e.pb && e.del.has(t)) r = false;
1740
+ read(getHasNode(e, t, r));
1543
1741
  }
1544
1742
  if (r === undefined) return undefined;
1545
1743
  // Array targets carry a real non-configurable `length` the proxy
@@ -1563,24 +1761,31 @@ const traps = {
1563
1761
  // Shallow slots store what was written VERBATIM — another store's proxy
1564
1762
  // passes through by reference (#2932; markRawOne skips proxies), while
1565
1763
  // deep stores unwrap to raw backings.
1566
- const o = e.s ? n : unwrapValue(n);
1567
- const f = ensurePB(e);
1764
+ const l = e.s ? n : unwrapValue(n);
1765
+ const o = ensurePB(e);
1568
1766
  pendingNotify.add(e);
1569
1767
  // Array length writes implicitly delete indices — the written-keys bound
1570
1768
  // can't see them, so poison to the full scan for this batch. Index
1571
1769
  // writes implicitly GROW length, so arrays always record it alongside.
1572
- if (Array.isArray(f)) {
1770
+ if (Array.isArray(o)) {
1573
1771
  if (t === "length") e.wk = WK_ALL; else if (e.wk !== WK_ALL) {
1574
1772
  const n = e.wk ??= new Set;
1575
1773
  n.add(t);
1576
1774
  n.add("length");
1577
1775
  }
1578
- } else if (e.wk !== WK_ALL) (e.wk ??= new Set).add(t);
1776
+ } else {
1777
+ if (e.wk !== WK_ALL) (e.wk ??= new Set).add(t);
1778
+ // Live own-key estimate for the overlay/clone choice (#3360): `in`
1779
+ // sees through an overlay to the committed keys, so this counts keys
1780
+ // NEW to the container. Deletes are not un-counted (a stale high
1781
+ // count only picks the overlay a little early).
1782
+ if (!(t in o)) e.kc++;
1783
+ }
1579
1784
  // Own data keys literally named "prototype"/"constructor" land as data —
1580
1785
  // defineProperty sidesteps a proto-chain setter named the same.
1581
1786
  if (UNSAFE_KEYS.has(t)) {
1582
- Object.defineProperty(f, t, {
1583
- value: o,
1787
+ Object.defineProperty(o, t, {
1788
+ value: l,
1584
1789
  writable: true,
1585
1790
  enumerable: true,
1586
1791
  configurable: true
@@ -1590,19 +1795,21 @@ const traps = {
1590
1795
  }
1591
1796
  // Overlay first-write DEFINES the own key: assignment through the proto
1592
1797
  // chain would reject on a non-writable committed property (the clone
1593
- // path normalized descriptors for exactly this — R51 parity).
1594
- if (e.ovl && !hasOwn.call(f, t)) {
1595
- Object.defineProperty(f, t, {
1596
- value: o,
1798
+ // path normalized descriptors for exactly this — R51 parity). A
1799
+ // plain-data-graded backing (sc 2, owned: every slot writable) takes the
1800
+ // bare assignment — it lands as an own key on the overlay all the same.
1801
+ if (e.ovl && e.sc !== 2 && !hasOwn.call(o, t)) {
1802
+ Object.defineProperty(o, t, {
1803
+ value: l,
1597
1804
  writable: true,
1598
1805
  enumerable: true,
1599
1806
  configurable: true
1600
1807
  });
1601
- } else f[t] = o;
1808
+ } else o[t] = l;
1602
1809
  if (e.del !== null) e.del.delete(t);
1603
1810
  // Shallow ingest: written records are sticky raw-marked (one entity is
1604
1811
  // never both deep-wrapped and raw — R41/#2932, shared invariant).
1605
- if (e.s && o !== null && typeof o === "object") markRawOne(o);
1812
+ if (e.s && l !== null && typeof l === "object") markRawOne(l);
1606
1813
  // Override-mode (post-await draft) writes have no setter exit — notify
1607
1814
  // per-op (setSignal equality-gates repeats).
1608
1815
  if (i) notifyWrites(e);
@@ -1619,10 +1826,14 @@ const traps = {
1619
1826
  ...n,
1620
1827
  value: unwrapValue(n.value)
1621
1828
  };
1622
- const o = ensurePB(e);
1829
+ const l = ensurePB(e);
1830
+ // A non-default data descriptor (or an accessor) leaves the plain-data
1831
+ // grade: the key reaches the committed backing as defined, so the spread
1832
+ // clone and bare-assignment paths no longer describe it.
1833
+ if (e.a || !(n.enumerable && n.writable && n.configurable)) e.sc = 1;
1623
1834
  pendingNotify.add(e);
1624
1835
  if (e.wk !== WK_ALL) (e.wk ??= new Set).add(t);
1625
- Object.defineProperty(o, t, n);
1836
+ Object.defineProperty(l, t, n);
1626
1837
  if (e.del !== null) e.del.delete(t);
1627
1838
  if (i) notifyWrites(e);
1628
1839
  return true;
@@ -1645,20 +1856,22 @@ const traps = {
1645
1856
 
1646
1857
  /** Low-level setter primitive: opens write mode on a next proxy, runs `fn`,
1647
1858
  * emits write-time notifications at outermost exit, applies returned
1648
- * replacements as adoptions. `guard=false` skips the owned-scope dev guard —
1649
- * projection recomputes legitimately write from inside their computed. */ function storeSetterNext(e, t, n = true) {
1859
+ * replacements as adoptions. `guard=false` skips the dev guards (owned-scope
1860
+ * write, thenable result) — projection recomputes legitimately write from
1861
+ * inside their computed, and their async derive is handled by the recompute,
1862
+ * not returned through here. */ function storeSetterNext(e, t, n = true) {
1650
1863
  const r = e[$TARGET];
1651
1864
  const i = writeScopes;
1652
1865
  writeScopes = new Set;
1653
1866
  writeScopes.add(scopeKey(r));
1654
1867
  writing++;
1655
- let o;
1868
+ let l;
1656
1869
  try {
1657
1870
  // No untrack: the writing flag already disables store-node linking
1658
1871
  // (draft reads never self-track, proj R2), while EXTERNAL reads (signals
1659
1872
  // inside a projection derive) must keep tracking — they are the derive's
1660
1873
  // dependencies.
1661
- o = t(e);
1874
+ l = t(e);
1662
1875
  } finally {
1663
1876
  writing--;
1664
1877
  writeScopes = i;
@@ -1670,17 +1883,18 @@ const traps = {
1670
1883
  for (const t of e) notifyWrites(t);
1671
1884
  }
1672
1885
  }
1673
- if (o !== undefined && o !== e && isWrappable(o)) {
1886
+ if (l !== undefined && l !== e && isWrappable(l)) {
1674
1887
  // Returned replacement: on an optimistic family (outside authoritative
1675
1888
  // writes) the replacement is itself an optimistic edit — diff it against
1676
1889
  // the visible view as engine writes (reverts at settle). Otherwise it is
1677
1890
  // an adoption of the incoming object (unowned).
1678
1891
  if (r.fam?.opt && !projectionWriteActive && !getWriteOverride()) {
1679
- optHooks.notifyOptimisticWrites(r, unwrapValue(o));
1892
+ optHooks.notifyOptimisticWrites(r, unwrapValue(l));
1680
1893
  } else {
1681
- adoptPB(r, unwrapValue(o));
1894
+ adoptPB(r, unwrapValue(l));
1682
1895
  }
1683
1896
  }
1897
+ if (writing === 0 && heldAdoptions.size) stageHeldAdoptions();
1684
1898
  }
1685
1899
 
1686
1900
  // Affects integration: the legacy affects machinery reads next targets
@@ -1732,6 +1946,79 @@ function createStoreNext(e, t = false) {
1732
1946
  return t !== undefined && t.fam?.opt === true;
1733
1947
  }
1734
1948
 
1949
+ // ---------------------------------------------------------------------------
1950
+ // visibility: what a reader sees on a record — ONE rule for the ownKeys /
1951
+ // getOwnPropertyDescriptor traps and the deep() walk (#3323). The walk used
1952
+ // to re-derive the trap rules over raw backings and missed each new one in
1953
+ // turn (the #3044 overlay merge, then #3323's chain, then optimistic presence
1954
+ // and value overrides); sharing the body makes parity structural.
1955
+ /** Keys visible on `target` served from `src` (= readSource(target)): the
1956
+ * pending-overlay merge (#3044) and, on optimistic families, presence-node
1957
+ * overrides (§6, FINDING-2's fix). Draft reads before the first write overlay
1958
+ * too (pb, once created, is seeded with the view). Authoritative-view reads
1959
+ * (until()'s predicate, truth-author drafts) skip the overlay. */ function visibleKeys(e, t) {
1960
+ let n;
1961
+ if (e.ovl && t === e.pb) {
1962
+ // Overlay merge (#3044): committed keys in their order, then this
1963
+ // batch's NEW keys, minus deletes.
1964
+ n = Reflect.ownKeys(e.v);
1965
+ const r = e.del;
1966
+ if (r !== null && r.size !== 0) n = n.filter(e => !r.has(e));
1967
+ for (const r of Reflect.ownKeys(t)) {
1968
+ if (!hasOwn.call(e.v, r)) n.push(r);
1969
+ }
1970
+ } else n = Reflect.ownKeys(t);
1971
+ // Drop the $OWNER stamp (owned backings carry it as an own enumerable
1972
+ // symbol). Symbols enumerate last, so the scan stops at the first string.
1973
+ for (let e = n.length - 1; e >= 0 && typeof n[e] === "symbol"; e--) {
1974
+ if (n[e] === $OWNER) {
1975
+ n.splice(e, 1);
1976
+ break;
1977
+ }
1978
+ }
1979
+ if (!authoritativeServe() && e.fam?.opt && e.h !== null && (!inDraft(e) || draftSeesOverrides(e))) {
1980
+ let t = null;
1981
+ const r = inDraft(e);
1982
+ for (const i of Reflect.ownKeys(e.h)) {
1983
+ const l = e.h[i];
1984
+ if (!(r ? hasActiveOverride(l) : visibleOverride(l))) continue;
1985
+ t ??= new Set(n);
1986
+ if (unwrapOverride(l.o?.Ce)) t.add(i); else t.delete(i);
1987
+ }
1988
+ if (t !== null) return [ ...t ];
1989
+ }
1990
+ return n;
1991
+ }
1992
+
1993
+ /** The data/accessor descriptor visible for `key` on `target` served from
1994
+ * `src`: overlay (#3044 — unwritten keys live on the committed backing,
1995
+ * deleted keys are absent) and optimistic presence overrides (an opt delete
1996
+ * hides the key; an opt add synthesizes a data descriptor from the value
1997
+ * node). `configurable` is the trap's concern (proxy invariant). */ function visibleDescriptor(e, t, n) {
1998
+ let r = Object.getOwnPropertyDescriptor(t, n);
1999
+ if (e.ovl && t === e.pb) {
2000
+ if (e.del !== null && e.del.has(n)) return undefined;
2001
+ if (r === undefined) r = Object.getOwnPropertyDescriptor(e.v, n);
2002
+ }
2003
+ if (!authoritativeServe() && e.fam?.opt && !inDraft(e)) {
2004
+ const t = e.h?.[n];
2005
+ if (t !== undefined && visibleOverride(t)) {
2006
+ if (!unwrapOverride(t.o?.Ce)) return undefined;
2007
+ // opt delete
2008
+ if (r === undefined) {
2009
+ const t = e.n?.[n];
2010
+ return {
2011
+ value: t !== undefined ? nodeValue(t, undefined) : undefined,
2012
+ writable: true,
2013
+ enumerable: true,
2014
+ configurable: true
2015
+ };
2016
+ }
2017
+ }
2018
+ }
2019
+ return r;
2020
+ }
2021
+
1735
2022
  /** Tracking deep snapshot (`deep()` for next targets): subscribes to the
1736
2023
  * key-set and deep-witness node at every reachable level, then returns the
1737
2024
  * plain view. Shared references and cycles handled via the visited set. */ function deepNext(e) {
@@ -1743,50 +2030,69 @@ function createStoreNext(e, t = false) {
1743
2030
  // in per-key nodes, and it walks TARGETS directly — no per-child proxy
1744
2031
  // round-trip (wrapNext → proxy → $TARGET trap) on the re-walk every
1745
2032
  // effect run performs.
1746
- const walkT = e => {
2033
+ // Resolve `child` (a raw or a stored proxy found under `t`) to the target
2034
+ // that `t`'s family serves for it — created on first visit, as the get trap
2035
+ // would. Undefined = leaf (unwrappable or raw-marked).
2036
+ const childTarget = (e, t, n) => {
2037
+ let r = lookupTarget(t, e.fam);
2038
+ if (r === undefined) {
2039
+ if (!isWrappable(t)) return undefined;
2040
+ wrapNext(t, e, n);
2041
+ // Stored proxies (chained slots) that this family passes through
2042
+ // resolve to their own target.
2043
+ r = lookupTarget(t, e.fam) ?? t[$TARGET];
2044
+ }
2045
+ return r;
2046
+ };
2047
+ const walkT = e => {
1747
2048
  const t = readSource(e);
1748
2049
  if (n.has(t)) return;
1749
2050
  n.add(t);
1750
2051
  read(getKeySetNode(e));
1751
2052
  read(getDeepNode(e));
1752
- const r = e.fam?.map ?? storeNextLookup;
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;
2053
+ // Chained backing (§7b): the committed backing IS another store's proxy.
2054
+ // Writes to the inner store bump the INNER record's witnesses; this
2055
+ // target's k/dk hear only its own family's writes (optimistic overrides).
2056
+ // Read through the whole chain — the $TRACK trap's rule (#2864 / R21)
2057
+ // applied to deep() (#3323). From `t.v`, not `src`: a pending backing is a
2058
+ // raw clone and would hide the chain. Non-chained targets pay one flag.
2059
+ for (let t = e; t.ch; ) {
2060
+ t = t.v[$TARGET];
2061
+ read(getKeySetNode(t));
2062
+ read(getDeepNode(t));
1768
2063
  }
1769
- for (const n of i) {
1770
- const i = Object.getOwnPropertyDescriptor(t, n) ?? Object.getOwnPropertyDescriptor(e.v, n);
2064
+ // Keys and children exactly as the traps serve them — the overlay merge
2065
+ // (#3044/#3283: a bare ownKeys over a pending backing mid-flush dropped
2066
+ // every untouched child from the re-subscribing effect's dependencies)
2067
+ // and optimistic presence overrides (a row added under a held action
2068
+ // lives in `h`/`n`, not the committed backing; the walk never reached its
2069
+ // record, so deep() was deaf to every write on it until settle).
2070
+ const r = e.fam?.opt === true;
2071
+ for (const n of visibleKeys(e, t)) {
2072
+ const i = visibleDescriptor(e, t, n);
1771
2073
  if (i === undefined) continue;
1772
2074
  if (i.get || i.set) {
1773
2075
  e.a = true;
1774
2076
  continue;
1775
2077
  // accessors track through their own reads when invoked
1776
2078
  }
1777
- const o = i.value;
1778
- if (o === null || typeof o !== "object") continue;
1779
- // Stored proxies (chained slots) resolve through their own target;
1780
- // raw children through the family map, created on first visit.
1781
- let f = o[$TARGET] ?? r.get(o);
1782
- if (f === undefined) {
1783
- if (!isWrappable(o)) continue;
1784
- wrapNext(o, e, n);
1785
- f = r.get(o);
1786
- if (f === undefined) continue;
2079
+ let l = i.value;
2080
+ // An active value override on an optimistic node shadows the backing's
2081
+ // child (an optimistic replacement `d[i] = {...}`) follow what reads
2082
+ // serve, as nodeValue's leading arm does.
2083
+ if (r) {
2084
+ const t = e.n?.[n];
2085
+ if (t !== undefined && hasActiveOverride(t) && !authoritativeServe()) l = unwrapOverride(t.o.Ce);
2086
+ }
2087
+ if (l === null || typeof l !== "object") continue;
2088
+ // Through a chain the descriptor yields the INNERMOST raw: resolve it to
2089
+ // the inner family's proxy so this level wraps the chained `view[i]` the
2090
+ // get trap serves, never a fresh non-chained wrapper of the base raw.
2091
+ if (e.ch && l[$TARGET] === undefined) l = resolveChainedRaw(e, n, l);
2092
+ const o = childTarget(e, l, n);
2093
+ if (o === undefined) continue;
1787
2094
  // raw-marked: leaf by contract
1788
- }
1789
- walkT(f);
2095
+ walkT(o);
1790
2096
  }
1791
2097
  };
1792
2098
  walkT(t);
@@ -1816,24 +2122,35 @@ function snapshotWalk(e, t, n) {
1816
2122
  // encountered and compose their views over the resolved base, innermost
1817
2123
  // outward.
1818
2124
  let i = null;
1819
- for (;;) {
1820
- let e = r?.[$TARGET]?.v !== undefined ? r[$TARGET] : undefined;
1821
- if (e === undefined && n !== null) e = n.map.get(r);
1822
- if (e === undefined) e = storeNextLookup.get(r);
1823
- if (e === undefined) break;
1824
- if (e.fam !== null) n = e.fam;
1825
- if (e.fam?.opt === true) (i ??= []).push(e);
2125
+ for (let e = true; ;e = false) {
2126
+ const t = r?.[$TARGET]?.v !== undefined;
2127
+ let l = t ? r[$TARGET] : undefined;
2128
+ if (l === undefined && n !== null) l = lookupTarget(r, n);
2129
+ if (l === undefined) l = lookupTarget(r, null);
2130
+ if (l === undefined) break;
2131
+ // Entering a level from a RAW below a chained family: the raw resolves to
2132
+ // the INNER store's target, but this family's wrapper for it — keyed by
2133
+ // the inner proxy, §7b — is where the outer overrides live. Start from
2134
+ // the wrapper so they compose (#3323: a nested optimistic write on a view
2135
+ // row was served by reads but missing from deep()/snapshot()). Entry only:
2136
+ // the descent then runs wrapper → inner → raw and terminates.
2137
+ if (e && !t && n !== null && l.fam !== n) {
2138
+ const e = n.map.get(l.px);
2139
+ if (e !== undefined) l = e;
2140
+ }
2141
+ if (l.fam !== null) n = l.fam;
2142
+ if (l.fam?.opt === true) (i ??= []).push(l);
1826
2143
  // The shared visibility decision (#3147): the speculative peek serves
1827
2144
  // pending staging, but a HELD landing is masked to committed exactly as
1828
2145
  // it is for per-key readers — the two families must answer alike while
1829
2146
  // a transaction holds store landings.
1830
- const t = pendingBackingVisible(e, true);
2147
+ const o = pendingBackingVisible(l, true);
1831
2148
  // Snapshot runs mid-flush (tracked memos execute before commit), so a
1832
2149
  // pending prototype overlay must present as a REAL merged container.
1833
- if (t && e.ovl) materializePB(e);
1834
- const o = t ? e.pb : e.v;
1835
- if (o === r) break;
1836
- r = o;
2150
+ if (o && l.ovl) materializePB(l);
2151
+ const f = o ? l.pb : l.v;
2152
+ if (f === r) break;
2153
+ r = f;
1837
2154
  }
1838
2155
  if (!isWrappable(r)) return r;
1839
2156
  // Optimistic families: compose the visible view; a composed view is a fresh
@@ -1844,40 +2161,40 @@ function snapshotWalk(e, t, n) {
1844
2161
  if (e !== r) {
1845
2162
  const i = t.get(r);
1846
2163
  if (i !== undefined) return i;
1847
- const o = Array.isArray(e);
1848
- const f = o ? [] : Object.create(Object.getPrototypeOf(e));
1849
- t.set(r, f);
2164
+ const l = Array.isArray(e);
2165
+ const o = l ? [] : Object.create(Object.getPrototypeOf(e));
2166
+ t.set(r, o);
1850
2167
  for (const r of Reflect.ownKeys(e)) {
1851
- if (o && r === "length") continue;
2168
+ if (l && r === "length" || r === $OWNER) continue;
1852
2169
  const i = e[r];
1853
- f[r] = i !== null && typeof i === "object" ? snapshotWalk(i, t, n) : i;
2170
+ o[r] = i !== null && typeof i === "object" ? snapshotWalk(i, t, n) : i;
1854
2171
  }
1855
- if (o) f.length = e.length;
1856
- return f;
2172
+ if (l) o.length = e.length;
2173
+ return o;
1857
2174
  }
1858
2175
  }
1859
- const o = t.get(r);
1860
- if (o !== undefined) return o;
2176
+ const l = t.get(r);
2177
+ if (l !== undefined) return l;
1861
2178
  // OWNED (written) subtrees snapshot as copies (§7b: identity is only for
1862
2179
  // subtrees "unmodified relative to source"): non-enumerable symbols are
1863
2180
  // excluded (recon-snap R29), and the copy registers BEFORE descent so
1864
2181
  // cycles keep identity (FINDING-3).
1865
- if (ownedRaw.has(r)) {
2182
+ if (isOwned(r)) {
1866
2183
  const e = Array.isArray(r);
1867
2184
  const i = e ? [] : Object.create(Object.getPrototypeOf(r));
1868
2185
  t.set(r, i);
1869
- for (const o of Reflect.ownKeys(r)) {
1870
- if (e && o === "length") continue;
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);
2186
+ for (const l of Reflect.ownKeys(r)) {
2187
+ if (e && l === "length" || l === $OWNER) continue;
2188
+ const o = Object.getOwnPropertyDescriptor(r, l);
2189
+ if (typeof l === "symbol" && !o.enumerable) continue;
2190
+ if (o.get || o.set) {
2191
+ Object.defineProperty(i, l, o);
1875
2192
  continue;
1876
2193
  }
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,
2194
+ const f = o.value;
2195
+ const s = f !== null && typeof f === "object" ? snapshotWalk(f, t, n) : f;
2196
+ if (o.enumerable && o.writable && o.configurable) i[l] = s; else Object.defineProperty(i, l, {
2197
+ ...o,
1881
2198
  value: s
1882
2199
  });
1883
2200
  }
@@ -1887,22 +2204,22 @@ function snapshotWalk(e, t, n) {
1887
2204
  // UNOWNED (shared/user) subtrees keep identity unless a descendant
1888
2205
  // substituted; copy-on-substitution preserves the documented CoW contract.
1889
2206
  t.set(r, r);
1890
- let f = null;
2207
+ let o = null;
1891
2208
  for (const e of Reflect.ownKeys(r)) {
1892
2209
  const i = Object.getOwnPropertyDescriptor(r, e);
1893
2210
  if (!i || i.get || i.set) continue;
1894
- const o = i.value;
1895
- if (o === null || typeof o !== "object") continue;
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);
2211
+ const l = i.value;
2212
+ if (l === null || typeof l !== "object") continue;
2213
+ const f = snapshotWalk(l, t, n);
2214
+ if (f !== l) {
2215
+ if (o === null) {
2216
+ o = Array.isArray(r) ? [ ...r ] : Object.create(Object.getPrototypeOf(r), Object.getOwnPropertyDescriptors(r));
2217
+ t.set(r, o);
1901
2218
  }
1902
- f[e] = l;
2219
+ o[e] = f;
1903
2220
  }
1904
2221
  }
1905
- return f ?? r;
2222
+ return o ?? r;
1906
2223
  }
1907
2224
 
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 };
2225
+ export { adoptPB, arrayStructureChanged, authoritativeRead, authoritativeServe, bumpDeep, createStoreNext, deepNext, getHasNode, getKeySetNode, getNode, hasAccessorFlag, hasActiveOverride, heldMaskView, materializePB, membershipChanged, notifyFold, notifyFoldTail, notifyKeyDiff, notifyKeyValue, runAuthoritative, snapshotNext, stagedTruthPB, storeHasFamily, storeHasOptimisticFamily, storeIsShallow, storeSetterNext, targetsEqual, unwrapValue, visibleOverride, wrapNext };