@solidjs/signals 2.0.0-rc.8 → 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 (90) hide show
  1. package/dist/dev-shared.js +1399 -285
  2. package/dist/dev.attribution.js +459 -307
  3. package/dist/dev.js +1864 -435
  4. package/dist/observe/affects.js +3 -1
  5. package/dist/observe/attribution.js +7 -1
  6. package/dist/observe/boundaries.js +209 -154
  7. package/dist/observe/core/action.js +18 -8
  8. package/dist/observe/core/async.js +220 -110
  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 +23 -1
  12. package/dist/observe/core/attribution-queries.js +28 -0
  13. package/dist/observe/core/attribution.js +262 -485
  14. package/dist/observe/core/constants.js +34 -1
  15. package/dist/observe/core/context.js +3 -3
  16. package/dist/observe/core/core.js +867 -367
  17. package/dist/observe/core/dev.js +78 -17
  18. package/dist/observe/core/effect.js +67 -51
  19. package/dist/observe/core/error-hooks.js +71 -0
  20. package/dist/observe/core/external.js +4 -4
  21. package/dist/observe/core/graph.js +37 -37
  22. package/dist/observe/core/heap.js +45 -45
  23. package/dist/observe/core/invariants.js +2 -0
  24. package/dist/observe/core/lanes.js +86 -49
  25. package/dist/observe/core/optimistic.js +242 -95
  26. package/dist/observe/core/owner.js +98 -84
  27. package/dist/observe/core/scheduler.js +543 -305
  28. package/dist/observe/core/verdict.js +247 -129
  29. package/dist/observe/index.js +7 -3
  30. package/dist/observe/map.js +126 -124
  31. package/dist/observe/signals.js +33 -17
  32. package/dist/observe/store/index.js +2 -0
  33. package/dist/observe/store/next/optimistic.js +141 -132
  34. package/dist/observe/store/next/projection.js +34 -21
  35. package/dist/observe/store/next/reconcile.js +58 -56
  36. package/dist/observe/store/next/store.js +260 -146
  37. package/dist/observe/store/store.js +5 -3
  38. package/dist/observe/store/utils.js +948 -135
  39. package/dist/prod/attribution.js +26 -17
  40. package/dist/prod/boundaries.js +128 -76
  41. package/dist/prod/core/action.js +16 -8
  42. package/dist/prod/core/async.js +251 -143
  43. package/dist/prod/core/constants.js +34 -1
  44. package/dist/prod/core/context.js +3 -3
  45. package/dist/prod/core/core.js +843 -347
  46. package/dist/prod/core/dev.js +17 -1
  47. package/dist/prod/core/effect.js +48 -34
  48. package/dist/prod/core/error-hooks.js +71 -0
  49. package/dist/prod/core/external.js +4 -4
  50. package/dist/prod/core/graph.js +37 -37
  51. package/dist/prod/core/heap.js +45 -45
  52. package/dist/prod/core/lanes.js +90 -53
  53. package/dist/prod/core/optimistic.js +247 -100
  54. package/dist/prod/core/owner.js +57 -45
  55. package/dist/prod/core/scheduler.js +543 -305
  56. package/dist/prod/core/verdict.js +245 -127
  57. package/dist/prod/index.js +7 -3
  58. package/dist/prod/map.js +112 -112
  59. package/dist/prod/signals.js +28 -12
  60. package/dist/prod/store/next/optimistic.js +135 -128
  61. package/dist/prod/store/next/projection.js +31 -20
  62. package/dist/prod/store/next/store.js +325 -252
  63. package/dist/prod/store/store.js +2 -2
  64. package/dist/prod/store/utils.js +946 -135
  65. package/dist/types/attribution.d.ts +7 -2
  66. package/dist/types/attribution.prod.d.ts +10 -1
  67. package/dist/types/boundaries.d.ts +10 -1
  68. package/dist/types/core/action.d.ts +12 -5
  69. package/dist/types/core/attribution-costs.d.ts +35 -0
  70. package/dist/types/core/attribution-feedback.d.ts +133 -0
  71. package/dist/types/core/attribution-hooks.d.ts +28 -3
  72. package/dist/types/core/attribution-queries.d.ts +10 -0
  73. package/dist/types/core/attribution.d.ts +51 -172
  74. package/dist/types/core/constants.d.ts +33 -0
  75. package/dist/types/core/core.d.ts +186 -5
  76. package/dist/types/core/dev.d.ts +129 -9
  77. package/dist/types/core/error-hooks.d.ts +71 -0
  78. package/dist/types/core/index.d.ts +3 -1
  79. package/dist/types/core/invariants.d.ts +4 -0
  80. package/dist/types/core/lanes.d.ts +31 -4
  81. package/dist/types/core/scheduler.d.ts +95 -2
  82. package/dist/types/core/types.d.ts +3 -0
  83. package/dist/types/index.d.ts +2 -2
  84. package/dist/types/signals.d.ts +8 -0
  85. package/dist/types/store/index.d.ts +2 -1
  86. package/dist/types/store/next/optimistic.d.ts +1 -1
  87. package/dist/types/store/next/store.d.ts +6 -5
  88. package/dist/types/store/next/target.d.ts +1 -1
  89. package/dist/types/store/utils.d.ts +177 -6
  90. package/package.json +1 -1
@@ -1,10 +1,10 @@
1
1
  import { attrHooks } from "../../core/attribution-hooks.js";
2
2
 
3
- import { unwrapOverride, $REFRESH, NOT_PENDING, CONFIG_OWNED_WRITE, CONFIG_OPTIMISTIC, STATUS_UNINITIALIZED, STATUS_ERROR, CONFIG_HELD_TRUTH, CONFIG_CHILDREN_FORBIDDEN, CONFIG_AUTHORITATIVE_READ } from "../../core/constants.js";
3
+ import { unwrapOverride, $REFRESH, CONFIG_CHILDREN_FORBIDDEN, CONFIG_OWNED_WRITE, NOT_PENDING, CONFIG_OPTIMISTIC, STATUS_UNINITIALIZED, STATUS_ERROR, CONFIG_AUTHORITATIVE_READ } from "../../core/constants.js";
4
4
 
