@solidjs/signals 2.0.0-rc.3 → 2.0.0-rc.5

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 (71) hide show
  1. package/dist/dev.js +2494 -271
  2. package/dist/node.cjs +3691 -1595
  3. package/dist/prod/affects.js +13 -12
  4. package/dist/prod/boundaries.js +43 -35
  5. package/dist/prod/core/action.js +3 -3
  6. package/dist/prod/core/async.js +137 -106
  7. package/dist/prod/core/constants.js +55 -1
  8. package/dist/prod/core/core.js +354 -247
  9. package/dist/prod/core/effect.js +47 -50
  10. package/dist/prod/core/error.js +13 -1
  11. package/dist/prod/core/external.js +4 -4
  12. package/dist/prod/core/graph.js +88 -52
  13. package/dist/prod/core/heap.js +38 -38
  14. package/dist/prod/core/lanes.js +34 -34
  15. package/dist/prod/core/optimistic.js +61 -58
  16. package/dist/prod/core/owner.js +38 -38
  17. package/dist/prod/core/scheduler.js +401 -174
  18. package/dist/prod/core/verdict.js +132 -65
  19. package/dist/prod/index.js +7 -3
  20. package/dist/prod/map.js +106 -106
  21. package/dist/prod/signals.js +253 -25
  22. package/dist/prod/store/index.js +2 -0
  23. package/dist/prod/store/next/optimistic.js +314 -121
  24. package/dist/prod/store/next/patch-hooks.js +13 -0
  25. package/dist/prod/store/next/patch.js +614 -0
  26. package/dist/prod/store/next/projection.js +23 -19
  27. package/dist/prod/store/next/reconcile.js +307 -120
  28. package/dist/prod/store/next/store.js +440 -117
  29. package/dist/prod/store/next/target.js +13 -4
  30. package/dist/prod/store/store.js +5 -5
  31. package/dist/types/core/async.d.ts +2 -0
  32. package/dist/types/core/attribution.d.ts +9 -4
  33. package/dist/types/core/constants.d.ts +54 -0
  34. package/dist/types/core/core.d.ts +34 -21
  35. package/dist/types/core/dev.d.ts +8 -0
  36. package/dist/types/core/error.d.ts +9 -0
  37. package/dist/types/core/graph.d.ts +22 -0
  38. package/dist/types/core/index.d.ts +2 -2
  39. package/dist/types/core/scheduler.d.ts +46 -0
  40. package/dist/types/core/types.d.ts +12 -0
  41. package/dist/types/index.d.ts +3 -3
  42. package/dist/types/signals.d.ts +108 -0
  43. package/dist/types/store/index.d.ts +2 -0
  44. package/dist/types/store/next/optimistic.d.ts +13 -10
  45. package/dist/types/store/next/patch-hooks.d.ts +41 -0
  46. package/dist/types/store/next/patch.d.ts +91 -0
  47. package/dist/types/store/next/projection.d.ts +1 -1
  48. package/dist/types/store/next/reconcile.d.ts +14 -0
  49. package/dist/types/store/next/store.d.ts +52 -2
  50. package/dist/types/store/next/target.d.ts +73 -8
  51. package/dist/types-cjs/core/async.d.cts +2 -0
  52. package/dist/types-cjs/core/attribution.d.cts +9 -4
  53. package/dist/types-cjs/core/constants.d.cts +54 -0
  54. package/dist/types-cjs/core/core.d.cts +34 -21
  55. package/dist/types-cjs/core/dev.d.cts +8 -0
  56. package/dist/types-cjs/core/error.d.cts +9 -0
  57. package/dist/types-cjs/core/graph.d.cts +22 -0
  58. package/dist/types-cjs/core/index.d.cts +2 -2
  59. package/dist/types-cjs/core/scheduler.d.cts +46 -0
  60. package/dist/types-cjs/core/types.d.cts +12 -0
  61. package/dist/types-cjs/index.d.cts +3 -3
  62. package/dist/types-cjs/signals.d.cts +108 -0
  63. package/dist/types-cjs/store/index.d.cts +2 -0
  64. package/dist/types-cjs/store/next/optimistic.d.cts +13 -10
  65. package/dist/types-cjs/store/next/patch-hooks.d.cts +41 -0
  66. package/dist/types-cjs/store/next/patch.d.cts +91 -0
  67. package/dist/types-cjs/store/next/projection.d.cts +1 -1
  68. package/dist/types-cjs/store/next/reconcile.d.cts +14 -0
  69. package/dist/types-cjs/store/next/store.d.cts +52 -2
  70. package/dist/types-cjs/store/next/target.d.cts +73 -8
  71. package/package.json +2 -2
