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

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 (57) hide show
  1. package/dist/dev.js +1618 -232
  2. package/dist/node.cjs +2262 -1199
  3. package/dist/prod/boundaries.js +4 -1
  4. package/dist/prod/core/async.js +152 -101
  5. package/dist/prod/core/constants.js +55 -1
  6. package/dist/prod/core/core.js +305 -234
  7. package/dist/prod/core/effect.js +28 -28
  8. package/dist/prod/core/error.js +13 -1
  9. package/dist/prod/core/external.js +2 -2
  10. package/dist/prod/core/graph.js +27 -27
  11. package/dist/prod/core/heap.js +30 -30
  12. package/dist/prod/core/lanes.js +32 -32
  13. package/dist/prod/core/optimistic.js +57 -57
  14. package/dist/prod/core/owner.js +34 -34
  15. package/dist/prod/core/scheduler.js +346 -152
  16. package/dist/prod/core/verdict.js +122 -65
  17. package/dist/prod/index.js +3 -3
  18. package/dist/prod/map.js +106 -106
  19. package/dist/prod/signals.js +321 -26
  20. package/dist/prod/store/next/optimistic.js +363 -151
  21. package/dist/prod/store/next/patch.js +6 -6
  22. package/dist/prod/store/next/projection.js +25 -21
  23. package/dist/prod/store/next/store.js +223 -113
  24. package/dist/prod/store/store.js +2 -2
  25. package/dist/types/core/async.d.ts +2 -0
  26. package/dist/types/core/attribution-hooks.d.ts +11 -0
  27. package/dist/types/core/attribution.d.ts +57 -4
  28. package/dist/types/core/constants.d.ts +54 -0
  29. package/dist/types/core/core.d.ts +19 -20
  30. package/dist/types/core/dev.d.ts +8 -2
  31. package/dist/types/core/error.d.ts +9 -0
  32. package/dist/types/core/index.d.ts +2 -2
  33. package/dist/types/core/scheduler.d.ts +39 -0
  34. package/dist/types/core/types.d.ts +12 -0
  35. package/dist/types/index.d.ts +3 -3
  36. package/dist/types/signals.d.ts +108 -11
  37. package/dist/types/store/next/optimistic.d.ts +13 -10
  38. package/dist/types/store/next/projection.d.ts +1 -1
  39. package/dist/types/store/next/store.d.ts +22 -0
  40. package/dist/types/store/next/target.d.ts +25 -0
  41. package/dist/types-cjs/core/async.d.cts +2 -0
  42. package/dist/types-cjs/core/attribution-hooks.d.cts +11 -0
  43. package/dist/types-cjs/core/attribution.d.cts +57 -4
  44. package/dist/types-cjs/core/constants.d.cts +54 -0
  45. package/dist/types-cjs/core/core.d.cts +19 -20
  46. package/dist/types-cjs/core/dev.d.cts +8 -2
  47. package/dist/types-cjs/core/error.d.cts +9 -0
  48. package/dist/types-cjs/core/index.d.cts +2 -2
  49. package/dist/types-cjs/core/scheduler.d.cts +39 -0
  50. package/dist/types-cjs/core/types.d.cts +12 -0
  51. package/dist/types-cjs/index.d.cts +3 -3
  52. package/dist/types-cjs/signals.d.cts +108 -11
  53. package/dist/types-cjs/store/next/optimistic.d.cts +13 -10
  54. package/dist/types-cjs/store/next/projection.d.cts +1 -1
  55. package/dist/types-cjs/store/next/store.d.cts +22 -0
  56. package/dist/types-cjs/store/next/target.d.cts +25 -0
  57. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
- import { unwrapOverride, $REFRESH, CONFIG_OWNED_WRITE, NOT_PENDING, CONFIG_OPTIMISTIC, STATUS_UNINITIALIZED, STATUS_ERROR, CONFIG_CHILDREN_FORBIDDEN } from "../../core/constants.js";
1
+ import { unwrapOverride, $REFRESH, NOT_PENDING, CONFIG_OWNED_WRITE, CONFIG_OPTIMISTIC, CONFIG_HELD_TRUTH, STATUS_UNINITIALIZED, STATUS_ERROR, CONFIG_CHILDREN_FORBIDDEN, CONFIG_AUTHORITATIVE_READ } from "../../core/constants.js";
2
2
 
3
- import { setSignal, isEqual, read, pendingCheckActive, latestReadActive, readNodeFast, READ_SLOW, signal, ext, setLatestReadActive, prepareComputed } from "../../core/core.js";
3
+ import { setSignal, isEqual, read, pendingCheckActive, latestReadActive, readNodeFast, READ_SLOW, signal, ext, setLatestReadActive, prepareComputed, context } from "../../core/core.js";
4
4
 
5
5
  import { projectionWriteActive, setProjectionWriteActive, activeTransition, schedule, currentTransition, setStoreCommitHook } from "../../core/scheduler.js";
6
6
 
@@ -193,7 +193,7 @@ function getNode(e, t, n) {
193
193
  // Optimistic families: arm the override slot — setSignal routes armed
194
194
  // nodes through the core engine (lanes, ownership, reverts all native).
195
195
  if (e.fam?.opt) {
196
- ext(o).De = NOT_PENDING;
196
+ ext(o).Pe = NOT_PENDING;
197
197
  o.T |= CONFIG_OPTIMISTIC;
198
198
  }
199
199
  // A node born inside a live mark's identity scope inherits the mark
@@ -226,7 +226,7 @@ function getHasNode(e, t, n) {
226
226
  }, e.fam?.node ?? undefined);
227
227
  o.T |= CONFIG_OWNED_WRITE;