5
- import { setSlotUnobserved, setSignal, isEqual, read, pendingCheckActive, latestReadActive, readNodeFast, READ_SLOW, signal, unlinkFirewallChild, ext, setLatestReadActive, prepareComputed, slotSignal, context } from "../../core/core.js";
5
+ 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";
6
6
 
7
- import { projectionWriteActive, setProjectionWriteActive, activeTransition, schedule, currentTransition, setStoreCommitHook } from "../../core/scheduler.js";
7
+ import { projectionWriteActive, setProjectionWriteActive, activeTransition, schedule, currentTransition, deferSlotRelease, setStoreCommitHook } from "../../core/scheduler.js";
8
8
 
9
9
  import "../../core/invariants.js";
10
10
 
@@ -157,7 +157,7 @@ function wrapNext(e, t = null, n = null, r = t?.fam ?? null) {
157
157
  // (privatization/adoption swap raw identity without changing the logical
158
158
  // value — only changed leaves notify, R9).
159
159
  const slotNodeEquals = function(e, t) {
160
- return isEqual(e, t) || sameLogicalSlot(this.rn, e, t);
160
+ return isEqual(e, t) || sameLogicalSlot(this.dn, e, t);
161
161
  };
162
162
 
163
163
  // Shared slot-node unobserved handler (create-floor diet): registered once;
@@ -166,8 +166,13 @@ const slotNodeEquals = function(e, t) {
166
166
  setSlotUnobserved(e => {
167
167
  // A live affects() mark keeps the node addressable (sweep parity).
168
168
  if (e.o?.t) return;
169
- const t = e.rn;
170
- const n = e._n;
169
+ // An active override or a staged write is state only the node holds (an
170
+ // optimistic signal keeps its override whether or not anything reads it —
171
+ // store parity, posture-store-parity S7): defer the release to the flush
172
+ // that resolves it (the scheduler's transient-node sweep).
173
+ if (hasActiveOverride(e) || e._e !== NOT_PENDING) return deferSlotRelease(e);
174
+ const t = e.dn;
175
+ const n = e.En;
171
176
  if (t.n && t.n[n] === e) {
172
177
  delete t.n[n];
173
178
  t.nc--;
@@ -195,11 +200,15 @@ r = -1) {
195
200
  // (stageHeldKey). Its held-adoption notification (stageHeldAdoptions)
196
201
  // ran before the node existed and the drain has nothing left to say.
197
202
  // Without this the node was born from whichever view its first reader
198
- // saw and never learned the other. (The held-FOLD twin a setter's
199
- // write to an unobserved key landing only in the pending backing — is
200
- // #3336's, and lands with #3337.)
201
- const l = heldAdoptionTransition(e);
202
- if (l !== null) n = e.hv[t];
203
+ // saw and never learned the other. Two kinds of hold, one rule (#3336):
204
+ // - a held FOLD (pb): a setter's write to an unobserved key landed only
205
+ // in the pending backing; committed is `v[key]`, staged is `pb[key]`
206
+ // (undefined for a deleted key);
207
+ // - a held ADOPTION (ht): the adopted value already swapped into `v`;
208
+ // committed is the held view `hv[key]`, staged is `v[key]`.
209
+ const l = heldFoldTransition(e);
210
+ let f = heldAdoptionTransition(e);
211
+ if (f !== null) n = e.hv[t]; else if ((f = l) !== null) n = e.v[t];
203
212
  // Create-floor diet: slotSignal bakes the whole node into one literal —
204
213
  // no options object, no equals/unobserved closures, no NodeExtension,
205
214
  // no post-construction expandos (acc + the wrap cache px/pxv are
@@ -210,7 +219,7 @@ r = -1) {
210
219
  // notification machinery. Projection nodes carry the projection
211
220
  // computed as their firewall: reads through them link the derive's
212
221
  // status/lifecycle (§7b).
213
- const f = o = slotSignal(n, slotNodeEquals, e, t,
222
+ const s = o = slotSignal(n, slotNodeEquals, e, t,
214
223
  // Accessor-ness resolved ONCE per node (no per-object descriptor
215
224
  // scan on reads): accessor keys serve through Reflect.get with the
216
225
  // proxy receiver.
@@ -220,17 +229,20 @@ r = -1) {
220
229
  // "signal". Gated on the engine being installed — node creation is
221
230
  // the hottest store path, and the disabled cost must stay one null
222
231
  // check (nodes created before enable() stay generically named).
223
- if (attrHooks !== null) f._name = "store." + String(t);
232
+ if (attrHooks !== null) {
233
+ s._name = "store." + String(t);
234
+ stampNodeOwner(s, e);
235
+ }
224
236
  // Optimistic families: arm the override slot — setSignal routes armed
225
237
  // nodes through the core engine (lanes, ownership, reverts all native).
226
238
  if (e.fam?.opt) {
227
- ext(f).De = NOT_PENDING;
228
- f.T |= CONFIG_OPTIMISTIC;
239
+ ext(s).Ne = NOT_PENDING;
240
+ s.C |= CONFIG_OPTIMISTIC;
229
241
  }
230
242
  // A node born inside a live mark's identity scope inherits the mark
231
243
  // (the declaration walk could only cover nodes existing then).
232
- if (t !== $AFFECTS && affectsScopesLive()) inheritAffectsMarks(f, e.v, t);
233
- if (l !== null) stageHeldKey(f, e.v[t], l);
244
+ if (t !== $AFFECTS && affectsScopesLive()) inheritAffectsMarks(s, e.v, t);
245
+ if (f !== null) stageHeldKey(s, l !== null ? e.del !== null && e.del.has(t) ? undefined : e.pb[t] : e.v[t], f);
234
246
  i[t] = o;
235
247
  e.nc++;
236
248
  markDescendants(e);
@@ -242,7 +254,7 @@ r = -1) {
242
254
  * a latest()-pull PLAIN_HOLD is not a transaction), else null. */ function heldAdoptionTransition(e) {
243
255
  if (e.ht === null || e.ht === PLAIN_HOLD || heldMaskView(e) === null) return null;
244
256
  const t = currentTransition(e.ht);
245
- return t.Ut === false ? t : null;
257
+ return t.yt === false ? t : null;
246
258
  }
247
259
 
248
260
  /**
@@ -258,11 +270,54 @@ r = -1) {
258
270
  * setSignal: it is not a new write (it staged with the adoption's batch) and
259
271
  * it walks no subscriber (the node has none yet). Transition-stamped now, as
260
272
  * `runFolded` does — no parked-flush pass will stamp it.
261
- */ function stageHeldKey(e, t, n) {
262
- if (slotNodeEquals.call(e, e.ge, t)) return;
263
- e.Pe = t;
264
- e.Ee = n;
265
- n.tn.push(e);
273
+ */
274
+ /** The live transaction holding `target`'s pending backing (the #3089
275
+ * write-time stamp, resolved through merges), else null. */ function liveFoldTransition(e) {
276
+ if (e.pb === null) return null;
277
+ const t = foldBatches.get(e);
278
+ if (t === undefined) return null;
279
+ const n = currentTransition(t);
280
+ return n.yt === false ? n : null;
281
+ }
282
+
283
+ /** liveFoldTransition for node materialization (stageHeldKey). Inside the
284
+ * draft the backing is the setter's working copy, not a flushed hold — its
285
+ * nodes take their writes at setter exit (notifyWrites). Optimistic families
286
+ * hold at the backing: tentative writes are node overrides over a discarded
287
+ * clone, and a truth-staged landing is masked from ordinary readers by
288
+ * heldTruthMasked — neither is a plain staged write to mirror. Chained
289
+ * backings serve the inner store's live value, never a node value. */ function heldFoldTransition(e) {
290
+ if (e.ch || e.fam?.opt === true || inDraft(e)) return null;
291
+ return liveFoldTransition(e);
292
+ }
293
+
294
+ /**
295
+ * Rule 1 at the backing, the hold half (core serve()'s stale-of-foreign and
296
+ * A29 arms, for a container instead of a node): given the transaction
297
+ * holding what a reader `c` (a pass; callers serve context-free readers the
298
+ * committed container themselves) is about to be served, is the STAGED
299
+ * container its to see? Its own hold, or none — yes. A foreign hold — a
300
+ * stale pass (render effect) keeps the committed frame and is recorded for
301
+ * replay at the hold's commit (recordStaleReplay, A15 / A26); a deriving
302
+ * pass takes the staged world and enters the transaction (enterStagedRead,
303
+ * A29). Shared by both hold kinds: a setter's fold (committed `v`, staged
304
+ * `pb`, the write-time stamp) and an adoption under a transaction
305
+ * (committed = the held view `hv`, staged = the adopted `v`, #3074).
306
+ */ function holdVisible(e, t) {
307
+ if (e === null || ownsHold(e)) return true;
308
+ if (stale) {
309
+ recordStaleReplay(e, t);
310
+ return false;
311
+ }
312
+ enterStagedRead(null, e);
313
+ return true;
314
+ }
315
+
316
+ function stageHeldKey(e, t, n) {
317
+ if (slotNodeEquals.call(e, e.Ae, t)) return;
318
+ e._e = t;
319
+ e.Te = n;
320
+ n.sn.push(e);
266
321
  }
267
322
 
268
323
  function sameLogicalSlot(e, t, n) {
@@ -271,6 +326,29 @@ function sameLogicalSlot(e, t, n) {
271
326
  return r !== undefined && r === lookupTarget(n, e.fam);
272
327
  }
273
328
 
329
+ /**
330
+ * Observe-tier: the owner each store root was created under. The proxy
331
+ * cannot carry `_owner` itself (`registerGraph`'s stamp is swallowed by the
332
+ * set trap outside a draft), so the root target keys it here and the store's
333
+ * nodes copy it into `_owner` as they are created — an `OBSERVE.exclude`d
334
+ * panel's store nodes are then excluded subjects, exactly like its signals.
335
+ * Both ends are gated with the naming on the engine being installed: node
336
+ * creation is the hottest store path, store creation is next, and the
337
+ * disabled cost of each stays one null check. A store created before
338
+ * `enable()` therefore has no recorded owner and its nodes are never excluded
339
+ * subjects — the same boundary the naming draws; a panel enables first.
340
+ */ const storeOwners = new WeakMap;
341
+
342
+ function storeRootOwner(e) {
343
+ let t = e;
344
+ while (t.u !== null) t = t.u;
345
+ return storeOwners.get(t);
346
+ }
347
+
348
+ function stampNodeOwner(e, t) {
349
+ e.nt = storeRootOwner(t) ?? null;
350
+ }
351
+
274
352
  function getHasNode(e, t, n) {
275
353
  const r = e.h ??= Object.create(null);
276
354
  let i = r[t];
@@ -279,16 +357,23 @@ function getHasNode(e, t, n) {
279
357
  equals: isEqual,
280
358
  unobserved() {
281
359
  if (o.o?.t) return;
360
+ // The structural twin of the value slot's rule (setSlotUnobserved,
361
+ // S7): an optimistic add/delete lives on this presence node as its
362
+ // override — releasing it with the override on would let `in`,
363
+ // `Object.keys` and descriptors fall back to committed structure
364
+ // while the action is live. Defer to the flush that resolves it.
365
+ if (hasActiveOverride(o) || o._e !== NOT_PENDING) return deferSlotRelease(o);
282
366
  if (e.h && e.h[t] === o) {
283
367
  delete e.h[t];
284
368
  unlinkFirewallChild(o);
285
369
  }
286
370
  }
287
371
  }, e.fam?.node ?? undefined);
288
- o.T |= CONFIG_OWNED_WRITE;
372
+ o.C |= CONFIG_OWNED_WRITE;
373
+ if (attrHooks !== null) stampNodeOwner(o, e);
289
374
  if (e.fam?.opt) {
290
- ext(o).De = NOT_PENDING;
291
- o.T |= CONFIG_OPTIMISTIC;
375
+ ext(o).Ne = NOT_PENDING;
376
+ o.C |= CONFIG_OPTIMISTIC;
292
377
  }
293
378
  if (affectsScopesLive()) inheritAffectsMarks(o, e.v, t);
294
379
  r[t] = i;
@@ -309,10 +394,11 @@ function getKeySetNode(e) {
309
394
  }
310
395
  }
311
396
  }, e.fam?.node ?? undefined);
312
- n.T |= CONFIG_OWNED_WRITE;
397
+ n.C |= CONFIG_OWNED_WRITE;
398
+ if (attrHooks !== null) stampNodeOwner(n, e);
313
399
  if (e.fam?.opt) {
314
- ext(n).De = NOT_PENDING;
315
- n.T |= CONFIG_OPTIMISTIC;
400
+ ext(n).Ne = NOT_PENDING;
401
+ n.C |= CONFIG_OPTIMISTIC;
316
402
  }
317
403
  e.k = t;
318
404
  markDescendants(e);
@@ -332,10 +418,11 @@ function getDeepNode(e) {
332
418
  }
333
419
  }
334
420
  }, e.fam?.node ?? undefined);
335
- n.T |= CONFIG_OWNED_WRITE;
421
+ n.C |= CONFIG_OWNED_WRITE;
422
+ if (attrHooks !== null) stampNodeOwner(n, e);
336
423
  if (e.fam?.opt) {
337
- ext(n).De = NOT_PENDING;
338
- n.T |= CONFIG_OPTIMISTIC;
424
+ ext(n).Ne = NOT_PENDING;
425
+ n.C |= CONFIG_OPTIMISTIC;
339
426
  }
340
427
  if (affectsScopesLive()) inheritAffectsMarks(n, e.v, $TRACK);
341
428
  e.dk = t;
@@ -496,14 +583,14 @@ function ensurePB(e) {
496
583
  if (n !== null) {
497
584
  for (const e of Reflect.ownKeys(n)) {
498
585
  const r = n[e];
499
- if (hasActiveOverride(r)) t[e] = unwrapOverride(r.o?.De);
586
+ if (hasActiveOverride(r)) t[e] = unwrapOverride(r.o?.Ne);
500
587
  }
501
588
  }
502
589
  const r = e.h;
503
590
  if (r !== null) {
504
591
  for (const e of Reflect.ownKeys(r)) {
505
592
  const n = r[e];
506
- if (hasActiveOverride(n) && !unwrapOverride(n.o?.De)) delete t[e];
593
+ if (hasActiveOverride(n) && !unwrapOverride(n.o?.Ne)) delete t[e];
507
594
  }
508
595
  }
509
596
  }
@@ -527,7 +614,7 @@ function ensurePB(e) {
527
614
  * foldHeld's node stamps). */ function heldMaskView(e) {
528
615
  const t = e.ht;
529
616
  if (t === null) return null;
530
- if (t !== PLAIN_HOLD && currentTransition(t)?.Ut === true) return e.ht = e.hv = null;
617
+ if (t !== PLAIN_HOLD && currentTransition(t)?.yt === true) return e.ht = e.hv = null;
531
618
  return e.hv;
532
619
  }
533
620
 
@@ -731,7 +818,7 @@ function drainFolds() {
731
818
  // keys; observed keys also hit the pending-node held check below).
732
819
  const e = foldBatches.get(t);
733
820
  if (e !== undefined) {
734
- if (currentTransition(e).Ut === false) {
821
+ if (currentTransition(e).yt === false) {
735
822
  foldOlds.set(t, n);
736
823
  continue;
737
824
  }
@@ -755,7 +842,7 @@ function drainFolds() {
755
842
  !plainProto(t.ovl ? t.v : i) ? Reflect.ownKeys(o) : e;
756
843
  for (const e of n) {
757
844
  const t = o[e];
758
- if (t !== undefined && t.Pe !== NOT_PENDING) {
845
+ if (t !== undefined && t._e !== NOT_PENDING) {
759
846
  r = true;
760
847
  break;
761
848
  }
@@ -857,31 +944,32 @@ function drainFolds() {
857
944
  */ function reportReplacedContainers(e, t, n, r) {
858
945
  const i = r ?? Reflect.ownKeys(n);
859
946
  const o = Array.isArray(n);
947
+ const l = storeRootOwner(e);
860
948
  for (const r of i) {
861
949
  if (o && r === "length" || r === $OWNER) continue;
862
950
  if (e.del !== null && e.del.has(r)) continue;
863
951
  const i = unwrapValue(t[r]);
864
- const l = unwrapValue(n[r]);
865
- if (i === null || l === null || typeof i !== "object" || typeof l !== "object" || i === l || targetsEqual(i, l)) continue;
952
+ const f = unwrapValue(n[r]);
953
+ if (i === null || f === null || typeof i !== "object" || typeof f !== "object" || i === f || targetsEqual(i, f)) continue;
866
954
  // Leaf census on the store side: leaves read through a draft are proxies
867
955
  // of the committed raws, so identity must be judged on unwrapped values.
868
- const f = Array.isArray(l);
869
- if (f !== Array.isArray(i)) continue;
870
- let s;
871
- let u = 0;
872
- if (f) {
873
- s = l.length;
874
- if (s > REPLACED_CENSUS_MAX) continue;
956
+ const s = Array.isArray(f);
957
+ if (s !== Array.isArray(i)) continue;
958
+ let u;
959
+ let a = 0;
960
+ if (s) {
961
+ u = f.length;
962
+ if (u > REPLACED_CENSUS_MAX) continue;
875
963
  const e = new Set;
876
964
  for (const t of i) e.add(unwrapValue(t));
877
- for (const t of l) if (e.has(unwrapValue(t))) u++;
965
+ for (const t of f) if (e.has(unwrapValue(t))) a++;
878
966
  } else {
879
- const e = Object.keys(l);
880
- s = e.length;
881
- if (s > REPLACED_CENSUS_MAX) continue;
882
- for (const t of e) if (sameLeaf(i[t], l[t])) u++;
967
+ const e = Object.keys(f);
968
+ u = e.length;
969
+ if (u > REPLACED_CENSUS_MAX) continue;
970
+ for (const t of e) if (sameLeaf(i[t], f[t])) a++;
883
971
  }
884
- attrHooks.storeReplaced(storePath(e) + "." + String(r), f, s, u, f ? i.length : Object.keys(i).length);
972
+ attrHooks.storeReplaced(storePath(e) + "." + String(r), s, u, a, s ? i.length : Object.keys(i).length, l);
885
973
  }
886
974
  }
887
975
 
@@ -1260,22 +1348,12 @@ const UNSAFE_KEYS = new Set([ "__proto__", "prototype", "constructor" ]);
1260
1348
  * observer) decides pending visibility, with Roots resolving to their parent
1261
1349
  * computed (#2687 — untracked reads inside mapArray Roots see in-flight
1262
1350
  * values mid-flush). CHILDREN_FORBIDDEN execution scopes (createTrackedEffect
1263
- * / onSettled callbacks) get COMMITTED visibility (#3006), same as core. */ function inOwnerContext() {
1351
+ * / onSettled callbacks) get COMMITTED visibility (#3006), same as core. */
1352
+ /** Core read()'s reader: the current computation, a root reading as its
1353
+ * parent computed (`context` persists under untrack — an untracked read
1354
+ * inside an effect is still that effect's read). */ function readerContext() {
1264
1355
  const e = getOwner();
1265
- if (e === null) return false;
1266
- const t = e.$t ? e.ln : e;
1267
- return t != null && !(t.T & CONFIG_CHILDREN_FORBIDDEN);
1268
- }
1269
-
1270
- /** CHILDREN_FORBIDDEN execution scope (createTrackedEffect / onSettled
1271
- * callbacks). Distinct from context-free: these scopes get committed
1272
- * visibility even against a projection's authoritative-elect pending
1273
- * backing (#3082) — parity with signals, where core read() serves
1274
- * committed to them regardless of staged writes. */ function inForbiddenScope() {
1275
- const e = getOwner();
1276
- if (e === null) return false;
1277
- const t = e.$t ? e.ln : e;
1278
- return t != null && !!(t.T & CONFIG_CHILDREN_FORBIDDEN);
1356
+ return e === null ? null : e.cn ? e.fn ?? null : e;
1279
1357
  }
1280
1358
 
1281
1359
  /** A pending fold is transition-held when any written node's parked value is
@@ -1285,19 +1363,24 @@ const UNSAFE_KEYS = new Set([ "__proto__", "prototype", "constructor" ]);
1285
1363
  if (t === null) return false;
1286
1364
  for (const e of Reflect.ownKeys(t)) {
1287
1365
  const n = t[e];
1288
- if (n.Pe !== NOT_PENDING && n.Ee != null && n.Ee.Ut !== true) return true;
1366
+ if (n._e !== NOT_PENDING && n.Te != null && n.Te.yt !== true) return true;
1289
1367
  }
1290
1368
  return false;
1291
1369
  }
1292
1370
 
1293
1371
  function readSource(e) {
1294
- // Held view first (#3074): an adoption staged under a live hold serves the
1295
- // pre-hold committed backing to committed-visibility readers. Speculative
1296
- // readers — drafts, write-override, owner-context computeds recomputing
1297
- // inside the transaction, and latest() reads see the adopted backing.
1298
- if (e.ht !== null && !latestReadActive && !inDraft(e) && !getWriteOverride() && !inOwnerContext()) {
1299
- const t = heldMaskView(e);
1300
- if (t !== null) return t;
1372
+ // Adoption hold first (#3074): an adoption staged under a live transaction
1373
+ // (or a latest()-pull, PLAIN_HOLD) serves the pre-hold committed view to
1374
+ // committed-visibility readers — context-free and children-forbidden ones,
1375
+ // and stale passes off a foreign hold. Drafts, write-override and latest()
1376
+ // see the adopted backing.
1377
+ const t = e.ht;
1378
+ if (t !== null && !latestReadActive && !inDraft(e) && !getWriteOverride()) {
1379
+ const n = heldMaskView(e);
1380
+ if (n !== null) {
1381
+ const e = readerContext();
1382
+ if (e === null || e.C & CONFIG_CHILDREN_FORBIDDEN || !holdVisible(t === PLAIN_HOLD ? null : currentTransition(t), e)) return n;
1383
+ }
1301
1384
  }
1302
1385
  return pendingBackingVisible(e, false) ? e.pb : e.v;
1303
1386
  }
@@ -1317,21 +1400,28 @@ function readSource(e) {
1317
1400
  * ordinary pending staging regardless of owner context (the documented
1318
1401
  * divergence from context-free per-key reads) — but never through a hold:
1319
1402
  * held truth stays masked exactly as it is for per-key readers. */ function pendingBackingVisible(e, t) {
1320
- return e.pb !== null && (inDraft(e) || getWriteOverride() ||
1321
- // Owner-context (and speculative-peek) readers see the pending
1322
- // backing EXCEPT held truth on an optimistic family (#3164 fold):
1323
- // a live pb on an opt family outside the draft/write-override windows
1324
- // is a staged landing (tentative drafts never outlive their setter),
1325
- // and only the authoritative postures and latest() see it (the
1326
- // backing-level twin of core read()'s A17-for-held-truth arm;
1327
- // ordinary readers keep committed until the transaction's reveal).
1328
- (t || inOwnerContext()) && !heldTruthMasked(e) ||
1329
- // A projection's pending backing is authoritative-elect: serve it to
1330
- // context-free readers too UNLESS a transition is holding the node
1331
- // commits (downstream async hold stale committed is the contract)
1332
- // or the reader is a CHILDREN_FORBIDDEN scope, which never observes
1333
- // its own unsettled write (#3082, signal parity per #3006).
1334
- e.fam !== null && !heldTruthMasked(e) && !foldHeld(e) && !inForbiddenScope());
1403
+ if (e.pb === null) return false;
1404
+ // The writer's own channels compose on the pending backing regardless.
1405
+ if (inDraft(e) || getWriteOverride()) return true;
1406
+ // HELD truth on an optimistic family (#3164 fold) is masked from ordinary
1407
+ // readers until the transaction's reveal (the backing-level twin of core
1408
+ // serve()'s CONFIG_HELD_TRUTH arm; authoritative postures and latest()
1409
+ // tunnel through inside heldTruthMasked).
1410
+ if (heldTruthMasked(e)) return false;
1411
+ const n = readerContext();
1412
+ if (n === null || n.C & CONFIG_CHILDREN_FORBIDDEN) {
1413
+ // No pass, or a children-forbidden one: the committed frame (A32) —
1414
+ // except the speculative peek (deep()/snapshot()), which sees ordinary
1415
+ // pending staging but never through a live foreign hold, and a
1416
+ // projection's pending backing, authoritative-elect for context-free
1417
+ // readers UNLESS a transition holds the node commits (downstream async
1418
+ // hold — stale committed is the contract; the write-time stamp covers
1419
+ // keys with no node, #3336) or the scope is children-forbidden (#3082).
1420
+ const r = liveFoldTransition(e);
1421
+ if (t) return r === null || ownsHold(r);
1422
+ return e.fam !== null && n === null && !foldHeld(e) && r === null;
1423
+ }
1424
+ return holdVisible(liveFoldTransition(e), n);
1335
1425
  }
1336
1426
 
1337
1427
  /** #3164 fold: HELD truth on an optimistic family — a pending backing
@@ -1376,18 +1466,13 @@ function isOwnAccessor(e, t) {
1376
1466
  }
1377
1467
  }
1378
1468
 
1379
- /** Active optimistic override on an armed node (armed slot idles at
1380
- * NOT_PENDING; undefined = unarmed plain node). */ function hasActiveOverride(e) {
1381
- return e.o?.De !== undefined && e.o?.De !== NOT_PENDING;
1382
- }
1383
-
1384
1469
  /** The reading computation is until()'s authoritative-view predicate — same
1385
1470
  * source of truth as core read()'s A17 carve-out (`context`, which persists
1386
1471
  * under untrack). optimisticView()'s composition gate consults exactly this:
1387
1472
  * write-side machinery (patch emission, tentative re-application) must keep
1388
1473
  * composing even when it runs inside an authoritative-write bracket. */ function authoritativeRead() {
1389
1474
  const e = context;
1390
- return e !== null && (e.T & CONFIG_AUTHORITATIVE_READ) !== 0;
1475
+ return e !== null && (e.C & CONFIG_AUTHORITATIVE_READ) !== 0;
1391
1476
  }
1392
1477
 
1393
1478
  /** Serve-side authoritative gate: until()'s predicate PLUS truth authors —
@@ -1408,19 +1493,17 @@ function isOwnAccessor(e, t) {
1408
1493
  * reader that forced it (backing commits eagerly; node values fold at flush).
1409
1494
  * FORCE sentinels never surface (they only bump subscribers of accessor
1410
1495
  * keys, which are served by the trap, not the node). */ function nodeValue(e, t) {
1411
- // latest() sees the in-flight parked value like an owner-context reader
1412
- // does (#3075) signal/memo parity for store-node-backed keys.
1413
- // Authoritative-view reads (until()'s predicate) skip the override arm
1414
- // only: staged pending values are authoritative, overrides are the
1415
- // caller's optimism.
1416
- const n = !authoritativeServe() && hasActiveOverride(e) ? unwrapOverride(e.o?.De) : e.Pe !== NOT_PENDING && (latestReadActive ||
1417
- // Owner-context pending visibility except HELD truth (#3164,
1418
- // see CONFIG_HELD_TRUTH: fold-staged or entangle-stolen
1419
- // confirming truth), which only authoritative/latest readers
1420
- // see (core read()'s A17-for-held-truth twin; ordinary readers
1421
- // keep committed until the transaction's reveal latest() is
1422
- // exempted by the leading arm above).
1423
- (inOwnerContext() || authoritativeServe()) && !(e.T & CONFIG_HELD_TRUTH && !authoritativeServe())) ? e.Pe : t;
1496
+ // Store-only tunnels, ahead of Rule 1: truth authors (authoritativeServe
1497
+ // the projection derive's draft, the write-override continuation) see
1498
+ // staged truth and never an override; latest() reaching this untracked
1499
+ // path for a store key sees the in-flight parked value like an
1500
+ // owner-context reader does (#3075), the visible override first.
1501
+ let n;
1502
+ if (authoritativeServe()) n = e._e !== NOT_PENDING ? e._e : t; else if (latestReadActive) n = visibleOverride(e) ? unwrapOverride(e.o?.Ne) : e._e !== NOT_PENDING ? e._e : t;
1503
+ // Otherwise the one slow selection core read() uses (serve): override,
1504
+ // lane gate, A28, readerSeesCommitted / A29 — with the BACKING as the
1505
+ // committed value (single-home rule, O6).
1506
+ else n = serve(e, readerContext(), e.ne || e, t);
1424
1507
  return n === FORCE ? t : n;
1425
1508
  }
1426
1509
 
@@ -1471,7 +1554,7 @@ function isOwnAccessor(e, t) {
1471
1554
  // Truth authors read the backing's own length — an optimistic row from
1472
1555
  // the caller's transaction must not shift where the author's next write
1473
1556
  // lands (#3108).
1474
- return (authoritativeServe() ? r : optHooks.optimisticView(e, r)).length;
1557
+ return (authoritativeServe() ? r : optHooks.optimisticView(e, r, inDraft(e))).length;
1475
1558
  }
1476
1559
  if (inDraft(e)) {
1477
1560
  // Optimistic drafts before their first write have no pending backing yet;
@@ -1481,26 +1564,26 @@ function isOwnAccessor(e, t) {
1481
1564
  // seeding rule, applied to the read side (#3108).
1482
1565
  if (e.fam?.opt && draftSeesOverrides(e) && !authoritativeServe()) {
1483
1566
  const n = e.n?.[t];
1484
- if (n !== undefined && hasActiveOverride(n)) f = unwrapOverride(n.o?.De);
1567
+ if (n !== undefined && hasActiveOverride(n)) f = unwrapOverride(n.o?.Ne);
1485
1568
  }
1486
1569
  } else {
1487
- if (i !== undefined) {
1488
- // §7b: a lane value on the outer node SHADOWS read-through an active
1489
- // override pierces the chained gate; otherwise chained backings always
1490
- // serve the live inner value.
1491
- if (getObserver() !== null) {
1492
- // read()'s plain-signal fast path hoisted over the call (legacy trap
1493
- // parity): READ_SLOW = a global read window or non-plain node.
1494
- let e = readNodeFast(i);
1495
- if (e === READ_SLOW) e = read(i);
1496
- if (!l || hasActiveOverride(i)) f = e === FORCE ? n : e;
1497
- } else if (!l || hasActiveOverride(i)) {
1498
- f = nodeValue(i, n);
1499
- }
1500
- } else if (getObserver() !== null) {
1501
- // First tracked read: create + link, and let the wrap-cache branch
1502
- // below populate px/pxv so read #2 skips wrapNext (slice 2).
1503
- read(i = getNode(e, t, n, o));
1570
+ // §7b: a lane value on the outer node SHADOWS read-through — an active
1571
+ // override pierces the chained gate; otherwise chained backings always
1572
+ // serve the live inner value.
1573
+ if (getObserver() !== null) {
1574
+ // First tracked read: create + link (the wrap-cache branch below
1575
+ // populates px/pxv so read #2 skips wrapNext, slice 2). The value is
1576
+ // served THROUGH the node from this read on a node born under a held
1577
+ // fold carries the hold (getNode, #3336), and the backing it was read
1578
+ // from does not.
1579
+ if (i === undefined) i = getNode(e, t, n, o);
1580
+ // read()'s plain-signal fast path hoisted over the call (legacy trap
1581
+ // parity): READ_SLOW = a global read window or non-plain node.
1582
+ let r = readNodeFast(i);
1583
+ if (r === READ_SLOW) r = read(i);
1584
+ if (!l || hasActiveOverride(i)) f = r === FORCE ? n : r;
1585
+ } else if (i !== undefined && (!l || hasActiveOverride(i))) {
1586
+ f = nodeValue(i, n);
1504
1587
  }
1505
1588
  }
1506
1589
  // Shallow stores serve data raw; store-proxy slots get boundary wrappers.
@@ -1685,7 +1768,7 @@ const traps = {
1685
1768
  // the caller's optimism is not truth (has-trap twin below).
1686
1769
  !authoritativeServe()) {
1687
1770
  const n = e.n?.[t];
1688
- if (n !== undefined && hasActiveOverride(n)) f = unwrapOverride(n.o?.De);
1771
+ if (n !== undefined && visibleOverride(n)) f = unwrapOverride(n.o?.Ne);
1689
1772
  }
1690
1773
  if (e.s) return serveShallow(e, t, f);
1691
1774
  return isWrappable(f) ? draftServe(e, wrapNext(f, e, t)) : f;
@@ -1712,11 +1795,11 @@ const traps = {
1712
1795
  if (hasActiveOverride(n)) r = !!i;
1713
1796
  } else if (!authoritativeServe()) {
1714
1797
  const n = e.h?.[t];
1715
- if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.De);
1798
+ if (n !== undefined && visibleOverride(n)) r = !!unwrapOverride(n.o?.Ne);
1716
1799
  }
1717
1800
  } else if (e.fam?.opt && draftSeesOverrides(e) && !authoritativeServe()) {
1718
1801
  const n = e.h?.[t];
1719
- if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.De);
1802
+ if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.Ne);
1720
1803
  }
1721
1804
  return r;
1722
1805
  },
@@ -1728,13 +1811,30 @@ const traps = {
1728
1811
  },
1729
1812
  getOwnPropertyDescriptor(e, t) {
1730
1813
  if (t === $OWNER) return undefined;
1731
- const n = visibleDescriptor(e, readSource(e), t);
1732
- if (n === undefined) return undefined;
1814
+ // A descriptor read is a PRESENCE read: it subscribes to the key's
1815
+ // presence node and witnesses affects()/isPending() exactly as `in` does
1816
+ // (structural oracle, 2026-09-17 — the trap read no node before, so a
1817
+ // render effect inspecting a key through getOwnPropertyDescriptor never
1818
+ // re-ran for an optimistic add or delete, and an isPending() probe over
1819
+ // it witnessed nothing). The value it reports rides the value node's
1820
+ // view through visibleDescriptor.
1821
+ if (pendingCheckActive) witnessAffectsMark(e, t);
1822
+ if (e.fam !== null && getObserver() === null && !inDraft(e)) firewallGate(e);
1823
+ const n = readSource(e);
1824
+ const r = visibleDescriptor(e, n, t);
1825
+ if (!inDraft(e) && getObserver() !== null) {
1826
+ // The node is born from the source's presence (as `has` births it),
1827
+ // not the override-adjusted answer.
1828
+ let r = t in n;
1829
+ if (r && e.del !== null && n === e.pb && e.del.has(t)) r = false;
1830
+ read(getHasNode(e, t, r));
1831
+ }
1832
+ if (r === undefined) return undefined;
1733
1833
  // Array targets carry a real non-configurable `length` the proxy
1734
1834
  // invariant forces us to report faithfully; everything else reports
1735
1835
  // configurable via target indirection (core R51).
1736
- if (!(t === "length" && Array.isArray(e))) n.configurable = true;
1737
- return n;
1836
+ if (!(t === "length" && Array.isArray(e))) r.configurable = true;
1837
+ return r;
1738
1838
  },
1739
1839
  set(e, t, n) {
1740
1840
  // Writes require the target's draft scope OR the projection write
@@ -1846,8 +1946,10 @@ const traps = {
1846
1946
 
1847
1947
  /** Low-level setter primitive: opens write mode on a next proxy, runs `fn`,
1848
1948
  * emits write-time notifications at outermost exit, applies returned
1849
- * replacements as adoptions. `guard=false` skips the owned-scope dev guard —
1850
- * projection recomputes legitimately write from inside their computed. */ function storeSetterNext(e, t, n = true) {
1949
+ * replacements as adoptions. `guard=false` skips the dev guards (owned-scope
1950
+ * write, thenable result) — projection recomputes legitimately write from
1951
+ * inside their computed, and their async derive is handled by the recompute,
1952
+ * not returned through here. */ function storeSetterNext(e, t, n = true) {
1851
1953
  const r = e[$TARGET];
1852
1954
  const i = writeScopes;
1853
1955
  writeScopes = new Set;
@@ -1895,7 +1997,18 @@ function createStoreNext(e, t = false) {
1895
1997
  n[$TARGET].s = true;
1896
1998
  markRawIngest(e);
1897
1999
  }
1898
- registerGraph(n, getOwner());
2000
+ {
2001
+ const e = getOwner();
2002
+ // Dev-tier graph registration (owner signal lists, onGraph); the
2003
+ // `_owner` write itself never reaches the proxy, see storeOwners.
2004
+ registerGraph(n, e);
2005
+ // Only once the engine is installed, like the node stamping it feeds: a
2006
+ // WeakMap.set per fresh store is a growing ephemeron table (~+35% on the
2007
+ // 2000-store create+commit shape, CodSpeed −11.7% on #3380's first cut)
2008
+ // and, disabled, buys nothing — a store created before enable() has no
2009
+ // excluded owner to inherit either way.
2010
+ if (attrHooks !== null) storeOwners.set(n[$TARGET], e);
2011
+ }
1899
2012
  const setter = e => storeSetterNext(n, e);
1900
2013
  return [ n, setter ];
1901
2014
  }
@@ -1967,11 +2080,12 @@ function createStoreNext(e, t = false) {
1967
2080
  }
1968
2081
  if (!authoritativeServe() && e.fam?.opt && e.h !== null && (!inDraft(e) || draftSeesOverrides(e))) {
1969
2082
  let t = null;
1970
- for (const r of Reflect.ownKeys(e.h)) {
1971
- const i = e.h[r];
1972
- if (!hasActiveOverride(i)) continue;
2083
+ const r = inDraft(e);
2084
+ for (const i of Reflect.ownKeys(e.h)) {
2085
+ const o = e.h[i];
2086
+ if (!(r ? hasActiveOverride(o) : visibleOverride(o))) continue;
1973
2087
  t ??= new Set(n);
1974
- if (unwrapOverride(i.o?.De)) t.add(r); else t.delete(r);
2088
+ if (unwrapOverride(o.o?.Ne)) t.add(i); else t.delete(i);
1975
2089
  }
1976
2090
  if (t !== null) return [ ...t ];
1977
2091
  }
@@ -1990,8 +2104,8 @@ function createStoreNext(e, t = false) {
1990
2104
  }
1991
2105
  if (!authoritativeServe() && e.fam?.opt && !inDraft(e)) {
1992
2106
  const t = e.h?.[n];
1993
- if (t !== undefined && hasActiveOverride(t)) {
1994
- if (!unwrapOverride(t.o?.De)) return undefined;
2107
+ if (t !== undefined && visibleOverride(t)) {
2108
+ if (!unwrapOverride(t.o?.Ne)) return undefined;
1995
2109
  // opt delete
1996
2110
  if (r === undefined) {
1997
2111
  const t = e.n?.[n];
@@ -2070,7 +2184,7 @@ function createStoreNext(e, t = false) {
2070
2184
  // serve, as nodeValue's leading arm does.
2071
2185
  if (r) {
2072
2186
  const t = e.n?.[n];
2073
- if (t !== undefined && hasActiveOverride(t) && !authoritativeServe()) o = unwrapOverride(t.o.De);
2187
+ if (t !== undefined && hasActiveOverride(t) && !authoritativeServe()) o = unwrapOverride(t.o.Ne);
2074
2188
  }
2075
2189
  if (o === null || typeof o !== "object") continue;
2076
2190
  // Through a chain the descriptor yields the INNERMOST raw: resolve it to
@@ -2210,4 +2324,4 @@ function snapshotWalk(e, t, n) {
2210
2324
  return l ?? r;
2211
2325
  }
2212
2326
 
2213
- 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, wrapNext };
2327
+ 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 };