@solidjs/signals 2.0.0-rc.14 → 2.0.0-rc.16

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 (98) hide show
  1. package/dist/dev-shared.js +882 -405
  2. package/dist/dev.attribution.js +172 -66
  3. package/dist/dev.js +1529 -745
  4. package/dist/observe/affects-marks.js +86 -0
  5. package/dist/observe/affects.js +32 -80
  6. package/dist/observe/boundaries.js +202 -166
  7. package/dist/observe/core/async.js +192 -141
  8. package/dist/observe/core/attribution-queries.js +1 -1
  9. package/dist/observe/core/attribution.js +315 -214
  10. package/dist/observe/core/constants.js +23 -4
  11. package/dist/observe/core/context.js +3 -3
  12. package/dist/observe/core/core.js +532 -360
  13. package/dist/observe/core/dev.js +63 -35
  14. package/dist/observe/core/effect.js +47 -47
  15. package/dist/observe/core/error.js +5 -3
  16. package/dist/observe/core/external.js +5 -6
  17. package/dist/observe/core/graph.js +56 -42
  18. package/dist/observe/core/heap.js +33 -33
  19. package/dist/observe/core/identities.js +62 -0
  20. package/dist/observe/core/lanes.js +242 -229
  21. package/dist/observe/core/owner.js +62 -50
  22. package/dist/observe/core/scheduler.js +322 -168
  23. package/dist/observe/core/truth.js +45 -0
  24. package/dist/observe/core/verdict.js +114 -64
  25. package/dist/observe/deferred.js +184 -0
  26. package/dist/observe/index.js +7 -1
  27. package/dist/observe/map.js +339 -254
  28. package/dist/observe/reveal.js +77 -77
  29. package/dist/observe/signals.js +54 -33
  30. package/dist/observe/store/affects.js +76 -69
  31. package/dist/observe/store/edges.js +39 -0
  32. package/dist/observe/store/hooks.js +13 -0
  33. package/dist/observe/store/index.js +0 -2
  34. package/dist/observe/store/optimistic.js +82 -44
  35. package/dist/observe/store/projection.js +83 -64
  36. package/dist/observe/store/reconcile.js +32 -30
  37. package/dist/observe/store/store.js +219 -123
  38. package/dist/prod/affects-marks.js +86 -0
  39. package/dist/prod/affects.js +33 -79
  40. package/dist/prod/boundaries.js +185 -149
  41. package/dist/prod/core/async.js +176 -135
  42. package/dist/prod/core/constants.js +23 -4
  43. package/dist/prod/core/context.js +3 -3
  44. package/dist/prod/core/core.js +521 -353
  45. package/dist/prod/core/effect.js +66 -66
  46. package/dist/prod/core/error.js +5 -3
  47. package/dist/prod/core/external.js +5 -6
  48. package/dist/prod/core/graph.js +56 -42
  49. package/dist/prod/core/heap.js +38 -38
  50. package/dist/prod/core/identities.js +62 -0
  51. package/dist/prod/core/lanes.js +237 -224
  52. package/dist/prod/core/owner.js +62 -51
  53. package/dist/prod/core/scheduler.js +312 -158
  54. package/dist/prod/core/truth.js +45 -0
  55. package/dist/prod/core/verdict.js +113 -63
  56. package/dist/prod/deferred.js +182 -0
  57. package/dist/prod/index.js +7 -1
  58. package/dist/prod/map.js +366 -281
  59. package/dist/prod/reveal.js +77 -77
  60. package/dist/prod/signals.js +59 -38
  61. package/dist/prod/store/affects.js +76 -69
  62. package/dist/prod/store/edges.js +39 -0
  63. package/dist/prod/store/hooks.js +13 -0
  64. package/dist/prod/store/index.js +0 -2
  65. package/dist/prod/store/optimistic.js +121 -84
  66. package/dist/prod/store/projection.js +46 -34
  67. package/dist/prod/store/reconcile.js +32 -30
  68. package/dist/prod/store/store.js +205 -116
  69. package/dist/types/affects-marks.d.ts +14 -0
  70. package/dist/types/boundaries.d.ts +5 -1
  71. package/dist/types/core/async.d.ts +10 -0
  72. package/dist/types/core/attribution.d.ts +6 -2
  73. package/dist/types/core/constants.d.ts +18 -1
  74. package/dist/types/core/core.d.ts +77 -5
  75. package/dist/types/core/dev.d.ts +43 -13
  76. package/dist/types/core/graph.d.ts +11 -0
  77. package/dist/types/core/identities.d.ts +17 -0
  78. package/dist/types/core/index.d.ts +2 -1
  79. package/dist/types/core/lanes.d.ts +2 -4
  80. package/dist/types/core/owner.d.ts +5 -3
  81. package/dist/types/core/scheduler.d.ts +59 -34
  82. package/dist/types/core/truth.d.ts +16 -0
  83. package/dist/types/core/types.d.ts +21 -6
  84. package/dist/types/core/verdict.d.ts +32 -0
  85. package/dist/types/deferred.d.ts +28 -0
  86. package/dist/types/index.d.ts +6 -0
  87. package/dist/types/map.d.ts +2 -2
  88. package/dist/types/signals.d.ts +28 -7
  89. package/dist/types/store/affects.d.ts +11 -0
  90. package/dist/types/store/edges.d.ts +9 -0
  91. package/dist/types/store/hooks.d.ts +48 -0
  92. package/dist/types/store/index.d.ts +10 -4
  93. package/dist/types/store/optimistic.d.ts +3 -2
  94. package/dist/types/store/projection.d.ts +4 -3
  95. package/dist/types/store/store.d.ts +4 -39
  96. package/dist/types/store/target.d.ts +16 -8
  97. package/dist/types/store/types.d.ts +14 -0
  98. package/package.json +1 -1
