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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/dist/dev-shared.js +6357 -0
  2. package/dist/dev.attribution.js +2485 -0
  3. package/dist/dev.js +2851 -7027
  4. package/dist/observe/affects.js +129 -0
  5. package/dist/observe/attribution.js +7 -0
  6. package/dist/observe/boundaries.js +641 -0
  7. package/dist/observe/core/action.js +177 -0
  8. package/dist/observe/core/async.js +846 -0
  9. package/dist/observe/core/attribution-costs.js +66 -0
  10. package/dist/observe/core/attribution-feedback.js +282 -0
  11. package/dist/observe/core/attribution-hooks.js +82 -0
  12. package/dist/observe/core/attribution-queries.js +28 -0
  13. package/dist/observe/core/attribution.js +2095 -0
  14. package/dist/observe/core/constants.js +248 -0
  15. package/dist/{types-cjs/core/context.d.cts → observe/core/context.js} +37 -12
  16. package/dist/observe/core/core.js +1789 -0
  17. package/dist/observe/core/dev.js +293 -0
  18. package/dist/observe/core/effect.js +195 -0
  19. package/dist/observe/core/error-hooks.js +71 -0
  20. package/dist/observe/core/error.js +80 -0
  21. package/dist/observe/core/external.js +98 -0
  22. package/dist/observe/core/graph.js +155 -0
  23. package/dist/observe/core/heap.js +147 -0
  24. package/dist/observe/core/invariants.js +45 -0
  25. package/dist/observe/core/lanes.js +205 -0
  26. package/dist/observe/core/optimistic.js +585 -0
  27. package/dist/observe/core/owner.js +322 -0
  28. package/dist/observe/core/scheduler.js +1428 -0
  29. package/dist/observe/core/verdict.js +669 -0
  30. package/dist/observe/index.js +59 -0
  31. package/dist/observe/map.js +484 -0
  32. package/dist/{types-cjs/signals.d.cts → observe/signals.js} +311 -317
  33. package/dist/observe/store/index.js +40 -0
  34. package/dist/observe/store/next/optimistic.js +660 -0
  35. package/dist/observe/store/next/projection.js +255 -0
  36. package/dist/observe/store/next/reconcile.js +375 -0
  37. package/dist/observe/store/next/store.js +2327 -0
  38. package/dist/observe/store/next/target.js +51 -0
  39. package/dist/observe/store/store.js +325 -0
  40. package/dist/observe/store/storePath.js +103 -0
  41. package/dist/observe/store/utils.js +1023 -0
  42. package/dist/prod/attribution.js +41 -0
  43. package/dist/prod/boundaries.js +128 -76
  44. package/dist/prod/core/action.js +65 -37
  45. package/dist/prod/core/async.js +324 -172
  46. package/dist/prod/core/constants.js +57 -1
  47. package/dist/prod/core/context.js +3 -3
  48. package/dist/prod/core/core.js +1003 -364
  49. package/dist/prod/core/dev.js +17 -1
  50. package/dist/prod/core/effect.js +76 -42
  51. package/dist/prod/core/error-hooks.js +71 -0
  52. package/dist/prod/core/external.js +4 -4
  53. package/dist/prod/core/graph.js +37 -37
  54. package/dist/prod/core/heap.js +61 -56
  55. package/dist/prod/core/lanes.js +107 -63
  56. package/dist/prod/core/optimistic.js +374 -93
  57. package/dist/prod/core/owner.js +62 -46
  58. package/dist/prod/core/scheduler.js +653 -286
  59. package/dist/prod/core/verdict.js +245 -127
  60. package/dist/prod/index.js +15 -5
  61. package/dist/prod/map.js +292 -141
  62. package/dist/prod/signals.js +29 -16
  63. package/dist/prod/store/next/optimistic.js +77 -55
  64. package/dist/prod/store/next/projection.js +31 -20
  65. package/dist/prod/store/next/reconcile.js +87 -84
  66. package/dist/prod/store/next/store.js +718 -401
  67. package/dist/prod/store/next/target.js +32 -10
  68. package/dist/prod/store/store.js +12 -14
  69. package/dist/prod/store/utils.js +950 -130
  70. package/dist/types/attribution.d.ts +20 -0
  71. package/dist/types/attribution.prod.d.ts +22 -0
  72. package/dist/types/boundaries.d.ts +10 -1
  73. package/dist/types/core/action.d.ts +12 -5
  74. package/dist/types/core/async.d.ts +1 -1
  75. package/dist/types/core/attribution-costs.d.ts +35 -0
  76. package/dist/types/core/attribution-feedback.d.ts +133 -0
  77. package/dist/types/core/attribution-hooks.d.ts +147 -9
  78. package/dist/types/core/attribution-queries.d.ts +10 -0
  79. package/dist/types/core/attribution.d.ts +216 -182
  80. package/dist/types/core/constants.d.ts +56 -0
  81. package/dist/types/core/core.d.ts +190 -2
  82. package/dist/types/core/dev.d.ts +246 -28
  83. package/dist/types/core/effect.d.ts +8 -1
  84. package/dist/types/core/error-hooks.d.ts +71 -0
  85. package/dist/types/core/index.d.ts +4 -1
  86. package/dist/types/core/invariants.d.ts +4 -0
  87. package/dist/types/core/lanes.d.ts +39 -5
  88. package/dist/types/core/scheduler.d.ts +153 -0
  89. package/dist/types/core/types.d.ts +32 -9
  90. package/dist/types/index.d.ts +10 -3
  91. package/dist/types/map.d.ts +2 -0
  92. package/dist/types/signals.d.ts +8 -0
  93. package/dist/types/store/index.d.ts +2 -0
  94. package/dist/types/store/next/optimistic.d.ts +1 -1
  95. package/dist/types/store/next/store.d.ts +11 -5
  96. package/dist/types/store/next/target.d.ts +37 -10
  97. package/dist/types/store/utils.d.ts +178 -0
  98. package/package.json +21 -17
  99. package/dist/node.cjs +0 -10541
  100. package/dist/node.dev.cjs +0 -13724
  101. package/dist/types-cjs/affects.d.cts +0 -47
  102. package/dist/types-cjs/boundaries.d.cts +0 -175
  103. package/dist/types-cjs/core/action.d.cts +0 -64
  104. package/dist/types-cjs/core/async.d.cts +0 -23
  105. package/dist/types-cjs/core/attribution-hooks.d.cts +0 -127
  106. package/dist/types-cjs/core/attribution.d.cts +0 -495
  107. package/dist/types-cjs/core/constants.d.cts +0 -159
  108. package/dist/types-cjs/core/core.d.cts +0 -185
  109. package/dist/types-cjs/core/dev.d.cts +0 -136
  110. package/dist/types-cjs/core/effect.d.cts +0 -30
  111. package/dist/types-cjs/core/error.d.cts +0 -56
  112. package/dist/types-cjs/core/external.d.cts +0 -15
  113. package/dist/types-cjs/core/graph.d.cts +0 -28
  114. package/dist/types-cjs/core/heap.d.cts +0 -24
  115. package/dist/types-cjs/core/index.d.cts +0 -13
  116. package/dist/types-cjs/core/invariants.d.cts +0 -59
  117. package/dist/types-cjs/core/lanes.d.cts +0 -54
  118. package/dist/types-cjs/core/optimistic.d.cts +0 -6
  119. package/dist/types-cjs/core/owner.d.cts +0 -124
  120. package/dist/types-cjs/core/scheduler.d.cts +0 -236
  121. package/dist/types-cjs/core/types.d.cts +0 -204
  122. package/dist/types-cjs/core/verdict.d.cts +0 -2
  123. package/dist/types-cjs/index.d.cts +0 -10
  124. package/dist/types-cjs/map.d.cts +0 -69
  125. package/dist/types-cjs/package.json +0 -3
  126. package/dist/types-cjs/store/index.d.cts +0 -18
  127. package/dist/types-cjs/store/next/optimistic.d.cts +0 -25
  128. package/dist/types-cjs/store/next/projection.d.cts +0 -8
  129. package/dist/types-cjs/store/next/reconcile.d.cts +0 -10
  130. package/dist/types-cjs/store/next/store.d.cts +0 -121
  131. package/dist/types-cjs/store/next/target.d.cts +0 -166
  132. package/dist/types-cjs/store/store.d.cts +0 -143
  133. package/dist/types-cjs/store/storePath.d.cts +0 -58
  134. package/dist/types-cjs/store/utils.d.cts +0 -74