228
228
  if (e.fam?.opt) {
229
- ext(o).De = NOT_PENDING;
229
+ ext(o).Pe = NOT_PENDING;
230
230
  o.T |= CONFIG_OPTIMISTIC;
231
231
  }
232
232
  if (affectsScopesLive()) inheritAffectsMarks(o, e.v, t);
@@ -247,7 +247,7 @@ function getKeySetNode(e) {
247
247
  }, e.fam?.node ?? undefined);
248
248
  n.T |= CONFIG_OWNED_WRITE;
249
249
  if (e.fam?.opt) {
250
- ext(n).De = NOT_PENDING;
250
+ ext(n).Pe = NOT_PENDING;
251
251
  n.T |= CONFIG_OPTIMISTIC;
252
252
  }
253
253
  e.k = t;
@@ -267,7 +267,7 @@ function getDeepNode(e) {
267
267
  }, e.fam?.node ?? undefined);
268
268
  n.T |= CONFIG_OWNED_WRITE;
269
269
  if (e.fam?.opt) {
270
- ext(n).De = NOT_PENDING;
270
+ ext(n).Pe = NOT_PENDING;
271
271
  n.T |= CONFIG_OPTIMISTIC;
272
272
  }
273
273
  if (affectsScopesLive()) inheritAffectsMarks(n, e.v, $TRACK);
@@ -350,8 +350,22 @@ function cloneRaw(e, t) {
350
350
  }
351
351
 
