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

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 (131) hide show
  1. package/dist/dev-shared.js +5243 -0
  2. package/dist/dev.attribution.js +2333 -0
  3. package/dist/dev.js +1758 -6621
  4. package/dist/observe/affects.js +127 -0
  5. package/dist/observe/attribution.js +1 -0
  6. package/dist/observe/boundaries.js +586 -0
  7. package/dist/observe/core/action.js +167 -0
  8. package/dist/observe/core/async.js +736 -0
  9. package/dist/observe/core/attribution-hooks.js +60 -0
  10. package/dist/observe/core/attribution.js +2318 -0
  11. package/dist/{types-cjs/core/constants.d.cts → observe/core/constants.js} +135 -71
  12. package/dist/{types-cjs/core/context.d.cts → observe/core/context.js} +37 -12
  13. package/dist/observe/core/core.js +1289 -0
  14. package/dist/observe/core/dev.js +232 -0
  15. package/dist/observe/core/effect.js +179 -0
  16. package/dist/observe/core/error.js +80 -0
  17. package/dist/observe/core/external.js +98 -0
  18. package/dist/observe/core/graph.js +155 -0
  19. package/dist/observe/core/heap.js +147 -0
  20. package/dist/observe/core/invariants.js +43 -0
  21. package/dist/observe/core/lanes.js +168 -0
  22. package/dist/observe/core/optimistic.js +438 -0
  23. package/dist/observe/core/owner.js +308 -0
  24. package/dist/observe/core/scheduler.js +1190 -0
  25. package/dist/observe/core/verdict.js +551 -0
  26. package/dist/observe/index.js +55 -0
  27. package/dist/observe/map.js +482 -0
  28. package/dist/{types-cjs/signals.d.cts → observe/signals.js} +305 -317
  29. package/dist/observe/store/index.js +38 -0
  30. package/dist/observe/store/next/optimistic.js +651 -0
  31. package/dist/observe/store/next/projection.js +242 -0
  32. package/dist/observe/store/next/reconcile.js +373 -0
  33. package/dist/observe/store/next/store.js +2213 -0
  34. package/dist/observe/store/next/target.js +51 -0
  35. package/dist/observe/store/store.js +323 -0
  36. package/dist/observe/store/storePath.js +103 -0
  37. package/dist/observe/store/utils.js +210 -0
  38. package/dist/prod/attribution.js +32 -0
  39. package/dist/prod/boundaries.js +3 -1
  40. package/dist/prod/core/action.js +52 -32
  41. package/dist/prod/core/async.js +177 -128
  42. package/dist/prod/core/attribution-hooks.js +3 -0
  43. package/dist/prod/core/constants.js +32 -1
  44. package/dist/prod/core/context.js +10 -16
  45. package/dist/prod/core/core.js +505 -286
  46. package/dist/prod/core/dev.js +2 -0
  47. package/dist/prod/core/effect.js +72 -43
  48. package/dist/prod/core/external.js +2 -2
  49. package/dist/prod/core/graph.js +40 -36
  50. package/dist/prod/core/heap.js +50 -51
  51. package/dist/prod/core/lanes.js +43 -22
  52. package/dist/prod/core/optimistic.js +224 -80
  53. package/dist/prod/core/owner.js +43 -39
  54. package/dist/prod/core/scheduler.js +347 -232
  55. package/dist/prod/core/verdict.js +71 -74
  56. package/dist/prod/index.js +9 -5
  57. package/dist/prod/map.js +311 -150
  58. package/dist/prod/signals.js +52 -38
  59. package/dist/prod/store/next/optimistic.js +127 -131
  60. package/dist/prod/store/next/projection.js +3 -3
  61. package/dist/prod/store/next/reconcile.js +149 -294
  62. package/dist/prod/store/next/store.js +694 -380
  63. package/dist/prod/store/next/target.js +32 -10
  64. package/dist/prod/store/store.js +12 -14
  65. package/dist/prod/store/utils.js +36 -27
  66. package/dist/types/attribution.d.ts +15 -0
  67. package/dist/types/attribution.prod.d.ts +13 -0
  68. package/dist/types/core/async.d.ts +1 -1
  69. package/dist/types/core/attribution-hooks.d.ts +183 -6
  70. package/dist/types/core/attribution.d.ts +435 -24
  71. package/dist/types/core/constants.d.ts +31 -0
  72. package/dist/types/core/core.d.ts +19 -3
  73. package/dist/types/core/dev.d.ts +165 -26
  74. package/dist/types/core/effect.d.ts +8 -1
  75. package/dist/types/core/heap.d.ts +5 -3
  76. package/dist/types/core/index.d.ts +2 -1
  77. package/dist/types/core/invariants.d.ts +1 -1
  78. package/dist/types/core/lanes.d.ts +17 -0
  79. package/dist/types/core/scheduler.d.ts +69 -4
  80. package/dist/types/core/types.d.ts +29 -9
  81. package/dist/types/index.d.ts +9 -2
  82. package/dist/types/map.d.ts +2 -0
  83. package/dist/types/signals.d.ts +10 -0
  84. package/dist/types/store/index.d.ts +4 -6
  85. package/dist/types/store/next/optimistic.d.ts +4 -2
  86. package/dist/types/store/next/reconcile.d.ts +5 -12
  87. package/dist/types/store/next/store.d.ts +8 -9
  88. package/dist/types/store/next/target.d.ts +56 -55
  89. package/dist/types/store/store.d.ts +14 -9
  90. package/dist/types/store/utils.d.ts +7 -0
  91. package/package.json +21 -16
  92. package/dist/node.cjs +0 -11121
  93. package/dist/prod/store/next/patch-hooks.js +0 -13
  94. package/dist/prod/store/next/patch.js +0 -614
  95. package/dist/types/store/next/patch-hooks.d.ts +0 -41
  96. package/dist/types/store/next/patch.d.ts +0 -91
  97. package/dist/types-cjs/affects.d.cts +0 -47
  98. package/dist/types-cjs/boundaries.d.cts +0 -175
  99. package/dist/types-cjs/core/action.d.cts +0 -64
  100. package/dist/types-cjs/core/async.d.cts +0 -23
  101. package/dist/types-cjs/core/attribution-hooks.d.cts +0 -63
  102. package/dist/types-cjs/core/attribution.d.cts +0 -239
  103. package/dist/types-cjs/core/core.d.cts +0 -176
  104. package/dist/types-cjs/core/dev.d.cts +0 -95
  105. package/dist/types-cjs/core/effect.d.cts +0 -30
  106. package/dist/types-cjs/core/error.d.cts +0 -56
  107. package/dist/types-cjs/core/external.d.cts +0 -15
  108. package/dist/types-cjs/core/graph.d.cts +0 -28
  109. package/dist/types-cjs/core/heap.d.cts +0 -22
  110. package/dist/types-cjs/core/index.d.cts +0 -13
  111. package/dist/types-cjs/core/invariants.d.cts +0 -59
  112. package/dist/types-cjs/core/lanes.d.cts +0 -44
  113. package/dist/types-cjs/core/optimistic.d.cts +0 -6
  114. package/dist/types-cjs/core/owner.d.cts +0 -124
  115. package/dist/types-cjs/core/scheduler.d.cts +0 -231
  116. package/dist/types-cjs/core/types.d.cts +0 -204
  117. package/dist/types-cjs/core/verdict.d.cts +0 -2
  118. package/dist/types-cjs/index.d.cts +0 -10
  119. package/dist/types-cjs/map.d.cts +0 -69
  120. package/dist/types-cjs/package.json +0 -3
  121. package/dist/types-cjs/store/index.d.cts +0 -21
  122. package/dist/types-cjs/store/next/optimistic.d.cts +0 -23
  123. package/dist/types-cjs/store/next/patch-hooks.d.cts +0 -41
  124. package/dist/types-cjs/store/next/patch.d.cts +0 -91
  125. package/dist/types-cjs/store/next/projection.d.cts +0 -8
  126. package/dist/types-cjs/store/next/reconcile.d.cts +0 -17
  127. package/dist/types-cjs/store/next/store.d.cts +0 -127
  128. package/dist/types-cjs/store/next/target.d.cts +0 -192
  129. package/dist/types-cjs/store/store.d.cts +0 -138
  130. package/dist/types-cjs/store/storePath.d.cts +0 -58
  131. package/dist/types-cjs/store/utils.d.cts +0 -74