@@ -23,18 +23,18 @@
23
23
  * ```
24
24
  */
25
25
  class NotReadyError extends Error {
26
- source;
27
26
  constructor(source) {
28
27
  super();
29
28
  this.source = source;
30
29
  }
31
30
  }
32
31
  class StatusError extends Error {
33
- source;
34
32
  constructor(source, original) {
35
- super(original instanceof Error ? original.message : String(original), {
36
- cause: original
37
- });
33
+ // Best-effort message: formatting must never replace the original (#4112).
34
+ try {
35
+ var message = String(original instanceof Error ? original.message : original);
36
+ } catch {}
37
+ super(message, { cause: original });
38
38
  this.source = source;
39
39
  }
40
40
  }
@@ -149,7 +149,7 @@ const CONFIG_SYNC = 1 << 6;
149
149
  // CONFIG_HELD_CHILDREN, CONFIG_INPUTS_PUBLISHED and CONFIG_ADOPTED_UNFLUSHED
150
150
  // (transaction holds, zombies, until()'s authoritative view) went with the
151
151
  // transactions.
152
- // L2 (step 2): three bits, all maintained by the node's commit.
152
+ // L2 (step 2): four bits, all maintained by the node's commit.
153
153
  /** The node has a pass awaiting commit: its live children (and `_disposal`)
154
154
  * are that pass's and were never shown — a re-pass tears them down on the
155
155
  * spot, while the frame parked on `_x._pendingFirstChild` keeps waiting for
@@ -162,6 +162,11 @@ const CONFIG_STAGED = 1 << 7;
162
162
  * parking flush they see the committed value and hold nothing, and are
163
163
  * re-derived at the landing. Set at the park seam on every staged node. */
164
164
  const CONFIG_HELD = 1 << 8;
165
+ /** A member of the uncommitted frame a held pass built (ruling A): set at
166
+ * the park seam on every computed under a staged node (`holdFrame`). Never
167
+ * shown, it has no screen: its reads are its transaction's, whichever flush
168
+ * runs it (`bornInto`, #4010). Cleared by its commit. */
169
+ const CONFIG_HELD_FRAME = 1 << 20;
165
170
  /** A15's reveal corollary (#3305): a commit published this flight's inputs
166
171
  * beneath it — the node was pending, nothing staged, when it committed — so
167
172
  * its committed value is torn against the visible frame. A render effect
@@ -186,6 +191,9 @@ const CONFIG_SLOT_NODE = 1 << 12;
186
191
  * leaf twin of REACTIVE_MANUAL_WRITE). Set by the user setter, cleared by
187
192
  * the derive's own write to the key. */
188
193
  const CONFIG_MANUAL_WRITE = 1 << 18;
194
+ /** D-41: the staged value in `_pendingValue` is an error — an
195
+ * answer of the held frame, published at its landing like a value. */
196
+ const CONFIG_STAGED_ERROR = 1 << 21;
189
197
  /** Lanes — the node carries a written guess (`optimisticWrite`): its own
190
198
  * source recomputing it, or a plain write landing on it, is the truth
191
199
  * (`supersede`). A lane's derived staging is not. */
@@ -194,15 +202,21 @@ const CONFIG_GUESS = 1 << 13;
194
202
  * pending is a question for its next pass — the verdict changed — so the
195
203
  * propagation re-derives it instead of marking it pending (as a kept-tail
196
204
  * link does, A30). Set at the window, never cleared: a reader that stopped
197
- * asking re-derives once and goes pending through its own read. */
198
- const CONFIG_VERDICT = 1 << 14;
205
+ * asking re-derives once and goes pending through its own read. The same
206
+ * bit as REACTIVE_PROBED: a pass's probe (or a read of a verdict memo,
207
+ * core.ts `read`) carries into the config at its end without a shift. */
208
+ const CONFIG_VERDICT = 1 << 15;
199
209
  /** A dependency's status is a question for this node's pass, not a fact
200
210
  * about its current one: the propagation re-derives it instead of marking
201
211
  * it, for errors as for pending (as CONFIG_VERDICT). A boundary's output
202
212
  * (show the content, or the fallback) and its `on` node (boundaries.ts); a
203
213
  * promise over an expression (`resolve`/`until`, signals.ts: the pass reads
204
214
  * the error and rejects, or re-asks a pending source). */
205
- const CONFIG_REDERIVE = 1 << 15;
215
+ const CONFIG_REDERIVE = 1 << 14;
216
+ /** An answered `createDeferred` node (deferred.ts): held because a
217
+ * downstream hold staged its landing, its re-pass is a lagging question,
218
+ * not an answer, and joins no transaction (D-40). */
219
+ const CONFIG_LAGS = 1 << 19;
206
220
  /** The reader wants the truth, not the lane's guess (`until`, signals.ts):
207
221
  * a read of displayed optimism serves the base the guess covers, makes the
208
222
  * pass no lane's, and the truth landing wakes it even when it confirms the
@@ -254,9 +268,13 @@ const CONFIG_WIDE = 1 << 26;
254
268
  * creation by dev builds only (the observe artifact mangles the option name,
255
269
  * as it does `_wide`). */
256
270
  const CONFIG_SUPERSEDES = 1 << 27;
271
+ const STATUS_NONE = 0;
257
272
  const STATUS_PENDING = 1 << 0;
258
273
  const STATUS_ERROR = 1 << 1;
259
274
  const STATUS_UNINITIALIZED = 1 << 2;
275
+ /** `catchHook`'s answer when the catcher is a boundary re-armed by
276
+ * `on` (truthy, as any catch): what it collects is its own, not the hold's. */
277
+ const CAUGHT_REARMED = 2;
260
278
  const EFFECT_RENDER = 1;
261
279
  const EFFECT_USER = 2;
262
280
  const EFFECT_TRACKED = 3;
@@ -405,13 +423,21 @@ const diagnostics = {
405
423
  };
406
424
  }
407
425
  };
426
+ // Flight identity → earliest declared start (see `AttributionSlot.markFlight`).
427
+ // Weak: a stamp must not keep a settled promise alive.
428
+ const flightOrigins = new WeakMap();
408
429
  const attributionSlot = {
409
430
  get installed() {
410
431
  return attrHooks;
411
432
  },
412
433
  withInteraction,
413
434
  withOrigin,
414
- currentOrigin
435
+ currentOrigin,
436
+ markFlight(flight, startedAt = performance.now()) {
437
+ // Earliest wins (`undefined <= n` is false: the first mark always lands).
438
+ if (!(flightOrigins.get(flight) <= startedAt)) flightOrigins.set(flight, startedAt);
439
+ },
440
+ flightOrigin: flight => flightOrigins.get(flight)
415
441
  };
416
442
  const RECORDS = Symbol.for("@solidjs/signals/observe/records");
417
443
  function recordsChannel() {
@@ -513,10 +539,11 @@ function isSuppressed(entry) {
513
539
  return suppressedEvents.has(entry);
514
540
  }
515
541
  // The repair guide: the `reactivity-diagnostics` skill `solid-js` ships,
516
- // one section per code, at its stable GitHub path. GitHub heading anchors
517
- // are lowercased with underscores kept (`### SILENT_HOLD` → `#silent_hold`).
518
- const GUIDE_URL =
519
- "https://github.com/solidjs/solid/blob/main/packages/solid/skills/reactivity-diagnostics/SKILL.md";
542
+ // one page per code on the docs site. The slug is the code lowercased with
543
+ // `_` → `-` (`STRICT_READ_UNTRACKED` → `strict-read-untracked`). The host
544
+ // flips to docs.solidjs.com at stable (one search-and-replace, with the JSDoc
545
+ // links).
546
+ const GUIDE_URL = "https://v2.solidjs.com/reference/solid-js/diagnostics/";
520
547
  const DEV = {
521
548
  hooks,
522
549
  getChildren,
@@ -526,7 +553,7 @@ const DEV = {
526
553
  getObservers,
527
554
  report: reportDiagnostic,
528
555
  guideUrl(code) {
529
- return `${GUIDE_URL}#${code.toLowerCase()}`;
556
+ return GUIDE_URL + code.toLowerCase().replace(/_/g, "-");
530
557
  }
531
558
  };
532
559
  /**
@@ -565,15 +592,29 @@ function ownerPath(subject) {
565
592
  }
566
593
  return path.length ? path.reverse() : undefined;
567
594
  }
595
+ /**
596
+ * The innermost source location on the subject's chain: the subject's own
597
+ * `_loc` (a signal or computation created with `{ loc }`), else the first
598
+ * `_loc` up the owners — a signal hops to its registering owner, then
599
+ * `_parent` upward (a component root carries its JSX site). The same walk
600
+ * `ownerPath` makes; `undefined` when nothing on the chain was located.
601
+ */
602
+ function locationOf(subject) {
603
+ // Owners have a `_parent` key (null on a root); a signal has none and hops
604
+ // to its registering `_owner` instead.
605
+ for (let node = subject; node; node = node._parent !== undefined ? node._parent : node._owner)
606
+ if (node._loc) return node._loc;
607
+ return undefined;
608
+ }
568
609
  /**
569
610
  * Records a diagnostic on the structured channel (listeners, captures) and
570
611
  * returns the entry. `subject` locates it: the current reactive `context` by
571
612
  * default (right for the synchronous rule checks — they fire inside the
572
613
  * scope that misbehaved); pass the node for scheduler-time findings whose
573
614
  * ambient context is the flush, or `null` for events that have no location
574
- * by nature. An `ownerPath` already on the event wins over the subject walk
575
- * (hosts whose owners are not signals' owners compute their own). Console
576
- * output is a separate, dev-tier step — see `reportDiagnostic`.
615
+ * by nature. An `ownerPath` (or `loc`) already on the event wins over the
616
+ * subject walk (hosts whose owners are not signals' owners compute their
617
+ * own). Console output is a separate, dev-tier step — see `reportDiagnostic`.
577
618
  */
578
619
  function emitDiagnostic(event, subject = context) {
579
620
  const entry = {
@@ -590,6 +631,10 @@ function emitDiagnostic(event, subject = context) {
590
631
  const path = ownerPath(subject);
591
632
  if (path) entry.ownerPath = path;
592
633
  }
634
+ if (entry.loc === undefined) {
635
+ const loc = locationOf(subject);
636
+ if (loc !== undefined) entry.loc = loc;
637
+ }
593
638
  const live = subject ?? undefined;
594
639
  if (live !== undefined) eventSubjects.set(entry, live);
595
640
  for (const listener of diagnosticListeners) listener(entry, live);
@@ -626,12 +671,31 @@ function takeFooter(entry) {
626
671
  * `reportDiagnostic(emitDiagnostic(…))`. Weak, keyed by the entry.
627
672
  */
628
673
  const eventSubjects = new WeakMap();
674
+ /**
675
+ * The `at` line of a console report: the event's `loc` as the compiler wrote
676
+ * it (`src/App.tsx:18:3`), or — in a browser, where a dev server serves the
677
+ * project root at `location.origin` — `http://localhost:5173/src/App.tsx:18:3`,
678
+ * which Chrome and Firefox link to the Sources panel. A path that leaves the
679
+ * root (`../`) or is absolute is not under the origin and prints as-is.
680
+ */
681
+ function locationLine(loc) {
682
+ if (
683
+ typeof document !== "undefined" &&
684
+ typeof location !== "undefined" &&
685
+ !loc.startsWith("..") &&
686
+ !loc.startsWith("/")
687
+ )
688
+ return `${location.origin}/${loc}`;
689
+ return loc;
690
+ }
629
691
  /**
630
692
  * The console face of a diagnostic — ONE entry per finding: the message, the
631
693
  * owner path (`in <App> › <TodoRow> › effect`) so a human can locate it, the
632
- * once-per-code footer as trailing lines, and — when the subject is a
633
- * binding effect the rendering runtime tagged — the element it writes, as a
634
- * second console argument (hover highlights it, click jumps to Elements).
694
+ * source location (`at src/App.tsx:18:3` — see `locationLine`) when a node on
695
+ * the chain was compiled with one, the once-per-code footer as trailing
696
+ * lines, and — when the subject is a binding effect the rendering runtime
697
+ * tagged — the element it writes, as a second console argument (hover
698
+ * highlights it, click jumps to Elements).
635
699
  * Severity picks the console method. Call sites report the entry
636
700
  * `emitDiagnostic` returned so the structured and console channels never
637
701
  * disagree. Dev-tier: in an observe build this is a no-op, so wiring paths
@@ -642,6 +706,7 @@ function reportDiagnostic(entry) {
642
706
  if (suppressedEvents.has(entry)) return;
643
707
  let text = entry.message;
644
708
  if (entry.ownerPath) text += `\n in ${entry.ownerPath.join(" › ")}`;
709
+ if (entry.loc) text += `\n at ${locationLine(entry.loc)}`;
645
710
  const footer = takeFooter(entry);
646
711
  if (footer) text += `\n${footer}`;
647
712
  const element = eventSubjects.get(entry)?._devElement;
@@ -1047,6 +1112,101 @@ function devCheckQuiescent(isQueuedForCommit) {
1047
1112
  return;
1048
1113
  }
1049
1114
 
1115
+ // Optional-module hooks: module bindings, not `GlobalQueue` statics, so a
1116
+ // bundle that never imports the installing module never assigns them — its
1117
+ // minifier folds every guarded call site (and the branch behind it) away.
1118
+ // Each module installs its own set at evaluation (pay-for-use).
1119
+ //
1120
+ // Lanes (lanes.ts; installed when `createOptimistic` — or verdict.ts — is
1121
+ // imported; every call site is behind a bit or a `passLane` nothing else sets).
1122
+ let laneReadHook = null;
1123
+ let laneStageHook = null;
1124
+ let laneOutcomeHook = null;
1125
+ let laneWriteHook = null;
1126
+ let applyGuessesHook = null;
1127
+ let laneSeamsHook = null;
1128
+ let laneCorrectionsHook = null;
1129
+ let endLanesHook = null;
1130
+ let lanesBlockedHook = null;
1131
+ function installLaneHooks(
1132
+ read,
1133
+ stage,
1134
+ outcome,
1135
+ write,
1136
+ applyGuesses,
1137
+ seams,
1138
+ corrections,
1139
+ end,
1140
+ blocked
1141
+ ) {
1142
+ laneReadHook = read;
1143
+ laneStageHook = stage;
1144
+ laneOutcomeHook = outcome;
1145
+ laneWriteHook = write;
1146
+ applyGuessesHook = applyGuesses;
1147
+ laneSeamsHook = seams;
1148
+ laneCorrectionsHook = corrections;
1149
+ endLanesHook = end;
1150
+ lanesBlockedHook = blocked;
1151
+ }
1152
+ // Boundaries (boundaries.ts): the display consumers between an observer
1153
+ // and the root. `catch` — status from a frame reader, nearest boundary
1154
+ // first (true: caught, the root never hears of it; a clear — flags 0 —
1155
+ // settles the reader there); `fresh` — a pass that read a hold (true: a
1156
+ // first pass a loading boundary that has not shown content caught,
1157
+ // `joinPass`; `held`: the flush's hold owns it, nothing catches it);
1158
+ // `hidden` — a frame reader behind a fallback (the boundary showing it)
1159
+ // is not on screen and holds nothing; `boundarySeam` — the seam's sweep (readers
1160
+ // gone or settled without a pass reveal; an `on` re-arm resolves);
1161
+ // `heldRun` — a queued run under a fallback-showing boundary waits for
1162
+ // the reveal (true: held; the boundary re-queues it by type). The
1163
+ // synchronous first render on creation builds the subtree, attached or
1164
+ // not; its updates and the user effects wait.
1165
+ let catchHook = null;
1166
+ let freshHook = null;
1167
+ let hiddenHook = null;
1168
+ let boundarySeamHook = null;
1169
+ let heldRunHook = null;
1170
+ function installBoundaryHooks(caught, fresh, hidden, seam, heldRun) {
1171
+ catchHook = caught;
1172
+ freshHook = fresh;
1173
+ hiddenHook = hidden;
1174
+ boundarySeamHook = seam;
1175
+ heldRunHook = heldRun;
1176
+ }
1177
+ /** Store (store/store.ts): `park` — the flush parked into `t`: the store
1178
+ * materializes and holds the container node of every staging that had none
1179
+ * (a staging needs the node as its home only when something can read the
1180
+ * frame through it; a hold can); `commit` — fold the pending backings whose
1181
+ * container nodes this flush committed (the owned-raw model's one mutation
1182
+ * point). */
1183
+ let storeParkHook = null;
1184
+ let storeCommitHook = null;
1185
+ function installStoreHooks(park, commit) {
1186
+ storeParkHook = park;
1187
+ storeCommitHook = commit;
1188
+ }
1189
+ /** `affects()` marks (affects.ts): the releases at a landing and at the seam
1190
+ * (ambient marks). */
1191
+ let releaseMarksHook = null;
1192
+ let releaseAmbientMarksHook = null;
1193
+ function installMarkHooks(release, releaseAmbient) {
1194
+ releaseMarksHook = release;
1195
+ releaseAmbientMarksHook = releaseAmbient;
1196
+ }
1197
+ /** Verdicts (verdict.ts). */
1198
+ let observeFlightHook = null;
1199
+ function installVerdictHooks(observeFlight) {
1200
+ observeFlightHook = observeFlight;
1201
+ }
1202
+ /** External-source bridge (wired by enableExternalSource(); undefined while
1203
+ * no config is active — including after _resetExternalSourceConfig()). */
1204
+ let wireExternalSourceHook = undefined;
1205
+ let externalUntrackHook = undefined;
1206
+ function setExternalHooks(wire, untrack) {
1207
+ wireExternalSourceHook = wire;
1208
+ externalUntrackHook = untrack;
1209
+ }
1050
1210
  /** Live transactions — opened, unmerged, not landed. Scanned at every seam
1051
1211
  * while non-empty; nothing on the plain path. */
1052
1212
  const transactions = [];
@@ -1185,10 +1345,7 @@ function blocked(t) {
1185
1345
  judge ??= t;
1186
1346
  // An action still running in it holds it open (action.ts). Its lanes
1187
1347
  // (lanes.ts): a lane under `t` blocks it while blocked itself.
1188
- const r =
1189
- t._open !== 0 ||
1190
- blockedBy(t._nodes, t) ||
1191
- (!!GlobalQueue._lanesBlocked && GlobalQueue._lanesBlocked(t));
1348
+ const r = t._open !== 0 || blockedBy(t._nodes, t) || !!lanesBlockedHook?.(t);
1192
1349
  judge = prev;
1193
1350
  return r;
1194
1351
  }
@@ -1262,7 +1419,7 @@ function blockedBy(nodes, owner, own = false) {
1262
1419
  // shows nothing — a mount the frame has not revealed — and holds
1263
1420
  // nothing.
1264
1421
  if (
1265
- (r._type === EFFECT_RENDER ||
1422
+ ((r._type === EFFECT_RENDER && r._statusFlags & STATUS_PENDING) ||
1266
1423
  r._flags & REACTIVE_FRAME_READ ||
1267
1424
  (r._config & CONFIG_REDERIVE && r._statusFlags === STATUS_PENDING)) &&
1268
1425
  (s._gen === r._depGen || r._x?._error != null) &&
@@ -1283,10 +1440,7 @@ function blockedBy(nodes, owner, own = false) {
1283
1440
  * everything under it, so a reader there is not on screen and holds
1284
1441
  * nothing — boundaries.ts). */
1285
1442
  function onScreen(r, t) {
1286
- return (
1287
- !(r._flags & REACTIVE_ZOMBIE && removalStagedBy(r, t)) &&
1288
- !(GlobalQueue._hidden && GlobalQueue._hidden(r))
1289
- );
1443
+ return !(r._flags & REACTIVE_ZOMBIE && removalStagedBy(r, t)) && !hiddenHook?.(r);
1290
1444
  }
1291
1445
  /** A15 (#3463): a reader whose removal is staged — a zombie, parked by a
1292
1446
  * pass awaiting its commit — is still on screen and live for every hold,
@@ -1296,13 +1450,8 @@ function onScreen(r, t) {
1296
1450
  * the nearest non-zombie ancestor. */
1297
1451
  function removalStagedBy(r, t) {
1298
1452
  let o = r;
1299
- while (o !== null && o._flags & REACTIVE_ZOMBIE) o = o._parent;
1300
- return (
1301
- o !== null &&
1302
- (o._config & CONFIG_HELD) !== 0 &&
1303
- o._x?._transaction != null &&
1304
- txOf(o) === resolveTx(t)
1305
- );
1453
+ while (o?._flags & REACTIVE_ZOMBIE) o = o._parent;
1454
+ return o?._config & CONFIG_HELD && o._x?._transaction != null && txOf(o) === resolveTx(t);
1306
1455
  }
1307
1456
  /** Unchanged passes this flush whose stale dependency tail awaits the
1308
1457
  * flush's verdict (A30, #3469): trimmed when the flush commits, kept when
@@ -1355,10 +1504,26 @@ function list(n, t) {
1355
1504
  function holdFrame(owner, t) {
1356
1505
  for (let c = owner._firstChild; c !== null; c = c._nextSibling) {
1357
1506
  if (c._x?._transaction?._lane) continue;
1358
- if (c._fn !== undefined && !(c._config & CONFIG_HELD)) holdNode(c, t);
1507
+ if (c._fn !== undefined) {
1508
+ if (!(c._config & CONFIG_HELD)) holdNode(c, t);
1509
+ c._config |= CONFIG_HELD_FRAME;
1510
+ }
1359
1511
  holdFrame(c, t);
1360
1512
  }
1361
1513
  }
1514
+ /** The nodes under `owner` a transaction other than `t` holds (boundaries.ts:
1515
+ * what an earlier hold staged under a re-armed boundary). */
1516
+ function heldUnder(owner, t, fn) {
1517
+ for (let c = owner._firstChild; c !== null; c = c._nextSibling) {
1518
+ if ((c._config & (CONFIG_HELD | CONFIG_OVERRIDE)) === CONFIG_HELD && txOf(c) !== t) fn(c);
1519
+ heldUnder(c, t, fn);
1520
+ }
1521
+ }
1522
+ /** `n` leaves the hold it was staged in: its next pass publishes mainline. */
1523
+ function unhold(n) {
1524
+ n._x._transaction = null;
1525
+ n._config &= ~CONFIG_HELD;
1526
+ }
1362
1527
  const dirtyQueue = {
1363
1528
  _heap: new Array(2000).fill(undefined),
1364
1529
  _marked: false,
@@ -1551,7 +1716,7 @@ class GlobalQueue {
1551
1716
  // Lanes: an ended action body judges its guesses now that their passes
1552
1717
  // have run (lanes.ts); a correction's re-derivations run in one more
1553
1718
  // pure round — the parent's flights before the parent is judged below.
1554
- if (GlobalQueue._laneCorrections?.()) runHeap(dirtyQueue, GlobalQueue._update);
1719
+ if (laneCorrectionsHook?.()) runHeap(dirtyQueue, GlobalQueue._update);
1555
1720
  const joined = flushTransaction;
1556
1721
  flushTransaction = null;
1557
1722
  // The frame's verdict is in: the guesses written since the last seam
@@ -1559,7 +1724,7 @@ class GlobalQueue {
1559
1724
  // parents a nested lane to it), or are dropped — the frame commits, and
1560
1725
  // there is nothing to be optimistic over. Their passes run next round;
1561
1726
  // a parent landing at this very seam ends the lane before they do.
1562
- GlobalQueue._applyGuesses?.(joined);
1727
+ applyGuessesHook?.(joined);
1563
1728
  // A lane's own flush (its flight landing, its reveal) parks nothing: the
1564
1729
  // lane's work reached it per pass; the frame's plain writes commit.
1565
1730
  // (Resolved: a guess over another transaction's guess merged them.)
@@ -1599,10 +1764,12 @@ class GlobalQueue {
1599
1764
  if (
1600
1765
  n._pendingValue !== NOT_PENDING &&
1601
1766
  !(n._statusFlags & (STATUS_PENDING | STATUS_UNINITIALIZED)) &&
1602
- n._equals &&
1603
- n._equals(n._value, n._pendingValue) &&
1604
1767
  n._x?._pendingFirstChild == null &&
1605
- n._x?._pendingDisposal == null
1768
+ n._x?._pendingDisposal == null &&
1769
+ // The comparator last: a pass that created children (an object
1770
+ // owning a fresh async memo, #4111) must not have it read them.
1771
+ // A NotReady read there is a change (`isSame`).
1772
+ isSame(n, n._value, n._pendingValue)
1606
1773
  ) {
1607
1774
  n._pendingValue = NOT_PENDING;
1608
1775
  n._config &= ~CONFIG_STAGED;
@@ -1616,7 +1783,7 @@ class GlobalQueue {
1616
1783
  if (n._config & CONFIG_STAGED) holdFrame(n, t);
1617
1784
  }
1618
1785
  pendingNodes.length = 0;
1619
- GlobalQueue._storePark?.(t);
1786
+ storeParkHook?.(t);
1620
1787
  // (This flush's runs are stashed with `t` below — after the landings,
1621
1788
  // so a `t` that lands at this very seam runs them first, ahead of
1622
1789
  // the runs it held from earlier flushes.)
@@ -1626,13 +1793,17 @@ class GlobalQueue {
1626
1793
  // through like the same read outside): it re-runs now, as the
1627
1794
  // holder's verdict lane's work — before the lane seams, which reveal
1628
1795
  // it (nothing commits this seam that could dispose it).
1629
- const zombies = deferredZombies.splice(0).sort((a, b) => a._height - b._height);
1630
- for (let i = 0; i < zombies.length; i++)
1631
- if (
1632
- (zombies[i]._flags & (REACTIVE_DISPOSED | REACTIVE_ZOMBIE)) === REACTIVE_ZOMBIE &&
1633
- zombies[i]._config & CONFIG_VERDICT
1634
- )
1635
- recompute(zombies[i]);
1796
+ // (Verdict readers exist only with the verdict engine installed.)
1797
+ const zombies = deferredZombies.splice(0);
1798
+ if (observeFlightHook) {
1799
+ zombies.sort((a, b) => a._height - b._height);
1800
+ for (let i = 0; i < zombies.length; i++)
1801
+ if (
1802
+ (zombies[i]._flags & (REACTIVE_DISPOSED | REACTIVE_ZOMBIE)) === REACTIVE_ZOMBIE &&
1803
+ zombies[i]._config & CONFIG_VERDICT
1804
+ )
1805
+ recompute(zombies[i]);
1806
+ }
1636
1807
  // The unchanged passes' tails stay linked (A30, #3469): their inputs
1637
1808
  // are held, and the committed frame still derives from them — no trim.
1638
1809
  } else {
@@ -1663,14 +1834,16 @@ class GlobalQueue {
1663
1834
  // The passes that read this frame's stagings as the screen read held
1664
1835
  // writes if it parked: the lane seam re-derives them on the committed
1665
1836
  // world (a lane's runs wait this round).
1666
- GlobalQueue._laneSeams?.(t !== null ? stagedReaders : null);
1667
- if (stagedReaders.length) stagedReaders.length = 0;
1837
+ if (laneSeamsHook) {
1838
+ laneSeamsHook(t !== null ? stagedReaders : null);
1839
+ if (stagedReaders.length) stagedReaders.length = 0;
1840
+ }
1668
1841
  this._queues = [[], []];
1669
1842
  for (let k = transactions.length - 1; k >= 0; k--) {
1670
1843
  const u = transactions[k];
1671
1844
  if (blocked(u)) continue;
1672
1845
  transactions.splice(k, 1);
1673
- GlobalQueue._endLanes?.(u);
1846
+ endLanesHook?.(u);
1674
1847
  land(u);
1675
1848
  // A landing's commits can dispose a zombie a transaction judged above
1676
1849
  // was blocked on (#3463: live "until the commit that disposes it") —
@@ -1679,12 +1852,13 @@ class GlobalQueue {
1679
1852
  }
1680
1853
  // The store folds the pending backings whose container nodes committed
1681
1854
  // — this flush's, or a landing's (store/store.ts installs it).
1682
- GlobalQueue._storeCommit?.();
1855
+ storeCommitHook?.();
1683
1856
  // Marks declared outside a transaction: the flush that carried them
1684
1857
  // parked (something async below) — the window is the frame's, the marks
1685
1858
  // are its transaction's; else they release at the seam, verdict-only,
1686
1859
  // nothing to show (affects.ts).
1687
- GlobalQueue._releaseAmbientMarks?.(t !== null && transactions.indexOf(t) !== -1 ? t : null);
1860
+ const live = t && liveTx(t);
1861
+ releaseAmbientMarksHook?.(live);
1688
1862
  // The effect phase: lane work first (displayed ahead of the frame), then
1689
1863
  // this flush's runs, then the runs the landings held from earlier
1690
1864
  // flushes — as one pass would have queued them (#3540: a shell
@@ -1692,18 +1866,17 @@ class GlobalQueue {
1692
1866
  // write; #3528: a display-ahead swap before the count it is shown
1693
1867
  // beside). A parked frame's runs wait with its transaction instead: the
1694
1868
  // effect phase applies a committed frame.
1695
- const u = t && liveTx(t);
1696
- const parked = u !== null;
1869
+ const parked = live !== null;
1697
1870
  // Attribution hook: this flush found its transaction incomplete — its
1698
1871
  // writes stay staged, its runs are stashed below. Before the effect
1699
1872
  // phase (the lanes' display-ahead runs are the visible acknowledgers);
1700
1873
  // `holdEnd` fires after it (`flush`).
1701
1874
  if (parked && attrHooks !== null) {
1702
- attrHooks.holdStart(u);
1875
+ attrHooks.holdStart(live);
1703
1876
  holding = true;
1704
1877
  }
1705
1878
  for (let i = 0; i < 2; i++) {
1706
- if (parked) append(u._queues[i], own[i]);
1879
+ if (parked) append(live._queues[i], own[i]);
1707
1880
  // The plain flush — no reveal, no landing, not parked — has nothing to
1708
1881
  // order around its own runs: they are the queue as they stand (the
1709
1882
  // same array, not a copy; `run` takes it whole). Every other seam
@@ -1723,7 +1896,7 @@ class GlobalQueue {
1723
1896
  }
1724
1897
  // Boundaries: readers that settled, landed or died this flush are
1725
1898
  // dropped; a fallback with none left reveals next round.
1726
- GlobalQueue._boundarySeam?.();
1899
+ boundarySeamHook?.();
1727
1900
  }
1728
1901
  run(type) {
1729
1902
  const effects = this._queues[type - 1];
@@ -1755,7 +1928,7 @@ class GlobalQueue {
1755
1928
  * first: a fallback catches what is not ready under it, and the root
1756
1929
  * never hears of it. */
1757
1930
  notify(node, mask, flags, error) {
1758
- if (GlobalQueue._catch && GlobalQueue._catch(node, flags & mask, error)) return true;
1931
+ if (catchHook?.(node, flags & mask, error)) return true;
1759
1932
  if (mask & STATUS_PENDING) {
1760
1933
  if (flags & STATUS_PENDING) {
1761
1934
  if (_enforceLoadingBoundary) _hitUnhandledAsync = true;
@@ -1792,8 +1965,11 @@ class GlobalQueue {
1792
1965
  function reruns(u) {
1793
1966
  for (let i = 0; i < u._reruns.length; i++) {
1794
1967
  const r = u._reruns[i];
1795
- if (r._flags & REACTIVE_DISPOSED) continue;
1796
- if (r._flags & REACTIVE_FRAME_READ) {
1968
+ // An errored reader's display is its error (D-39): the landing gives it
1969
+ // nothing to re-derive, and a re-run would be a read that retries the
1970
+ // errored source (#3945).
1971
+ if (r._flags & REACTIVE_DISPOSED || r._statusFlags & STATUS_ERROR) continue;
1972
+ if (r._flags & REACTIVE_FRAME_READ || r._config & CONFIG_VERDICT) {
1797
1973
  r._flags &= ~REACTIVE_FRAME_READ;
1798
1974
  r._modified = false;
1799
1975
  // Its frame is replaced by the re-derivation: a run one of its
@@ -1811,7 +1987,7 @@ function land(u) {
1811
1987
  // (`_nodes` still lists them).
1812
1988
  if (attrHooks !== null) attrHooks.transitionSettled(u);
1813
1989
  reruns(u);
1814
- if (u._marks !== null) GlobalQueue._releaseMarks(u._marks);
1990
+ if (u._marks !== null) releaseMarksHook(u._marks);
1815
1991
  // Old children die in the commits (cleanups first), then the stashed
1816
1992
  // effects run ahead of this flush's own. A node a lane took over since
1817
1993
  // (a guess written over the staged truth) is the lane's to land.
@@ -1821,6 +1997,10 @@ function land(u) {
1821
1997
  n._x._transaction = null;
1822
1998
  commitPendingNode(n);
1823
1999
  }
2000
+ // A cleanup those commits ran that wrote a node `u` still held joined
2001
+ // `u`, which has left `transactions`: the write is this landing's, and
2002
+ // the next flush must not park in it (#4089).
2003
+ if (flushTransaction === u) flushTransaction = null;
1824
2004
  releaseQueues(u);
1825
2005
  }
1826
2006
  /** The held runs join this flush's effect phase after its own runs (the
@@ -1940,8 +2120,20 @@ function commitPendingNode(n) {
1940
2120
  // commit beneath a flight publishes its inputs (A15 reveal corollary,
1941
2121
  // #3305): a reveal of it must observe, not show the pre-flight value.
1942
2122
  const inFlight = n._pendingValue === NOT_PENDING && (c._statusFlags & STATUS_PENDING) !== 0;
1943
- n._config = inFlight ? (n._config & ~CONFIG_HELD) | CONFIG_INPUTS_PUBLISHED : n._config & -1409;
1944
- if (n._pendingValue !== NOT_PENDING) {
2123
+ const err = n._config & CONFIG_STAGED_ERROR;
2124
+ // An answer commits: the error it replaces leaves the display (D-41).
2125
+ if (!inFlight && c._x) c._x._shownError = undefined;
2126
+ n._config = inFlight ? (n._config & -1048833) | CONFIG_INPUTS_PUBLISHED : n._config & -3147137;
2127
+ // D-41: the staged answer is an error — published now, when a value would
2128
+ // show, through the ordinary error path (`stageError`). Its readers first
2129
+ // see it in the cycle after this seam: a tracked re-read there is not a
2130
+ // retry (`read`), or a boundary re-derived by it would retry it forever.
2131
+ if (err) {
2132
+ const e = n._pendingValue;
2133
+ n._pendingValue = NOT_PENDING;
2134
+ n._time = clock + 1;
2135
+ notifyStatus(c, STATUS_ERROR, e);
2136
+ } else if (n._pendingValue !== NOT_PENDING) {
1945
2137
  n._value = n._pendingValue;
1946
2138
  n._pendingValue = NOT_PENDING;
1947
2139
  // A node born staged (recompute) initializes at this commit.
@@ -2432,13 +2624,15 @@ function getOwner() {
2432
2624
  /**
2433
2625
  * Low-level: registers `fn` as a disposal callback on the current owner.
2434
2626
  * Most code should use `onCleanup()` from `solid-js`, which adds dev-mode
2435
- * checks. `cleanup()` is the unchecked primitive used by internals.
2627
+ * checks. `cleanup()` is the unchecked primitive used by internals; `owner`
2628
+ * (internal) targets a specific owner's disposal list instead of the ambient
2629
+ * one.
2436
2630
  */
2437
- function cleanup(fn) {
2438
- if (!context) return fn;
2439
- if (!context._disposal) context._disposal = fn;
2440
- else if (Array.isArray(context._disposal)) context._disposal.push(fn);
2441
- else context._disposal = [context._disposal, fn];
2631
+ function cleanup(fn, owner = context) {
2632
+ if (!owner) return fn;
2633
+ if (!owner._disposal) owner._disposal = fn;
2634
+ else if (Array.isArray(owner._disposal)) owner._disposal.push(fn);
2635
+ else owner._disposal = [owner._disposal, fn];
2442
2636
  return fn;
2443
2637
  }
2444
2638
  /**
@@ -2475,12 +2669,18 @@ function createOwner(options) {
2475
2669
  const parent = context;
2476
2670
  const transparent = options?.transparent ?? false;
2477
2671
  // Prod and observe boilerplates (see core.ts computed()). The observe
2478
- // literal carries the `_name` slot the rendering layer fills with the
2479
- // component label (`owner._name = "<App>"`) — a slot, so labelling a root
2480
- // is a plain store rather than a shape fork between labelled and plain roots.
2672
+ // literal carries the `_name` and `_loc` slots the rendering layer fills
2673
+ // with the component label and JSX site (`owner._name = "<App>"`,
2674
+ // `owner._loc = "src/App.tsx:3:5"`) — slots, so labelling a root is a
2675
+ // plain store rather than a shape fork between labelled and plain roots.
2481
2676
  const owner = {
2482
2677
  id: inheritId(options, transparent, parent),
2483
2678
  _config: transparent ? CONFIG_TRANSPARENT : 0,
2679
+ // Present from birth: disposeChildren marks death here on roots too, and
2680
+ // a first store of a missing property is a shape transition plus a
2681
+ // properties-store allocation per root — on a 100×4 table teardown that
2682
+ // was 12 KB and most of disposal's time (2026-10-17).
2683
+ _flags: REACTIVE_NONE,
2484
2684
  _root: true,
2485
2685
  _parentComputed: parent?._root ? parent._parentComputed : parent,
2486
2686
  _firstChild: null,
@@ -2492,7 +2692,8 @@ function createOwner(options) {
2492
2692
  _x: null,
2493
2693
  _parent: parent,
2494
2694
  dispose: disposeRootSelf,
2495
- _name: undefined
2695
+ _name: undefined,
2696
+ _loc: undefined
2496
2697
  };
2497
2698
  if (parent && parent._config & CONFIG_CHILDREN_FORBIDDEN) {
2498
2699
  emitDiagnostic({
@@ -2544,11 +2745,14 @@ function createOwner(options) {
2544
2745
  * const detached = runWithOwner(null, () => createRoot(d => d));
2545
2746
  * ```
2546
2747
  *
2547
- * @description https://docs.solidjs.com/reference/reactive-utilities/create-root
2748
+ * @description https://v2.solidjs.com/reference/solid-js/advanced/owner-introspection/create-root
2548
2749
  */
2549
2750
  function createRoot(init, options) {
2550
2751
  const owner = createOwner(options);
2551
- return runWithOwner(owner, () => init(() => owner.dispose()));
2752
+ // A bound function, not an arrow: an arrow would share this scope, which
2753
+ // holds `init`, so keeping `dispose` would keep `init` and all it closes
2754
+ // over alive (#4095).
2755
+ return runWithOwner(owner, () => init(disposeRootSelf.bind(owner, true)));
2552
2756
  }
2553
2757
 
2554
2758
  /** The store's shared slot-node release handler (store/store.ts installs it
@@ -2581,12 +2785,14 @@ function unlinkSubs(link) {
2581
2785
  // read. The settle path runs this same last-one-out check when that
2582
2786
  // observer releases (the untracked-read dormancy sweep guards on
2583
2787
  // pending identically).
2788
+ // During a pass's teardown of its unfinished frame, the release waits
2789
+ // for the pass to end (`deferredReleases`, #4142).
2584
2790
  const c = dep;
2585
2791
  c._fn &&
2586
2792
  c._config & CONFIG_AUTO_DISPOSE &&
2587
2793
  !(c._flags & REACTIVE_ZOMBIE) &&
2588
2794
  !(c._statusFlags & STATUS_PENDING) &&
2589
- unobserved(c);
2795
+ (deferredReleases ? deferredReleases.push(c) : unobserved(c));
2590
2796
  }
2591
2797
  }
2592
2798
  return nextDep;
@@ -2615,6 +2821,17 @@ function clearDeps(el) {
2615
2821
  el._deps = null;
2616
2822
  el._depsTail = null;
2617
2823
  }
2824
+ /**
2825
+ * Last-one-out releases held back while a pass tears down the frame of an
2826
+ * earlier pass that never committed (#4142). The pass is about to rebuild
2827
+ * the same children, which read the same lazy nodes again. Released on the
2828
+ * spot, a lazy async memo whose only reader was the old frame's child memo
2829
+ * was torn down, re-ran on the new child's read, went pending, and the cycle
2830
+ * repeated forever. `recompute` collects them around that teardown and
2831
+ * releases what is still unobserved when the pass ends.
2832
+ */
2833
+ let deferredReleases = null;
2834
+ const deferReleases = list => (deferredReleases = list);
2618
2835
  function unobserved(el) {
2619
2836
  deleteFromHeap(el, dirtyQueue);
2620
2837
  clearDeps(el);
@@ -2816,6 +3033,43 @@ function releaseSettledDependents(el) {
2816
3033
  // (releaseSettledDependents shape): identity holders can sit below an
2817
3034
  // intermediate whose own error state has since been scrubbed or replaced
2818
3035
  // (e.g. an error boundary's tree node).
3036
+ /** D-41: an error a pass of a held node found is an answer of
3037
+ * that frame — it ends the wait like a value and shows when a value would.
3038
+ * Staged like one: `_pendingValue` carries it (CONFIG_STAGED_ERROR), readers
3039
+ * in the hold derive from it (`serve` throws it), and the commit publishes
3040
+ * it through the ordinary error path (`commitPendingNode` → `notifyStatus`),
3041
+ * dependents included. Not for a lane pass, nor a node with no shown value
3042
+ * (born held — its error has no frame to wait behind): those publish now,
3043
+ * as before. Returns whether the error was staged. */
3044
+ function stageError(el, error, inPass) {
3045
+ // Held, or — for a pass's own throw — derived from a staging of a flush
3046
+ // (REACTIVE_STAGED_READ is this pass's; a landing after the pass cannot
3047
+ // read it, the flush it read a staging of has committed or parked since).
3048
+ // Staged whether or not the flush has joined a hold yet: a pass that runs
3049
+ // before its async sibling joins is the seam's to decide, like a value
3050
+ // (#4109). A flush that parks nothing publishes it at its commit (S-38).
3051
+ if (
3052
+ !(
3053
+ el._config & CONFIG_HELD ||
3054
+ (inPass && el._flags & REACTIVE_STAGED_READ && globalQueue._running)
3055
+ ) ||
3056
+ passLane
3057
+ )
3058
+ return false;
3059
+ // The node answered (an error ends the wait — no longer pending, nor
3060
+ // blocked); a shown error stays its display until the commit.
3061
+ clearPendingSources(el);
3062
+ if (el._x) el._x._blocked = false;
3063
+ if (!((el._statusFlags &= STATUS_ERROR | STATUS_UNINITIALIZED) & STATUS_ERROR))
3064
+ setPendingError(el);
3065
+ el._pendingValue = error instanceof StatusError ? error : new StatusError(el, error);
3066
+ el._config |= CONFIG_STAGED | CONFIG_STAGED_ERROR;
3067
+ // Readers parked on the flight re-run and derive from the staging; the
3068
+ // verdict readers the walk wakes need a flush (#4106).
3069
+ settlePendingSource(el);
3070
+ schedule();
3071
+ return true;
3072
+ }
2819
3073
  function settleErroredDependents(el, error) {
2820
3074
  let scheduled = false;
2821
3075
  const visited = new Set();
@@ -3062,7 +3316,11 @@ function handleAsync(
3062
3316
  el._time = clock;
3063
3317
  return;
3064
3318
  }
3065
- notifyStatus(el, stillPending ? STATUS_PENDING : STATUS_ERROR, error);
3319
+ // D-41: a held node's error is staged (`stageError`). A derived store's
3320
+ // readers learn its error through the family, not the derive's
3321
+ // dependents: it publishes now.
3322
+ if (stillPending || setter || !stageError(el, error))
3323
+ notifyStatus(el, stillPending ? STATUS_PENDING : STATUS_ERROR, error);
3066
3324
  // A NotReady rejection is a landing into another pending source. The
3067
3325
  // rejected flight will never settle its self entry, so transfer ownership
3068
3326
  // after notifyStatus has propagated the replacement source.
@@ -3080,7 +3338,7 @@ function handleAsync(
3080
3338
  // skip this stale async result — the upcoming flush will recompute the node
3081
3339
  // with the new value, creating a fresh Promise that supersedes this one.
3082
3340
  if (el._flags & REACTIVE_DIRTY) return;
3083
- const wasUninitialized = !!(el._statusFlags & STATUS_UNINITIALIZED);
3341
+ const wasUninitialized = el._statusFlags & STATUS_UNINITIALIZED;
3084
3342
  landStatus(el);
3085
3343
  // Attribution hook: lets the engine snapshot state before the landing
3086
3344
  // branches, so it can tell whether the plain path's setSignal committed a
@@ -3093,22 +3351,27 @@ function handleAsync(
3093
3351
  handleError(error);
3094
3352
  return;
3095
3353
  }
3354
+ // A store derive's landing commits through its setter: unheld, the
3355
+ // shown error leaves now; held, at the derive's commit.
3356
+ if (!(el._config & CONFIG_HELD)) el._x._shownError = undefined;
3096
3357
  if (wasUninitialized) landStatus(el, true);
3358
+ // Attribution hook: this path landed through the setter (derived
3359
+ // stores, projections), which does not go through setSignal, so the
3360
+ // plain branch's asyncEnd never ran and the flight stayed open — the
3361
+ // next flight counted a committed answer as abandoned (#3947).
3362
+ // direct=true: asyncEnd finalizes either way and stamps an async write
3363
+ // only if the commit moved `_value`/`_time` or parked `_pendingValue`.
3364
+ // A store setter does not move this node's `_value`, so no stamp is
3365
+ // recorded and waterfall chaining through derived stores stays
3366
+ // unmeasured. Outside the try (#2883 — see attribution-hooks.ts).
3367
+ if (attrHooks !== null) attrHooks.asyncEnd(el, undefined, value, true);
3097
3368
  } else {
3098
3369
  // CARVE 2: the override-covered landing (hold + A18 supersession) and the
3099
3370
  // lane-routed landing (derived override, lane effect queue) went with
3100
- // the optimistic engine; every landing is the plain setSignal. A first
3101
- // answer born into a hold still live lands into it (A29, #3800).
3102
- let t = el._x._bornIn;
3103
- el._x._bornIn = null;
3104
- if (wasUninitialized && t && (t = liveTx(t))) holdNode(el, t);
3105
- try {
3106
- setSignal(el, () => value);
3107
- } catch (e) {
3108
- // Same containment as above: setSignal's comparator throw is the only
3109
- // pre-commit failure here, and there is no user callsite to throw to.
3110
- notifyStatus(el, STATUS_ERROR, e);
3111
- }
3371
+ // the optimistic engine; every landing is the plain setSignal. A node
3372
+ // already held stages its answer into that hold (`setSignal`). Its
3373
+ // comparator throw is contained there (`isSame`, A3).
3374
+ setSignal(el, () => value);
3112
3375
  // Attribution hook: this path landed through setSignal, whose write
3113
3376
  // hook already saw any committed change — direct=false lets the engine
3114
3377
  // reclassify that write as an async landing iff it actually committed.
@@ -3153,15 +3416,14 @@ function handleAsync(
3153
3416
  // close registration uses the ambient owner; and DEFERRED (the flattening
3154
3417
  // path — a thenable resolved to an iterable in a later microtask), where
3155
3418
  // there is no caller to serve and no ambient owner: sync-settled steps
3156
- // write through asyncWrite, and close registration goes through the slot
3157
- // the thenable branch pre-registered while it still owned the context.
3158
- // Returns whether a sync answer landed (first yield or empty completion) —
3159
- // meaningful only in the live posture.
3160
- const consumeIterator = (source, registerClose) => {
3419
+ // write through asyncWrite, and close registration targets `owner` (el
3420
+ // itself) directly. Returns whether a sync answer landed (first yield or
3421
+ // empty completion) — meaningful only in the live posture.
3422
+ const consumeIterator = (source, owner) => {
3161
3423
  const it = source[Symbol.asyncIterator]();
3162
3424
  let hadValue = false;
3163
3425
  let completed = false;
3164
- let initialRead = !registerClose;
3426
+ let initialRead = !owner;
3165
3427
  const close = () => {
3166
3428
  if (completed) return;
3167
3429
  completed = true;
@@ -3170,7 +3432,7 @@ function handleAsync(
3170
3432
  if (isThenable(returned)) returned.then(undefined, () => {});
3171
3433
  } catch {}
3172
3434
  };
3173
- registerClose ? registerClose(close) : cleanup(close);
3435
+ cleanup(close, owner);
3174
3436
  // Flight-identity cancellation (#3122): the registration above is the
3175
3437
  // owner-death backstop; the teardown slot fires at the _inFlight release
3176
3438
  // sites so supersede stops this stream immediately.
@@ -3178,8 +3440,15 @@ function handleAsync(
3178
3440
  // Release check before each next pull: an unobserved lazy node must tear
3179
3441
  // down (its close above runs via disposal, closing the iterator) instead
3180
3442
  // of pumping the stream forever with zero subscribers (#2935).
3443
+ // Later pulls run from an async continuation with no compute catch above
3444
+ // them: a throw invoking the protocol (next(), the step's then) settles
3445
+ // as this flight's error instead of escaping as a rejection (#4112).
3181
3446
  const iterateOrRelease = () => {
3182
- if (!settleAutodispose()) iterate();
3447
+ try {
3448
+ if (!settleAutodispose()) iterate();
3449
+ } catch (e) {
3450
+ handleError(e);
3451
+ }
3183
3452
  };
3184
3453
  const iterate = () => {
3185
3454
  let syncResult,
@@ -3264,7 +3533,7 @@ function handleAsync(
3264
3533
  // stream itself rather than settling on the iterable object. One level
3265
3534
  // only: A+ `then` already collapses nested thenables, so the resolved
3266
3535
  // value is never itself a thenable.
3267
- const flattenIfIterable = (value, registerClose) => {
3536
+ const flattenIfIterable = (value, owner) => {
3268
3537
  let innerIterator = false;
3269
3538
  if (typeof value === "object" && value !== null) {
3270
3539
  untrack(() => {
@@ -3272,8 +3541,9 @@ function handleAsync(
3272
3541
  });
3273
3542
  }
3274
3543
  if (!innerIterator) return false;
3275
- const landed = consumeIterator(value, registerClose);
3276
- if (!registerClose) liveLanded = landed;
3544
+ syncValue = undefined;
3545
+ const landed = consumeIterator(value, owner);
3546
+ if (!owner) liveLanded = landed;
3277
3547
  return true;
3278
3548
  };
3279
3549
  if (thenable) {
@@ -3281,29 +3551,32 @@ function handleAsync(
3281
3551
  rejected = false,
3282
3552
  syncError,
3283
3553
  isSync = true;
3284
- // Close registration for the flattening path. Consumption starts in a
3285
- // microtask where the ambient owner is gone (or worse, someone else's),
3286
- // so `cleanup()` can't be used — the close targets el's disposal list
3287
- // directly, exactly where a live cleanup() during this recompute would
3288
- // have put it. Only a flight that actually flattens becomes
3289
- // disposal-bearing.
3290
- const registerDeferredClose = fn => {
3291
- if (!el._disposal) el._disposal = fn;
3292
- else if (Array.isArray(el._disposal)) el._disposal.push(fn);
3293
- else el._disposal = [el._disposal, fn];
3294
- };
3295
3554
  watchAsyncTail(el, result).then(
3296
3555
  v => {
3297
3556
  if (isSync) {
3298
3557
  syncValue = v;
3299
3558
  resolved = true;
3300
- } else if (
3301
- el._x?._inFlight === result &&
3302
- !(el._flags & REACTIVE_DISPOSED) &&
3303
- flattenIfIterable(v, registerDeferredClose)
3304
- );
3305
- else {
3306
- asyncWrite(v);
3559
+ } else {
3560
+ // Flattening consumes the stream as the value: each yield lands
3561
+ // through asyncWrite under this flight's identity; the first one
3562
+ // clears pending exactly like a plain promise resolution would.
3563
+ // Disposed nodes never start a pump (their disposal list already
3564
+ // ran, so nothing could close the iterator). Consumption starts in
3565
+ // a microtask where the ambient owner is gone, so the close
3566
+ // registers on el's own disposal list. Iterator setup here has no
3567
+ // compute catch above it: a protocol throw settles as this
3568
+ // flight's error (#4112).
3569
+ try {
3570
+ if (
3571
+ el._x?._inFlight === result &&
3572
+ !(el._flags & REACTIVE_DISPOSED) &&
3573
+ flattenIfIterable(v, el)
3574
+ )
3575
+ return;
3576
+ asyncWrite(v);
3577
+ } catch (e) {
3578
+ handleError(e);
3579
+ }
3307
3580
  settleAutodispose();
3308
3581
  }
3309
3582
  },
@@ -3407,6 +3680,8 @@ function notifyStatus(el, status, error, blockStatus) {
3407
3680
  // (CARVE 2: the lane assignment went with the engine.)
3408
3681
  {
3409
3682
  if (status === STATUS_PENDING && pendingSource) {
3683
+ // D-41: a re-ask of a shown error keeps it the display (`_shownError`).
3684
+ if (el._statusFlags & STATUS_ERROR) el._x._shownError = el._x._error;
3410
3685
  addPendingSource(el, pendingSource);
3411
3686
  el._statusFlags = STATUS_PENDING | (el._statusFlags & STATUS_UNINITIALIZED);
3412
3687
  // Preserve the current source on this propagation so readers park on
@@ -3486,7 +3761,8 @@ function propagateStatus(el, status, error, downstreamBlockStatus) {
3486
3761
  if (
3487
3762
  passLane === null &&
3488
3763
  (sub._config & (CONFIG_HELD | CONFIG_OVERRIDE)) === CONFIG_HELD &&
3489
- globalQueue._running
3764
+ globalQueue._running &&
3765
+ !freshHook?.(sub, pendingSource._time === clock)
3490
3766
  )
3491
3767
  joinFuture(txOf(sub));
3492
3768
  return;
@@ -3501,7 +3777,7 @@ function propagateStatus(el, status, error, downstreamBlockStatus) {
3501
3777
  // the dependent's pending is the lane's own flight (`laneStage`).
3502
3778
  {
3503
3779
  // (No answer: a pending propagation leaves a written guess a guess.)
3504
- if (passLane !== null) GlobalQueue._laneStage(sub, passLane, true, el);
3780
+ if (laneStageHook && passLane !== null) laneStageHook(sub, passLane, true, el);
3505
3781
  else {
3506
3782
  queuePendingNode(sub);
3507
3783
  // A15 (#3443): pending propagates onto a held memo without
@@ -3543,8 +3819,29 @@ GlobalQueue._update = el => {
3543
3819
  // (REACTIVE_LANE_DIRTY — a lane shows ahead of any park): it runs now, as
3544
3820
  // the lane's work, for the reveal this seam (#3463).
3545
3821
  else if (el._flags & REACTIVE_ZOMBIE && !(el._flags & REACTIVE_LANE_DIRTY)) deferZombie(el);
3546
- else recompute(el);
3822
+ // A lane frame's member (parked by a lane pass, #3698) follows mainline
3823
+ // writes only: a lane's dirty on it is cancelled — the lane's apply retires
3824
+ // it, and the pass that replaced it already decided what that screen shows
3825
+ // (#4137: a `<Show>` branch withdrawn by its own lane re-ran on a falsy
3826
+ // condition).
3827
+ // (Lanes exist only with the lane engine installed: folded away without.)
3828
+ else if (laneStageHook && el._flags & REACTIVE_ZOMBIE && laneFrameMember(el)) {
3829
+ deleteFromHeap(el, dirtyQueue);
3830
+ el._flags &= -65540;
3831
+ } else recompute(el);
3547
3832
  };
3833
+ /** The zombie's frame was parked by lane work: the nearest non-zombie owner
3834
+ * (the one whose pass parked it) carries a lane's derived value — or, an
3835
+ * effect (no lane value: `laneStage`), is listed in a lane (an `insert()`
3836
+ * hole's render effect). */
3837
+ function laneFrameMember(el) {
3838
+ let o = el._parent;
3839
+ while (o._flags & REACTIVE_ZOMBIE) o = o._parent;
3840
+ return (
3841
+ (o._config & (CONFIG_OVERRIDE | CONFIG_GUESS)) === CONFIG_OVERRIDE ||
3842
+ (o._type && o._x?._transaction?._lane)
3843
+ );
3844
+ }
3548
3845
  GlobalQueue._dispose = disposeChildren;
3549
3846
  const PRIMITIVE_IN_FORBIDDEN_SCOPE_MESSAGE =
3550
3847
  "[PRIMITIVE_IN_FORBIDDEN_SCOPE] Cannot create reactive primitives inside createTrackedEffect or owner-backed onSettled";
@@ -3579,6 +3876,10 @@ const REACTIVE_WRITE_IN_OWNED_SCOPE_REFRESH_MESSAGE =
3579
3876
  let tracking = false;
3580
3877
  let context = null;
3581
3878
  let snapshotCaptureActive = false;
3879
+ /** The lane at the root of the running chain of first passes: the lane whose
3880
+ * pass created the chain's first node (a mount), inherited by every first
3881
+ * pass nested under it, whose seats are mainline (`recompute`, #4166). */
3882
+ let laneRoot;
3582
3883
  let snapshotSources = null;
3583
3884
  function ownerInSnapshotScope(owner) {
3584
3885
  while (owner) {
@@ -3638,6 +3939,7 @@ function recompute(el, create = false) {
3638
3939
  // §12d: any recompute can clean a marked subscriber — invalidate skips.
3639
3940
  bumpNotifyEpoch();
3640
3941
  const isEffect = el._type;
3942
+ let released;
3641
3943
  // Lanes (plan sec. 28): the seat of a pass is its node's. A node carrying a lane's
3642
3944
  // derived value runs as the lane's whoever dirtied it — a sync write, a
3643
3945
  // boundary reset, a frame rerun — so its children and its result are the
@@ -3653,18 +3955,27 @@ function recompute(el, create = false) {
3653
3955
  // derivation's pass into the lane (`read`); a leaf's never moves.
3654
3956
  // Restored at the end, after this pass's staging and runs have been
3655
3957
  // routed.
3958
+ // Lanes exist only with the lane engine installed (`laneStageHook`): a
3959
+ // bundle without it seats every pass on mainline, and folds the seat away.
3656
3960
  const prevLane = passLane;
3961
+ // The mount a first pass belongs to: its creator's lane, or the one its
3962
+ // creator's own mount came from — a render effect a verdict lane's mount's
3963
+ // content creates is that mount's too (#4166). Lanes only, like the seat.
3964
+ const prevMount = laneRoot;
3965
+ const mount = laneStageHook && (laneRoot = create && (prevLane || prevMount));
3657
3966
  setPassLane(
3658
- (el._flags & REACTIVE_LANE_DIRTY ||
3659
- (el._config & (CONFIG_OVERRIDE | CONFIG_GUESS)) === CONFIG_OVERRIDE) &&
3660
- el._x?._transaction?._lane
3661
- ? el._x._transaction
3662
- : // Loose `!=`: false with no creator lane (undefined == null).
3663
- create &&
3664
- prevLane?._parent._verdict != prevLane &&
3665
- (creatorPass(context)?._flags ?? 0) & REACTIVE_RECOMPUTING_DEPS
3666
- ? prevLane
3667
- : null
3967
+ !laneStageHook
3968
+ ? null
3969
+ : (el._flags & REACTIVE_LANE_DIRTY ||
3970
+ (el._config & (CONFIG_OVERRIDE | CONFIG_GUESS)) === CONFIG_OVERRIDE) &&
3971
+ el._x?._transaction?._lane
3972
+ ? el._x._transaction
3973
+ : // Loose `!=`: false with no creator lane (undefined == null).
3974
+ create &&
3975
+ prevLane?._parent._verdict != prevLane &&
3976
+ creatorPass(context)?._flags & REACTIVE_RECOMPUTING_DEPS
3977
+ ? prevLane
3978
+ : null
3668
3979
  );
3669
3980
  // Attribution hook: fired before this run touches the dep list — `_deps`
3670
3981
  // still holds the previous run's links (the subscriptions that could have
@@ -3691,17 +4002,30 @@ function recompute(el, create = false) {
3691
4002
  // joins all the same. Not a render effect (rule 3): it is the frame.
3692
4003
  // Re-run outside its transaction's flush it publishes mainline — the
3693
4004
  // value it staged there is superseded (`frameRead` remembers it for the
3694
- // landing). A frame born into the transaction (uninitialized: never run,
3695
- // A29) keeps staging: live writes keep it current, it effects at the
3696
- // swap (ruling A).
4005
+ // landing). A frame born into the transaction (`bornInto`: never run,
4006
+ // A29, or mounted by a pass of it still awaiting its commit, #4010) keeps
4007
+ // staging: live writes keep it current, it effects at the swap (ruling
4008
+ // A).
4009
+ // A pending flight is the transition's own work (#3884). A probe on an
4010
+ // earlier pass left CONFIG_VERDICT, and a frame read here would serve the
4011
+ // committed screen, settle the flight and drop the hold — a superseding
4012
+ // write's verdict would depend on that probe. Cleared for this pass; a
4013
+ // probe in the body re-stamps it after the plain reads.
4014
+ if (el._statusFlags & STATUS_PENDING) el._config &= ~CONFIG_VERDICT;
3697
4015
  if (el._config & CONFIG_HELD) {
3698
4016
  const tx = txOf(el);
3699
4017
  // Held by a blocked lane: the frame joins nothing — a lane never holds
3700
4018
  // a sync write (#3460). The pass is the lane's if it reads the lane's
3701
4019
  // world (`read`), and has left it otherwise.
3702
- if (tx._lane);
3703
- else if (isEffect !== EFFECT_RENDER && !(el._config & CONFIG_VERDICT)) joinPassTx(tx);
3704
- else if (tx !== flushTransaction && !(el._statusFlags & STATUS_UNINITIALIZED)) {
4020
+ // An answered deferred node's re-pass is a lagging question (D-40):
4021
+ // it joins nothing, so the write that reached it commits mainline.
4022
+ if (tx._lane || el._config & CONFIG_LAGS);
4023
+ else if (isEffect !== EFFECT_RENDER && !(el._config & CONFIG_VERDICT))
4024
+ joinPassTx(tx, freshHook?.(el, el._statusFlags & STATUS_UNINITIALIZED));
4025
+ else if (
4026
+ tx !== flushTransaction &&
4027
+ !(el._statusFlags & STATUS_UNINITIALIZED || el._config & CONFIG_HELD_FRAME)
4028
+ ) {
3705
4029
  // Published mainline, it is not held: the frame this pass builds is
3706
4030
  // replaced on the spot (#3404) — this flush's commit retires the
3707
4031
  // committed frame the held pass had parked (`commitPendingNode`),
@@ -3717,8 +4041,20 @@ function recompute(el, create = false) {
3717
4041
  // frame parked before it stays parked. Otherwise they ARE the frame:
3718
4042
  // park them until this pass commits. Tracked effects run after the seam
3719
4043
  // and own nothing to park.
3720
- if (isEffect === EFFECT_TRACKED || el._config & CONFIG_STAGED) disposeChildren(el);
3721
- else if (el._firstChild !== null || el._disposal !== null) parkChildren(el);
4044
+ if (isEffect === EFFECT_TRACKED || el._config & CONFIG_STAGED) {
4045
+ // A lazy node only those children read is released when this pass
4046
+ // ends, if the pass doesn't read it again (#4142).
4047
+ const prev = deferredReleases;
4048
+ deferReleases((released = []));
4049
+ disposeChildren(el);
4050
+ deferReleases(prev);
4051
+ } else if (el._firstChild !== null || el._disposal !== null) parkChildren(el);
4052
+ // An error a pass staged (D-41) is replaced by this pass's answer: a
4053
+ // value, a flight, or an error of its own (#4109, #4145).
4054
+ if (el._config & CONFIG_STAGED_ERROR) {
4055
+ el._pendingValue = NOT_PENDING;
4056
+ el._config ^= CONFIG_STAGED_ERROR;
4057
+ }
3722
4058
  clearSignals(el);
3723
4059
  }
3724
4060
  const wasUninitialized = !!(el._statusFlags & STATUS_UNINITIALIZED);
@@ -3735,6 +4071,8 @@ function recompute(el, create = false) {
3735
4071
  // settlePendingSource walk never runs for a landing that was preempted
3736
4072
  // (#3181).
3737
4073
  const wasPendingSource = el._x?._pendingSources?.has(el);
4074
+ // D-41: this pass's error is staged, not published (`stageError`).
4075
+ let stagedErr = false;
3738
4076
  // A19 exc. 2: a `refresh()` re-asks the question already answered — quiet
3739
4077
  // for the verdict — unless the question in flight is a new one, which the
3740
4078
  // re-ask does not launder (a poll during a quiet confirm stays quiet).
@@ -3749,19 +4087,20 @@ function recompute(el, create = false) {
3749
4087
  // frame, and the splice/reawaken paths key off it. A zombie reruns for
3750
4088
  // live writes until the commit that disposes it, so every per-pass wipe
3751
4089
  // — here, the finally below, updateIfNecessary — carries it (#3543).
3752
- el._flags = REACTIVE_RECOMPUTING_DEPS | (el._flags & REACTIVE_ZOMBIE);
4090
+ el._flags = REACTIVE_RECOMPUTING_DEPS | (el._flags & (REACTIVE_ZOMBIE | REACTIVE_REASK));
3753
4091
  el._time = clock;
3754
- // The pass's previous value: its staging, else the lane's value for a
3755
- // lane's node (lanes.ts), else the committed one.
4092
+ // The pass's previous value: its staging, else a lane node's lane value
4093
+ // when it has one (lanes.ts), else the committed one.
3756
4094
  let value =
3757
4095
  el._pendingValue !== NOT_PENDING
3758
4096
  ? el._pendingValue
3759
- : el._config & CONFIG_OVERRIDE
4097
+ : el._config & CONFIG_OVERRIDE && el._x._lane !== NOT_PENDING
3760
4098
  ? el._x._lane
3761
4099
  : el._value;
3762
4100
  let oldHeight = el._height;
3763
4101
  let missedWake = false;
3764
- // L2: did this pass read the future (REACTIVE_JOINED, set by read)?
4102
+ // L2: did this pass read the future (REACTIVE_JOINED, set by read) — or,
4103
+ // a lane's mount, a staging of this flush (#3851, #4166)?
3765
4104
  let joined = false;
3766
4105
  let prevTracking = tracking;
3767
4106
  let prevStrictRead = false;
@@ -3806,11 +4145,15 @@ function recompute(el, create = false) {
3806
4145
  // retry can actually run.
3807
4146
  parkLoadingWindow(el, e);
3808
4147
  } else {
3809
- if (notReady) ext(el)._blocked = true;
3810
- notifyStatus(el, notReady ? STATUS_PENDING : STATUS_ERROR, e);
3811
- // The replacement source is fully propagated now. If no new flight
3812
- // re-owned self, retire the superseded flight and its dependent copies.
3813
- if (notReady && wasPendingSource && !el._x?._inFlight) settlePendingSource(el);
4148
+ // D-41: a pass of a held node, or one that read a staging of this
4149
+ // flush, stages its error (`stageError`).
4150
+ if (notReady || !(stagedErr = stageError(el, e, true))) {
4151
+ if (notReady) ext(el)._blocked = true;
4152
+ notifyStatus(el, notReady ? STATUS_PENDING : STATUS_ERROR, e);
4153
+ // The replacement source is fully propagated now. If no new flight
4154
+ // re-owned self, retire the superseded flight and its dependent copies.
4155
+ if (notReady && wasPendingSource && !el._x?._inFlight) settlePendingSource(el);
4156
+ }
3814
4157
  // A re-park drops what the earlier pass carried (#3456): a source this
3815
4158
  // pass no longer reaches — its branch switched, or a fresh flight
3816
4159
  // replaced the inputs' pending with its own — stays copied onto
@@ -3818,14 +4161,16 @@ function recompute(el, create = false) {
3818
4161
  // stops at this node (nothing left to retire) before it finds them. A
3819
4162
  // dependent then waits forever on a flight it has no path to. The
3820
4163
  // re-park twin of the unchanged-value recovery sweep below; dependents
3821
- // with another path keep the source (retryReaches).
3822
- if (notReady && outgoingPendingSources)
4164
+ // with another path keep the source (retryReaches). A staged error
4165
+ // answered what the pass carried: the same.
4166
+ if ((notReady || stagedErr) && outgoingPendingSources)
3823
4167
  for (const source of outgoingPendingSources)
3824
4168
  if (source !== el && !el._x?._pendingSources?.has(source))
3825
4169
  settlePendingSource(el, source);
3826
4170
  }
3827
4171
  } finally {
3828
4172
  tracking = prevTracking;
4173
+ if (laneStageHook) laneRoot = prevMount;
3829
4174
  strictRead = prevStrictRead;
3830
4175
  // Consume the missed-wake latch (#3037, set by insertSubs): a dep write
3831
4176
  // landed beneath this pass on a link it had already validated. The wipe
@@ -3833,10 +4178,11 @@ function recompute(el, create = false) {
3833
4178
  // (markNode(c) in read()) marks the running node as part of ordinary
3834
4179
  // bookkeeping, and those marks are correctly discarded here.
3835
4180
  missedWake = (el._flags & REACTIVE_MISSED_WAKE) !== 0;
3836
- joined = (el._flags & REACTIVE_JOINED) !== 0;
3837
- // A verdict reader is one for as long as it probes: a pass that entered
3838
- // no window (REACTIVE_PROBED) is an ordinary derivation again.
3839
- if (!(el._flags & REACTIVE_PROBED)) el._config &= ~CONFIG_VERDICT;
4181
+ joined = !!(el._flags & (mount ? REACTIVE_JOINED | REACTIVE_STAGED_READ : REACTIVE_JOINED));
4182
+ // A verdict reader is one for as long as it probes, or reads a memo
4183
+ // that does (`read`): a pass that did neither (REACTIVE_PROBED, the same
4184
+ // bit) is an ordinary derivation again.
4185
+ el._config = (el._config & ~CONFIG_VERDICT) | (el._flags & REACTIVE_PROBED);
3840
4186
  // REACTIVE_DISPOSED survives too (#3621): the pass may have disposed its
3841
4187
  // own owner (a memo calling its root's `dispose()`, a cleanup doing so
3842
4188
  // #3601/#3606), and `disposeChildren` set the flag on this node
@@ -3855,6 +4201,7 @@ function recompute(el, create = false) {
3855
4201
  REACTIVE_PROBE_UNANSWERED)) |
3856
4202
  (create ? el._flags & REACTIVE_SNAPSHOT_STALE : 0);
3857
4203
  context = oldcontext;
4204
+ if (released) for (const node of released) releaseIfSettledUnobserved(node);
3858
4205
  // A19 exc. 2: a pass that went pending on a `refresh()` re-asks the
3859
4206
  // question already answered — quiet for the verdict; any other pending
3860
4207
  // pass is a new question. Settled, there is nothing to classify.
@@ -3883,15 +4230,15 @@ function recompute(el, create = false) {
3883
4230
  // seat that read none of the lane's world has left it: its result is the
3884
4231
  // frame's (a derivation whose branch no longer reaches the guess). A
3885
4232
  // guess is written, not derived — it never leaves this way.
3886
- let lane = passLane;
4233
+ let lane = !laneStageHook ? null : passLane;
3887
4234
  // Listed before its staging, a pending pass included (the lane's own
3888
4235
  // flight is the lane's); false: the pass left the lane (lanes.ts).
3889
- const errored = !!el._x?._error;
3890
- if (lane !== null) {
3891
- if (GlobalQueue._laneStage(el, lane, create, errored)) setPassLane(lane);
4236
+ const errored = !!el._x?._error || stagedErr;
4237
+ if (laneStageHook && lane !== null) {
4238
+ if (laneStageHook(el, lane, create, errored)) setPassLane(lane);
3892
4239
  else lane = null;
3893
4240
  }
3894
- if (!el._x?._error) {
4241
+ if (!errored) {
3895
4242
  // Observe-tier fan-in (HUGE_FAN_IN): the validated prefix [_deps.._depsTail]
3896
4243
  // IS this pass's distinct sources — count it here rather than per link. A
3897
4244
  // begin/end bracket around the pass plus a per-link increment measured
@@ -3918,17 +4265,9 @@ function recompute(el, create = false) {
3918
4265
  : el._config & CONFIG_OVERRIDE && el._x._lane !== NOT_PENDING
3919
4266
  ? el._x._lane
3920
4267
  : el._value;
3921
- let valueChanged = false;
3922
- try {
3923
- valueChanged =
3924
- (!isEffect && wasUninitialized) || !el._equals || !el._equals(compareValue, value);
3925
- } catch (e) {
3926
- // A throwing user comparator is an error of this node's computation.
3927
- // Route it through the same status path as a compute-phase throw so
3928
- // error boundaries contain it; otherwise it unwinds the scheduler
3929
- // flush, bypassing every boundary and wedging the queue (#2837).
3930
- notifyStatus(el, STATUS_ERROR, e);
3931
- }
4268
+ // A throwing comparator is contained inside `isSame` (A3, #2837; #4111).
4269
+ const valueChanged =
4270
+ (!isEffect && wasUninitialized) || !!el._x?._shownError || !isSame(el, compareValue, value);
3932
4271
  // A committed derived change becomes a cause for this node's subscribers,
3933
4272
  // chaining their attribution through this node to the root write.
3934
4273
  if (attrHooks !== null) {
@@ -3972,7 +4311,7 @@ function recompute(el, create = false) {
3972
4311
  !create &&
3973
4312
  lane === null &&
3974
4313
  el._config & CONFIG_GUESS &&
3975
- GlobalQueue._laneOutcome(el, value, errored)
4314
+ laneOutcomeHook(el, value, errored)
3976
4315
  );
3977
4316
  else if (errored);
3978
4317
  else if (valueChanged) {
@@ -4006,9 +4345,7 @@ function recompute(el, create = false) {
4006
4345
  create
4007
4346
  ? !(
4008
4347
  (flushTransaction !== null || passTx !== null) &&
4009
- (joined ||
4010
- (prevLane && el._flags & REACTIVE_STAGED_READ) ||
4011
- (creatorPass(oldcontext)?._flags ?? 0) & REACTIVE_JOINED)
4348
+ (joined || creatorPass(oldcontext)?._flags & REACTIVE_JOINED)
4012
4349
  )
4013
4350
  : isEffect && el._pendingValue === NOT_PENDING
4014
4351
  ) {
@@ -4035,6 +4372,14 @@ function recompute(el, create = false) {
4035
4372
  // derives from the future (`read`), an untracked one throws
4036
4373
  // (A19 exc. 1).
4037
4374
  el._statusFlags |= STATUS_UNINITIALIZED;
4375
+ // A boundary that has not shown content catches it: its fallback
4376
+ // waits for the commit (#3540). (The pass's own status clear above
4377
+ // dropped what the read recorded.) Not when the flush has joined
4378
+ // the hold and the pass is its own (not a verdict lane's mount,
4379
+ // #3851): the mount is part of the hold, and the boundary appears
4380
+ // at its commit, its fallback never seen (D-13, U-3) — a `<Show>`
4381
+ // mounting it over a landing included.
4382
+ freshHook?.(el, !mount && flushTransaction);
4038
4383
  }
4039
4384
  }
4040
4385
  // insertSubs only walks _subs (no scheduling of its own), so a
@@ -4068,16 +4413,16 @@ function recompute(el, create = false) {
4068
4413
  }
4069
4414
  // A first pass that went pending where the born-held arm would have held
4070
4415
  // its value (it read a held node, or a verdict lane's mount read a staging,
4071
- // #3851) is born into that transaction without holding it: the hold never
4072
- // waits for a first load (the direction rule), but the answer lands into it
4073
- // while it is live (A29, #3800).
4074
- if (
4075
- create &&
4076
- lane === null &&
4077
- (joined || (prevLane && el._flags & REACTIVE_STAGED_READ)) &&
4078
- el._statusFlags & STATUS_PENDING
4079
- )
4080
- ext(el)._bornIn = flushTransaction ?? passTx;
4416
+ // #3851) is held like it: its answer is the hold's, and the hold waits
4417
+ // for it (A29, #3800). A boundary that has not shown content catches it
4418
+ // and it is held all the same — its answer reveals no earlier than the
4419
+ // hold's commit (D-12) — but the hold does not wait for it: its only
4420
+ // readers are behind the fallback (`_hidden`; F-4, #3540). A boundary
4421
+ // re-armed by `on` owns what it collects outright (D-14): not held.
4422
+ if (create && lane === null && joined && el._statusFlags & STATUS_PENDING) {
4423
+ const t = flushTransaction ?? passTx;
4424
+ if (t && !t._lane && freshHook?.(el) !== CAUGHT_REARMED) holdNode(el, t);
4425
+ }
4081
4426
  // Dependencies are the committed frame's until it is replaced (A30, #3410):
4082
4427
  // a pass that staged its value leaves the previous pass's tail linked for
4083
4428
  // `commitPendingNode` to trim, so a write to a dependency the committed
@@ -4188,12 +4533,12 @@ function computed(fn, options) {
4188
4533
  const loading = options !== null && typeof options === "object" && "loadingValue" in options;
4189
4534
  // Two literals, one per tier, selected at build time (the observe flag is
4190
4535
  // a literal after replacement; the untaken branch is dead code). The observe
4191
- // literal is the prod literal plus its `_name` slot — a slot in the
4192
- // boilerplate, because a post-construction `self._name = …` forces a
4536
+ // literal is the prod literal plus its `_name` and `_loc` slots — slots in
4537
+ // the boilerplate, because a post-construction `self._name = …` forces a
4193
4538
  // hidden-class transition and an out-of-object property store on EVERY node
4194
4539
  // (measured: the whole of the observe tier's creation overhead). Keep the
4195
4540
  // two in sync — the dist artifact test pins observe's key set to prod's
4196
- // plus `_name`.
4541
+ // plus the slots.
4197
4542
  const self = {
4198
4543
  id: inheritId(options, transparent, context),
4199
4544
  _config:
@@ -4235,6 +4580,7 @@ function computed(fn, options) {
4235
4580
  // The slot is always present (hidden class); plumbing leaves it
4236
4581
  // unset, which `ownerPath` skips.
4237
4582
  _name: options?._plumbing ? undefined : (options?.name ?? "computed"),
4583
+ _loc: options?.loc,
4238
4584
  // Observe: the verdict windows this pass entered (verdict.ts) — a
4239
4585
  // slot, so the stamp is never a write after construction.
4240
4586
  _devWindows: 0
@@ -4261,13 +4607,13 @@ function ext(el) {
4261
4607
  _pendingFirstChild: null,
4262
4608
  _pendingDisposal: null,
4263
4609
  _transaction: null,
4264
- _bornIn: null,
4265
4610
  _reask: false,
4266
4611
  _flushed: NOT_PENDING,
4267
4612
  _flushedAt: -1,
4268
4613
  _q: 0,
4269
4614
  _lane: NOT_PENDING,
4270
- _marks: 0
4615
+ _marks: 0,
4616
+ _shownError: undefined
4271
4617
  });
4272
4618
  }
4273
4619
  /** A tracked read of a held node (the reader derives from its transaction's
@@ -4283,8 +4629,13 @@ function ext(el) {
4283
4629
  * boundary's, not the tick's (A29's boundary exemption, #3540). */
4284
4630
  function joinPass(c, el) {
4285
4631
  c._flags |= REACTIVE_JOINED;
4632
+ // A new memo's first pass is the hold's work (born held) but not the
4633
+ // tick's: a render effect that reads it decides — under a boundary that
4634
+ // catches it, the boundary's; otherwise the tick joins the hold (#3540:
4635
+ // the reader's position, not where the memo was created). Its catch, if
4636
+ // any, is at its staging.
4286
4637
  if (el._statusFlags & STATUS_UNINITIALIZED || c._type !== EFFECT_RENDER)
4287
- joinPassTx(txOf(el), GlobalQueue._fresh?.(c));
4638
+ joinPassTx(txOf(el), (!c._type && c._statusFlags & STATUS_UNINITIALIZED) || freshHook?.(c));
4288
4639
  }
4289
4640
  /** A15's stale reader (shared-hole and reveal corollaries): a render effect
4290
4641
  * reading a node held by a transaction that is not the flush's. It is served
@@ -4292,11 +4643,11 @@ function joinPass(c, el) {
4292
4643
  * re-derived after that transaction's landing (`_reruns`,
4293
4644
  * REACTIVE_FRAME_READ) so the frame catches up with it once it is the
4294
4645
  * committed world. Not a frame born into that transaction (held and never
4295
- * committed — created by one of its held passes, A29): that one reads its
4296
- * transaction's values and keeps staging. Not of a flight whose inputs are
4297
- * already visible (CONFIG_INPUTS_PUBLISHED, #3305): its committed value
4298
- * would tear against them, so the reveal observes the flight instead. Once
4299
- * per pass.
4646
+ * committed — created by one of its held passes, A29; `bornInto`): that
4647
+ * one reads its transaction's values and keeps staging. Not of a flight
4648
+ * whose inputs are already visible (CONFIG_INPUTS_PUBLISHED, #3305): its
4649
+ * committed value would tear against them, so the reveal observes the
4650
+ * flight instead. Once per pass.
4300
4651
  *
4301
4652
  * Lane work is a stale reader too (maintainer, 2026-10-01: a lane sees the
4302
4653
  * screen plus its own guesses). A held write is held because showing it
@@ -4321,10 +4672,11 @@ function frameRead(c, el) {
4321
4672
  // read the screen. Lane work with no committed value yet reads a flight
4322
4673
  // as a mount's memo does: it enters, and the boundary it mounts catches
4323
4674
  // the pending (#3540).
4324
- const verdict = c._config & CONFIG_VERDICT;
4675
+ // (Verdict readers and lanes exist only with their engines installed.)
4676
+ const verdict = observeFlightHook && c._config & CONFIG_VERDICT;
4677
+ const lane = laneStageHook && passLane !== null;
4325
4678
  if (
4326
- (passLane === null ||
4327
- (c._statusFlags & STATUS_UNINITIALIZED && el._statusFlags & STATUS_PENDING)) &&
4679
+ (!lane || (c._statusFlags & STATUS_UNINITIALIZED && el._statusFlags & STATUS_PENDING)) &&
4328
4680
  !verdict &&
4329
4681
  (c._type !== EFFECT_RENDER || el._config & CONFIG_INPUTS_PUBLISHED)
4330
4682
  )
@@ -4333,14 +4685,22 @@ function frameRead(c, el) {
4333
4685
  // Lane work and verdict readers are never the transaction's pass, whichever
4334
4686
  // flush runs it: a node T holds re-enters T when it re-runs (`recompute`'s
4335
4687
  // head), and the pass may then read a guess and become the lane's.
4336
- if (
4337
- (t === flushTransaction && passLane === null && !verdict) ||
4338
- (c._statusFlags & STATUS_UNINITIALIZED && c._config & CONFIG_HELD && txOf(c) === t)
4339
- )
4340
- return false;
4688
+ if ((t === flushTransaction && !lane && !verdict) || bornInto(c, t)) return false;
4341
4689
  staleReader(c, t);
4342
4690
  return true;
4343
4691
  }
4692
+ /** A node born into transaction `t`: held by it and never committed (A29,
4693
+ * ruling A) — uninitialized (born held), or a member of the frame one of its
4694
+ * held passes built (CONFIG_HELD_FRAME). Its branch exists only in `t`: it
4695
+ * has no screen to read, so it reads `t`'s values, whichever flush runs it,
4696
+ * and keeps staging (#4010). */
4697
+ function bornInto(c, t) {
4698
+ return (
4699
+ (c._statusFlags & STATUS_UNINITIALIZED || c._config & CONFIG_HELD_FRAME) &&
4700
+ c._config & CONFIG_HELD &&
4701
+ txOf(c) === t
4702
+ );
4703
+ }
4344
4704
  /** The computed whose pass a creation under `owner` belongs to (a root's is
4345
4705
  * its nearest computed ancestor's), or null outside every pass. */
4346
4706
  function creatorPass(owner) {
@@ -4412,6 +4772,7 @@ function createEffectNode(fn, effectFn, errorFn, type, options) {
4412
4772
  _type: type,
4413
4773
  _x: null,
4414
4774
  _name: options?.name ?? "effect",
4775
+ _loc: options?.loc,
4415
4776
  _devWindows: 0
4416
4777
  };
4417
4778
  // Effects dispatch status through the SHARED notifier (statusNotifierOf,
@@ -4463,7 +4824,7 @@ function setupComputedNode(self, options) {
4463
4824
  if (context) linkChild(context, self);
4464
4825
  DEV.hooks.onOwner?.(self);
4465
4826
  if (parent) self._height = parent._height + 1;
4466
- GlobalQueue._wireExternalSource?.(self);
4827
+ wireExternalSourceHook?.(self);
4467
4828
  !options?.lazy && recompute(self, true);
4468
4829
  if (snapshotCaptureActive && !options?.lazy) {
4469
4830
  if (!(self._statusFlags & STATUS_PENDING) && !(self._config & CONFIG_NO_SNAPSHOT)) {
@@ -4475,7 +4836,7 @@ function setupComputedNode(self, options) {
4475
4836
  }
4476
4837
  function signal(v, options) {
4477
4838
  // Prod and observe boilerplates — see computed(). The observe literal adds
4478
- // `_name` and `_owner` (the creating owner, stamped by registerGraph for
4839
+ // `_name`, `_loc` and `_owner` (the creating owner, stamped by registerGraph for
4479
4840
  // createSignal nodes so ownerPath can locate signal subjects; null here,
4480
4841
  // and staying null on internal signals — one shape either way).
4481
4842
  const s = {
@@ -4491,6 +4852,7 @@ function signal(v, options) {
4491
4852
  _notifiedAt: -1,
4492
4853
  _x: null,
4493
4854
  _name: options?.name ?? "signal",
4855
+ _loc: options?.loc,
4494
4856
  _owner: null
4495
4857
  };
4496
4858
  s._internal = false;
@@ -4514,7 +4876,8 @@ function isEqual(a, b) {
4514
4876
  * object, no NodeExtension, no post-construction expandos. `ownedWrite` is
4515
4877
  * baked in: the setter carries the owned-scope guard; node-level writes are
4516
4878
  * the store's notification machinery. The observe literal adds the label
4517
- * slot (the store relabels it `store.<key>` when the engine is installed). */
4879
+ * and location slots (the store relabels `store.<key>` and stamps the
4880
+ * store's `loc` when the engine is installed). */
4518
4881
  function slotSignal(v, equals, host, key, acc) {
4519
4882
  const s = {
4520
4883
  _equals: equals,
@@ -4532,6 +4895,7 @@ function slotSignal(v, equals, host, key, acc) {
4532
4895
  px: undefined,
4533
4896
  pxv: undefined,
4534
4897
  _name: "signal",
4898
+ _loc: undefined,
4535
4899
  _owner: null
4536
4900
  };
4537
4901
  s._internal = false;
@@ -4568,7 +4932,11 @@ function exitCallback() {
4568
4932
  }
4569
4933
  /**
4570
4934
  * Dev-only: > 0 while owner teardown runs cleanups (`_disposal` entries and
4571
- * effect-returned cleanups — owner.ts), for the same reason. Kept apart from
4935
+ * effect-returned cleanups — owner.ts), for the same reason. The owned-scope
4936
+ * write guard (setSignal, markRefresh, the store setter) stands down here
4937
+ * too: a cleanup is teardown, not part of whichever computation is disposing
4938
+ * its owner (a list pass removing a row, a branch switching away), so its
4939
+ * writes are imperative like an effect callback's (#4165). Kept apart from
4572
4940
  * `callbackDepth` because its sites cannot use try/finally (the frame would
4573
4941
  * survive into prod): a throwing cleanup leaves it raised, and dev.ts resets
4574
4942
  * it on the next microtask, which teardown — synchronous — never spans.
@@ -4595,6 +4963,16 @@ function setStrictRead(v) {
4595
4963
  * Use `untrack` inside a memo or effect when you need to read a signal once
4596
4964
  * without making the surrounding computation depend on its future changes.
4597
4965
  *
4966
+ * `untrack` stops dependency tracking, not async settlement. Reading an
4967
+ * uninitialized async source still suspends the owning computation; it runs
4968
+ * again when that source first resolves. Afterward, the untracked read is a
4969
+ * snapshot and does not subscribe the computation to later changes.
4970
+ *
4971
+ * If the first read must return immediately, give the async source a
4972
+ * `loadingValue` before reading it with `untrack`. `isPending` and `latest`
4973
+ * inspect updates to an existing value; neither supplies a missing initial
4974
+ * value.
4975
+ *
4598
4976
  * Pass a `strictReadLabel` string to enable a dev-mode warning: any reactive
4599
4977
  * read inside `fn` that isn't inside a nested tracking scope will log a
4600
4978
  * warning naming the label.
@@ -4608,17 +4986,12 @@ function setStrictRead(v) {
4608
4986
  * log(snapshot);
4609
4987
  * }
4610
4988
  * );
4989
+ *
4990
+ * const color = createMemo<string>(() => fetchColor(), { loadingValue: "gray" });
4991
+ * const initialColor = untrack(color); // "gray" while the fetch is unresolved
4611
4992
  * ```
4612
4993
  */
4613
4994
  function untrack(fn, strictReadLabel) {
4614
- if (
4615
- GlobalQueue._externalUntrack === null &&
4616
- !tracking &&
4617
- !strictRead &&
4618
- !strictReadLabel &&
4619
- asyncTailFlights === 0
4620
- )
4621
- return fn();
4622
4995
  const prevTracking = tracking;
4623
4996
  const prevStrictRead = strictRead;
4624
4997
  tracking = false;
@@ -4627,7 +5000,7 @@ function untrack(fn, strictReadLabel) {
4627
5000
  untrackDepth++;
4628
5001
  }
4629
5002
  try {
4630
- if (GlobalQueue._externalUntrack) return GlobalQueue._externalUntrack(fn);
5003
+ if (externalUntrackHook) return externalUntrackHook(fn);
4631
5004
  return fn();
4632
5005
  } finally {
4633
5006
  tracking = prevTracking;
@@ -4699,7 +5072,7 @@ function prepareComputed(comp, refresh) {
4699
5072
  * False means the reader derives from the staged value.
4700
5073
  */
4701
5074
  function readerSeesCommitted(el, c) {
4702
- return !!(!c || el._pendingValue === NOT_PENDING || c._config & CONFIG_CHILDREN_FORBIDDEN);
5075
+ return !c || el._pendingValue === NOT_PENDING || c._config & CONFIG_CHILDREN_FORBIDDEN;
4703
5076
  }
4704
5077
  /** A28 — set when a node is staged (queuePendingNode) OUTSIDE a flush;
4705
5078
  * cleared when the next flush begins. The read sites test this one module
@@ -4778,6 +5151,7 @@ function pullComputed(owner, c) {
4778
5151
  if (height >= c._height && owner._parent !== c) c._height = height + 1;
4779
5152
  }
4780
5153
  function read(el) {
5154
+ DEV.hooks.onRead?.(el);
4781
5155
  if (asyncTailFlights !== 0 && !tracking && untrackDepth === 0)
4782
5156
  checkPostAwaitRead(
4783
5157
  el,
@@ -4828,7 +5202,7 @@ function read(el) {
4828
5202
  if (!c || el._pendingValue === NOT_PENDING || c._config & CONFIG_CHILDREN_FORBIDDEN)
4829
5203
  return el._value;
4830
5204
  // (A plain signal is never uninitialized — the carve-out is `serve`'s.)
4831
- if (c._config & CONFIG_VERDICT && stagedScreen(c)) return el._value;
5205
+ if (observeFlightHook && c._config & CONFIG_VERDICT && stagedScreen(c)) return el._value;
4832
5206
  stagedRead(c);
4833
5207
  return el._pendingValue;
4834
5208
  }
@@ -4844,7 +5218,15 @@ function read(el) {
4844
5218
  if (c) {
4845
5219
  if (tracking) {
4846
5220
  link(el, c);
4847
- if (owner._fn) pullComputed(owner, c);
5221
+ if (owner._fn) {
5222
+ pullComputed(owner, c);
5223
+ // F-6 (#4050): a memo over a verdict reader's value derives from a
5224
+ // display-ahead read — a verdict reader from this pass's end, as if
5225
+ // it probed inline: it reads held values as the screen and shows the
5226
+ // verdict now. Not an effect: a render effect is a frame reader
5227
+ // already, and a user effect's posture is its own reads'.
5228
+ if (!c._type) c._flags |= owner._config & CONFIG_VERDICT;
5229
+ }
4848
5230
  }
4849
5231
  // L2, as in the fast block above (tracked or not). A node born into the
4850
5232
  // future (uninitialized) has no committed value: every reader derives
@@ -4861,8 +5243,9 @@ function read(el) {
4861
5243
  }
4862
5244
  // Lanes: a lane's node (after the pull — the node is current). NOT_PENDING
4863
5245
  // falls through: lane work's read of a pending member throws like any.
4864
- if (el._config & CONFIG_OVERRIDE) {
4865
- const v = GlobalQueue._laneRead(c, el);
5246
+ // (CONFIG_OVERRIDE is the lane engine's: no engine, no lane's node.)
5247
+ if (laneReadHook && el._config & CONFIG_OVERRIDE) {
5248
+ const v = laneReadHook(c, el);
4866
5249
  if (v !== NOT_PENDING) return v;
4867
5250
  }
4868
5251
  // A stale reader of a held flight (rule 3, A15's reveal corollary) is served
@@ -4877,7 +5260,8 @@ function read(el) {
4877
5260
  // effect keeps its DOM by throwing, a memo has no DOM and is handed the
4878
5261
  // value. `[isPending(x), x()]` reads `[true, stale]` in either order (A10)
4879
5262
  // — unless the flight committed beneath inputs already on screen
4880
- // (`observeFlight`).
5263
+ // (`observeFlight`), or the reader was born into the flight's transaction
5264
+ // (#4010): it has no screen, and waits on the flight with its frame.
4881
5265
  if (
4882
5266
  owner._statusFlags & STATUS_PENDING &&
4883
5267
  !committed &&
@@ -4886,9 +5270,11 @@ function read(el) {
4886
5270
  ) {
4887
5271
  // A reader with no committed value yet sees the flight pending: a
4888
5272
  // boundary a lane mounts shows its fallback (A29's boundary exemption).
4889
- if (passLane !== null && !(c?._statusFlags & STATUS_UNINITIALIZED)) committed = true;
4890
- else if (c !== null && c._config & CONFIG_VERDICT)
4891
- committed = GlobalQueue._observeFlight(c, owner);
5273
+ // (A lane, and a verdict reader, exist only with their engines installed.)
5274
+ if (laneStageHook && passLane !== null && !(c?._statusFlags & STATUS_UNINITIALIZED))
5275
+ committed = true;
5276
+ else if (observeFlightHook && c !== null && c._config & CONFIG_VERDICT)
5277
+ committed = observeFlightHook(c, owner);
4892
5278
  }
4893
5279
  if (owner._statusFlags & STATUS_PENDING && !committed) {
4894
5280
  // A reader landing on a pending node throws; an untracked read of an
@@ -4957,6 +5343,9 @@ function read(el) {
4957
5343
  }
4958
5344
  }
4959
5345
  if (committed) return el._value;
5346
+ // D-41: an untracked read reads the screen, and a shown error is on it
5347
+ // until the answer that replaces it commits.
5348
+ if (!c && el._x?._shownError) throw el._x._shownError;
4960
5349
  const value = serve(el, c);
4961
5350
  if (
4962
5351
  !c &&
@@ -5003,9 +5392,16 @@ function serve(el, c) {
5003
5392
  return u;
5004
5393
  }
5005
5394
  if (readerSeesCommitted(el, c)) return el._value;
5006
- if (c._config & CONFIG_VERDICT && !(el._statusFlags & STATUS_UNINITIALIZED) && stagedScreen(c))
5395
+ if (
5396
+ observeFlightHook &&
5397
+ c._config & CONFIG_VERDICT &&
5398
+ !(el._statusFlags & STATUS_UNINITIALIZED) &&
5399
+ stagedScreen(c)
5400
+ )
5007
5401
  return el._value;
5008
5402
  stagedRead(c, el);
5403
+ // D-41: the staged answer is an error (`stageError`).
5404
+ if (el._config & CONFIG_STAGED_ERROR) throw el._pendingValue;
5009
5405
  return el._pendingValue;
5010
5406
  }
5011
5407
  /** A pass read a staging of this flush: it derives from what the flush may
@@ -5017,11 +5413,14 @@ function serve(el, c) {
5017
5413
  * write never shows through the lane. A verdict lane's work likewise: the
5018
5414
  * lane holds verdicts, not the frame's other stagings (#3851) — except a
5019
5415
  * verdict reader, which answered for itself (the lane seam, lanes.ts). Not
5020
- * a read of a node born staged: it has no committed value to re-derive on,
5021
- * and a re-run would read the same staging and re-queue every round. */
5416
+ * a read of a node born staged at all: it has no committed value to
5417
+ * re-derive on, and a re-run would read the same staging and re-queue every
5418
+ * round (#3969: nor does it make a later lane read a re-derive, `enterLane`). */
5022
5419
  function stagedRead(c, el) {
5023
- c._flags |= REACTIVE_STAGED_READ;
5024
- if (passLane !== null && !(el?._statusFlags & STATUS_UNINITIALIZED)) stagedReaders.push(c);
5420
+ if (!(el?._statusFlags & STATUS_UNINITIALIZED)) {
5421
+ c._flags |= REACTIVE_STAGED_READ;
5422
+ if (laneSeamsHook && passLane !== null) stagedReaders.push(c);
5423
+ }
5025
5424
  }
5026
5425
  /** A10 for a staged node: a verdict reader (the pass entered a window) that
5027
5426
  * also reads a frame's proposal plainly sees the screen — `[latest(q), q()]`
@@ -5030,9 +5429,10 @@ function stagedRead(c, el) {
5030
5429
  * #3322 — one run, on the committed world), or with the hold. Before any
5031
5430
  * transaction the answer is the seam's and the reader watches it
5032
5431
  * (verdict.ts). A node born into the future has no screen: its staging is
5033
- * its only value (A29). */
5432
+ * its only value (A29). Nor has a reader born into the flush's transaction
5433
+ * (`bornInto`, #4010): its branch exists only there. */
5034
5434
  function stagedScreen(c) {
5035
- if (flushTransaction === null) return false;
5435
+ if (flushTransaction === null || bornInto(c, flushTransaction)) return false;
5036
5436
  staleReader(c, flushTransaction);
5037
5437
  return true;
5038
5438
  }
@@ -5050,9 +5450,10 @@ function notePromotedWrite(el) {
5050
5450
  el._config |= CONFIG_PROMOTED;
5051
5451
  promotedWrites.push(el);
5052
5452
  }
5053
- /** Cold half of setSignal's snapshot arm (see there): first write during
5054
- * capture to a plain signal without a live snapshot records the pre-write
5055
- * value. Only plain user signals qualify. Computeds reach setSignal from an
5453
+ /** Cold half of setSignal's snapshot arm (see there), shared with a store
5454
+ * container's structural notify: first write during capture to a plain
5455
+ * signal without a live snapshot records the pre-write value. Only plain
5456
+ * user signals qualify. Computeds reach setSignal from an
5056
5457
  * async landing (asyncWrite), and a value arriving from async during the pass
5057
5458
  * REVEALS — the creation-time arm skips pending computeds for the same
5058
5459
  * reason. Firewall leaves belong to a projection whose compute is the tree's
@@ -5068,11 +5469,46 @@ function captureWriteSnapshot(el, current) {
5068
5469
  el._config |= CONFIG_HAS_SNAPSHOT;
5069
5470
  snapshotSources.add(el);
5070
5471
  }
5472
+ /**
5473
+ * A callback run before every signal or store write lands, while set, with
5474
+ * the written signal or the store proxy whose setter runs. The
5475
+ * server-components client sets it while slot occurrences are left
5476
+ * unmounted after hydration (deferred hydration): a write may be one an
5477
+ * unmounted component would have observed, so the client mounts what is
5478
+ * left before the write applies, and clears the hook. `null` otherwise.
5479
+ */
5480
+ let writeHook = null;
5481
+ /** @internal */
5482
+ function setWriteHook(hook) {
5483
+ writeHook = hook;
5484
+ }
5485
+ /**
5486
+ * `el`'s comparator verdict (`_equals`), the one guarded call for recompute,
5487
+ * setSignal (user setters and async landings) and the seam's
5488
+ * staged-equals-committed check.
5489
+ * - A comparator that read an unresolved async source has not shown the
5490
+ * values equal (#4111): falsy, a change, whose readers suspend in their own
5491
+ * tracked context.
5492
+ * - Any other throw is an error of `el`'s computation (A3, #2837): routed
5493
+ * through the status path so error boundaries contain it, instead of
5494
+ * unwinding a flush or a setter; answered "same" so nothing commits.
5495
+ * @internal
5496
+ */
5497
+ function isSame(el, a, b) {
5498
+ if (el._equals === isEqual) return a === b;
5499
+ try {
5500
+ return el._equals && el._equals(a, b);
5501
+ } catch (e) {
5502
+ if (!(e instanceof NotReadyError)) return (notifyStatus(el, STATUS_ERROR, e), true);
5503
+ }
5504
+ }
5071
5505
  function setSignal(el, v) {
5506
+ if (writeHook !== null) writeHook(el);
5072
5507
  if (
5073
5508
  !(el._config & CONFIG_OWNED_WRITE) &&
5074
5509
  !(context && context._config & CONFIG_CHILDREN_FORBIDDEN) &&
5075
- context
5510
+ context &&
5511
+ !disposalDepth
5076
5512
  ) {
5077
5513
  emitDiagnostic({
5078
5514
  code: "REACTIVE_WRITE_IN_OWNED_SCOPE",
@@ -5091,7 +5527,7 @@ function setSignal(el, v) {
5091
5527
  // Lanes: a write landing on a guess is its truth (A18 — an async guess's
5092
5528
  // own landing), judged against the guess; one landing on a lane's
5093
5529
  // derivation (its flight) is the lane's staging (lanes.ts).
5094
- if (el._config & CONFIG_OVERRIDE) return GlobalQueue._laneWrite(el, v);
5530
+ if (laneWriteHook && el._config & CONFIG_OVERRIDE) return laneWriteHook(el, v);
5095
5531
  // L2 (T4) / A34 (1): a write to a node whose staged state is held is a
5096
5532
  // second proposal for the slot — the same value again or another — and
5097
5533
  // the two cannot finish at different times: the writer's tick joins the
@@ -5099,8 +5535,12 @@ function setSignal(el, v) {
5099
5535
  if (el._config & CONFIG_HELD) joinFuture(txOf(el));
5100
5536
  // Uninitialized check first: the first commit has no previous value, so the
5101
5537
  // user comparator must not run against `undefined` (matches recompute).
5538
+ // D-41: a write over a shown error is a change whatever the last good
5539
+ // value was — the error leaves at its commit (`commitPendingNode`).
5102
5540
  const valueChanged =
5103
- !!(el._statusFlags & STATUS_UNINITIALIZED) || !el._equals || !el._equals(currentValue, v);
5541
+ !!(el._statusFlags & STATUS_UNINITIALIZED) ||
5542
+ !!el._x?._shownError ||
5543
+ !isSame(el, currentValue, v);
5104
5544
  if (!valueChanged) return v;
5105
5545
  // Attribution hook: this committed write is where a re-run chain begins.
5106
5546
  if (attrHooks !== null) attrHooks.write(el, currentValue, v);
@@ -5158,8 +5598,21 @@ function setMemo(el, v) {
5158
5598
  !(el._flags & REACTIVE_MANUAL_WRITE) &&
5159
5599
  txOf(el) !== flushTransaction;
5160
5600
  // The writer read the committed frame; its updater composes on it.
5161
- if (held && typeof v === "function") v = v(el._value);
5162
- const result = setSignal(el, v);
5601
+ if (held && typeof v === "function") {
5602
+ const value = v(el._value);
5603
+ v = () => value;
5604
+ }
5605
+ // #4110: the write replaces a failed derivation's outcome. It lands without
5606
+ // re-running `_fn`, and nothing else clears STATUS_ERROR, so without this
5607
+ // every later read keeps throwing the error the derivation left behind.
5608
+ // The error stays the display until the write commits (D-41).
5609
+ if (el._statusFlags & STATUS_ERROR) {
5610
+ el._x._shownError = el._x._error;
5611
+ notifyStatus(el, STATUS_NONE, undefined);
5612
+ }
5613
+ v = setSignal(el, v);
5614
+ // A write replaces an error a pass staged (D-41): it is a value again.
5615
+ el._config &= ~CONFIG_STAGED_ERROR;
5163
5616
  if (held) {
5164
5617
  // The one write that re-runs: the staging it replaced was derived from
5165
5618
  // inputs the writer never saw, so the hold re-derives over the write.
@@ -5167,7 +5620,7 @@ function setMemo(el, v) {
5167
5620
  insertIntoHeap(el, dirtyQueue);
5168
5621
  schedule();
5169
5622
  } else if (el._pendingValue !== NOT_PENDING) el._flags |= REACTIVE_MANUAL_WRITE;
5170
- return result;
5623
+ return v;
5171
5624
  }
5172
5625
  /**
5173
5626
  * Executes `fn` with the given `owner` set as the current owner. Any reactive
@@ -5234,7 +5687,8 @@ function markRefresh(node) {
5234
5687
  if (
5235
5688
  context &&
5236
5689
  !((node._config ?? 0) & CONFIG_OWNED_WRITE) &&
5237
- !(context._config & CONFIG_CHILDREN_FORBIDDEN)
5690
+ !(context._config & CONFIG_CHILDREN_FORBIDDEN) &&
5691
+ !disposalDepth
5238
5692
  ) {
5239
5693
  emitDiagnostic({
5240
5694
  code: "REACTIVE_WRITE_IN_OWNED_SCOPE",
@@ -5268,171 +5722,194 @@ function markRefresh(node) {
5268
5722
  }
5269
5723
 
5270
5724
  export {
5271
- CONFIG_INPUTS_PUBLISHED as $,
5272
- list as A,
5273
- clock as B,
5725
+ setPassLane as $,
5726
+ tracking as A,
5727
+ CONFIG_CHILDREN_FORBIDDEN as B,
5274
5728
  CONFIG_GUESS as C,
5275
- insertSubs as D,
5276
- EFFECT_RENDER as E,
5277
- laneDirty as F,
5729
+ passLane as D,
5730
+ STATUS_PENDING as E,
5731
+ EFFECT_RENDER as F,
5278
5732
  GlobalQueue as G,
5279
- merge as H,
5280
- newTransaction as I,
5281
- reruns as J,
5282
- CONFIG_STAGED as K,
5283
- releaseQueues as L,
5284
- ownFlights as M,
5733
+ sameLane as H,
5734
+ flushTransaction as I,
5735
+ REACTIVE_SCREEN_READ as J,
5736
+ REACTIVE_FRAME_READ as K,
5737
+ attrHooks as L,
5738
+ commitPendingNode as M,
5285
5739
  NOT_PENDING as N,
5286
- setPassLane as O,
5287
- REACTIVE_JOINED as P,
5288
- stagedRead as Q,
5740
+ REACTIVE_IN_HEAP as O,
5741
+ REACTIVE_DIRTY as P,
5742
+ REACTIVE_CHECK as Q,
5289
5743
  REACTIVE_DISPOSED as R,
5290
- STATUS_PENDING as S,
5291
- attrHooks as T,
5292
- holdNode as U,
5293
- joinFuture as V,
5294
- CONFIG_SLOT_NODE as W,
5295
- commitPendingNode as X,
5296
- REACTIVE_IN_HEAP as Y,
5297
- REACTIVE_DIRTY as Z,
5298
- REACTIVE_CHECK as _,
5299
- CONFIG_OVERRIDE as a,
5300
- CONFIG_REDERIVE as a$,
5301
- setVerdict as a0,
5302
- context as a1,
5303
- REACTIVE_PROBED as a2,
5304
- globalQueue as a3,
5305
- staleReader as a4,
5306
- REACTIVE_STAGED_READ as a5,
5307
- verdict as a6,
5308
- link as a7,
5309
- pullComputed as a8,
5310
- strictRead as a9,
5311
- EFFECT_USER as aA,
5312
- reportDiagnostic as aB,
5313
- computed as aC,
5314
- staleValues as aD,
5315
- CONFIG_AUTO_DISPOSE as aE,
5316
- EFFECT_TRACKED as aF,
5317
- unwrapStatusError as aG,
5318
- haltReactivity as aH,
5319
- setStrictRead as aI,
5320
- StatusError as aJ,
5321
- trimStaleDeps as aK,
5322
- setTrackedQueueCallback as aL,
5323
- setEffectStatusNotify as aM,
5324
- _hitUnhandledAsync as aN,
5325
- resetUnhandledAsync as aO,
5326
- ROOT_ERROR_HOOK as aP,
5327
- registerGraph as aQ,
5328
- runWithOwner as aR,
5329
- setMemo as aS,
5330
- $REFRESH as aT,
5331
- markRefresh as aU,
5332
- createRoot as aV,
5333
- getObserver as aW,
5334
- dispose as aX,
5335
- TimeoutError as aY,
5336
- untrack as aZ,
5337
- dirtyQueue as a_,
5338
- warnStrictReadUntracked as aa,
5339
- markLateLinker as ab,
5340
- spectating as ac,
5341
- STATUS_ERROR as ad,
5342
- unflushedValue as ae,
5343
- stagedReaders as af,
5344
- enterAction as ag,
5345
- setEffectCallback as ah,
5346
- exitAction as ai,
5347
- isThenable as aj,
5348
- actionDepth as ak,
5349
- flush as al,
5350
- setQuestion as am,
5351
- emitDiagnostic as an,
5352
- enterCallback as ao,
5353
- exitCallback as ap,
5354
- getOwner as aq,
5355
- inEffectCallback as ar,
5356
- signal as as,
5357
- setSignal as at,
5358
- cleanup as au,
5359
- read as av,
5360
- NoOwnerError as aw,
5361
- ContextNotFoundError as ax,
5362
- createEffectNode as ay,
5363
- recompute as az,
5364
- CONFIG_VERDICT as b,
5365
- passTx as b0,
5366
- CONFIG_FRESH_READ as b1,
5367
- createOwner as b2,
5368
- REACTIVE_WRITE_IN_OWNED_SCOPE_SIGNAL_MESSAGE as b3,
5369
- ownedScopeWriteMessage as b4,
5370
- DEV as b5,
5371
- isEqual as b6,
5372
- CONFIG_MANUAL_WRITE as b7,
5373
- notePromotedWrite as b8,
5374
- slotSignal as b9,
5375
- setSnapshotCapture as bA,
5376
- setAttributionHooks as bB,
5377
- liveRootOwners as bC,
5378
- ownerPath as bD,
5379
- isSuppressed as bE,
5380
- blockersOf as bF,
5381
- CONFIG_PLUMBING as bG,
5382
- anyExcluded as bH,
5383
- isExcluded as bI,
5384
- CONFIG_WIDE as bJ,
5385
- GRAPH_SIZE_WARN_AT as bK,
5386
- noteFanOut as bL,
5387
- CONFIG_SUPERSEDES as bM,
5388
- stagedScreen as ba,
5389
- CONFIG_PROMOTED as bb,
5390
- setSlotUnobserved as bc,
5391
- joinPassTx as bd,
5392
- asyncTailFlights as be,
5393
- untrackDepth as bf,
5394
- checkPostAwaitRead as bg,
5395
- insertIntoHeap as bh,
5396
- isDisposed as bi,
5397
- handleAsync as bj,
5398
- addPendingSource as bk,
5399
- propagateStatus as bl,
5400
- forEachDependent as bm,
5401
- settlePendingSource as bn,
5402
- notifyEpoch as bo,
5403
- SUPPORTS_PROXY as bp,
5404
- REACTIVE_ZOMBIE as bq,
5405
- OBSERVE as br,
5406
- clearSnapshots as bs,
5407
- enforceLoadingBoundary as bt,
5408
- getNextChildId as bu,
5409
- markSnapshotScope as bv,
5410
- peekNextChildId as bw,
5411
- releaseSnapshotScope as bx,
5412
- resetErrorHalt as by,
5413
- setConsoleFooter as bz,
5414
- REACTIVE_PROBE_UNANSWERED as c,
5415
- blocked as d,
5744
+ STATUS_UNINITIALIZED as S,
5745
+ CONFIG_INPUTS_PUBLISHED as T,
5746
+ releaseQueues as U,
5747
+ CONFIG_SLOT_NODE as V,
5748
+ holdNode as W,
5749
+ joinFuture as X,
5750
+ reruns as Y,
5751
+ CONFIG_STAGED as Z,
5752
+ merge as _,
5753
+ NotReadyError as a,
5754
+ setMemo as a$,
5755
+ REACTIVE_JOINED as a0,
5756
+ REACTIVE_STAGED_READ as a1,
5757
+ stagedRead as a2,
5758
+ setVerdict as a3,
5759
+ context as a4,
5760
+ installVerdictHooks as a5,
5761
+ REACTIVE_PROBED as a6,
5762
+ verdict as a7,
5763
+ link as a8,
5764
+ pullComputed as a9,
5765
+ cleanup as aA,
5766
+ read as aB,
5767
+ DEV as aC,
5768
+ CONFIG_TRANSPARENT as aD,
5769
+ NoOwnerError as aE,
5770
+ ContextNotFoundError as aF,
5771
+ createEffectNode as aG,
5772
+ recompute as aH,
5773
+ EFFECT_USER as aI,
5774
+ reportDiagnostic as aJ,
5775
+ computed as aK,
5776
+ staleValues as aL,
5777
+ CONFIG_AUTO_DISPOSE as aM,
5778
+ EFFECT_TRACKED as aN,
5779
+ heldRunHook as aO,
5780
+ unwrapStatusError as aP,
5781
+ haltReactivity as aQ,
5782
+ setStrictRead as aR,
5783
+ StatusError as aS,
5784
+ trimStaleDeps as aT,
5785
+ setTrackedQueueCallback as aU,
5786
+ setEffectStatusNotify as aV,
5787
+ _hitUnhandledAsync as aW,
5788
+ resetUnhandledAsync as aX,
5789
+ ROOT_ERROR_HOOK as aY,
5790
+ registerGraph as aZ,
5791
+ runWithOwner as a_,
5792
+ strictRead as aa,
5793
+ warnStrictReadUntracked as ab,
5794
+ staleReader as ac,
5795
+ globalQueue as ad,
5796
+ joinPass as ae,
5797
+ markLateLinker as af,
5798
+ spectating as ag,
5799
+ unflushedValue as ah,
5800
+ CONFIG_STAGED_ERROR as ai,
5801
+ stagedReaders as aj,
5802
+ bornInto as ak,
5803
+ enterAction as al,
5804
+ setEffectCallback as am,
5805
+ exitAction as an,
5806
+ isThenable as ao,
5807
+ actionDepth as ap,
5808
+ flush as aq,
5809
+ setQuestion as ar,
5810
+ emitDiagnostic as as,
5811
+ enterCallback as at,
5812
+ exitCallback as au,
5813
+ getOwner as av,
5814
+ inEffectCallback as aw,
5815
+ setExternalHooks as ax,
5816
+ signal as ay,
5817
+ setSignal as az,
5818
+ CONFIG_AUTHORITATIVE as b,
5819
+ isSuppressed as b$,
5820
+ $REFRESH as b0,
5821
+ markRefresh as b1,
5822
+ createRoot as b2,
5823
+ getObserver as b3,
5824
+ dispose as b4,
5825
+ TimeoutError as b5,
5826
+ untrack as b6,
5827
+ dirtyQueue as b7,
5828
+ CONFIG_REDERIVE as b8,
5829
+ passTx as b9,
5830
+ settlePendingSource as bA,
5831
+ notifyEpoch as bB,
5832
+ SUPPORTS_PROXY as bC,
5833
+ releaseMarksHook as bD,
5834
+ installMarkHooks as bE,
5835
+ CONFIG_LAGS as bF,
5836
+ REACTIVE_REASK as bG,
5837
+ heldUnder as bH,
5838
+ unhold as bI,
5839
+ laneStageHook as bJ,
5840
+ REACTIVE_ZOMBIE as bK,
5841
+ installBoundaryHooks as bL,
5842
+ CAUGHT_REARMED as bM,
5843
+ OBSERVE as bN,
5844
+ clearSnapshots as bO,
5845
+ enforceLoadingBoundary as bP,
5846
+ getNextChildId as bQ,
5847
+ markSnapshotScope as bR,
5848
+ peekNextChildId as bS,
5849
+ releaseSnapshotScope as bT,
5850
+ resetErrorHalt as bU,
5851
+ setConsoleFooter as bV,
5852
+ setSnapshotCapture as bW,
5853
+ setWriteHook as bX,
5854
+ setAttributionHooks as bY,
5855
+ liveRootOwners as bZ,
5856
+ ownerPath as b_,
5857
+ CONFIG_FRESH_READ as ba,
5858
+ createOwner as bb,
5859
+ writeHook as bc,
5860
+ REACTIVE_WRITE_IN_OWNED_SCOPE_SIGNAL_MESSAGE as bd,
5861
+ ownedScopeWriteMessage as be,
5862
+ isEqual as bf,
5863
+ CONFIG_MANUAL_WRITE as bg,
5864
+ snapshotCaptureActive as bh,
5865
+ captureWriteSnapshot as bi,
5866
+ notePromotedWrite as bj,
5867
+ slotSignal as bk,
5868
+ stagedScreen as bl,
5869
+ CONFIG_PROMOTED as bm,
5870
+ setSlotUnobserved as bn,
5871
+ installStoreHooks as bo,
5872
+ joinPassTx as bp,
5873
+ asyncTailFlights as bq,
5874
+ untrackDepth as br,
5875
+ checkPostAwaitRead as bs,
5876
+ disposalDepth as bt,
5877
+ insertIntoHeap as bu,
5878
+ isDisposed as bv,
5879
+ handleAsync as bw,
5880
+ addPendingSource as bx,
5881
+ propagateStatus as by,
5882
+ forEachDependent as bz,
5883
+ CONFIG_OVERRIDE as c,
5884
+ blockersOf as c0,
5885
+ CONFIG_PLUMBING as c1,
5886
+ anyExcluded as c2,
5887
+ isExcluded as c3,
5888
+ CONFIG_WIDE as c4,
5889
+ GRAPH_SIZE_WARN_AT as c5,
5890
+ noteFanOut as c6,
5891
+ CONFIG_SUPERSEDES as c7,
5892
+ newTransaction as d,
5416
5893
  enqueueSub as e,
5417
- CONFIG_AUTHORITATIVE as f,
5418
- tracking as g,
5419
- CONFIG_CHILDREN_FORBIDDEN as h,
5420
- sameLane as i,
5421
- CONFIG_HELD as j,
5422
- flushTransaction as k,
5423
- STATUS_UNINITIALIZED as l,
5424
- NotReadyError as m,
5894
+ CONFIG_VERDICT as f,
5895
+ REACTIVE_PROBE_UNANSWERED as g,
5896
+ blocked as h,
5897
+ installLaneHooks as i,
5898
+ REACTIVE_LANE_DIRTY as j,
5899
+ CONFIG_HELD as k,
5900
+ clock as l,
5901
+ insertSubs as m,
5425
5902
  nextQuestion as n,
5426
- REACTIVE_SCREEN_READ as o,
5427
- passLane as p,
5903
+ ownFlights as o,
5904
+ laneDirty as p,
5428
5905
  question as q,
5429
5906
  resolveTx as r,
5430
5907
  schedule as s,
5431
5908
  txOf as t,
5432
- REACTIVE_FRAME_READ as u,
5433
- REACTIVE_LANE_READ as v,
5434
- ext as w,
5435
- REACTIVE_RECOMPUTING_DEPS as x,
5436
- queuePendingNode as y,
5437
- REACTIVE_LANE_DIRTY as z
5909
+ REACTIVE_LANE_READ as u,
5910
+ ext as v,
5911
+ REACTIVE_RECOMPUTING_DEPS as w,
5912
+ queuePendingNode as x,
5913
+ list as y,
5914
+ STATUS_ERROR as z
5438
5915
  };