352
352
  function ensurePB(e) {
353
- if (activeTransition !== null) foldBatches.set(e, activeTransition);
354
353
  let t = e.pb;
354
+ // Truth-staged backing hand-off (#3164 fold): a TENTATIVE draft opening on
355
+ // a target whose pending backing is truth-staged (a landing folded into a
356
+ // retaining transaction — it carries a foldBatches stamp) must not share
357
+ // the container. Tentative writes would pollute staged truth, and the
358
+ // tentative discard (notifyOptimisticWrites nulls pb) would destroy the
359
+ // landing. Park the staged backing and open a fresh draft seeded from the
360
+ // optimistic view below; the tentative discard restores it. The
361
+ // tentativePBs guard scopes this to draft OPEN: the draft's own backing
362
+ // (foldBatches-stamped by its first write when an action's transition is
363
+ // ambient) must not be parked by its own later writes.
364
+ if (t !== null && !tentativePBs.has(t) && e.fam?.opt === true && !projectionWriteActive && !getWriteOverride() && foldBatches.has(e)) {
365
+ stagedTruthPB.set(e, t);
366
+ t = e.pb = null;
367
+ }
368
+ if (activeTransition !== null) foldBatches.set(e, activeTransition);
355
369
  if (t === null) {
356
370
  // Prototype-chain overlay (#3044): plain-data non-array containers
357
371
  // outside projection/optimistic families open drafts in O(1) — own keys
@@ -369,18 +383,19 @@ function ensurePB(e) {
369
383
  // seed from committed truth — seeding overrides there would fold a lane
370
384
  // value into the committed home ("authority wins at reveal" would break).
371
385
  if (e.fam?.opt && !projectionWriteActive && !getWriteOverride()) {
386
+ tentativePBs.add(t);
372
387
  const n = e.n;
373
388
  if (n !== null) {
374
389
  for (const e of Reflect.ownKeys(n)) {
375
390
  const r = n[e];
376
- if (hasActiveOverride(r)) t[e] = unwrapOverride(r.o?.De);
391
+ if (hasActiveOverride(r)) t[e] = unwrapOverride(r.o?.Pe);
377
392
  }
378
393
  }
379
394
  const r = e.h;
380
395
  if (r !== null) {
381
396
  for (const e of Reflect.ownKeys(r)) {
382
397
  const n = r[e];
383
- if (hasActiveOverride(n) && !unwrapOverride(n.o?.De)) delete t[e];
398
+ if (hasActiveOverride(n) && !unwrapOverride(n.o?.Pe)) delete t[e];
384
399
  }
385
400
  }
386
401
  }
@@ -406,7 +421,7 @@ function ensurePB(e) {
406
421
  * foldHeld's node stamps). */ function heldMaskView(e) {
407
422
  const t = e.ht;
408
423
  if (t === null) return null;
409
- if (t !== PLAIN_HOLD && currentTransition(t)?.fn === true) return e.ht = e.hv = null;
424
+ if (t !== PLAIN_HOLD && currentTransition(t)?.sn === true) return e.ht = e.hv = null;
410
425
  return e.hv;
411
426
  }
412
427
 
@@ -491,6 +506,17 @@ function queueFold(e) {
491
506
  * Refreshed on every write; resolved through currentTransition at drain
492
507
  * (transitions merge — same rule as heldMaskView). */ const foldBatches = new WeakMap;
493
508
 
509
+ /** Parked truth-staged pending backings (#3164 fold): a tentative draft that
510
+ * opens while a folded landing's backing is live moves the staged container
511
+ * here (see ensurePB); the tentative discard in notifyOptimisticWrites
512
+ * restores it in place of the usual null. */ const stagedTruthPB = new WeakMap;
513
+
514
+ /** Backings opened by TENTATIVE drafts (optimistic user setters): ensurePB's
515
+ * truth-park must not fire against the draft's own container on its second
516
+ * and later writes (the first write stamps foldBatches whenever an action's
517
+ * transition is ambient). Entries die with their draft — tentative backings
518
+ * are consumed at setter exit. */ const tentativePBs = new WeakSet;
519
+
494
520
  /** Committed-time privatization for parent-chain slot updates (path copying). */ function privatizeCommitted(e) {
495
521
  if (ownedRaw.has(e.v)) return;
496
522
  const t = cloneRaw(e.v, e);
@@ -525,7 +551,7 @@ function drainFolds() {
525
551
  // keys; observed keys also hit the pending-node held check below).
526
552
  const e = foldBatches.get(t);
527
553
  if (e !== undefined) {
528
- if (currentTransition(e).fn === false) {
554
+ if (currentTransition(e).sn === false) {
529
555
  foldOlds.set(t, n);
530
556
  continue;
531
557
  }
@@ -549,7 +575,7 @@ function drainFolds() {
549
575
  !plainProto(t.ovl ? t.v : i) ? Reflect.ownKeys(o) : e;
550
576
  for (const e of n) {
551
577
  const t = o[e];
552
- if (t !== undefined && t.Pe !== NOT_PENDING) {
578
+ if (t !== undefined && t.Re !== NOT_PENDING) {
553
579
  r = true;
554
580
  break;
555
581
  }
@@ -723,15 +749,15 @@ function drainFolds() {
723
749
  // projection recompute can run before the prior fold commits) — the
724
750
  // node's OWN current value is the true old side, and setSignal's
725
751
  // internal equality already checks exactly that.
726
- const f = e.del !== null && e.del.has(o) ? undefined : t[o];
727
- setSignal(i, () => f);
752
+ const l = e.del !== null && e.del.has(o) ? undefined : t[o];
753
+ setSignal(i, () => l);
728
754
  }
729
755
  }
730
- const f = e.h;
731
- if (f !== null) {
732
- const n = o ?? Reflect.ownKeys(f);
756
+ const l = e.h;
757
+ if (l !== null) {
758
+ const n = o ?? Reflect.ownKeys(l);
733
759
  for (const r of n) {
734
- const n = f[r];
760
+ const n = l[r];
735
761
  if (n !== undefined) setSignal(n, r in t && !(e.del !== null && e.del.has(r)));
736
762
  }
737
763
  }
@@ -781,7 +807,13 @@ function drainFolds() {
781
807
  // IMMEDIATE — landed truth shows to untracked readers even while a
782
808
  // downstream consumer's own async still holds the effect-level reveal
783
809
  // (spec-async "verdicts never inherit consumers' in-flight state").
784
- if (e.fam !== null && e.pb !== null && getWriteOverride()) {
810
+ // EXCEPT under an active transaction (#3164 fold): a landing riding a
811
+ // retaining transaction (the optimistic module's aroundWrite binds it)
812
+ // stages instead — ensurePB stamped foldBatches, so the backing commits
813
+ // with the transaction and the reveal is atomic at settle. The pinned
814
+ // immediate-commit contract is stated over the no-transaction microtask
815
+ // posture, which `activeTransition === null` is exactly.
816
+ if (e.fam !== null && e.pb !== null && getWriteOverride() && activeTransition === null) {
785
817
  // Landed truth (post-await write-override): immediately visible to every
786
818
  // reader — any staged held view is superseded.
787
819
  if (e.ht !== null) e.ht = e.hv = null;
@@ -849,9 +881,9 @@ i = true) {
849
881
  if (i?.get !== o?.get || i?.set !== o?.set || i?.value !== o?.value) setSignal(e, () => FORCE);
850
882
  return;
851
883
  }
852
- const f = i?.value;
853
- const l = o?.value;
854
- if (!isEqual(f, l) && !targetsEqual(f, l)) setSignal(e, typeof l === "function" ? () => l : l);
884
+ const l = i?.value;
885
+ const f = o?.value;
886
+ if (!isEqual(l, f) && !targetsEqual(l, f)) setSignal(e, typeof f === "function" ? () => f : f);
855
887
  } else {
856
888
  const i = n[t];
857
889
  const o = r[t];
@@ -976,7 +1008,7 @@ const UNSAFE_KEYS = new Set([ "__proto__", "prototype", "constructor" ]);
976
1008
  * / onSettled callbacks) get COMMITTED visibility (#3006), same as core. */ function inOwnerContext() {
977
1009
  const e = getOwner();
978
1010
  if (e === null) return false;
979
- const t = e.Ct ? e.Ot : e;
1011
+ const t = e.Gt ? e.Dt : e;
980
1012
  return t != null && !(t.T & CONFIG_CHILDREN_FORBIDDEN);
981
1013
  }
982
1014
 
@@ -987,7 +1019,7 @@ const UNSAFE_KEYS = new Set([ "__proto__", "prototype", "constructor" ]);
987
1019
  * committed to them regardless of staged writes. */ function inForbiddenScope() {
988
1020
  const e = getOwner();
989
1021
  if (e === null) return false;
990
- const t = e.Ct ? e.Ot : e;
1022
+ const t = e.Gt ? e.Dt : e;
991
1023
  return t != null && !!(t.T & CONFIG_CHILDREN_FORBIDDEN);
992
1024
  }
993
1025
 
@@ -998,7 +1030,7 @@ const UNSAFE_KEYS = new Set([ "__proto__", "prototype", "constructor" ]);
998
1030
  if (t === null) return false;
999
1031
  for (const e of Reflect.ownKeys(t)) {
1000
1032
  const n = t[e];
1001
- if (n.Pe !== NOT_PENDING && n._e != null && n._e.fn !== true) return true;
1033
+ if (n.Re !== NOT_PENDING && n.Ae != null && n.Ae.sn !== true) return true;
1002
1034
  }
1003
1035
  return false;
1004
1036
  }
@@ -1012,20 +1044,52 @@ function readSource(e) {
1012
1044
  const t = heldMaskView(e);
1013
1045
  if (t !== null) return t;
1014
1046
  }
1015
- // Signal-parity visibility (core read(): owner-context reads serve
1016
- // _pendingValue, context-free reads serve committed — effects recompute
1017
- // BEFORE commitPendingNodes in the flush, so the pending view must be
1018
- // servable). Drafts (setter window OR projection write-override) and
1019
- // owner-context reads see the pending backing; context-free reads see
1020
- // committed. Node reads apply the same rule, so both homes agree.
1021
- if (e.pb !== null && (inDraft(e) || getWriteOverride() || inOwnerContext() ||
1047
+ return pendingBackingVisible(e, false) ? e.pb : e.v;
1048
+ }
1049
+
1050
+ /** The single pb-vs-committed visibility decision (#3147), shared by per-key
1051
+ * backing reads (readSource) and deep()/snapshot composition (snapshotWalk)
1052
+ * so the two reader families can never disagree about a HELD landing.
1053
+ *
1054
+ * Signal-parity visibility (core read(): owner-context reads serve
1055
+ * _pendingValue, context-free reads serve committed — effects recompute
1056
+ * BEFORE commitPendingNodes in the flush, so the pending view must be
1057
+ * servable). Drafts (setter window OR projection write-override) and
1058
+ * owner-context reads see the pending backing; context-free reads see
1059
+ * committed. Node reads apply the same rule, so all homes agree.
1060
+ *
1061
+ * `speculative` is deep()/snapshot's posture: an untrack/deep PEEK that sees
1062
+ * ordinary pending staging regardless of owner context (the documented
1063
+ * divergence from context-free per-key reads) — but never through a hold:
1064
+ * held truth stays masked exactly as it is for per-key readers. */ function pendingBackingVisible(e, t) {
1065
+ return e.pb !== null && (inDraft(e) || getWriteOverride() ||
1066
+ // Owner-context (and speculative-peek) readers see the pending
1067
+ // backing — EXCEPT held truth on an optimistic family (#3164 fold):
1068
+ // a live pb on an opt family outside the draft/write-override windows
1069
+ // is a staged landing (tentative drafts never outlive their setter),
1070
+ // and only the authoritative postures and latest() see it (the
1071
+ // backing-level twin of core read()'s A17-for-held-truth arm;
1072
+ // ordinary readers keep committed until the transaction's reveal).
1073
+ (t || inOwnerContext()) && !heldTruthMasked(e) ||
1022
1074
  // A projection's pending backing is authoritative-elect: serve it to
1023
1075
  // context-free readers too UNLESS a transition is holding the node
1024
1076
  // commits (downstream async hold — stale committed is the contract)
1025
1077
  // or the reader is a CHILDREN_FORBIDDEN scope, which never observes
1026
1078
  // its own unsettled write (#3082, signal parity per #3006).
1027
- e.fam !== null && !foldHeld(e) && !inForbiddenScope())) return e.pb;
1028
- return e.v;
1079
+ e.fam !== null && !heldTruthMasked(e) && !foldHeld(e) && !inForbiddenScope());
1080
+ }
1081
+
1082
+ /** #3164 fold: HELD truth on an optimistic family — a pending backing
1083
+ * stamped by a live transition that retains optimism — is masked from
1084
+ * ordinary readers (they keep committed until the transaction's reveal);
1085
+ * the authoritative postures and latest() tunnel through. Un-stamped
1086
+ * backings and optimism-free transitions keep ordinary mid-batch/
1087
+ * speculation visibility. */ function heldTruthMasked(e) {
1088
+ if (e.fam?.opt !== true || latestReadActive || authoritativeServe()) return false;
1089
+ const t = foldBatches.get(e);
1090
+ // opt families are only created by createOptimisticStore, whose module
1091
+ // install populates optHooks — the assertion holds by construction.
1092
+ return t !== undefined && optHooks.retainsOptimism(t);
1029
1093
  }
1030
1094
 
1031
1095
  const hasOwn = Object.prototype.hasOwnProperty;
@@ -1057,7 +1121,27 @@ function isOwnAccessor(e, t) {
1057
1121
 
1058
1122
  /** Active optimistic override on an armed node (armed slot idles at
1059
1123
  * NOT_PENDING; undefined = unarmed plain node). */ function hasActiveOverride(e) {
1060
- return e.o?.De !== undefined && e.o?.De !== NOT_PENDING;
1124
+ return e.o?.Pe !== undefined && e.o?.Pe !== NOT_PENDING;
1125
+ }
1126
+
1127
+ /** The reading computation is until()'s authoritative-view predicate — same
1128
+ * source of truth as core read()'s A17 carve-out (`context`, which persists
1129
+ * under untrack). optimisticView()'s composition gate consults exactly this:
1130
+ * write-side machinery (patch emission, tentative re-application) must keep
1131
+ * composing even when it runs inside an authoritative-write bracket. */ function authoritativeRead() {
1132
+ const e = context;
1133
+ return e !== null && (e.T & CONFIG_AUTHORITATIVE_READ) !== 0;
1134
+ }
1135
+
1136
+ /** Serve-side authoritative gate: until()'s predicate PLUS truth authors —
1137
+ * the projection derive's draft (wrapDraft trap brackets, runAuthoritative;
1138
+ * the same posture pair ensurePB classifies drafts by). A source computing
1139
+ * the next truth must never read its callers' tentative overlays: a derive
1140
+ * continuation's `store.push` computing its index from an action's
1141
+ * optimistic row landed truth in the wrong slot and corrupted committed
1142
+ * state (#3108). Trap-level overlay serves gate on this so values, length,
1143
+ * membership, and keys leave the authoritative view together. */ function authoritativeServe() {
1144
+ return projectionWriteActive || getWriteOverride() || authoritativeRead();
1061
1145
  }
1062
1146
 
1063
1147
  /** Context-aware node view for reads outside tracking: active override >
@@ -1069,7 +1153,17 @@ function isOwnAccessor(e, t) {
1069
1153
  * keys, which are served by the trap, not the node). */ function nodeValue(e, t) {
1070
1154
  // latest() sees the in-flight parked value like an owner-context reader
1071
1155
  // does (#3075) — signal/memo parity for store-node-backed keys.
1072
- const n = hasActiveOverride(e) ? unwrapOverride(e.o?.De) : e.Pe !== NOT_PENDING && (latestReadActive || inOwnerContext()) ? e.Pe : t;
1156
+ // Authoritative-view reads (until()'s predicate) skip the override arm
1157
+ // only: staged pending values are authoritative, overrides are the
1158
+ // caller's optimism.
1159
+ const n = !authoritativeServe() && hasActiveOverride(e) ? unwrapOverride(e.o?.Pe) : e.Re !== NOT_PENDING && (latestReadActive ||
1160
+ // Owner-context pending visibility — except HELD truth (#3164,
1161
+ // see CONFIG_HELD_TRUTH: fold-staged or entangle-stolen
1162
+ // confirming truth), which only authoritative/latest readers
1163
+ // see (core read()'s A17-for-held-truth twin; ordinary readers
1164
+ // keep committed until the transaction's reveal — latest() is
1165
+ // exempted by the leading arm above).
1166
+ (inOwnerContext() || authoritativeServe()) && !(e.T & CONFIG_HELD_TRUTH && !authoritativeServe())) ? e.Re : t;
1073
1167
  return n === FORCE ? t : n;
1074
1168
  }
1075
1169
 
@@ -1079,7 +1173,7 @@ function isOwnAccessor(e, t) {
1079
1173
  * node is linked only for adoption-swap notification, its value never
1080
1174
  * shadows the live chain. */ function serveDataKey(e, t, n, r, i) {
1081
1175
  const o = e.ch && r === e.v;
1082
- let f = n;
1176
+ let l = n;
1083
1177
  // §6: on optimistic arrays LENGTH IS A VIEW, not a node value — one home
1084
1178
  // (backing ± presence overrides) for both length and indices makes torn
1085
1179
  // iteration impossible (a length node's value rides different visibility
@@ -1094,15 +1188,20 @@ function isOwnAccessor(e, t) {
1094
1188
  read(getNode(e, t, n));
1095
1189
  }
1096
1190
  }
1097
- return optHooks.optimisticView(e, r).length;
1191
+ // Truth authors read the backing's own length — an optimistic row from
1192
+ // the caller's transaction must not shift where the author's next write
1193
+ // lands (#3108).
1194
+ return (authoritativeServe() ? r : optHooks.optimisticView(e, r)).length;
1098
1195
  }
1099
1196
  if (inDraft(e)) {
1100
1197
  // Optimistic drafts before their first write have no pending backing yet;
1101
1198
  // reads must still see the live optimistic view (compose, not clobber —
1102
1199
  // #2951). Once ensurePB runs, the seeded clone carries the view.
1103
- if (e.fam?.opt && e.pb === null) {
1200
+ // AUTHORITATIVE drafts (projection derive) never overlay ensurePB's
1201
+ // seeding rule, applied to the read side (#3108).
1202
+ if (e.fam?.opt && e.pb === null && !authoritativeServe()) {
1104
1203
  const n = e.n?.[t];
1105
- if (n !== undefined && hasActiveOverride(n)) f = unwrapOverride(n.o?.De);
1204
+ if (n !== undefined && hasActiveOverride(n)) l = unwrapOverride(n.o?.Pe);
1106
1205
  }
1107
1206
  } else {
1108
1207
  if (i !== undefined) {
@@ -1114,28 +1213,28 @@ function isOwnAccessor(e, t) {
1114
1213
  // parity): READ_SLOW = a global read window or non-plain node.
1115
1214
  let e = readNodeFast(i);
1116
1215
  if (e === READ_SLOW) e = read(i);
1117
- if (!o || hasActiveOverride(i)) f = e === FORCE ? n : e;
1216
+ if (!o || hasActiveOverride(i)) l = e === FORCE ? n : e;
1118
1217
  } else if (!o || hasActiveOverride(i)) {
1119
- f = nodeValue(i, n);
1218
+ l = nodeValue(i, n);
1120
1219
  }
1121
1220
  } else if (getObserver() !== null) {
1122
1221
  read(getNode(e, t, n));
1123
1222
  }
1124
1223
  }
1125
1224
  // Shallow stores serve data raw; store-proxy slots get boundary wrappers.
1126
- if (e.s) return serveShallow(e, t, f);
1225
+ if (e.s) return serveShallow(e, t, l);
1127
1226
  if (i !== undefined) {
1128
1227
  // Wrap cache (see getNode): only wrappables are ever cached, so a hit
1129
1228
  // skips isWrappable too — pointer-compare replaces both checks.
1130
- if (i.pxv === f && f !== undefined) return draftServe(e, i.px);
1131
- if (!isWrappable(f)) return f;
1132
- const n = wrapNext(f, e, t);
1229
+ if (i.pxv === l && l !== undefined) return draftServe(e, i.px);
1230
+ if (!isWrappable(l)) return l;
1231
+ const n = wrapNext(l, e, t);
1133
1232
  i.px = n;
1134
- i.pxv = f;
1233
+ i.pxv = l;
1135
1234
  return draftServe(e, n);
1136
1235
  }
1137
- if (!isWrappable(f)) return f;
1138
- return draftServe(e, wrapNext(f, e, t));
1236
+ if (!isWrappable(l)) return l;
1237
+ return draftServe(e, wrapNext(l, e, t));
1139
1238
  }
1140
1239
 
1141
1240
  /** §6c store-wide status gate for reads that DON'T flow through a node:
@@ -1269,15 +1368,15 @@ const traps = {
1269
1368
  // that is own on NEITHER.
1270
1369
  const o = e.ovl && r === e.pb;
1271
1370
  if ((t === "constructor" || t === "__proto__" || t === "prototype") && !hasOwn.call(r, t) && !(o && hasOwn.call(e.v, t))) return undefined;
1272
- let f = r[t];
1273
- if (f === undefined ? !hasOwn.call(r, t) && !(o && hasOwn.call(e.v, t)) : false) {
1371
+ let l = r[t];
1372
+ if (l === undefined ? !hasOwn.call(r, t) && !(o && hasOwn.call(e.v, t)) : false) {
1274
1373
  // Inherited: prototype getters/methods run with the proxy receiver.
1275
- f = Reflect.get(r, t, n);
1276
- if (typeof f === "function") return f;
1374
+ l = Reflect.get(r, t, n);
1375
+ if (typeof l === "function") return l;
1277
1376
  // proto methods untracked
1278
1377
  // Reading a currently-absent own key subscribes to it (R12) — for any
1279
1378
  // target OUTSIDE its own draft scope, even mid-setter (#3037, above).
1280
- if (f === undefined && !inDraft(e)) {
1379
+ if (l === undefined && !inDraft(e)) {
1281
1380
  if (getObserver() !== null) read(getNode(e, t, undefined));
1282
1381
  const n = e.n?.[t];
1283
1382
  if (n) {
@@ -1285,16 +1384,19 @@ const traps = {
1285
1384
  if (e.s) return serveShallow(e, t, r);
1286
1385
  return isWrappable(r) ? draftServe(e, wrapNext(r, e, t)) : r;
1287
1386
  }
1288
- } else if (f === undefined && inDraft(e) && e.fam?.opt && e.pb === null) {
1387
+ } else if (l === undefined && inDraft(e) && e.fam?.opt && e.pb === null &&
1388
+ // AUTHORITATIVE drafts (landing folds) never seed from overrides —
1389
+ // the caller's optimism is not truth (has-trap twin below).
1390
+ !authoritativeServe()) {
1289
1391
  const n = e.n?.[t];
1290
- if (n !== undefined && hasActiveOverride(n)) f = unwrapOverride(n.o?.De);
1392
+ if (n !== undefined && hasActiveOverride(n)) l = unwrapOverride(n.o?.Pe);
1291
1393
  }
1292
- if (e.s) return serveShallow(e, t, f);
1293
- return isWrappable(f) ? draftServe(e, wrapNext(f, e, t)) : f;
1394
+ if (e.s) return serveShallow(e, t, l);
1395
+ return isWrappable(l) ? draftServe(e, wrapNext(l, e, t)) : l;
1294
1396
  }
1295
- if (typeof f === "function" && !hasOwn.call(r, t) && !(o && hasOwn.call(e.v, t))) return f;
1397
+ if (typeof l === "function" && !hasOwn.call(r, t) && !(o && hasOwn.call(e.v, t))) return l;
1296
1398
  // proto method
1297
- return serveDataKey(e, t, f, r, i);
1399
+ return serveDataKey(e, t, l, r, i);
1298
1400
  },
1299
1401
  has(e, t) {
1300
1402
  if (t === $TARGET || t === $PROXY || t === $TRACK) return true;
@@ -1307,15 +1409,17 @@ const traps = {
1307
1409
  if (!inDraft(e)) {
1308
1410
  if (getObserver() !== null) {
1309
1411
  const n = getHasNode(e, t, r);
1310
- const i = read(n);
1412
+ // Authoritative-view readers get the right answer for free: core read()
1413
+ // skips the override arm for them, so nv is authoritative presence.
1414
+ const i = read(n);
1311
1415
  if (hasActiveOverride(n)) r = !!i;
1312
- } else {
1416
+ } else if (!authoritativeServe()) {
1313
1417
  const n = e.h?.[t];
1314
- if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.De);
1418
+ if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.Pe);
1315
1419
  }
1316
- } else if (e.fam?.opt && e.pb === null) {
1420
+ } else if (e.fam?.opt && e.pb === null && !authoritativeServe()) {
1317
1421
  const n = e.h?.[t];
1318
- if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.De);
1422
+ if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.Pe);
1319
1423
  }
1320
1424
  return r;
1321
1425
  },
@@ -1338,14 +1442,15 @@ const traps = {
1338
1442
  // Optimistic membership overlay: presence-node overrides add/remove keys
1339
1443
  // (per-transaction lifecycle rides the nodes — §6, FINDING-2's fix).
1340
1444
  // Draft reads before the first write overlay too (pb, once created, is
1341
- // seeded with the view).
1342
- if (e.fam?.opt && e.h !== null && (!inDraft(e) || e.pb === null)) {
1445
+ // seeded with the view). Authoritative-view reads (until()'s predicate,
1446
+ // truth-author drafts) skip the overlay.
1447
+ if (!authoritativeServe() && e.fam?.opt && e.h !== null && (!inDraft(e) || e.pb === null)) {
1343
1448
  let t = null;
1344
1449
  for (const r of Reflect.ownKeys(e.h)) {
1345
1450
  const i = e.h[r];
1346
1451
  if (!hasActiveOverride(i)) continue;
1347
1452
  t ??= new Set(n);
1348
- if (unwrapOverride(i.o?.De)) t.add(r); else t.delete(r);
1453
+ if (unwrapOverride(i.o?.Pe)) t.add(r); else t.delete(r);
1349
1454
  }
1350
1455
  if (t !== null) return [ ...t ];
1351
1456
  }
@@ -1360,10 +1465,10 @@ const traps = {
1360
1465
  if (e.del !== null && e.del.has(t)) return undefined;
1361
1466
  if (r === undefined) r = Object.getOwnPropertyDescriptor(e.v, t);
1362
1467
  }
1363
- if (e.fam?.opt && !inDraft(e)) {
1468
+ if (!authoritativeServe() && e.fam?.opt && !inDraft(e)) {
1364
1469
  const n = e.h?.[t];
1365
1470
  if (n !== undefined && hasActiveOverride(n)) {
1366
- if (!unwrapOverride(n.o?.De)) return undefined;
1471
+ if (!unwrapOverride(n.o?.Pe)) return undefined;
1367
1472
  // opt delete
1368
1473
  if (r === undefined) {
1369
1474
  const n = e.n?.[t];
@@ -1399,23 +1504,23 @@ const traps = {
1399
1504
  // passes through by reference (#2932; markRawOne skips proxies), while
1400
1505
  // deep stores unwrap to raw backings.
1401
1506
  const o = e.s ? n : unwrapValue(n);
1402
- const f = ensurePB(e);
1507
+ const l = ensurePB(e);
1403
1508
  pendingNotify.add(e);
1404
1509
  // Array length writes implicitly delete indices — the written-keys bound
1405
1510
  // can't see them, so poison to the full scan for this batch. Index
1406
1511
  // writes implicitly GROW length, so arrays always record it alongside.
1407
- const l = pcOf(e);
1408
- if (Array.isArray(f)) {
1409
- if (t === "length") l.wk = WK_ALL; else if (l.wk !== WK_ALL) {
1410
- const e = l.wk ??= new Set;
1512
+ const f = pcOf(e);
1513
+ if (Array.isArray(l)) {
1514
+ if (t === "length") f.wk = WK_ALL; else if (f.wk !== WK_ALL) {
1515
+ const e = f.wk ??= new Set;
1411
1516
  e.add(t);
1412
1517
  e.add("length");
1413
1518
  }
1414
- } else if (l.wk !== WK_ALL) (l.wk ??= new Set).add(t);
1519
+ } else if (f.wk !== WK_ALL) (f.wk ??= new Set).add(t);
1415
1520
  // Own data keys literally named "prototype"/"constructor" land as data —
1416
1521
  // defineProperty sidesteps a proto-chain setter named the same.
1417
1522
  if (UNSAFE_KEYS.has(t)) {
1418
- Object.defineProperty(f, t, {
1523
+ Object.defineProperty(l, t, {
1419
1524
  value: o,
1420
1525
  writable: true,
1421
1526
  enumerable: true,
@@ -1427,14 +1532,14 @@ const traps = {
1427
1532
  // Overlay first-write DEFINES the own key: assignment through the proto
1428
1533
  // chain would reject on a non-writable committed property (the clone
1429
1534
  // path normalized descriptors for exactly this — R51 parity).
1430
- if (e.ovl && !hasOwn.call(f, t)) {
1431
- Object.defineProperty(f, t, {
1535
+ if (e.ovl && !hasOwn.call(l, t)) {
1536
+ Object.defineProperty(l, t, {
1432
1537
  value: o,
1433
1538
  writable: true,
1434
1539
  enumerable: true,
1435
1540
  configurable: true
1436
1541
  });
1437
- } else f[t] = o;
1542
+ } else l[t] = o;
1438
1543
  if (e.del !== null) e.del.delete(t);
1439
1544
  // Shallow ingest: written records are sticky raw-marked (one entity is
1440
1545
  // never both deep-wrapped and raw — R41/#2932, shared invariant).
@@ -1464,8 +1569,8 @@ const traps = {
1464
1569
  };
1465
1570
  const o = ensurePB(e);
1466
1571
  pendingNotify.add(e);
1467
- const f = pcOf(e);
1468
- if (f.wk !== WK_ALL) (f.wk ??= new Set).add(t);
1572
+ const l = pcOf(e);
1573
+ if (l.wk !== WK_ALL) (l.wk ??= new Set).add(t);
1469
1574
  Object.defineProperty(o, t, n);
1470
1575
  if (e.del !== null) e.del.delete(t);
1471
1576
  if (i) notifyWrites(e);
@@ -1603,15 +1708,15 @@ function createStoreNext(e, t = false) {
1603
1708
  if (o === null || typeof o !== "object") continue;
1604
1709
  // Stored proxies (chained slots) resolve through their own target;
1605
1710
  // raw children through the family map, created on first visit.
1606
- let f = o[$TARGET] ?? r.get(o);
1607
- if (f === undefined) {
1711
+ let l = o[$TARGET] ?? r.get(o);
1712
+ if (l === undefined) {
1608
1713
  if (!isWrappable(o)) continue;
1609
1714
  wrapNext(o, e, n);
1610
- f = r.get(o);
1611
- if (f === undefined) continue;
1715
+ l = r.get(o);
1716
+ if (l === undefined) continue;
1612
1717
  // raw-marked: leaf by contract
1613
1718
  }
1614
- walkT(f);
1719
+ walkT(l);
1615
1720
  }
1616
1721
  };
1617
1722
  walkT(t);
@@ -1648,12 +1753,17 @@ function snapshotWalk(e, t, n) {
1648
1753
  if (e === undefined) break;
1649
1754
  if (e.fam !== null) n = e.fam;
1650
1755
  if (e.fam?.opt === true) (i ??= []).push(e);
1756
+ // The shared visibility decision (#3147): the speculative peek serves
1757
+ // pending staging, but a HELD landing is masked to committed exactly as
1758
+ // it is for per-key readers — the two families must answer alike while
1759
+ // a transaction holds store landings.
1760
+ const t = pendingBackingVisible(e, true);
1651
1761
  // Snapshot runs mid-flush (tracked memos execute before commit), so a
1652
1762
  // pending prototype overlay must present as a REAL merged container.
1653
- if (e.ovl) materializePB(e);
1654
- const t = e.pb ?? e.v;
1655
- if (t === r) break;
1656
- r = t;
1763
+ if (t && e.ovl) materializePB(e);
1764
+ const o = t ? e.pb : e.v;
1765
+ if (o === r) break;
1766
+ r = o;
1657
1767
  }
1658
1768
  if (!isWrappable(r)) return r;
1659
1769
  // Optimistic families: compose the visible view; a composed view is a fresh
@@ -1665,15 +1775,15 @@ function snapshotWalk(e, t, n) {
1665
1775
  const i = t.get(r);
1666
1776
  if (i !== undefined) return i;
1667
1777
  const o = Array.isArray(e);
1668
- const f = o ? [] : Object.create(Object.getPrototypeOf(e));
1669
- t.set(r, f);
1778
+ const l = o ? [] : Object.create(Object.getPrototypeOf(e));
1779
+ t.set(r, l);
1670
1780
  for (const r of Reflect.ownKeys(e)) {
1671
1781
  if (o && r === "length") continue;
1672
1782
  const i = e[r];
1673
- f[r] = i !== null && typeof i === "object" ? snapshotWalk(i, t, n) : i;
1783
+ l[r] = i !== null && typeof i === "object" ? snapshotWalk(i, t, n) : i;
1674
1784
  }
1675
- if (o) f.length = e.length;
1676
- return f;
1785
+ if (o) l.length = e.length;
1786
+ return l;
1677
1787
  }
1678
1788
  }
1679
1789
  const o = t.get(r);
@@ -1688,16 +1798,16 @@ function snapshotWalk(e, t, n) {
1688
1798
  t.set(r, i);
1689
1799
  for (const o of Reflect.ownKeys(r)) {
1690
1800
  if (e && o === "length") continue;
1691
- const f = Object.getOwnPropertyDescriptor(r, o);
1692
- if (typeof o === "symbol" && !f.enumerable) continue;
1693
- if (f.get || f.set) {
1694
- Object.defineProperty(i, o, f);
1801
+ const l = Object.getOwnPropertyDescriptor(r, o);
1802
+ if (typeof o === "symbol" && !l.enumerable) continue;
1803
+ if (l.get || l.set) {
1804
+ Object.defineProperty(i, o, l);
1695
1805
  continue;
1696
1806
  }
1697
- const l = f.value;
1698
- const u = l !== null && typeof l === "object" ? snapshotWalk(l, t, n) : l;
1699
- if (f.enumerable && f.writable && f.configurable) i[o] = u; else Object.defineProperty(i, o, {
1700
- ...f,
1807
+ const f = l.value;
1808
+ const u = f !== null && typeof f === "object" ? snapshotWalk(f, t, n) : f;
1809
+ if (l.enumerable && l.writable && l.configurable) i[o] = u; else Object.defineProperty(i, o, {
1810
+ ...l,
1701
1811
  value: u
1702
1812
  });
1703
1813
  }
@@ -1707,22 +1817,22 @@ function snapshotWalk(e, t, n) {
1707
1817
  // UNOWNED (shared/user) subtrees keep identity unless a descendant
1708
1818
  // substituted; copy-on-substitution preserves the documented CoW contract.
1709
1819
  t.set(r, r);
1710
- let f = null;
1820
+ let l = null;
1711
1821
  for (const e of Reflect.ownKeys(r)) {
1712
1822
  const i = Object.getOwnPropertyDescriptor(r, e);
1713
1823
  if (!i || i.get || i.set) continue;
1714
1824
  const o = i.value;
1715
1825
  if (o === null || typeof o !== "object") continue;
1716
- const l = snapshotWalk(o, t, n);
1717
- if (l !== o) {
1718
- if (f === null) {
1719
- f = Array.isArray(r) ? [ ...r ] : Object.create(Object.getPrototypeOf(r), Object.getOwnPropertyDescriptors(r));
1720
- t.set(r, f);
1826
+ const f = snapshotWalk(o, t, n);
1827
+ if (f !== o) {
1828
+ if (l === null) {
1829
+ l = Array.isArray(r) ? [ ...r ] : Object.create(Object.getPrototypeOf(r), Object.getOwnPropertyDescriptors(r));
1830
+ t.set(r, l);
1721
1831
  }
1722
- f[e] = l;
1832
+ l[e] = f;
1723
1833
  }
1724
1834
  }
1725
- return f ?? r;
1835
+ return l ?? r;
1726
1836
  }
1727
1837
 
1728
- export { adoptPB, bumpDeep, createStoreNext, deepNext, getHasNode, getKeySetNode, getNode, hasAccessorFlag, hasActiveOverride, materializePB, notifyFold, notifyFoldTail, notifyKeyDiff, notifyKeyValue, pcOf, runAuthoritative, snapshotNext, storeHasFamily, storeHasOptimisticFamily, storeIsShallow, storeSetterNext, targetIsPlain, targetsEqual, unwrapValue, wrapNext };
1838
+ export { adoptPB, arrayStructureChanged, authoritativeRead, authoritativeServe, bumpDeep, createStoreNext, deepNext, getHasNode, getKeySetNode, getNode, hasAccessorFlag, hasActiveOverride, materializePB, membershipChanged, notifyFold, notifyFoldTail, notifyKeyDiff, notifyKeyValue, pcOf, runAuthoritative, snapshotNext, stagedTruthPB, storeHasFamily, storeHasOptimisticFamily, storeIsShallow, storeSetterNext, targetIsPlain, targetsEqual, unwrapValue, wrapNext };