@@ -2,7 +2,7 @@ import { CONFIG_HAS_LANE, NOT_PENDING } from "./constants.js";
2
2
 
3
3
  import { ext } from "./core.js";
4
4
 
5
- import { currentTransition, activeTransition } from "./scheduler.js";
5
+ import { currentTransition, waitingTransition, activeTransition } from "./scheduler.js";
6
6
 
7
7
  // Map from optimistic signal to its lane (reused for multiple writes to same signal)
8
8
  const signalLanes = new WeakMap;
@@ -20,15 +20,15 @@ const activeLanes = new Set;
20
20
  }
21
21
  // Detect parent lane: _parentSource chains from pendingSignal → pendingValueComputed → original.
22
22
  // The child lane should not merge with the parent lane.
23
- const i = n.o?.Tt;
24
- const t = i?.o?.Je;
23
+ const i = n.o?.Gt;
24
+ const t = i?.o?.Oe;
25
25
  const r = t ? findLane(t) : null;
26
26
  e = {
27
27
  nn: n,
28
- Oe: new Set,
28
+ he: new Set,
29
29
  tn: [ [], [] ],
30
30
  rn: null,
31
- Ae: activeTransition,
31
+ ge: activeTransition,
32
32
  an: r
33
33
  };
34
34
  signalLanes.set(n, e);
@@ -39,8 +39,8 @@ const activeLanes = new Set;
39
39
  // parent-child is a property of the nodes, not of write order — otherwise
40
40
  // the owner's write merges the companion's subscribers into this lane and
41
41
  // their effects wait on its async instead of flushing immediately.
42
- adoptCompanionLane(n.o?.ye, e);
43
- adoptCompanionLane(n.o?.Ce, e);
42
+ adoptCompanionLane(n.o?.Le, e);
43
+ adoptCompanionLane(n.o?.Qe, e);
44
44
  return e;
45
45
  }
46
46
 
@@ -61,6 +61,27 @@ function adoptCompanionLane(n, e) {
61
61
  return n;
62
62
  }
63
63
 