@@ -1,20 +1,22 @@
1
- import { NOT_PENDING, REACTIVE_DISPOSED, REACTIVE_DIRTY, REACTIVE_CHECK, unwrapOverride, REACTIVE_ZOMBIE, STATUS_UNINITIALIZED, STATUS_PENDING, REACTIVE_OPTIMISTIC_DIRTY, REACTIVE_MANUAL_WRITE, CONFIG_HAS_COMPANIONS, CONFIG_CHILD_COMPANIONS, STATUS_ERROR, REACTIVE_RECOMPUTING_DEPS } from "./constants.js";
1
+ import { NOT_PENDING, REACTIVE_DISPOSED, REACTIVE_DIRTY, REACTIVE_CHECK, unwrapOverride, REACTIVE_RECOMPUTING_DEPS, STATUS_PENDING, STATUS_UNINITIALIZED, REACTIVE_OPTIMISTIC_DIRTY, REACTIVE_MANUAL_WRITE, CONFIG_OVERRIDE_SUPERSEDED, CONFIG_HAS_COMPANIONS, CONFIG_ADOPTED_UNFLUSHED, CONFIG_CHILD_COMPANIONS, STATUS_ERROR } from "./constants.js";
2
2
 
3
- import { setSignal, pendingCheckActive, setPendingCheckActive, prepareComputed, read, context, stale, currentOptimisticLane, tracking, ext, setLatestReadActive, setContextInternal, optimisticComputed, latestReadActive, optimisticSignal } from "./core.js";
3
+ import { setSignal, visibleOverride, unflushedValue, context, markLateLinker, read, stale, hasActiveOverride, prepareComputed, tracking, ext, setLatestReadActive, setContextInternal, optimisticComputed, unflushed, unflushedCompanions, setPendingCheckActive, latestReadActive, optimisticSignal, pendingCheckActive } from "./core.js";
4
4
 
5
5
  import { NotReadyError } from "./error.js";
6
6
 
7
7
  import { link } from "./graph.js";
8
8
 
9
- import { insertIntoHeap, queueFor, markHeap, enqueueSub } from "./heap.js";
9
+ import { dispose } from "./owner.js";
10
+
11
+ import { insertIntoHeap, queueFor, enqueueSub } from "./heap.js";
10
12
 
11
13
  import "./invariants.js";
12
14
 
13
- import { findLane, laneHeld, hasActiveOverride, assignOrMergeLane } from "./lanes.js";
15
+ import { readsHeldCommitted, assignOrMergeLane } from "./lanes.js";
14
16
 
15
17
  import { installOptimisticEngine } from "./optimistic.js";
16
18
 