@@ -1,8 +1,8 @@
1
- import { computed, ext, setSignal, isEqual } from "../../core/core.js";
1
+ import { CONFIG_AUTO_DISPOSE, STATUS_PENDING, NOT_PENDING, CONFIG_OPTIMISTIC, CONFIG_HELD_TRUTH, unwrapOverride } from "../../core/constants.js";
2
2
 
3
- import { CONFIG_AUTO_DISPOSE, STATUS_PENDING, NOT_PENDING, unwrapOverride, CONFIG_OPTIMISTIC } from "../../core/constants.js";
3
+ import { computed, setSignal, isEqual } from "../../core/core.js";
4
4
 
5
- import { GlobalQueue, insertSubs, schedule, globalQueue } from "../../core/scheduler.js";
5
+ import { activeTransition, GlobalQueue, runAsTransitionBatch, globalQueue, currentTransition } from "../../core/scheduler.js";
6
6
 
7
7
  import "../../core/invariants.js";
8
8
 
@@ -16,7 +16,11 @@ import { $TARGET, markRawIngest, setNextOptimisticViewResolver, isWrappable, raw
16
16
 
17
17
  import { runProjectionComputedNext } from "./projection.js";
18
18
 
19
- import { wrapNext, runAuthoritative, storeSetterNext, hasActiveOverride, unwrapValue, getNode, getHasNode, targetsEqual, getKeySetNode, bumpDeep } from "./store.js";
19
+ import { wrapNext, runAuthoritative, storeSetterNext, hasActiveOverride, unwrapValue, targetsEqual, getNode, getHasNode, getKeySetNode, bumpDeep, stagedTruthPB, authoritativeRead } from "./store.js";
20
+
21
+ import { patchHooks, rowHooks } from "./patch-hooks.js";
22
+
23
+ import { sameKey, buildIdentityRowOps } from "./reconcile.js";
20
24
 
21
25
  import { setOptHooks } from "./target.js";
22
26
 
@@ -29,12 +33,32 @@ import { setOptHooks } from "./target.js";
29
33
  * armed presence nodes (the §6 overlay), so structural optimism reverts with
30
34
  * the same per-transaction granularity (FINDING-2's fix by construction).
31
35
  *
32
- * Derived form = an optimistic projection: the derive's recompute and its
33
- * async commits run under projectionWriteActive (authoritative landings
34
- * commit silently beneath any active overrides). The transitionBlocked
35
- * store-half (#2951) is installed here for next-shaped targets, chaining the
36
+ * Derived form = an optimistic projection. Landings follow the fold rule
37
+ * (#3164, RUL-2 as re-ruled): while a transaction retains optimistic edits
38
+ * on the family, truth that lands STAGES into that transaction — a keyed
39
+ * identity-preserving walk written through the ordinary staged setter
40
+ * channel — and reveals atomically at settle, exactly like a signal landing
41
+ * under an active override (asyncWrite's held branch). Optimistic edits are
42
+ * never consumed by landings; they live exactly as long as their transaction
43
+ * and die by engine-native revert. With no retainer, landings commit
44
+ * immediately under projectionWriteActive (authoritative, silently beneath
45
+ * any bare-write overrides — those ride the flight's own transition, #2951).
46
+ * Authoritative readers (until()'s predicate) tunnel into staged truth via
47
+ * the node read path's pending-value arm. The transitionBlocked store-half
48
+ * (#2951) is installed here for next-shaped targets, chaining the
36
49
  * legacy/engine checks.
37
- */ let blockedInstalled = false;
50
+ */
51
+ /** #3164 fold: a stamped truth is HELD (masked from ordinary readers until
52
+ * the reveal) only while its transition is live AND retaining optimism —
53
+ * overrides are what make partial-coverage composition a tear. A plain
54
+ * async transition carries no overrides, so downstream computes must see
55
+ * staged values to converge (normal speculation). Resolves merges first:
56
+ * merge unions optimistic nodes/stores into the target. */ function transitionHoldsOptimism(e) {
57
+ const t = currentTransition(e);
58
+ return t.sn !== true && (t.ze.length !== 0 || t.En.size !== 0);
59
+ }
60
+
61
+ let blockedInstalled = false;
38
62
 
39
63
  function installNextBlockedHalf() {
40
64
  if (blockedInstalled) return;
@@ -45,20 +69,45 @@ function installNextBlockedHalf() {
45
69
  setOptHooks({
46
70
  notifyOptimisticWrites: notifyOptimisticWrites,
47
71
  optimisticView: optimisticView,
48
- applyTentative: applyTentative
72
+ applyTentative: applyTentative,
73
+ retainsOptimism: transitionHoldsOptimism
49
74
  });
50
75
  setNextOptimisticViewResolver((e, t) => optimisticView(e, t));
51
76
  // Scheduler flush tails call _clearOptimisticStores whenever tracked
52
77
  // stores exist; next has no layer to clear — reverts are engine-native —
53
78
  // so the hook only empties the batch set.
54
- if (!GlobalQueue.Vt) {
55
- GlobalQueue.Vt = e => {
79
+ if (!GlobalQueue.qt) {
80
+ GlobalQueue.qt = e => {
81
+ // Patch channel (revert site): engine-native reverts flip node values
82
+ // back to committed; patched records need a forced DOM re-apply from
83
+ // the post-revert view. Emission only — next keeps no layer to clear.
84
+ for (const t of e) {
85
+ const e = t?.[$TARGET];
86
+ const n = e?.fam?.overlaid;
87
+ if (n !== undefined) {
88
+ for (const e of n) {
89
+ if (e.pc !== null && e.pc.p !== null) patchHooks.emitPatchOptimistic(e, null, null);
90
+ // Row-ops resync (family increment 2): reverts flip node values
91
+ // back engine-natively; a driven list must rebuild retention by
92
+ // row identity against the post-revert view (resolved from the
93
+ // target at drain — overrides are gone by then).
94
+ if (e.pc !== null && e.pc.ro !== null) rowHooks.emitRowOpsOptimistic(e, null, null);
95
+ // Keyset resync (classic channel twin): the keyset node's own
96
+ // revert can compare EQUAL (a landing's bump matched the
97
+ // tentative bump) while the arrangement underneath changed —
98
+ // mapArray/ownKeys subscribers must re-read the post-revert
99
+ // view. Authoritative bump: never re-arm the node we are
100
+ // clearing.
101
+ if (e.k !== null) runAuthoritative(() => setSignal(e.k, e => e + 1));
102
+ }
103
+ }
104
+ }
56
105
  e.clear();
57
106
  };
58
107
  }
59
- const e = GlobalQueue.En;
60
- GlobalQueue.En = t => {
61
- for (const e of t.cn) {
108
+ const e = GlobalQueue.dn;
109
+ GlobalQueue.dn = t => {
110
+ for (const e of t.En) {
62
111
  const t = e?.[$TARGET];
63
112
  const n = t?.fam?.node;
64
113
  // The hold exists to keep optimistic state alive until the store's own
@@ -80,10 +129,10 @@ function familyHasLiveOverrides(e) {
80
129
  if (t === null) continue;
81
130
  for (const e of Reflect.ownKeys(t)) {
82
131
  const n = t[e];
83
- if (n.o?.De !== undefined && n.o?.De !== NOT_PENDING) return true;
132
+ if (n.o?.Oe !== undefined && n.o?.Oe !== NOT_PENDING) return true;
84
133
  }
85
134
  }
86
- if (e.k !== null && e.k.o?.De !== undefined && e.k.o?.De !== NOT_PENDING) return true;
135
+ if (e.k !== null && e.k.o?.Oe !== undefined && e.k.o?.Oe !== NOT_PENDING) return true;
87
136
  }
88
137
  t.clear();
89
138
  // nothing live — drop the bookkeeping
@@ -106,32 +155,216 @@ function createOptimisticStoreNext(e, t, n) {
106
155
  };
107
156
  const s = wrapNext(o, null, null, r);
108
157
  r.px = s;
158
+ // Same key resolution the projection channels use ("id" default) — replay's
159
+ // satisfaction rule reads it off the family.
160
+ const l = n?.key === undefined ? "id" : n.key;
161
+ r.key = typeof l === "function" ? l : l === null ? null : e => isWrappable(e) ? e[l] : undefined;
109
162
  if (r.shallow) {
110
163
  s[$TARGET].s = true;
111
164
  markRawIngest(o);
112
165
  }
113
166
  if (i) {
114
167
  const t = e;
115
- // Async commits land outside the computed's sync body — re-apply the
116
- // authoritative-write posture there too. Landings consume the family's
117
- // tentative overrides (RUL-2: visible landed truth replaces optimism)
118
- // both the reconcile-channel commit and per-op post-await draft writes.
119
- const consume = () => consumeOverridesNext(r);
120
- const wrapCommit = e => {
121
- runAuthoritative(e);
122
- consume();
168
+ // Landing router (#3164 fold ruling): while a transaction retains
169
+ // optimistic edits on this family, truth landings stage INTO it and
170
+ // reveal atomically at settle; with no retainer they commit immediately
171
+ // under the authoritative posture (async commits land outside the
172
+ // computed's sync body, so the posture is re-applied here).
173
+ const wrapCommit = (e, t) => {
174
+ const n = retainingTransition(r);
175
+ if (n === null) runAuthoritative(e); else stageLanding(r, n, t);
176
+ };
177
+ // Draft writes (the derive mutating its draft, sync body and post-await
178
+ // continuations alike) are the same truth channel per-operation: bind
179
+ // each op to the retaining transaction so its node writes stage and its
180
+ // backing fold defers (ensurePB stamps foldBatches with the swapped-in
181
+ // batch; the write-override eager-commit branch in notifyWrites yields
182
+ // to any active transaction).
183
+ const aroundDraftWrite = e => {
184
+ const t = retainingTransition(r);
185
+ if (t === null) e(); else runFolded(t, e);
123
186
  };
124
187
  let i;
125
188
  if (n?.seedLoadingValue) i = {
126
189
  loadingValue: undefined
127
190
  };
128
191
  const o = computed(() => {
129
- runAuthoritative(() => runProjectionComputedNext(s, t, n?.key === undefined ? "id" : n.key, wrapCommit, consume));
192
+ runAuthoritative(() => runProjectionComputedNext(s, t, n?.key === undefined ? "id" : n.key, wrapCommit, aroundDraftWrite));
130
193
  }, i);
131
194
  o.T &= ~CONFIG_AUTO_DISPOSE;
132
195
  r.node = o;
133
196
  }
134
- return [ s, e => storeSetterNext(s, e) ];
197
+ return [ s, e => {
198
+ // Retention ledger (#3164): record the owning transaction so landings
199
+ // know to fold. Captured at entry — the action machinery has the
200
+ // transaction ambient while user code runs; a bare write with no
201
+ // transaction retains nothing (it rides the flight's own transition
202
+ // per #2951 and dies with it).
203
+ const t = activeTransition;
204
+ storeSetterNext(s, e);
205
+ if (t !== null) (r.rt ??= new Set).add(t);
206
+ } ];
207
+ }
208
+
209
+ /** Resolve a retained transition through its merge chain (`_done` holds the
210
+ * merge target while merged, `true` once settled). Null = dead. */ function liveTransition(e) {
211
+ while (typeof e.sn === "object") e = e.sn;
212
+ return e.sn === true ? null : e;
213
+ }
214
+
215
+ /** The transaction truth landings fold into: the first live member of the
216
+ * family's retention ledger (dead members prune here). Multiple live
217
+ * retainers entangle through their shared family writes and settle
218
+ * together, so folding into the first reaches all of them. */ function retainingTransition(e) {
219
+ const t = e.rt;
220
+ if (t === undefined || t.size === 0) return null;
221
+ let n = null;
222
+ for (const e of t) {
223
+ const i = liveTransition(e);
224
+ if (i === null) t.delete(e); else n ??= i;
225
+ }
226
+ return n;
227
+ }
228
+
229
+ /** Fold a landing into the retaining transaction (#3164): the landed value
230
+ * is written through the ORDINARY staged setter channel — node writes park
231
+ * as `_pendingValue` registered with the transaction's batch (speculation
232
+ * and until()'s authoritative tunnel see them; live view stays coherent),
233
+ * and the backing fold defers via the foldBatches stamp — under the
234
+ * authoritative posture, so armed nodes take the engine bypass and no
235
+ * override is created. The engine's own commit machinery reveals everything
236
+ * atomically when the transaction settles (transitions never abort: failed
237
+ * actions still commit — only optimistic overrides revert). */ function stageLanding(e, t, n) {
238
+ runFolded(t, () => runAuthoritative(() => storeSetterNext(e.px, t => {
239
+ stagedApply(t, unwrapValue(n), e.key ?? null);
240
+ }, false)));
241
+ }
242
+
243
+ /** Run a fold write inside the retaining transaction's batch, then
244
+ * transition-stamp its staged nodes NOW (parity with the parked-transition
245
+ * flush path's reassignPendingTransition): the stamp is what routes stale
246
+ * (render) readers to the committed value — core read's cross-transaction
247
+ * guard — and what makes foldHeld defer the backing for context-free
248
+ * readers. A microtask staging never crosses that flush path, so without
249
+ * the stamp a render effect's speculative recompute would compose staged
250
+ * truth with live overrides — the #3164 tear, one window later. Armed
251
+ * nodes additionally raise CONFIG_HELD_TRUTH: their staged value is
252
+ * confirming truth masked from ordinary readers until the reveal (plain
253
+ * staged nodes stay visible — normal speculation; override-covered nodes
254
+ * stay unarmed — the override is their display and its revert their
255
+ * notification, A17). */ function runFolded(e, t) {
256
+ runAsTransitionBatch(e, t);
257
+ const n = e.Lt;
258
+ for (let t = 0; t < n.length; t++) {
259
+ const i = n[t];
260
+ i.Ae = e;
261
+ if (i.T & CONFIG_OPTIMISTIC && !hasActiveOverride(i)) i.T |= CONFIG_HELD_TRUTH;
262
+ }
263
+ }
264
+
265
+ /** Keyed identity-preserving deep merge through live draft proxies — the
266
+ * staged twin of the adoption walk. Reads see the pending backing (staged
267
+ * view), so consecutive landings during one hold compose; key-matched rows
268
+ * keep their raw (and so their proxy) in the slot with only changed leaves
269
+ * written; unmatched rows land wholesale. Runs inside stageLanding's
270
+ * authoritative bracket: drafts seed from committed truth, never overlays. */ function stagedApply(e, t, n) {
271
+ const i = Array.isArray(e);
272
+ if (i && Array.isArray(t)) {
273
+ const i = t.length;
274
+ if (n !== null) {
275
+ // Occurrence-aware key queues (parity with the adoption window):
276
+ // duplicate keys match per occurrence, each current row consumed once.
277
+ let o = null;
278
+ const r = e.length;
279
+ for (let t = 0; t < r; t++) {
280
+ const i = unwrapValue(e[t]);
281
+ if (!isWrappable(i)) continue;
282
+ const r = n(i);
283
+ if (r === undefined) continue;
284
+ const s = (o ??= new Map).get(r);
285
+ if (s === undefined) o.set(r, [ i ]); else s.push(i);
286
+ }
287
+ // Echo adoption: an optimistic structural add whose key the landing
288
+ // confirms must keep its raw (and so its proxy — list drivers keep the
289
+ // DOM row). Tentative rows never reach committed truth (they live in
290
+ // node overrides), so key-match the draft target's active override
291
+ // rows as a secondary pool. Committed rows queued first own their
292
+ // keys; overlay rows only extend coverage. Adopted raws enter staged
293
+ // truth; at settle the override reverts and the reveal re-seats the
294
+ // same raw.
295
+ const s = e[$TARGET]?.n;
296
+ if (s != null) {
297
+ for (const e of Reflect.ownKeys(s)) {
298
+ const t = s[e];
299
+ if (!hasActiveOverride(t)) continue;
300
+ const i = unwrapValue(unwrapOverride(t.o.Oe));
301
+ if (!isWrappable(i)) continue;
302
+ const r = n(i);
303
+ if (r === undefined) continue;
304
+ const l = (o ??= new Map).get(r);
305
+ if (l === undefined) o.set(r, [ i ]); else l.push(i);
306
+ }
307
+ }
308
+ for (let r = 0; r < i; r++) {
309
+ const i = t[r];
310
+ let s;
311
+ if (isWrappable(i) && o !== null) {
312
+ const e = n(i);
313
+ if (e !== undefined) {
314
+ for (const [t, n] of o) {
315
+ if (!sameKey(t, e)) continue;
316
+ s = n.shift();
317
+ if (n.length === 0) o.delete(t);
318
+ break;
319
+ }
320
+ }
321
+ }
322
+ if (s !== undefined) {
323
+ if (unwrapValue(e[r]) !== s) e[r] = s;
324
+ stagedApply(e[r], i, n);
325
+ } else {
326
+ const t = unwrapValue(e[r]);
327
+ if (!isEqual(t, i) && !targetsEqual(t, i)) e[r] = i;
328
+ }
329
+ }
330
+ } else {
331
+ for (let o = 0; o < i; o++) {
332
+ const i = t[o];
333
+ const r = unwrapValue(e[o]);
334
+ if (r === i) continue;
335
+ if (isWrappable(i) && isWrappable(r) && Array.isArray(i) === Array.isArray(r)) stagedApply(e[o], i, n); else if (!isEqual(r, i) && !targetsEqual(r, i)) e[o] = i;
336
+ }
337
+ }
338
+ if (e.length !== i) e.length = i;
339
+ return;
340
+ }
341
+ // Object merge; also the degenerate root-kind-change shape (arrays accept
342
+ // keyed writes/deletes, so a wholesale restatement still lands staged).
343
+ for (const o of Reflect.ownKeys(t)) {
344
+ if (i && o === "length") continue;
345
+ const r = t[o];
346
+ const s = unwrapValue(e[o]);
347
+ if (s === r) continue;
348
+ if (isWrappable(r) && isWrappable(s) && Array.isArray(r) === Array.isArray(s)) {
349
+ // Different-keyed entities never merge (tentative-channel parity):
350
+ // the incoming object replaces the slot wholesale.
351
+ if (n !== null) {
352
+ const t = n(s);
353
+ const i = n(r);
354
+ if (t !== undefined && i !== undefined && !sameKey(t, i)) {
355
+ e[o] = r;
356
+ continue;
357
+ }
358
+ }
359
+ stagedApply(e[o], r, n);
360
+ } else if (!isEqual(s, r) && !targetsEqual(s, r)) {
361
+ e[o] = r;
362
+ }
363
+ }
364
+ for (const n of Reflect.ownKeys(e)) {
365
+ if (i && n === "length" || n in t) continue;
366
+ delete e[n];
367
+ }
135
368
  }
136
369
 
137
370
  // ---- optimistic-only store machinery (moved from next/store.ts /
@@ -146,15 +379,30 @@ function createOptimisticStoreNext(e, t, n) {
146
379
  // at plain flush end. The blocked-check store-half keeps that transaction
147
380
  // from settling while the firewall is pending.
148
381
  const n = e.fam?.node;
149
- if (n?._e) globalQueue.initTransition(n._e);
382
+ if (n?.Ae) globalQueue.initTransition(n.Ae);
150
383
  const i = e.v;
384
+ // Patch channel (override-application site): the draft IS the intended
385
+ // visible state; prev is the view before these overrides apply. Bypasses
386
+ // the transition stash — optimism is visible in flight.
387
+ if (e.pc !== null && e.pc.p !== null) patchHooks.emitPatchOptimistic(e, t, optimisticView(e, i));
388
+ // Row-ops channel (family increment 2): optimistic STRUCTURE on an array
389
+ // rides node overrides — it never enters the reconcile walk — so a driven
390
+ // list must get its structural ops here, lane-timed. Identity diff of the
391
+ // pre-write optimistic view against the draft; aligned writes emit nothing.
392
+ if (e.pc !== null && e.pc.ro !== null && Array.isArray(t)) {
393
+ const n = optimisticView(e, i);
394
+ if (Array.isArray(n)) {
395
+ const i = buildIdentityRowOps(n, t);
396
+ if (i !== null) rowHooks.emitRowOpsOptimistic(e, t, i);
397
+ }
398
+ }
151
399
  const visible = (t, n) => {
152
400
  const i = e.n?.[t];
153
- return i !== undefined && hasActiveOverride(i) ? unwrapOverride(i.o?.De) : n;
401
+ return i !== undefined && hasActiveOverride(i) ? unwrapOverride(i.o?.Oe) : n;
154
402
  };
155
403
  const visiblePresent = t => {
156
404
  const n = e.h?.[t];
157
- return n !== undefined && hasActiveOverride(n) ? !!unwrapOverride(n.o?.De) : t in i;
405
+ return n !== undefined && hasActiveOverride(n) ? !!unwrapOverride(n.o?.Oe) : t in i;
158
406
  };
159
407
  let o = false;
160
408
  const r = Array.isArray(t);
@@ -194,91 +442,22 @@ function createOptimisticStoreNext(e, t, n) {
194
442
  // (structural ones already ride the key-set bump above).
195
443
  bumpDeep(e);
196
444
  // Discard the draft — committed raw is untouched (revert target by
197
- // construction). Register the root store for the scheduler's settle hooks
198
- // and the target for landing consumption (RUL-2).
199
- e.pb = null;
445
+ // construction) restoring any truth-staged backing this draft displaced
446
+ // (#3164 fold: ensurePB parked it so tentative writes could not pollute
447
+ // staged truth). Register the root store for the scheduler's settle hooks.
448
+ e.pb = stagedTruthPB.get(e) ?? null;
449
+ if (e.pb !== null) stagedTruthPB.delete(e);
200
450
  (e.fam.overlaid ??= new Set).add(e);
201
- GlobalQueue.In?.(e.fam.px ?? e.px);
202
- }
203
-
204
- /**
205
- * Landing consumption (RUL-2): fresh authoritative data supersedes every
206
- * tentative override in the family. Mirrors legacy clearProjectionOverride —
207
- * drop the override, clear lane/ownership, notify subscribers whose visible
208
- * value changes (reversion effects go to regular queues via the projection
209
- * write posture the caller holds).
210
- */ function consumeOverridesNext(e) {
211
- const t = e.overlaid;
212
- if (t === undefined || t.size === 0) return;
213
- runAuthoritative(() => {
214
- for (const e of t) {
215
- const drop = (e, t) => {
216
- if (!hasActiveOverride(e)) return;
217
- const n = unwrapOverride(e.o?.De);
218
- // Full legacy reset (clearOptimisticOverride parity): the landing is
219
- // authoritative NOW — fold committed into the node directly instead
220
- // of riding a transaction's commit (whose queues may be stashed with
221
- // the transaction parked; the wake would strand until it settles).
222
- ext(e).De = NOT_PENDING;
223
- e.T |= CONFIG_OPTIMISTIC;
224
- const i = e.o;
225
- if (i) {
226
- i.Et = null;
227
- i.Be = undefined;
228
- }
229
- e.Pe = NOT_PENDING;
230
- e.be = t;
231
- if (!e.Ue || !e.Ue(n, t)) {
232
- insertSubs(e, true);
233
- schedule();
234
- }
235
- };
236
- // Landing consumes STRUCTURAL optimism only (legacy layer parity):
237
- // membership edits, array length, and the value overrides written WITH
238
- // them (a key carrying an active presence override is an add/delete —
239
- // classified BEFORE the adoption may have made the key exist in landed
240
- // data). A pure value override on a key the landing carries stays with
241
- // its owning transaction (rapid-toggle contract: a live action's edit
242
- // of an existing entity rides on top of landed truth).
243
- const t = Array.isArray(e.v);
244
- const n = e.h;
245
- let i = null;
246
- if (n !== null) {
247
- for (const e of Reflect.ownKeys(n)) {
248
- if (hasActiveOverride(n[e])) (i ??= new Set).add(e);
249
- }
250
- }
251
- const o = e.n;
252
- if (o !== null) {
253
- for (const n of Reflect.ownKeys(o)) {
254
- const r = i?.has(n) || !(n in e.v) || t && n === "length";
255
- if (!r) continue;
256
- drop(o[n], t && n === "length" ? e.v.length : e.v[n]);
257
- }
258
- }
259
- if (n !== null) {
260
- for (const t of Reflect.ownKeys(n)) drop(n[t], t in e.v);
261
- }
262
- if (e.k !== null && hasActiveOverride(e.k)) {
263
- ext(e.k).De = NOT_PENDING;
264
- e.k.T |= CONFIG_OPTIMISTIC;
265
- const t = e.k.o;
266
- if (t) {
267
- t.Et = null;
268
- t.Be = undefined;
269
- }
270
- insertSubs(e.k, true);
271
- schedule();
272
- }
273
- }
274
- t.clear();
275
- });
451
+ GlobalQueue._n?.(e.fam.px ?? e.px);
276
452
  }
277
453
 
278
454
  /** Optimistic-view composition for snapshot/deep (O1: snapshot is the CURRENT
279
455
  * view, lane values included; a fresh copy per call during pending windows —
280
- * RUL-12). Returns `src` untouched when no override is active on `t`. */ function optimisticView(e, t) {
281
- if (e.fam?.opt !== true) return t;
456
+ * RUL-12). Returns `src` untouched when no override is active on `t`.
457
+ * Authoritative-view reads (until()'s predicate) skip composition entirely:
458
+ * the predicate observes authoritative truth, never the caller's tentative
459
+ * overlay. (Write-side emission callers never run under such a compute.) */ function optimisticView(e, t) {
460
+ if (e.fam?.opt !== true || authoritativeRead()) return t;
282
461
  let n = null;
283
462
  const ensure = () => n ??= Array.isArray(t) ? [ ...t ] : {
284
463
  ...t
@@ -288,7 +467,7 @@ function createOptimisticStoreNext(e, t, n) {
288
467
  for (const e of Reflect.ownKeys(i)) {
289
468
  const n = i[e];
290
469
  if (!hasActiveOverride(n)) continue;
291
- const o = unwrapOverride(n.o?.De);
470
+ const o = unwrapOverride(n.o?.Oe);
292
471
  if (e === "length" && Array.isArray(t)) {
293
472
  if (t.length !== o) ensure().length = o;
294
473
  } else if (!isEqual(t[e], o)) ensure()[e] = o;
@@ -299,7 +478,7 @@ function createOptimisticStoreNext(e, t, n) {
299
478
  for (const e of Reflect.ownKeys(o)) {
300
479
  const i = o[e];
301
480
  if (!hasActiveOverride(i)) continue;
302
- const r = !!unwrapOverride(i.o?.De);
481
+ const r = !!unwrapOverride(i.o?.Oe);
303
482
  if (!r && e in (n ?? t)) delete ensure()[e];
304
483
  }
305
484
  }
@@ -322,7 +501,9 @@ function applyTentative(e, t, n) {
322
501
  if (n) {
323
502
  const i = n(e);
324
503
  const o = n(t);
325
- if (i !== undefined && o !== undefined && i !== o) return null;
504
+ // SameValueZero (re-audit 3, P1-3): parity with the plain reconcile
505
+ // channel — NaN keys are self-equal.
506
+ if (i !== undefined && o !== undefined && !sameKey(i, o)) return null;
326
507
  }
327
508
  return r.get(unwrapValue(e)) ?? null;
328
509
  };
@@ -337,23 +518,35 @@ function applyTentative(e, t, n) {
337
518
  const t = n(r);
338
519
  if (t !== undefined) {
339
520
  if (i === null) {
521
+ // Occurrence-aware index queues (re-audit 3, P1-3): parity with
522
+ // the plain adoption window — duplicate keys match per
523
+ // occurrence, each view row consumed once.
340
524
  i = new Map;
341
525
  for (let t = 0; t < e.length; t++) {
342
526
  const o = unwrapValue(e[t]);
343
527
  if (isWrappable(o)) {
344
528
  const e = n(o);
345
- if (e !== undefined && !i.has(e)) i.set(e, o);
529
+ if (e === undefined) continue;
530
+ const r = i.get(e);
531
+ if (r === undefined) i.set(e, t); else if (Array.isArray(r)) r.push(t); else i.set(e, [ r, t ]);
346
532
  }
347
533
  }
348
534
  }
349
- s = i.get(t);
535
+ const o = i.get(t);
536
+ if (o === undefined) s = undefined; else if (Array.isArray(o)) {
537
+ s = unwrapValue(e[o.shift()]);
538
+ if (o.length === 1) i.set(t, o[0]);
539
+ } else {
540
+ s = unwrapValue(e[o]);
541
+ i.delete(t);
542
+ }
350
543
  } else s = unwrapValue(e[o]);
351
544
  } else s = unwrapValue(e[o]);
352
- const c = match(s, r);
353
- if (c !== null) {
545
+ const a = match(s, r);
546
+ if (a !== null) {
354
547
  // Keep the existing row in the slot (identity preserved); recurse.
355
548
  u[o] = unwrapValue(s);
356
- l.push([ c, r ]);
549
+ l.push([ a, r ]);
357
550
  }
358
551
  }
359
552
  } else {
@@ -369,10 +562,10 @@ function applyTentative(e, t, n) {
369
562
  }
370
563
  // Flat overrides for this level (adds, removals, moved slots, length, leaf
371
564
  // values) — preserve any live user draft backing across the call.
372
- const c = e.pb;
565
+ const a = e.pb;
373
566
  e.pb = null;
374
567
  notifyOptimisticWrites(e, u);
375
- e.pb = c;
568
+ e.pb = a;
376
569
  for (let e = 0; e < l.length; e++) applyTentative(l[e][0], unwrapValue(l[e][1]), n);
377
570
  }
378
571
 
@@ -382,4 +575,4 @@ function shallowWithSymbols(e) {
382
575
  return t;
383
576
  }
384
577
 
385
- export { consumeOverridesNext, createOptimisticStoreNext, notifyOptimisticWrites, optimisticView };
578
+ export { createOptimisticStoreNext, notifyOptimisticWrites, optimisticView, transitionHoldsOptimism };
@@ -0,0 +1,13 @@
1
+ let patchHooks = null;
2
+
3
+ let rowHooks = null;
4
+
5
+ function installPatchHooks(o) {
6
+ patchHooks = o;
7
+ }
8
+
9
+ function installRowHooks(o) {
10
+ rowHooks = o;
11
+ }
12
+
13
+ export { installPatchHooks, installRowHooks, patchHooks, rowHooks };