64
+ /**
65
+ * Is the lane held? `_pendingAsync` records the async the lane OWNS (derived
66
+ * under it); a transaction's reporter map records the async a render effect
67
+ * OBSERVED pending with no boundary taking it (INV-3, the one registration
68
+ * site). A hold needs both — the same rule the transaction itself uses, so a
69
+ * memo nobody renders, or one a fallback-showing boundary caught, cannot tear
70
+ * a frame and holds nothing (#3289). An orphan lane has no observation record
71
+ * and never holds.
72
+ *
73
+ * The observation is looked up per NODE, in whichever live transaction
74
+ * recorded it — not in this lane's transaction. Lanes merge across
75
+ * transactions (#2912: ownership never travels through lanes), so after a
76
+ * merge the root's transaction holds the observations of only one member;
77
+ * the async the other member's transaction observed must hold the merged
78
+ * reveal just the same (A15 for lanes, #3335).
79
+ */ function laneHeld(n) {
80
+ if (!n.ge) return false;
81
+ for (const e of n.he) if (waitingTransition(e) !== null) return true;
82
+ return false;
83
+ }
84
+
64
85
  /**
65
86
  * Merge two lanes when their dependency graphs overlap.
66
87
  */ function mergeLanes(n, e) {
@@ -71,8 +92,8 @@ function adoptCompanionLane(n, e) {
71
92
  // Move (not copy) the merged lane's work: after the merge all routing goes
72
93
  // through findLane() to the root, so anything left behind here is dead —
73
94
  // and anything *added* here later is a routing bug (INV-5).
74
- for (const i of e.Oe) n.Oe.add(i);
75
- e.Oe.clear();
95
+ for (const i of e.he) n.he.add(i);
96
+ e.he.clear();
76
97
  n.tn[0].push(...e.tn[0]);
77
98
  n.tn[1].push(...e.tn[1]);
78
99
  e.tn[0].length = 0;
@@ -83,11 +104,11 @@ function adoptCompanionLane(n, e) {
83
104
  /**
84
105
  * Resolve a node's lane: follow union-find chain, verify active, clear if stale.
85
106
  */ function resolveLane(n) {
86
- const e = n.o?.Je;
107
+ const e = n.o?.Oe;
87
108
  if (!e) return undefined;
88
109
  const i = findLane(e);
89
110
  if (activeLanes.has(i)) return i;
90
- if (n.o !== null) n.o.Je = undefined;
111
+ if (n.o !== null) n.o.Oe = undefined;
91
112
  return undefined;
92
113
  }
93
114
 
@@ -97,19 +118,19 @@ function resolveTransition(n) {
97
118
  // transactions (#2912) — the merged root's _transition would hand this
98
119
  // node's override to whichever action wrote last through the shared
99
120
  // reader. Chase merge chains; a dead owner settled through another path.
100
- if (hasActiveOverride(n) && n.o?.Nt) {
101
- const e = ext(n).Nt = currentTransition(n.o?.Nt);
102
- if (e.sn !== true) return e;
103
- if (n.o !== null) n.o.Nt = null;
121
+ if (hasActiveOverride(n) && n.o?.Ot) {
122
+ const e = ext(n).Ot = currentTransition(n.o?.Ot);
123
+ if (e.ft !== true) return e;
124
+ if (n.o !== null) n.o.Ot = null;
104
125
  }
105
- return resolveLane(n)?.Ae ?? n.Ae;
126
+ return resolveLane(n)?.ge ?? n.ge;
106
127
  }
107
128
 
108
129
  /**
109
130
  * Check if a node has an active optimistic override.
110
131
  */ function hasActiveOverride(n) {
111
132
  const e = n.o;
112
- return e !== null && e.Pe !== undefined && e.Pe !== NOT_PENDING;
133
+ return e !== null && e.be !== undefined && e.be !== NOT_PENDING;
113
134
  }
114
135
 
115
136
  /**
@@ -117,13 +138,13 @@ function resolveTransition(n) {
117
138
  * a different active lane), merge unless the node has an active override.
118
139
  */ function assignOrMergeLane(n, e) {
119
140
  const i = findLane(e);
120
- const t = n.o?.Je;
141
+ const t = n.o?.Oe;
121
142
  if (t) {
122
143
  // If the subscriber's lane was merged into another lane, it's stale —
123
144
  // replace it with the new source lane instead of following the merge chain
124
145
  // (which would incorrectly merge the new lane into the old group)
125
146
  if (t.rn) {
126
- ext(n).Je = e;
147
+ ext(n).Oe = e;
127
148
  n.T |= CONFIG_HAS_LANE;
128
149
  return;
129
150
  }
@@ -133,15 +154,15 @@ function resolveTransition(n) {
133
154
  // Parent-child lanes stay independent so isPending resolves without
134
155
  // waiting for the parent's async. The child keeps ownership.
135
156
  if (i.an && findLane(i.an) === r) {
136
- ext(n).Je = e;
157
+ ext(n).Oe = e;
137
158
  n.T |= CONFIG_HAS_LANE;
138
159
  } else if (r.an && findLane(r.an) === i) ; else mergeLanes(i, r);
139
160
  }
140
161
  return;
141
162
  }
142
163
  }
143
- ext(n).Je = e;
164
+ ext(n).Oe = e;
144
165
  n.T |= CONFIG_HAS_LANE;
145
166
  }
146
167
 
147
- export { activeLanes, assignOrMergeLane, findLane, getOrCreateLane, hasActiveOverride, mergeLanes, resolveLane, resolveTransition, signalLanes };
168
+ export { activeLanes, assignOrMergeLane, findLane, getOrCreateLane, hasActiveOverride, laneHeld, mergeLanes, resolveLane, resolveTransition, signalLanes };
@@ -1,14 +1,14 @@
1
- import { NOT_PENDING, unwrapOverride, STATUS_UNINITIALIZED, REACTIVE_DIRTY, REACTIVE_CHECK, CONFIG_HAS_LANE, OVERRIDE_UNDEFINED, STATUS_PENDING, EFFECT_RENDER, REACTIVE_MANUAL_WRITE, REACTIVE_OPTIMISTIC_DIRTY, EFFECT_USER } from "./constants.js";
1
+ import { NOT_PENDING, unwrapOverride, STATUS_UNINITIALIZED, REACTIVE_DIRTY, REACTIVE_CHECK, CONFIG_HAS_LANE, OVERRIDE_UNDEFINED, STATUS_PENDING, CONFIG_OVERRIDE_SUPERSEDED, EFFECT_RENDER, CONFIG_AUTHORITATIVE_OBSERVED, REACTIVE_MANUAL_WRITE, REACTIVE_OPTIMISTIC_DIRTY, LANE_RUN, EFFECT_USER } from "./constants.js";
2
2
 
3
- import { ext, latestReadActive, currentOptimisticLane, stale } from "./core.js";
3
+ import { ext, stale, latestReadActive, currentOptimisticLane } from "./core.js";
4
4
 
5
5
  import { NotReadyError } from "./error.js";
6
6
 
7
7
  import "./invariants.js";
8
8
 
9
- import { resolveTransition, getOrCreateLane, hasActiveOverride, activeLanes, signalLanes, findLane, resolveLane, assignOrMergeLane } from "./lanes.js";
9
+ import { resolveTransition, getOrCreateLane, hasActiveOverride, activeLanes, signalLanes, laneHeld, findLane, resolveLane, assignOrMergeLane } from "./lanes.js";
10
10
 
11
- import { GlobalQueue, activeTransition, globalQueue, clock, insertSubs, schedule } from "./scheduler.js";
11
+ import { GlobalQueue, activeTransition, globalQueue, origin, clock, insertSubs, schedule, queuePendingNode } from "./scheduler.js";
12
12
 
13
13
  /**
14
14
  * The optimistic write engine, moved out of core.ts/scheduler.ts. Everything
@@ -24,9 +24,9 @@ import { GlobalQueue, activeTransition, globalQueue, clock, insertSubs, schedule
24
24
  * once the gate holds — the same late-binding contract as verdict.ts.
25
25
  */
26
26
  /** The optimistic half of setSignal, fired when `_overrideValue !== undefined`. */ function optimisticWrite(e, n) {
27
- const t = e.o?.Pe !== NOT_PENDING;
28
- const i = t ? unwrapOverride(e.o?.Pe) : e.be;
29
- if (typeof n === "function") n = n(i);
27
+ const i = e.o?.be !== NOT_PENDING;
28
+ const t = i ? unwrapOverride(e.o?.be) : e.me;
29
+ if (typeof n === "function") n = n(t);
30
30
  const u = !!(e.S & STATUS_UNINITIALIZED) ||
31
31
  // A dirty node's _value is stale (its queued recompute hasn't run — e.g.
32
32
  // a latest() shadow marked by the previous landing's companion snap), so
@@ -34,36 +34,47 @@ import { GlobalQueue, activeTransition, globalQueue, clock, insertSubs, schedule
34
34
  // push returning the shadow to that stale value was dropped, the snap
35
35
  // recompute then committed the parent's old value, and the banner showed
36
36
  // the previous transition's target (#3041 follow-up).
37
- !!((e.ie ?? 0) & (REACTIVE_DIRTY | REACTIVE_CHECK)) || !e.pe || !e.pe(i, n);
37
+ !!((e.ie ?? 0) & (REACTIVE_DIRTY | REACTIVE_CHECK)) || !e.ve || !e.ve(t, n);
38
38
  if (!u) {
39
39
  // Same-value write with an active override still entangles the current
40
- // action's transition — the hold must outlast all overlapping actions.
41
- if (t) {
40
+ // action's transition — the hold must outlast all overlapping actions
41
+ // and renews the override's PROVENANCE: the newer action re-asks the
42
+ // question, so an older action's answer arriving later is stale to it
43
+ // too (#3331; a same-value re-prediction otherwise let the first
44
+ // action's slow source supersede and restart the downstream flight).
45
+ if (i) {
42
46
  const n = resolveTransition(e);
43
47
  if (n && activeTransition !== n) globalQueue.initTransition(n);
48
+ if (origin > e.o.Rt) e.o.Rt = origin;
44
49
  }
45
50
  return n;
46
51
  }
47
- if (t) globalQueue.initTransition(resolveTransition(e));
52
+ if (i) globalQueue.initTransition(resolveTransition(e));
48
53
  // No revert target is stashed: while the override is active every reader
49
54
  // sees it (A17), so authoritative arrivals commit silently into _value and
50
55
  // reverting is just dropping the override — _value is already correct.
51
- else globalQueue.m.ze.push(e);
56
+ else globalQueue.m.it.push(e);
52
57
  // Stamp ownership on the node (post-merge, so entangled writers share the
53
58
  // joint root). resolveTransition prefers this over the lane's _transition,
54
59
  // which a shared subscriber can merge across transactions (#2912).
55
- ext(e).Nt = activeTransition;
56
- const l = getOrCreateLane(e);
57
- ext(e).Je = l;
58
- e.T |= CONFIG_HAS_LANE;
60
+ ext(e).Ot = activeTransition;
61
+ ext(e).It = clock;
62
+ // Provenance: the action asking. An answer an OLDER action's flight brings
63
+ // back is a stale question and holds silently to commit (#3331).
64
+ ext(e).Rt = origin;
65
+ const r = getOrCreateLane(e);
66
+ ext(e).Oe = r;
67
+ // A fresh override re-masks: whatever truth is staged, this write is the
68
+ // value for the graph again until the source answers it (#3331).
69
+ e.T = (e.T | CONFIG_HAS_LANE) & ~CONFIG_OVERRIDE_SUPERSEDED;
59
70
  // Literal undefined must not land raw: the slot doubles as the optimistic
60
71
  // brand, and erasing it makes the write invisible and routes follow-up
61
72
  // writes off the optimistic path into permanent commits (#2898).
62
- ext(e).Pe = n === undefined ? OVERRIDE_UNDEFINED : n;
73
+ ext(e).be = n === undefined ? OVERRIDE_UNDEFINED : n;
63
74
  // syncCompanions only pokes _pendingSignal/_latestValueComputed — with
64
75
  // neither companion present the call is a guaranteed no-op.
65
- (e.o?.ye !== undefined || e.o?.Ce !== undefined) && GlobalQueue.Ue !== null && GlobalQueue.Ue(e, n);
66
- if (e.oe !== undefined) e.Te = clock;
76
+ (e.o?.Le !== undefined || e.o?.Qe !== undefined) && GlobalQueue.pe !== null && GlobalQueue.pe(e, n);
77
+ if (e.oe !== undefined) e._e = clock;
67
78
  // §12e: computed-only slot
68
79
  insertSubs(e, true);
69
80
  schedule();
@@ -75,9 +86,9 @@ import { GlobalQueue, activeTransition, globalQueue, clock, insertSubs, schedule
75
86
  * while one of its optimistic nodes holds an active override that is still
76
87
  * pending on real (non-affects-sentinel) async.
77
88
  */ function transitionBlocked(e) {
78
- for (let n = 0; n < e.ze.length; n++) {
79
- const t = e.ze[n];
80
- if (hasActiveOverride(t) && "S" in t && t.S & STATUS_PENDING && t.o?._ instanceof NotReadyError) {
89
+ for (let n = 0; n < e.it.length; n++) {
90
+ const i = e.it[n];
91
+ if (hasActiveOverride(i) && "S" in i && i.S & STATUS_PENDING && i.o?._ instanceof NotReadyError) {
81
92
  return true;
82
93
  }
83
94
  }
@@ -89,44 +100,146 @@ function resolveOptimisticNodes(e) {
89
100
  // notifications) may push fresh optimistic nodes; only this batch settles
90
101
  // now, so iterate a fixed window and splice it out at the end.
91
102
  const n = e.length;
92
- for (let t = 0; t < n; t++) {
93
- const n = e[t];
94
- if (n.o !== null) n.o.Je = undefined;
103
+ for (let i = 0; i < n; i++) {
104
+ const n = e[i];
105
+ if (n.o !== null) n.o.Oe = undefined;
95
106
  // Revert is a pure drop: there is no revert target to commit —
96
107
  // override-covered authoritative values hold in _pendingValue and
97
108
  // elevate on their OWN transition's schedule (A18 as re-ruled 2026-07-07).
98
109
  if (!(n.S & STATUS_PENDING)) n.S &= ~STATUS_UNINITIALIZED;
99
- const i = n.o?.Pe;
100
- ext(n).Pe = NOT_PENDING;
101
- if (i !== NOT_PENDING && n.be !== unwrapOverride(i)) insertSubs(n, true);
102
- n.Ae = null;
103
- if (n.o !== null) n.o.Nt = null;
110
+ const t = n.o?.be;
111
+ ext(n).be = NOT_PENDING;
112
+ // A superseded override's subscribers already re-derived from the truth
113
+ // when it arrived (#3331) — the drop changes nothing they read. Everyone
114
+ // else learns of the correction here: this drop IS their notification.
115
+ const u = (n.T & CONFIG_OVERRIDE_SUPERSEDED) !== 0;
116
+ n.T &= ~CONFIG_OVERRIDE_SUPERSEDED;
117
+ if (!u && t !== NOT_PENDING && n.me !== unwrapOverride(t)) insertSubs(n, true);
118
+ n.ge = null;
119
+ if (n.o !== null) n.o.Ot = null;
104
120
  }
105
121
  // Settlement checkpoint (#2838): companions caught in this batch (or owned
106
122
  // by a node in it) re-derive from committed state, so verdicts survive the
107
123
  // transition that produced them (A19 — pending is a property of the data).
108
- for (let t = 0; t < n; t++) {
109
- const n = e[t];
110
- if (n.o?.ye || n.o?.Ce) GlobalQueue.un(n);
111
- const i = n.o?.Tt;
112
- if (i && (i.o?.ye === n || i.o?.Ce === n)) GlobalQueue.un(i);
124
+ for (let i = 0; i < n; i++) {
125
+ const n = e[i];
126
+ if (n.o?.Le || n.o?.Qe) GlobalQueue.un(n);
127
+ const t = n.o?.Gt;
128
+ if (t && (t.o?.Le === n || t.o?.Qe === n)) GlobalQueue.un(t);
113
129
  }
114
130
  e.splice(0, n);
115
131
  }
116
132
 
133
+ /**
134
+ * A18 supersession (#3331): the node's own source arrived with a value that
135
+ * differs from its active override. "Knowing otherwise" ends the optimism for
136
+ * the graph at once: the override stays only as the DISPLAYED value (untracked
137
+ * reads, the applied frame) until the owning transaction commits, while
138
+ * tracked readers see the staged truth and re-derive from it as that
139
+ * transaction's held work — so async downstream restarts now, not at the
140
+ * revert (no waterfall).
141
+ *
142
+ * The lane's job for this node is over: a lane applies an optimistic view
143
+ * ahead of its transaction, and there is no optimistic view left — the
144
+ * corrected cascade is plain transaction-held work (staged memos, effect runs
145
+ * in the stashable queues). Demote the node and every cascade member that
146
+ * rides this lane; the caller then notifies on the plain channel. Runners the
147
+ * lane still holds for demoted effects (the optimistic frame that never got to
148
+ * apply) defer to the regular queue in runEffect. In a lane merged with a
149
+ * still-optimistic source, members shared with that source lose their lane
150
+ * too and simply wait for the transaction — less optimistic, never torn.
151
+ *
152
+ * A later arrival EQUAL to the override (an earlier action's answer superseded
153
+ * this one's; now this one's answer confirms it) ends the supersession: the
154
+ * graph re-derives from the override, which is the truth again. Notifies the
155
+ * subscribers in both cases. A plain matching confirmation (no supersession
156
+ * in force) is A17-silent for ordinary subscribers and wakes only an
157
+ * authoritative-view reader (until()'s predicate, refresh()'s waiter) that
158
+ * observed this node past its override — "authoritative arrival equal to the
159
+ * override" is exactly the acknowledgment it waits for (#3164, #3303). The
160
+ * wake hook is installed by the setters of that bit; optional here because
161
+ * the bit only implies the optimistic engine was consulted.
162
+ */ function supersedeOverride(e, n) {
163
+ const i = !e.ve || !e.ve(n, unwrapOverride(e.o.be));
164
+ if (!i) {
165
+ if (!(e.T & CONFIG_OVERRIDE_SUPERSEDED)) {
166
+ if (e.T & CONFIG_AUTHORITATIVE_OBSERVED) GlobalQueue.Qt?.(e);
167
+ return;
168
+ }
169
+ e.T &= ~CONFIG_OVERRIDE_SUPERSEDED;
170
+ } else {
171
+ // Provenance (#3331): an answer brought back by an OLDER action than the
172
+ // one that wrote this override answers a question the user has since
173
+ // changed. It is staged like any other landing and reveals if it is
174
+ // still the truth when the transaction commits, but it does not move the
175
+ // graph now — a slow source must not leak back in over a newer intent.
176
+ // 0 is mainline (no action): always the current question.
177
+ if (origin && origin < e.o.Rt) return;
178
+ e.T |= CONFIG_OVERRIDE_SUPERSEDED;
179
+ const n = e.o?.Oe;
180
+ if (n) {
181
+ const i = findLane(n);
182
+ const t = [ e ];
183
+ while (t.length) {
184
+ const e = t.pop();
185
+ const n = e.o?.Oe;
186
+ if (!n || findLane(n) !== i) continue;
187
+ e.o.Oe = undefined;
188
+ i.he.delete(e);
189
+ for (let n = e.u; n !== null; n = n.Te) t.push(n.Ie);
190
+ }
191
+ }
192
+ }
193
+ insertSubs(e);
194
+ }
195
+
196
+ /** read()'s value for a tracked reader of a superseded node: the truth —
197
+ * staged, or already committed (a mainline landing commits at the head of
198
+ * its flush, ahead of the heap run, and the override drops only at the
199
+ * batch's end; in between the graph must not fall back to the override it
200
+ * has left) — or the displayed override for a stale (render) reader of some
201
+ * OTHER transaction, the same visibility a foreign transaction's staged
202
+ * write has. */ function supersededRead(e) {
203
+ if (stale && e.ge && activeTransition !== e.ge) return unwrapOverride(e.o?.be);
204
+ return e.Ge !== NOT_PENDING ? e.Ge : e.me;
205
+ }
206
+
207
+ /**
208
+ * An authoritative store landing on an override-covered node — a derived
209
+ * optimistic store's own truth arriving over a tentative edit through the
210
+ * projection-write channel (setSignal under projectionWriteActive). The
211
+ * store twin of asyncWrite's override branch: the truth stages for its
212
+ * transaction's commit whatever its relation to the committed value (a
213
+ * landing equal to committed still differs from the override), companions
214
+ * learn of it, and supersedeOverride decides the rest — A18 supersession with
215
+ * action provenance, or an A17-silent confirmation (#3331). Before this the
216
+ * landing took setSignal's plain path: a differing truth staged silently
217
+ * under the override and the graph never moved until the commit.
218
+ */ function landOnOverride(e, n) {
219
+ const i = e.Ge === NOT_PENDING ? e.me : e.Ge;
220
+ if (typeof n === "function") n = n(i);
221
+ if (e.Ge === NOT_PENDING) queuePendingNode(e);
222
+ e.Ge = n;
223
+ GlobalQueue.pe?.(e, n);
224
+ supersedeOverride(e, n);
225
+ schedule();
226
+ return n;
227
+ }
228
+
117
229
  function runQueue(e, n) {
118
- for (let t = 0; t < e.length; t++) e[t](n);
230
+ for (let i = 0; i < e.length; i++) e[i](n | LANE_RUN);
119
231
  }
120
232
 
121
233
  /**
122
- * Run effects from all lanes that are ready (no pending async).
234
+ * Run effects from all lanes that are ready (no OBSERVED pending async — see
235
+ * laneHeld).
123
236
  */ function runLaneEffects(e) {
124
237
  for (const n of activeLanes) {
125
- if (n.rn || n.Oe.size > 0) continue;
126
- const t = n.tn[e - 1];
127
- if (t.length) {
238
+ if (n.rn || laneHeld(n)) continue;
239
+ const i = n.tn[e - 1];
240
+ if (i.length) {
128
241
  n.tn[e - 1] = [];
129
- runQueue(t, e);
242
+ runQueue(i, e);
130
243
  }
131
244
  }
132
245
  // Optimistic patch applications ride the same visibility slot as lane
@@ -136,14 +249,14 @@ function runQueue(e, n) {
136
249
 
137
250
  function cleanupCompletedLanes(e) {
138
251
  for (const n of activeLanes) {
139
- const t = e ? n.Ae === e : !n.Ae;
140
- if (!t) continue;
252
+ const i = e ? n.ge === e : !n.ge;
253
+ if (!i) continue;
141
254
  if (!n.rn) {
142
255
  if (n.tn[0].length) runQueue(n.tn[0], EFFECT_RENDER);
143
256
  if (n.tn[1].length) runQueue(n.tn[1], EFFECT_USER);
144
257
  }
145
- if (n.nn.o?.Je === n) if (n.nn.o !== null) n.nn.o.Je = undefined;
146
- n.Oe.clear();
258
+ if (n.nn.o?.Oe === n) if (n.nn.o !== null) n.nn.o.Oe = undefined;
259
+ n.he.clear();
147
260
  n.tn[0].length = 0;
148
261
  n.tn[1].length = 0;
149
262
  activeLanes.delete(n);
@@ -152,31 +265,51 @@ function cleanupCompletedLanes(e) {
152
265
  }
153
266
 
154
267
  /** read()'s per-lane suspension test (pending-throw path, lane context). */ function laneSuspends(e) {
268
+ // An UNINITIALIZED async source suspends regardless of lane (#3276): a
269
+ // lane mismatch preserves an already-committed stale value, but a source
270
+ // with no committed truth has nothing to serve — the cross-lane read
271
+ // surfaced a fabricated `undefined` where latest() itself suspends
272
+ // (latestRead rethrows NotReady for tracked uninitialized reads). Lives
273
+ // here rather than read()'s throw path so the floor bundles don't pay:
274
+ // this is only reachable under a lane, which implies the engine.
275
+ if (e.S & STATUS_UNINITIALIZED) return true;
155
276
  // Per-lane suspension: only throw if in same lane as pending async
156
277
  // AND the node doesn't have an active override (overrides are the visible value,
157
278
  // downstream in the lane should read the override, not throw)
158
- const n = e.o?.Je;
279
+ const n = e.o?.Oe;
159
280
  if (!n) return false;
160
281
  return findLane(n) === findLane(currentOptimisticLane) && !hasActiveOverride(e);
161
282
  }
162
283
 
284
+ /**
285
+ * read()'s reveal carve-out asks whether a pending node is routed through a
286
+ * LIVE lane: a lane-derived flight's inputs are already revealed through the
287
+ * lane (the override, or latest()'s fresh value), so a stale reader of another
288
+ * transaction must hold on the flight rather than show the node's committed
289
+ * value beside them (#3334). Exact, not sticky: `resolveLane` clears a lane
290
+ * reference the engine has since retired, so a node that was once lane-routed
291
+ * and is now pending under a plain hold is judged by that hold alone.
292
+ */ function laneLive(e) {
293
+ return resolveLane(e) !== undefined;
294
+ }
295
+
163
296
  /**
164
297
  * read()'s entanglement gate: a reader recomputing under an optimistic lane
165
298
  * that reads a pending mid-transition write sees the committed value; the sub
166
299
  * is recorded for replay at commit.
167
- */ function gatedRead(e, n, t) {
168
- if (latestReadActive || e.Re === NOT_PENDING || e.oe || n !== e && !(n.ie & REACTIVE_MANUAL_WRITE)) {
300
+ */ function gatedRead(e, n, i) {
301
+ if (latestReadActive || e.Ge === NOT_PENDING || e.oe || n !== e && !(n.ie & REACTIVE_MANUAL_WRITE)) {
169
302
  return false;
170
303
  }
171
- activeTransition.cn.add(t);
304
+ activeTransition.lt.add(i);
172
305
  return true;
173
306
  }
174
307
 
175
308
  /**
176
309
  * read()'s value selection under a lane: return the committed `_value` for
177
310
  * optimistic/lane-assigned signals, stale-mode reads, and pending owners.
178
- */ function laneReadsCommitted(e, n, t) {
179
- if (e.o?.Pe !== undefined || !!e.o?.Je || !!(n.S & STATUS_PENDING)) {
311
+ */ function laneReadsCommitted(e, n, i) {
312
+ if (e.o?.be !== undefined || !!e.o?.Oe || !!(n.S & STATUS_PENDING)) {
180
313
  // The committed view hides a staged in-flight value that will promote
181
314
  // silently (commitPendingNode never re-notifies). gatedRead records plain
182
315
  // signals for replay at commit; async memos are excluded from it by the
@@ -184,11 +317,17 @@ function cleanupCompletedLanes(e) {
184
317
  // already settled (laneAsyncSettled keeps _optimisticLane) served its
185
318
  // committed value to a reader that never re-ran after the landing, so a
186
319
  // pending-gated branch stayed one value behind permanently (#3041
187
- // follow-up). Record the reader under the same replay contract.
188
- if (e.Re !== NOT_PENDING) (activeTransition ?? globalQueue.m).cn.add(t);
320
+ // follow-up). Record the reader under the same replay contract — when
321
+ // the commit will actually change what it read: a staged value equal to
322
+ // the committed one (a lane recompute already published it, INV-11)
323
+ // promotes to the same view, and a replay would only re-run effects
324
+ // against an unchanged frame (#3330). An override-covered node's revert
325
+ // notifies its own subscribers when the truth differs (resolveOptimistic
326
+ // Nodes), so the reader is recorded only for the staged-vs-committed gap.
327
+ if (e.Ge !== NOT_PENDING && e.Ge !== e.me) (activeTransition ?? globalQueue.m).lt.add(i);
189
328
  return true;
190
329
  }
191
- if (n === e && stale && t.o?.Tt !== e) {
330
+ if (n === e && stale && i.o?.Gt !== e) {
192
331
  // The committed view can hide a staged write (a lane member — even just
193
332
  // an isPending companion flip — puts the reader "under a lane"). The
194
333
  // staged value commits with no re-delivery (commitPendingNode never
@@ -200,7 +339,7 @@ function cleanupCompletedLanes(e) {
200
339
  // into the transaction (#3041 follow-up: a pending-gated branch that
201
340
  // first read its async source during the landing flush stayed one value
202
341
  // behind permanently); with none, into the ambient batch.
203
- if (e.Re !== NOT_PENDING) (activeTransition ?? globalQueue.m).cn.add(t);
342
+ if (e.Ge !== NOT_PENDING) (activeTransition ?? globalQueue.m).lt.add(i);
204
343
  return true;
205
344
  }
206
345
  return false;
@@ -223,16 +362,16 @@ function cleanupCompletedLanes(e) {
223
362
  // recompute() runs plain: the value stages and commits with the flush.
224
363
  // (el's own override slot excludes companions themselves; a lane merged
225
364
  // into a real optimistic lane resolves to a non-companion source.)
226
- if (!globalQueue.fn && !activeTransition && !n.Ae && n.nn.o?.Tt !== undefined && e.o?.Pe === undefined) {
227
- if (e.o !== null) e.o.Je = undefined;
365
+ if (!globalQueue.sn && !activeTransition && !n.ge && n.nn.o?.Gt !== undefined && e.o?.be === undefined) {
366
+ if (e.o !== null) e.o.Oe = undefined;
228
367
  return false;
229
368
  }
230
369
  return n;
231
370
  }
232
- for (let n = e.ut; n; n = n.lt) {
233
- const t = n.ot;
234
- if (t.ie & REACTIVE_OPTIMISTIC_DIRTY) {
235
- const n = resolveLane(t);
371
+ for (let n = e.fe; n; n = n.ae) {
372
+ const i = n.Se;
373
+ if (i.ie & REACTIVE_OPTIMISTIC_DIRTY) {
374
+ const n = resolveLane(i);
236
375
  if (n) {
237
376
  e.ie |= REACTIVE_OPTIMISTIC_DIRTY;
238
377
  assignOrMergeLane(e, n);
@@ -243,21 +382,22 @@ function cleanupCompletedLanes(e) {
243
382
  return null;
244
383
  }
245
384
 
246
- /** recompute()'s catch path: track pending async in the current lane. */ function laneAsyncPending(e) {
385
+ /** recompute()'s catch path: record the pending async as the current lane's
386
+ * (ownership — laneHeld decides the hold). The lane source's isPending
387
+ * companion is NOT refreshed here: its verdict never read _pendingAsync, and
388
+ * the source's own write/commit/settlement paths keep it current. */ function laneAsyncPending(e) {
247
389
  const n = findLane(currentOptimisticLane);
248
390
  if (n.nn !== e) {
249
- n.Oe.add(e);
250
- ext(e).Je = n;
391
+ n.he.add(e);
392
+ ext(e).Oe = n;
251
393
  e.T |= CONFIG_HAS_LANE;
252
- GlobalQueue.de !== null && GlobalQueue.de(n.nn);
253
394
  }
254
395
  }
255
396
 
256
397
  /** recompute()'s success path: the node's async settled, clear it from its lane. */ function laneAsyncSettled(e) {
257
398
  const n = resolveLane(e);
258
399
  if (n) {
259
- n.Oe.delete(e);
260
- GlobalQueue.de !== null && GlobalQueue.de(n.nn);
400
+ n.he.delete(e);
261
401
  }
262
402
  }
263
403
 
@@ -272,19 +412,23 @@ function trackOptimisticStore(e) {
272
412
  * create optimistic state (verdict.ts at module top level, createOptimistic
273
413
  * and createOptimisticStore at first call) BEFORE any optimistic node exists.
274
414
  */ function installOptimisticEngine() {
275
- if (GlobalQueue.xt !== null) return;
276
- GlobalQueue.xt = optimisticWrite;
277
- GlobalQueue.Tn = resolveOptimisticNodes;
278
- GlobalQueue.dn = transitionBlocked;
279
- GlobalQueue.In = cleanupCompletedLanes;
280
- GlobalQueue.Nn = runLaneEffects;
281
- GlobalQueue.vt = gatedRead;
282
- GlobalQueue.Vt = laneSuspends;
283
- GlobalQueue.bt = laneReadsCommitted;
284
- GlobalQueue.Be = recomputeLane;
285
- GlobalQueue.$e = laneAsyncPending;
286
- GlobalQueue.Xe = laneAsyncSettled;
287
- GlobalQueue._n = trackOptimisticStore;
415
+ if (GlobalQueue.Bt !== null) return;
416
+ GlobalQueue.Bt = optimisticWrite;
417
+ GlobalQueue.fn = resolveOptimisticNodes;
418
+ GlobalQueue.cn = transitionBlocked;
419
+ GlobalQueue.dn = cleanupCompletedLanes;
420
+ GlobalQueue.In = runLaneEffects;
421
+ GlobalQueue.ye = supersedeOverride;
422
+ GlobalQueue.Yt = supersededRead;
423
+ GlobalQueue.zt = landOnOverride;
424
+ GlobalQueue.Zt = gatedRead;
425
+ GlobalQueue.qt = laneSuspends;
426
+ GlobalQueue.Mt = laneLive;
427
+ GlobalQueue.Kt = laneReadsCommitted;
428
+ GlobalQueue.nt = recomputeLane;
429
+ GlobalQueue.ot = laneAsyncPending;
430
+ GlobalQueue.ut = laneAsyncSettled;
431
+ GlobalQueue.Nn = trackOptimisticStore;
288
432
  }
289
433
 
290
434
  export { installOptimisticEngine };