17
- import { GlobalQueue, insertSubs, schedule, activeTransition, currentTransition, activeAffectsMarks } from "./scheduler.js";
19
+ import { GlobalQueue, insertSubs, schedule, activeTransition, currentTransition, runAsTransitionBatch, activeAffectsMarks } from "./scheduler.js";
18
20
 
19
21
  /**
20
22
  * The isPending()/latest() verdict layer, moved out of core.ts. Importing this
@@ -44,35 +46,76 @@ let pendingProbe = null;
44
46
  * the one-load gate at the call sites). The snap then iterates exactly the
45
47
  * children someone asked verdicts of — O(companions) — never the full
46
48
  * `_child` chain, which carries one node per materialized leaf (the
47
- * O(all-leaves-ever-read)-per-update pathology). Entries are permanent like
48
- * the companions themselves; a store with no leaf-level isPending()/latest()
49
- * reads never allocates the set or pays the walk. */ function markFirewallChildCompanions(e) {
50
- const t = e.rt;
49
+ * O(all-leaves-ever-read)-per-update pathology). Entries live as long as the
50
+ * store addresses the leaf the unobserved sweep's `unlinkFirewallChild`
51
+ * drops them (#3503); a store with no leaf-level isPending()/latest() reads
52
+ * never allocates the set or pays the walk. */ function markFirewallChildCompanions(e) {
53
+ const t = e.Te;
51
54
  if (!t) return;
52
55
  t.T |= CONFIG_CHILD_COMPANIONS;
53
- (ext(t).dt ??= new Set).add(e);
56
+ (ext(t).bt ??= new Set).add(e);
54
57
  }
55
58
 
56
59
  function getPendingSignal(e) {
57
- let t = e.o?.ye;
60
+ let t = e.o?.je;
58
61
  if (!t) {
59
62
  // Start false, write true if pending - ensures reversion returns to false
60
63
  t = optimisticSignal(false, {
61
64
  ownedWrite: true
62
65
  });
63
- ext(e).ye = t;
66
+ ext(e).je = t;
64
67
  e.T |= CONFIG_HAS_COMPANIONS;
65
68
  markFirewallChildCompanions(e);
66
- ext(t).St = e;
67
- if (computePendingState(e)) setSignal(t, true);
69
+ ext(t).Ht = e;
70
+ if (computePendingState(e)) backfillCompanion(e, t, true);
71
+ joinUnflushedResync(e);
68
72
  }
69
73
  return t;
70
74
  }
71
75
 
76
+ /**
77
+ * A lazily created companion's first write mirrors state the owner already
78
+ * carries — a held write, a pending verdict. The companion is created wherever
79
+ * the first latest()/isPending() read happens to run, but the write belongs
80
+ * to whatever HOLDS that state: written in the ambient window, the override
81
+ * would register in the ambient batch and revert when that flush's round ends
82
+ * — the shadow re-derived from the committed view, the verdict flipped false —
83
+ * while the owner's hold was still on (#3336: A and B differing only in
84
+ * whether a companion existed before the hold). A companion created lazily
85
+ * answers as if it had always existed: its backfill is registered with the
86
+ * owner's transaction and lives and reverts with it. A hold with no
87
+ * transaction (a pending async, a same-flush staged write) is ambient and the
88
+ * write stays ambient. (A28 (3), lifted from #3337.)
89
+ */ function backfillCompanion(e, t, n) {
90
+ const i = e.Ge;
91
+ if (i) runAsTransitionBatch(i, () => setSignal(t, n)); else setSignal(t, n);
92
+ }
93
+
94
+ /** A28: a companion created while its source carries an UNFLUSHED write joins
95
+ * the flush-start re-sync like a companion that existed at the write —
96
+ * syncCompanions only reaches companions that exist at write time. Without
97
+ * this the flush brings it current by a plain recompute instead of the
98
+ * optimistic write: its readers are then staged under whatever transaction
99
+ * the round entered (a memo over latest() of a held source was held with the
100
+ * source, and its untracked reads answered the previous value until the hold
101
+ * committed) rather than direct-committed as the optimistic view they are. */ function joinUnflushedResync(e) {
102
+ if (unflushed(e)) unflushedCompanions.push(e);
103
+ }
104
+
105
+ /** The staged value the verdict channels answer for (A28): while a node
106
+ * carries an unflushed write its `_pendingValue` is not yet part of any
107
+ * flushed world, so the channels answer for the value the last flush left
108
+ * staged (a held node's stash) or for nothing (NOT_PENDING). */ function flushedStaged(e) {
109
+ if (!unflushed(e)) return e.ve;
110
+ // Ambient, or adopted before any flush (CONFIG_ADOPTED_UNFLUSHED): nothing
111
+ // a flush carried is staged for it. A held rewrite: the stash.
112
+ return e.Ge === null || e.T & CONFIG_ADOPTED_UNFLUSHED ? NOT_PENDING : e.o.gt;
113
+ }
114
+
72
115
  function collectPendingSources(e) {
73
116
  if (!pendingProbe) return;
74
117
  pendingProbe.sources.add(e);
75
- const t = e.rt || e;
118
+ const t = e.Te || e;
76
119
  if (t !== e) pendingProbe.sources.add(t);
77
120
  }
78
121
 
@@ -105,17 +148,17 @@ function collectPendingSources(e) {
105
148
  if (e.S & STATUS_ERROR) return false;
106
149
  if (t.has(e)) return false;
107
150
  t.add(e);
108
- const n = e.rt;
151
+ const n = e.Te;
109
152
  if (n && markWalk(n, t)) return true;
110
153
  // Mid-recompute (the clearStatus companion poke runs before
111
154
  // trimStaleDeps), only the validated prefix [_deps.._depsTail] is this
112
155
  // pass's dependency set — walking past it would read dropped deps and
113
156
  // latch a stale verdict on the companion.
114
157
  const i = e;
115
- const r = i.ie & REACTIVE_RECOMPUTING_DEPS ? i.Be : undefined;
158
+ const r = i.ue & REACTIVE_RECOMPUTING_DEPS ? i.ot : undefined;
116
159
  if (r !== null) {
117
- for (let e = i.lt ?? null; e !== null; e = e.ot) {
118
- if (!e.ve && markWalk(e.st, t)) return true;
160
+ for (let e = i.Se ?? null; e !== null; e = e.de) {
161
+ if (!e.He && markWalk(e.Ee, t)) return true;
119
162
  if (e === r) break;
120
163
  }
121
164
  }
@@ -123,11 +166,11 @@ function collectPendingSources(e) {
123
166
  }
124
167
 
125
168
  function quietPending(e) {
126
- if (e.o?.le) {
127
- for (const t of e.o.le) if (!t.o?.De) return false;
169
+ if (e.o?.ae) {
170
+ for (const t of e.o.ae) if (!t.o?.be) return false;
128
171
  return true;
129
172
  }
130
- return e.o?.De ?? false;
173
+ return e.o?.be ?? false;
131
174
  }
132
175
 
133
176
  // NOTE: a loadingValue node's open loading window (_loading) is verdict-quiet
@@ -143,50 +186,68 @@ function newQuestionInFlight(e) {
143
186
 
144
187
  function computePendingState(e) {
145
188
  const t = e;
146
- if (t.ie & REACTIVE_DISPOSED) return false;
189
+ if (t.ue & REACTIVE_DISPOSED) return false;
147
190
  // Mark coverage is transitive by dep-graph reachability: a latest() shadow
148
191
  // reaches its owner (and a store leaf its firewall) through its own deps,
149
192
  // so the one walk covers direct marks, derivation, and companion chains.
150
193
  // Gated: apps with no live mark pay one integer compare.
151
194
  if (activeAffectsMarks !== 0 && markWalk(e, new Set)) return true;
152
- const n = e.rt;
153
- if (e.o?.St) {
154
- const t = e.o?.St;
155
- const n = t.rt || t;
195
+ const n = e.Te;
196
+ if (e.o?.Ht) {
197
+ const t = e.o?.Ht;
198
+ const n = t.Te || t;
156
199
  return newQuestionInFlight(n);
157
200
  }
158
- if (n && e.Re !== NOT_PENDING && !hasActiveOverride(e)) {
159
- return !!(n.ie & REACTIVE_MANUAL_WRITE) || !n.o?.Ie && !(n.S & STATUS_PENDING) || !!(n.S & STATUS_PENDING) && quietPending(n);
201
+ const i = flushedStaged(e);
202
+ if (n && i !== NOT_PENDING && !hasActiveOverride(e)) {
203
+ return !!(n.ue & REACTIVE_MANUAL_WRITE) || !n.o?.Re && !(n.S & STATUS_PENDING) || !!(n.S & STATUS_PENDING) && quietPending(n);
160
204
  }
161
205
  // `!comp._loading`: a hold created while the loading window is still open is
162
206
  // the window's own landing in flight to its commit — verdict-quiet like the
163
207
  // rest of the window (the UNINITIALIZED check suppresses exactly this frame
164
208
  // for windowless first loads; born-committed nodes need their own gate, #2990).
165
- if (e.Re !== NOT_PENDING && !(t.S & STATUS_UNINITIALIZED) && !t.Ne) {
166
- if (hasActiveOverride(e)) return !e.pe || !e.pe(e.Re, unwrapOverride(e.o?.Pe));
209
+ // A18 (d) for a body-end supersession (#3427): the truth at hand is the
210
+ // COMMITTED value nothing staged — yet the display still shows the
211
+ // override; pending iff they differ, as for a staged arrival below.
212
+ if (e.T & CONFIG_OVERRIDE_SUPERSEDED && e.ve === NOT_PENDING && visibleOverride(e)) return !e.Fe || !e.Fe(e.Qe, unwrapOverride(e.o?.Ce));
213
+ // A28 (2): an unflushed write is not yet observable — the verdict answers
214
+ // for the flushed staged value.
215
+ if (i !== NOT_PENDING && !t.ge) {
216
+ // A18 (d): under a displayed override the observable value is the
217
+ // override, so the verdict is "the arrived truth differs from it" —
218
+ // even before the node's first commit. The UNINITIALIZED suppression
219
+ // below is A19 exception (1), "no observable value exists to be
220
+ // non-final"; an override is one (a node whose first landing was held
221
+ // by a reveal it never got to commit, then superseded under its
222
+ // override, read false here).
223
+ if (visibleOverride(e)) return !e.Fe || !e.Fe(i, unwrapOverride(e.o?.Ce));
167
224
  // A quiet re-ask's held landing still answers the same question: the
168
225
  // classification survives the landing (asyncWrite) and dies with the
169
226
  // commit (commitPendingNode) — verdict-quiet through the reveal, like
170
227
  // the loading window above (#3178).
171
- if (!t.o?.De) return true;
228
+ // A staged value equal to the committed one is no proposal (A34, #3494): the
229
+ // observable value IS final (A19). The coalesced `setShow(false);
230
+ // setShow(true)` read pending through the flush that carried it — and,
231
+ // stamped into a hold that flush opened, until the hold settled.
232
+ if (!(t.S & STATUS_UNINITIALIZED) && !t.o?.be && (!e.Fe || !e.Fe(e.Qe, i))) return true;
172
233
  }
173
234
  return newQuestionInFlight(t);
174
235
  }
175
236
 
176
237
  function syncCompanions(e, t) {
177
- if (e.o?.ye) updatePendingSignal(e);
178
- if (e.o?.Ce) setSignal(e.o?.Ce, t);
238
+ if (e.o?.je) updatePendingSignal(e);
239
+ if (e.o?.xe) setSignal(e.o?.xe, t);
179
240
  }
180
241
 
181
242
  function updatePendingSignal(e) {
182
- if (e.o?.ye) {
183
- setSignal(e.o?.ye, computePendingState(e));
243
+ if (e.o?.je) {
244
+ setSignal(e.o?.je, computePendingState(e));
184
245
  }
185
- if (e.o?.Ce) updatePendingSignal(e.o?.Ce);
246
+ if (e.o?.xe) updatePendingSignal(e.o?.xe);
186
247
  }
187
248
 
188
249
  function updateChildCompanions(e) {
189
- const t = e.o?.dt;
250
+ const t = e.o?.bt;
190
251
  if (t === undefined) return;
191
252
  for (const e of t) updatePendingSignal(e);
192
253
  }
@@ -205,9 +266,9 @@ function updateChildCompanions(e) {
205
266
  const visit = e => {
206
267
  if (i.has(e)) return;
207
268
  i.add(e);
208
- if (e.o?.ye || e.o?.Ce) n(e);
209
- for (let t = e.u; t !== null; t = t.ae) visit(t.ce);
210
- for (let t = e.o?.i ?? null; t !== null; t = t.Se) {
269
+ if (e.o?.je || e.o?.xe) n(e);
270
+ for (let t = e.u; t !== null; t = t.Ne) visit(t._e);
271
+ for (let t = e.o?.i ?? null; t !== null; t = t.De) {
211
272
  visit(t);
212
273
  }
213
274
  };
@@ -228,7 +289,7 @@ function updateChildCompanions(e) {
228
289
  if (suppressedProbes.size === 0) return;
229
290
  let t = false;
230
291
  for (const [n, i] of suppressedProbes) {
231
- const r = n.Ae;
292
+ const r = n.Ge;
232
293
  const s = r ? currentTransition(r) : null;
233
294
  if (!s) {
234
295
  suppressedProbes.delete(n);
@@ -236,11 +297,11 @@ function updateChildCompanions(e) {
236
297
  }
237
298
  if (s !== e) continue;
238
299
  suppressedProbes.delete(n);
239
- const o = n.o?.ye?.o?.$e;
300
+ const o = n.o?.je?.o?.Ue;
240
301
  for (const e of i) {
241
- if (e.ie & REACTIVE_DISPOSED) continue;
242
- e.ie |= REACTIVE_OPTIMISTIC_DIRTY;
243
- if (o) assignOrMergeLane(e, o); else if (e.o !== null) e.o.$e = undefined;
302
+ if (e.ue & REACTIVE_DISPOSED) continue;
303
+ e.ue |= REACTIVE_OPTIMISTIC_DIRTY;
304
+ if (o) assignOrMergeLane(e, o); else if (e.o !== null) e.o.Ue = undefined;
244
305
  enqueueSub(e);
245
306
  t = true;
246
307
  }
@@ -250,20 +311,32 @@ function updateChildCompanions(e) {
250
311
 
251
312
  function snapCompanionsToState(e) {
252
313
  suppressedProbes.size !== 0 && suppressedProbes.delete(e);
253
- const t = e.o?.ye;
254
- if (t && (t.o?.Pe === undefined || t.o?.Pe === NOT_PENDING)) {
314
+ const t = e.o?.je;
315
+ if (t && (t.o?.Ce === undefined || t.o?.Ce === NOT_PENDING)) {
255
316
  const n = computePendingState(e);
256
- if (t.be !== n || t.Re !== NOT_PENDING) {
257
- t.be = n;
258
- t.Re = NOT_PENDING;
317
+ if (t.Qe !== n || t.ve !== NOT_PENDING) {
318
+ t.Qe = n;
319
+ t.ve = NOT_PENDING;
259
320
  insertSubs(t);
260
321
  schedule();
261
322
  }
262
323
  }
263
- const n = e.o?.Ce;
264
- if (n && !(n.ie & REACTIVE_DISPOSED)) {
265
- if ((n.o?.Pe === undefined || n.o?.Pe === NOT_PENDING) && n.Re === NOT_PENDING && !Object.is(n.be, e.be) && !(n.ie & (REACTIVE_DIRTY | REACTIVE_CHECK))) {
266
- n.ie |= REACTIVE_DIRTY;
324
+ const n = e.o?.xe;
325
+ if (n && !(n.ue & REACTIVE_DISPOSED)) {
326
+ // A leaf whose firewall is disposed (the projection's teardown snaps its
327
+ // companion-bearing leaves): the shadow's compute reads through a
328
+ // disposed, possibly still-pending projection and would sit
329
+ // NotReady/uninitialized forever — never derived, its backfilled override
330
+ // dropped at the settle — against a leaf whose committed value differs
331
+ // (INV-4 at the next quiescence; spec O5). It dies with its source;
332
+ // getLatestValueComputed treats a disposed shadow as absent, so a later
333
+ // read recreates it from the committed view.
334
+ if (e.Te?.ue & REACTIVE_DISPOSED) {
335
+ dispose(n);
336
+ return;
337
+ }
338
+ if ((n.o?.Ce === undefined || n.o?.Ce === NOT_PENDING) && n.ve === NOT_PENDING && !Object.is(n.Qe, e.Qe) && !(n.ue & (REACTIVE_DIRTY | REACTIVE_CHECK))) {
339
+ n.ue |= REACTIVE_DIRTY;
267
340
  insertIntoHeap(n, queueFor(n));
268
341
  insertSubs(n);
269
342
  schedule();
@@ -273,14 +346,14 @@ function snapCompanionsToState(e) {
273
346
  }
274
347
 
275
348
  function getLatestValueComputed(e) {
276
- let t = e.o?.Ce;
349
+ let t = e.o?.xe;
277
350
  // A shadow disposed while unobserved (its gated reader unmounted at a
278
351
  // landing) is a corpse: sync writes into it equality-swallow against its
279
352
  // frozen _value, and a later read revives it via recompute — clearing
280
353
  // DISPOSED and re-deriving from the committed view, so the banner showed
281
354
  // the previous transition's target (#3041 follow-up). Treat it as absent;
282
355
  // recreation backfills from the in-flight write below.
283
- if (t && t.ie & REACTIVE_DISPOSED) t = undefined;
356
+ if (t && t.ue & REACTIVE_DISPOSED) t = undefined;
284
357
  if (!t) {
285
358
  const n = latestReadActive;
286
359
  setLatestReadActive(false);
@@ -289,17 +362,26 @@ function getLatestValueComputed(e) {
289
362
  const r = context;
290
363
  setContextInternal(null);
291
364
  // Detach from owner so it isn't disposed with effects
292
- t = optimisticComputed(() => read(e));
293
- ext(e).Ce = t;
365
+ GlobalQueue.jt = true;
366
+ try {
367
+ t = optimisticComputed(() => read(e), {
368
+ ownedWrite: true
369
+ });
370
+ } finally {
371
+ GlobalQueue.jt = false;
372
+ }
373
+ ext(e).xe = t;
294
374
  e.T |= CONFIG_HAS_COMPANIONS;
295
375
  markFirewallChildCompanions(e);
296
- ext(t).St = e;
376
+ ext(t).Ht = e;
297
377
  // Parent-child lane relationship
298
378
  // Backfill an in-flight write (mirrors getPendingSignal): the companion is
299
379
  // created lazily, possibly after the write was processed — syncCompanions
300
380
  // only pushes into companions that already exist, so the first latest()
301
381
  // read inside a held transition showed the committed value (#3041).
302
- if (e.Re !== NOT_PENDING && !hasActiveOverride(e)) setSignal(t, e.Re);
382
+ const s = flushedStaged(e);
383
+ if (s !== NOT_PENDING && !hasActiveOverride(e)) backfillCompanion(e, t, s);
384
+ joinUnflushedResync(e);
303
385
  setContextInternal(r);
304
386
  setPendingCheckActive(i);
305
387
  setLatestReadActive(n);
@@ -307,59 +389,83 @@ function getLatestValueComputed(e) {
307
389
  return t;
308
390
  }
309
391
 
310
- /** The latest()-mode read path, installed as GlobalQueue._latestRead. */ function latestRead(e) {
392
+ /** The latest()-mode read path, installed as GlobalQueue._latestRead. */
393
+ /** A7: the source has no visible value yet — judged on the OWNER, as read()
394
+ * does: a store leaf behind a projection's firewall is a plain signal whose
395
+ * `_value` is the seed (A25: a draft, never a value), and read() routes a
396
+ * latest() read here before its own firewall/status logic. An override
397
+ * displays a value even before the first commit (A17). */ function uninitializedSource(e) {
398
+ const t = e.Te || e;
399
+ return !!(t.S & STATUS_UNINITIALIZED) && !hasActiveOverride(e);
400
+ }
401
+
402
+ function latestRead(e) {
403
+ // A leaf of a DISPOSED projection has no flushed world left to mirror: a
404
+ // shadow created for it now would read through the dead firewall, sit
405
+ // NotReady/uninitialized forever, and no teardown would ever retire it (the
406
+ // firewall's already ran — spec O5). Serve the committed value; create
407
+ // nothing. (A read of a disposed node freezes at its last commit, #3024.)
408
+ if (e.Te?.ue & REACTIVE_DISPOSED) return e.Qe;
311
409
  const t = getLatestValueComputed(e);
312
410
  const n = latestReadActive;
313
411
  setLatestReadActive(false);
314
- const i = e.o?.Pe !== undefined && e.o?.Pe !== NOT_PENDING ? unwrapOverride(e.o?.Pe) : e.be;
315
- let r;
316
- try {
317
- // An untracked latest() read has no reading context, so read() never
318
- // performs its mid-tick pull — a plain write queued between two latest()
319
- // calls left a still-subscribed shadow at its previous speculative value
320
- // until the flush (#2922). Mirror the tracked-read pull here: mark the
321
- // queued staleness through the graph, then bring the shadow up to date.
322
- const e = queueFor(t);
323
- if (t.qe >= e.Me && !(t.ie & (REACTIVE_DISPOSED | REACTIVE_ZOMBIE))) {
324
- markHeap(e);
325
- // Suspend probe collection during the pull (mirrors pendingCheckRead's
326
- // prepare): a probe through latest() answers for the SHADOW — the
327
- // read() dispatch collects it deliberately, so the verdict reflects
328
- // async still in flight for the latest view, not the parent's held
329
- // write. A stale shadow recomputing HERE ran its `read(parent)` with
330
- // the probe still live and collected the parent too, so the verdict
331
- // depended on whether anything had pulled the shadow current earlier
332
- // in the tick (#3104: reading latest(m) flipped a later
333
- // latest(() => isPending(x)) from true to false).
334
- const n = pendingCheckActive;
335
- setPendingCheckActive(false);
336
- try {
337
- prepareComputed(t, true);
338
- } finally {
339
- setPendingCheckActive(n);
340
- }
412
+ const i = visibleOverride(e) ? unwrapOverride(e.o?.Ce) : e.Qe;
413
+ // A28: an unflushed write is not the staged value latest() serves. The
414
+ // shadow was written at the source's write to mirror it (A8) — consult it
415
+ // only once a flush has carried the write.
416
+ const r = unflushedValue(e);
417
+ if (r !== NOT_PENDING) {
418
+ // The reader derived from the flushed world because of an unflushed
419
+ // write: it runs again in the flush that carries it (late linker)
420
+ // a pull may have cleared the mark the write's walk set.
421
+ if (context !== null && context.ue & REACTIVE_RECOMPUTING_DEPS) markLateLinker(context);
422
+ // Link the reader to the shadow so the flush that carries the write
423
+ // updates it (the shadow itself already mirrors the write, A8).
424
+ try {
425
+ read(t);
426
+ } catch {
427
+ /* the flushed value answers */} finally {
428
+ setLatestReadActive(n);
341
429
  }
342
- r = read(t);
430
+ // An ambient write: the visible value (override or committed). A rewrite
431
+ // of a held node: the staged value the last flush left.
432
+ return e.Ge === null ? i : r;
433
+ }
434
+ let s;
435
+ try {
436
+ // No mid-tick pull: writes become visible at flush (A28), so before the
437
+ // flush the shadow is exactly as current as the flushed world — the read
438
+ // below serves it as is. (#2922's pull, which brought the shadow current
439
+ // against the unflushed write, is superseded: `flush()` first to read
440
+ // your own write.)
441
+ s = read(t);
343
442
  } catch (t) {
344
- if (t instanceof NotReadyError && (!context || !(e.S & STATUS_UNINITIALIZED))) return i;
443
+ // A NotReady from the shadow of an INITIALIZED source means the shadow
444
+ // is mid-flight: serve the visible (committed / override) value. An
445
+ // uninitialized source has no visible value — latest() throws in every
446
+ // scope rather than fabricate `undefined` for a `T` that excludes it
447
+ // (A7; the unowned scope used to return undefined here).
448
+ if (t instanceof NotReadyError && !uninitializedSource(e)) return i;
345
449
  throw t;
346
450
  } finally {
347
451
  setLatestReadActive(n);
348
452
  }
349
- if (t.S & STATUS_PENDING) return i;
350
- if (stale && currentOptimisticLane && t.o?.$e) {
351
- const e = findLane(t.o?.$e);
352
- const n = findLane(currentOptimisticLane);
353
- if (e !== n && laneHeld(e)) {
354
- return i;
355
- }
453
+ if (t.S & STATUS_PENDING) {
454
+ if (uninitializedSource(e)) throw new NotReadyError(e);
455
+ return i;
356
456
  }
457
+ // A render effect off the shadow's HELD lane sees the committed value and
458
+ // re-runs at the release (#3460; lanes mirror transitions — see
459
+ // readsHeldCommitted). Was: only a reader under ANOTHER lane; a mainline
460
+ // reader, mounted or re-run by a sync write mid-hold, showed the
461
+ // speculative value beside the lane's deferred readers.
462
+ if (stale && context !== null && readsHeldCommitted(t, context)) return e.Qe;
357
463
  // A shadow recomputed by the pull above (not at creation) holds its fresh
358
464
  // speculative value in _pendingValue; a contextless read() only surfaces
359
465
  // _value. Overrides stay authoritative (A17), and stale readers keep the
360
466
  // other transition's committed view, matching read()'s own selection.
361
- if (t.Re !== NOT_PENDING && !hasActiveOverride(t) && !(stale && t.Ae && activeTransition !== t.Ae)) return t.Re;
362
- return r;
467
+ if (t.ve !== NOT_PENDING && !hasActiveOverride(t) && !(stale && t.Ge && activeTransition !== t.Ge)) return t.ve;
468
+ return s;
363
469
  }
364
470
 
365
471
  /**
@@ -371,16 +477,23 @@ function getLatestValueComputed(e) {
371
477
  * `_parentSource` but is a plain signal (no `_fn`) that never goes pending,
372
478
  * so the `_fn` check is belt-and-braces for this call site.
373
479
  */ function latestShadowWithInitializedParent(e) {
374
- if (typeof e.oe !== "function") return false;
375
- const t = e.o?.St;
480
+ if (typeof e.ce !== "function") return false;
481
+ const t = e.o?.Ht;
376
482
  if (t === undefined) return false;
377
- const n = t.rt || t;
483
+ const n = t.Te || t;
378
484
  return !(n.S & STATUS_UNINITIALIZED);
379
485
  }
380
486
 
381
487
  /** The isPending()-probe read path, installed as GlobalQueue._pendingCheck. */ function pendingCheckRead(e, t, n, i) {
382
488
  setPendingCheckActive(false);
383
- if (typeof e.oe === "function") prepareComputed(e, true);
489
+ if (typeof e.ce === "function") {
490
+ GlobalQueue.jt = true;
491
+ try {
492
+ prepareComputed(e, true);
493
+ } finally {
494
+ GlobalQueue.jt = false;
495
+ }
496
+ }
384
497
  const r = n.S;
385
498
  if (t && r & STATUS_PENDING && r & STATUS_UNINITIALIZED &&
386
499
  // The suspend-throw is for a genuinely-first-load source: the tracked
@@ -410,9 +523,9 @@ function getLatestValueComputed(e) {
410
523
  * reader saw is an input, and pending remains the truth for every reader
411
524
  * (#3028).
412
525
  */ function heldAwaitingAsync(e) {
413
- const t = e.Ae;
526
+ const t = e.Ge;
414
527
  const n = t ? currentTransition(t) : activeTransition;
415
- if (!n || n.sn) return false;
528
+ if (!n || n.Tt) return false;
416
529
  // A plain staged write (a signal/store leaf — no _fn) held while an action
417
530
  // is still running is an INPUT to a computation still in flight (#3078):
418
531
  // the pairing rule must not suppress the verdict, or a memo recomputing
@@ -422,19 +535,24 @@ function getLatestValueComputed(e) {
422
535
  // awaiting reveal — where the pairing rule stands even inside an open
423
536
  // action (#2831: a reader that saw the new value must not also see
424
537
  // pending); still-computing answers are covered by the reporter scan.
425
- if (n.ue.length && !e.oe) return true;
426
- // A node not yet stamped with a transition only qualifies through the
427
- // action check above; the reporter scan below is for transition-held
428
- // writes whose source async is still computing.
429
- if (!t) return false;
430
- for (const [e, t] of n._e) {
538
+ if (n.pe.length && !e.ce) return true;
539
+ // The reporter scan runs for an unstamped node too (#3457): a node staged
540
+ // AFTER the transaction opened is pushed straight into the transaction's
541
+ // batch (queuePendingNode, once initTransition adopted it) and only gets
542
+ // its `_transition` stamp when the flush stashes the hold, but its staged
543
+ // value is already the transaction's, and `t` resolved to that very
544
+ // transaction above. Gating on the stamp let a memo whose recompute read
545
+ // a sync memo's fresh staged value mid-flush pair "not pending" with it
546
+ // (A10) while the transaction's async source was still computing, so a
547
+ // memo-wrapped isPending() read false where a direct probe read true.
548
+ for (const [e, t] of n.oe) {
431
549
  if (t.size && e.S & STATUS_PENDING && e.o?._?.source === e) return true;
432
550
  }
433
551
  return false;
434
552
  }
435
553
 
436
554
  function recordFreshRead(e, t) {
437
- if (pendingProbe !== null && e.Re !== NOT_PENDING && t === e.Re) {
555
+ if (pendingProbe !== null && e.ve !== NOT_PENDING && t === e.ve) {
438
556
  if (heldAwaitingAsync(e)) return;
439
557
  pendingProbe.freshReads.add(e);
440
558
  }
@@ -442,10 +560,10 @@ function recordFreshRead(e, t) {
442
560
 
443
561
  function applyReask(e, t) {
444
562
  const n = !!(e.S & STATUS_PENDING);
445
- const i = t && !(n && !e.o?.De);
446
- const r = n && (e.o?.De ?? false) !== i;
563
+ const i = t && !(n && !e.o?.be);
564
+ const r = n && (e.o?.be ?? false) !== i;
447
565
  // Allocation-free for the quiet case: false is the extension default.
448
- if (i) ext(e).De = true; else if (e.o !== null) e.o.De = false;
566
+ if (i) ext(e).be = true; else if (e.o !== null) e.o.be = false;
449
567
  return r;
450
568
  }
451
569
 
@@ -497,7 +615,7 @@ function isPending(e) {
497
615
  // this flush (a downstream async pends and holds it), the suppression was
498
616
  // wrong and the wrapper must re-ask (#3028). Remember who to wake — the
499
617
  // async registration (GlobalQueue.notify) triggers wakeSuppressedProbes.
500
- if (!i.found && i.suppressed.length && context && typeof context.oe === "function") {
618
+ if (!i.found && i.suppressed.length && context && typeof context.ce === "function") {
501
619
  for (const e of i.suppressed) {
502
620
  let t = suppressedProbes.get(e);
503
621
  if (!t) suppressedProbes.set(e, t = new Set);
@@ -526,26 +644,26 @@ function isPending(e) {
526
644
  // Hook installation (same late-binding pattern as GlobalQueue._update /
527
645
  // _propagateAffects): core call sites fire these behind the same guards the
528
646
  // direct calls used, so behavior is identical once this module loads.
529
- GlobalQueue.Ue = syncCompanions;
647
+ GlobalQueue.me = syncCompanions;
530
648
 
531
- GlobalQueue.de = updatePendingSignal;
649
+ GlobalQueue.Oe = updatePendingSignal;
532
650
 
533
- GlobalQueue.me = updateChildCompanions;
651
+ GlobalQueue.Me = updateChildCompanions;
534
652
 
535
- GlobalQueue.un = snapCompanionsToState;
653
+ GlobalQueue.En = snapCompanionsToState;
536
654
 
537
- GlobalQueue.Vt = latestRead;
655
+ GlobalQueue.zt = latestRead;
538
656
 
539
- GlobalQueue.bt = pendingCheckRead;
657
+ GlobalQueue.Jt = pendingCheckRead;
540
658
 
541
- GlobalQueue.Lt = recordFreshRead;
659
+ GlobalQueue.nn = recordFreshRead;
542
660
 
543
- GlobalQueue.nt = applyReask;
661
+ GlobalQueue.Nt = applyReask;
544
662
 
545
663
  GlobalQueue.k = repollDownstreamVerdicts;
546
664
 
547
- GlobalQueue.zt = witnessAffects;
665
+ GlobalQueue.pn = witnessAffects;
548
666
 
549
- GlobalQueue.Kt = wakeSuppressedProbes;
667
+ GlobalQueue.vn = wakeSuppressedProbes;
550
668
 
551
669
  export { isPending, latest };