@solidjs/signals 2.0.0-rc.19 → 2.0.0-rc.20

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 (74) hide show
  1. package/dist/dev-shared.js +553 -264
  2. package/dist/dev.attribution.js +18 -18
  3. package/dist/dev.js +1030 -439
  4. package/dist/observe/affects.js +12 -2
  5. package/dist/observe/boundaries.js +168 -115
  6. package/dist/observe/core/async.js +57 -37
  7. package/dist/observe/core/attribution.js +6 -6
  8. package/dist/observe/core/constants.js +9 -1
  9. package/dist/observe/core/copies.js +10 -0
  10. package/dist/observe/core/core.js +261 -181
  11. package/dist/observe/core/effect.js +32 -27
  12. package/dist/observe/core/graph.js +7 -7
  13. package/dist/observe/core/heap.js +29 -29
  14. package/dist/observe/core/lanes.js +301 -101
  15. package/dist/observe/core/owner.js +13 -13
  16. package/dist/observe/core/scheduler.js +124 -69
  17. package/dist/observe/core/truth.js +9 -7
  18. package/dist/observe/core/verdict.js +160 -98
  19. package/dist/observe/deferred.js +1 -1
  20. package/dist/observe/index.js +2 -0
  21. package/dist/observe/map.js +120 -116
  22. package/dist/observe/reveal.js +48 -48
  23. package/dist/observe/signals.js +5 -5
  24. package/dist/observe/store/affects.js +2 -2
  25. package/dist/observe/store/optimistic.js +272 -176
  26. package/dist/observe/store/projection.js +21 -10
  27. package/dist/observe/store/reconcile.js +8 -2
  28. package/dist/observe/store/store.js +91 -55
  29. package/dist/observe/store/types.js +11 -1
  30. package/dist/observe/store/utils.js +1 -1
  31. package/dist/prod/affects.js +12 -2
  32. package/dist/prod/boundaries.js +164 -109
  33. package/dist/prod/core/async.js +201 -181
  34. package/dist/prod/core/constants.js +9 -1
  35. package/dist/prod/core/copies.js +10 -0
  36. package/dist/prod/core/core.js +244 -164
  37. package/dist/prod/core/dev.js +108 -3
  38. package/dist/prod/core/effect.js +28 -23
  39. package/dist/prod/core/graph.js +9 -9
  40. package/dist/prod/core/heap.js +16 -16
  41. package/dist/prod/core/identities.js +1 -1
  42. package/dist/prod/core/lanes.js +307 -95
  43. package/dist/prod/core/owner.js +22 -22
  44. package/dist/prod/core/scheduler.js +266 -211
  45. package/dist/prod/core/truth.js +9 -7
  46. package/dist/prod/core/verdict.js +161 -99
  47. package/dist/prod/deferred.js +1 -1
  48. package/dist/prod/index.js +2 -0
  49. package/dist/prod/map.js +78 -74
  50. package/dist/prod/reveal.js +34 -34
  51. package/dist/prod/signals.js +3 -3
  52. package/dist/prod/store/affects.js +15 -15
  53. package/dist/prod/store/optimistic.js +185 -89
  54. package/dist/prod/store/projection.js +49 -38
  55. package/dist/prod/store/reconcile.js +8 -2
  56. package/dist/prod/store/store.js +99 -63
  57. package/dist/prod/store/types.js +11 -1
  58. package/dist/types/affects.d.ts +3 -2
  59. package/dist/types/boundaries.d.ts +14 -2
  60. package/dist/types/core/async.d.ts +2 -1
  61. package/dist/types/core/constants.d.ts +8 -0
  62. package/dist/types/core/copies.d.ts +54 -0
  63. package/dist/types/core/core.d.ts +15 -0
  64. package/dist/types/core/dev.d.ts +1 -1
  65. package/dist/types/core/lanes.d.ts +8 -1
  66. package/dist/types/core/scheduler.d.ts +36 -4
  67. package/dist/types/core/truth.d.ts +3 -1
  68. package/dist/types/core/types.d.ts +6 -0
  69. package/dist/types/index.d.ts +2 -0
  70. package/dist/types/store/hooks.d.ts +15 -1
  71. package/dist/types/store/store.d.ts +2 -1
  72. package/dist/types/store/target.d.ts +5 -0
  73. package/dist/types/store/types.d.ts +7 -0
  74. package/package.json +1 -1
@@ -152,6 +152,11 @@ const REACTIVE_SCREEN_READ = 1 << 18;
152
152
  * later read of the same pass routed it into a verdict lane — that was not
153
153
  * the probe's answer (lanes.ts). Per pass. */
154
154
  const REACTIVE_PROBE_UNANSWERED = 1 << 19;
155
+ /** The pass opened a verdict window of its own (`isPending`/`latest`) — not
156
+ * only read a verdict memo (F-6, core.ts `read`). An async memo whose pass
157
+ * did not is D-25's hold work while its flight is up (verdict.ts
158
+ * `holdWork`). Per pass (1 << 20 is lanes.ts's LEFT_SHOWN). */
159
+ const REACTIVE_WINDOW = 1 << 21;
155
160
  // Static configuration bits packed into Owner/Computed/Signal _config.
156
161
  const CONFIG_OWNED_WRITE = 1 << 0;
157
162
  const CONFIG_NO_SNAPSHOT = 1 << 1;
@@ -299,6 +304,9 @@ const CONFIG_KEY_BAG = 1 << 28;
299
304
  * joins nothing, so two mounts under one boundary made in separate flushes
300
305
  * keep their holds apart (D-4, S-5 rows 1′–3′, #4283). */
301
306
  const CONFIG_FRAME = 1 << 23;
307
+ /** The node's last answering pass registered a flight: an async memo. Kept
308
+ * by the verdict engine alone (verdict.ts `holdWork`), which reads it. */
309
+ const CONFIG_ASYNC = 1 << 29;
302
310
  const STATUS_NONE = 0;
303
311
  const STATUS_PENDING = 1 << 0;
304
312
  const STATUS_ERROR = 1 << 1;
@@ -328,6 +336,69 @@ const $REFRESH = Symbol("refresh");
328
336
 
329
337
  const NewSet = Set;
330
338
 
339
+ const COPY = { rooted: false };
340
+ let warned = false;
341
+ let shared;
342
+ {
343
+ shared = globalThis.Solid$$copies ||= { copies: 0, running: undefined };
344
+ shared.copies++;
345
+ }
346
+ function warnOnce(detail) {
347
+ if (warned) return;
348
+ warned = true;
349
+ console.warn(
350
+ `You appear to have multiple instances of Solid: ${detail} Make sure every package resolves the same solid-js (e.g. \`resolve.dedupe\` in Vite) and that libraries keep solid-js external.`
351
+ );
352
+ }
353
+ function markCopyRooted() {
354
+ COPY.rooted = true;
355
+ }
356
+ /**
357
+ * Marks this copy as the one running; returns the slot's previous value. A
358
+ * nested run of the same copy (most runs inside a flush) only reads the slot.
359
+ */
360
+ function enterCopy() {
361
+ if (shared.copies < 2) return COPY;
362
+ const prev = shared.running;
363
+ if (prev !== COPY) shared.running = COPY;
364
+ return prev;
365
+ }
366
+ function exitCopy(prev) {
367
+ if (prev !== COPY) shared.running = prev;
368
+ }
369
+ /** @internal Runs `fn` with this copy marked as the one running. */
370
+ function runAsSolidCopy(fn) {
371
+ if (shared.copies < 2) return fn();
372
+ const prev = enterCopy();
373
+ try {
374
+ return fn();
375
+ } finally {
376
+ exitCopy(prev);
377
+ }
378
+ }
379
+ /** @internal An ownerless component or context read: warns if another copy is running. */
380
+ function checkForeignOwnerless() {
381
+ if (shared.copies < 2) return;
382
+ const running = shared.running;
383
+ if (running && running !== COPY)
384
+ warnOnce(
385
+ "a component or useContext() from one copy ran inside a component rendered by another, so it has no owner and will not react or find its providers."
386
+ );
387
+ }
388
+ /**
389
+ * An untracked read: warns if another copy's computation is running, unless
390
+ * both copies render through roots of their own (an app calling into an
391
+ * isolated copy's API, which reads its own state untracked by design).
392
+ */
393
+ function checkForeignRead() {
394
+ if (shared.copies < 2) return;
395
+ const running = shared.running;
396
+ if (running && running !== COPY && !(running.rooted && COPY.rooted))
397
+ warnOnce(
398
+ "a signal or store from one copy was read inside a computation of another, so the read is not tracked and will not update."
399
+ );
400
+ }
401
+
331
402
  let attrHooks = null;
332
403
  /**
333
404
  * The installed engine, registered on `globalThis` as well (the records
@@ -1560,6 +1631,7 @@ function createOwner(options) {
1560
1631
  */
1561
1632
  function createRoot(init, options) {
1562
1633
  const owner = createOwner(options);
1634
+ markCopyRooted();
1563
1635
  // A bound function, not an arrow: an arrow would share this scope, which
1564
1636
  // holds `init`, so keeping `dispose` would keep `init` and all it closes
1565
1637
  // over alive (#4095).
@@ -1582,6 +1654,19 @@ let laneSeamsHook = null;
1582
1654
  let laneCorrectionsHook = null;
1583
1655
  let endLanesHook = null;
1584
1656
  let lanesBlockedHook = null;
1657
+ /** R5 (lanes.ts `laneSeams` parks a frame flight): the landing's re-derive
1658
+ * of a reader adopts the flight instead (true: adopted, nothing to queue). */
1659
+ let laneAdoptHook = null;
1660
+ /** core.ts `frameRead` served lane work or a verdict reader a held node's
1661
+ * committed value (lanes.ts `laneServed`). */
1662
+ let laneServedHook = null;
1663
+ /** A guess was written (lanes.ts `optimisticWrite`): the arms only a guess
1664
+ * reaches (CONFIG_GUESS) — a build without `createOptimistic` or
1665
+ * `createOptimisticStore` never sets it, and its bundler folds them. */
1666
+ let guessing = false;
1667
+ function armGuessing() {
1668
+ guessing = true;
1669
+ }
1585
1670
  function installLaneHooks(
1586
1671
  read,
1587
1672
  stage,
@@ -1591,7 +1676,9 @@ function installLaneHooks(
1591
1676
  seams,
1592
1677
  corrections,
1593
1678
  end,
1594
- blocked
1679
+ blocked,
1680
+ adopt,
1681
+ served
1595
1682
  ) {
1596
1683
  laneReadHook = read;
1597
1684
  laneStageHook = stage;
@@ -1602,6 +1689,8 @@ function installLaneHooks(
1602
1689
  laneCorrectionsHook = corrections;
1603
1690
  endLanesHook = end;
1604
1691
  lanesBlockedHook = blocked;
1692
+ laneAdoptHook = adopt;
1693
+ laneServedHook = served;
1605
1694
  }
1606
1695
  // Boundaries (boundaries.ts): the display consumers between an observer
1607
1696
  // and the root. `catch` — status from a frame reader, nearest boundary
@@ -1650,8 +1739,12 @@ function installMarkHooks(release, releaseAmbient) {
1650
1739
  }
1651
1740
  /** Verdicts (verdict.ts). */
1652
1741
  let observeFlightHook = null;
1653
- function installVerdictHooks(observeFlight) {
1742
+ /** D-25's hold work (verdict.ts `holdWork`): on `source` going pending,
1743
+ * whether verdict reader `n` waits on it instead of re-deriving. */
1744
+ let holdWorkHook = null;
1745
+ function installVerdictHooks(observeFlight, holdWork) {
1654
1746
  observeFlightHook = observeFlight;
1747
+ holdWorkHook = holdWork;
1655
1748
  }
1656
1749
  /** External-source bridge (wired by enableExternalSource(); undefined while
1657
1750
  * no config is active — including after _resetExternalSourceConfig()). */
@@ -1712,6 +1805,10 @@ function setPassLane(l) {
1712
1805
  * schedules the seam that consumes it: membership is the tick's — the same
1713
1806
  * synchronous frame — and the next tick's writes are not this one's. */
1714
1807
  let flushTransaction = null;
1808
+ /** The flush's transaction while only a `latest()` read of a staging opened
1809
+ * it (verdict.ts `provisionalVerdict`): it holds nothing, and a verdict
1810
+ * reader's plain read of a staging in it is the frame's read (D-22). */
1811
+ let latestOpened = null;
1715
1812
  /** The transaction the passes running OUTSIDE a flush this tick derive from
1716
1813
  * (a mainline mount, a mainline re-pass): the passes' own (A29, creation-
1717
1814
  * time form — "the entry is the pass's alone"): their results are staged
@@ -2213,7 +2310,7 @@ class GlobalQueue {
2213
2310
  // pure round — the parent's flights before the parent is judged below.
2214
2311
  if (laneCorrectionsHook?.()) runHeap(dirtyQueue, GlobalQueue._update);
2215
2312
  const joined = flushTransaction;
2216
- flushTransaction = null;
2313
+ flushTransaction = latestOpened = null;
2217
2314
  // The frame's verdict is in: the guesses written since the last seam
2218
2315
  // open their lane under the frame's transaction (a lane's own flush
2219
2316
  // parents a nested lane to it), or are dropped — the frame commits, and
@@ -2331,7 +2428,7 @@ class GlobalQueue {
2331
2428
  // world (a lane's runs wait this round).
2332
2429
  if (laneSeamsHook) {
2333
2430
  this._queues = lanes;
2334
- laneSeamsHook(t !== null ? stagedReaders : null);
2431
+ laneSeamsHook(t);
2335
2432
  if (stagedReaders.length) stagedReaders.length = 0;
2336
2433
  }
2337
2434
  this._queues = [[], []];
@@ -2464,17 +2561,46 @@ function reruns(u) {
2464
2561
  const r = u._reruns[i];
2465
2562
  // An errored reader's display is its error (D-39): the landing gives it
2466
2563
  // nothing to re-derive, and a re-run would be a read that retries the
2467
- // errored source (#3945).
2468
- if (r._flags & REACTIVE_DISPOSED || r._statusFlags & STATUS_ERROR) continue;
2469
- if (r._flags & REACTIVE_FRAME_READ || r._config & CONFIG_VERDICT) {
2470
- frame ||= r._type;
2471
- r._flags &= ~REACTIVE_FRAME_READ;
2564
+ // errored source (#3945) — unless that source is errored no longer: the
2565
+ // reader was served a shown error the landing replaces (`throwShown`).
2566
+ // Nor is the reader's own error: its pass threw over the committed value
2567
+ // of what the landing carries, served as the screen (A10; a lane's node
2568
+ // reading its parent's hold, or a pass with no value yet reading a
2569
+ // staging, `stagedScreen`) — the landing is the new answer, and the
2570
+ // reader re-derives on it (A15; D-39). A first load read so has no other
2571
+ // run: its error would stay the screen (async-gen 91501 #1158, #119).
2572
+ // A boundary's structure (its tree, its output: CONFIG_FRAME, no effect
2573
+ // type) re-derives all the same: what it read as the screen is the
2574
+ // content the landing replaces, and only its pass links the new content
2575
+ // whose recovery must reach it (D-3, D-39, #4405).
2576
+ const s = r._x?._error?.source;
2577
+ if (
2578
+ r._flags & REACTIVE_DISPOSED ||
2579
+ (r._statusFlags & STATUS_ERROR &&
2580
+ (r._type || !(r._config & CONFIG_FRAME)) &&
2581
+ s !== r &&
2582
+ (s?._statusFlags ?? STATUS_ERROR) & STATUS_ERROR)
2583
+ )
2584
+ continue;
2585
+ if (r._flags & REACTIVE_FRAME_READ || (observeFlightHook && r._config & CONFIG_VERDICT)) {
2586
+ // A memo re-deriving its own error holds the release as an effect
2587
+ // does (`land`): run first, the held runs showed that error beside a
2588
+ // frame about to replace it (#4407; #4284: the grouping never changes
2589
+ // the screen).
2590
+ frame ||= r._type || s === r;
2591
+ // A lane's reveal leaves the mark to the transaction: a reader that
2592
+ // also read a held node as committed (`staleReader`) still observes
2593
+ // that flight until it re-runs — cleared here, the transaction landed
2594
+ // at this seam with the flight up and voided the reveal's run (#4397).
2595
+ if (!u._lane) r._flags &= ~REACTIVE_FRAME_READ;
2472
2596
  r._modified = false;
2473
2597
  // Its frame is replaced by the re-derivation: a run one of its
2474
2598
  // children queued this flush (re-run by the same landing) is for the
2475
2599
  // frame being replaced — void too (#3404).
2476
2600
  for (let c = r._firstChild; c !== null; c = c._nextSibling) if (c._type) c._modified = false;
2477
- enqueueSub(r);
2601
+ // A frame flight the seam parked (lanes.ts `laneSeams`, R5) asked the
2602
+ // question this landing would: adopted, it is asked once (F-9).
2603
+ if (!laneAdoptHook?.(r)) enqueueSub(r);
2478
2604
  }
2479
2605
  }
2480
2606
  u._reruns.length = 0;
@@ -2598,11 +2724,22 @@ function insertSubs(node) {
2598
2724
  * inert for its pass (`recompute` seats a lane only from its own `_x`), it
2599
2725
  * is a live write if the effect becomes a zombie later this flush — an
2600
2726
  * on-screen reader whose removal an adopted write stages follows the lane
2601
- * (the lane-membership ruling, 2026-10-05; `_update`). */
2727
+ * (the lane-membership ruling, 2026-10-05; `_update`).
2728
+ *
2729
+ * A render effect re-run by a verdict memo's change (`createMemo(() =>
2730
+ * latest(x))`, CONFIG_VERDICT) is the verdict reader the same read inline
2731
+ * makes it, for that pass (F-6; D-26 `[s(), L()]`, #4284): a frame reader
2732
+ * of its other reads — a staging's screen, a flight's committed value. One
2733
+ * an earlier propagation this flush seated in another lane leaves that
2734
+ * seat: its pass starts from the frame's, as the inline effect's does, and
2735
+ * `laneRead` re-derives it on the screen if that lane holds (`entered`). */
2602
2736
  function laneDirty(node, l) {
2603
2737
  for (let s = node._subs; s !== null; s = s._nextSub) {
2604
- const t = s._sub._x?._transaction;
2605
- if (t?._lane ? sameLane(t, l) : s._sub._type) s._sub._flags |= REACTIVE_LANE_DIRTY;
2738
+ const c = s._sub,
2739
+ t = c._x?._transaction;
2740
+ if (c._type === EFFECT_RENDER && node._config & CONFIG_VERDICT)
2741
+ ((c._config |= CONFIG_VERDICT), (c._flags &= ~REACTIVE_LANE_DIRTY));
2742
+ if (t?._lane ? sameLane(t, l) : c._type) c._flags |= REACTIVE_LANE_DIRTY;
2606
2743
  }
2607
2744
  }
2608
2745
  /** One lane, or two of one link group (#3335): one reveal unit. */
@@ -2630,7 +2767,7 @@ function commitPendingNode(n) {
2630
2767
  const inFlight = n._pendingValue === NOT_PENDING && (c._statusFlags & STATUS_PENDING) !== 0;
2631
2768
  const err = n._config & CONFIG_STAGED_ERROR;
2632
2769
  // An answer commits: the error it replaces leaves the display (D-41).
2633
- if (!inFlight && c._x) c._x._shownError = undefined;
2770
+ if (!inFlight && c._x) c._x._shownError = null;
2634
2771
  n._config = inFlight ? (n._config & -1048833) | CONFIG_INPUTS_PUBLISHED : n._config & -3147137;
2635
2772
  // D-41: the staged answer is an error — published now, when a value would
2636
2773
  // show, through the ordinary error path (`stageError`). Its readers first
@@ -3315,8 +3452,9 @@ function settlePendingSource(el, source = el) {
3315
3452
  // A19: a verdict reader holds no pending of its own (it was re-derived
3316
3453
  // when the source went pending, `propagateStatus`), and a landing
3317
3454
  // equal to the committed value notifies nobody — the source settling
3318
- // is its verdict changing: it runs again.
3319
- return node._config & CONFIG_VERDICT && enqueueSub(node);
3455
+ // is its verdict changing: it runs again. (Verdict readers exist only
3456
+ // with the verdict engine installed.)
3457
+ return holdWorkHook && node._config & CONFIG_VERDICT && enqueueSub(node);
3320
3458
  visited.add(node);
3321
3459
  node._time = clock;
3322
3460
  const remaining = node._x?._pendingSources?.values().next().value;
@@ -3416,7 +3554,10 @@ function handleAsync(
3416
3554
  });
3417
3555
  }
3418
3556
  if (!thenable && !iterator) {
3419
- if (el._x !== null) el._x._inFlight = null;
3557
+ if (el._x !== null) {
3558
+ el._x._inFlight = null;
3559
+ if (holdWorkHook) el._config &= ~CONFIG_ASYNC;
3560
+ }
3420
3561
  // A sync landing is the first real answer for a loadingValue node.
3421
3562
  el._loading = false;
3422
3563
  return result;
@@ -3449,6 +3590,11 @@ function handleAsync(
3449
3590
  // fired _flightTeardown. A future non-recompute registration path must
3450
3591
  // release it here before overwriting _inFlight.
3451
3592
  ext(el)._inFlight = result;
3593
+ // D-25 (verdict.ts `holdWork`): an async memo whose answering pass opened
3594
+ // no verdict window of its own (`markVerdictReader` clears the bit).
3595
+ // Kept by the verdict engine alone (`holdWorkHook`): a build without it
3596
+ // folds the bit's upkeep away.
3597
+ if (holdWorkHook && !(el._flags & REACTIVE_WINDOW)) el._config |= CONFIG_ASYNC;
3452
3598
  // The question this flight answers (A18 provenance, lanes.ts): the action
3453
3599
  // whose slice asked it; mainline is always the current question.
3454
3600
  el._x._q = question || MAINLINE_QUESTION;
@@ -3544,7 +3690,7 @@ function handleAsync(
3544
3690
  // a verdict reader of the next flight would suspend instead of being
3545
3691
  // served the screen; held, both at the derive's commit.
3546
3692
  if (!(el._config & CONFIG_HELD)) {
3547
- el._x._shownError = undefined;
3693
+ el._x._shownError = null;
3548
3694
  el._config &= ~CONFIG_INPUTS_PUBLISHED;
3549
3695
  }
3550
3696
  if (wasUninitialized) landStatus(el, true);
@@ -3872,8 +4018,7 @@ function notifyStatus(el, status, error) {
3872
4018
  !(error instanceof NotReadyError)
3873
4019
  )
3874
4020
  error = new StatusError(el, error);
3875
- const pendingSource =
3876
- status === STATUS_PENDING && error instanceof NotReadyError ? error.source : undefined;
4021
+ const pendingSource = status === STATUS_PENDING && error instanceof NotReadyError && error.source;
3877
4022
  // (CARVE 2: the lane assignment went with the engine.)
3878
4023
  if (status === STATUS_PENDING && pendingSource) {
3879
4024
  // D-41: a re-ask of a shown error keeps it the display (`_shownError`).
@@ -3908,9 +4053,14 @@ function notifyStatus(el, status, error) {
3908
4053
  // status, not its readers': they read the guess. The flight is the parent
3909
4054
  // transition's to wait on — the frame it was asked in holds (A17: "a
3910
4055
  // transition whose optimistic node is still pending on its own fetch is
3911
- // not complete"), through the node itself (`blocked`).
3912
- if (status === STATUS_PENDING && el._config & CONFIG_GUESS) {
3913
- if (passLane === null) joinFuture(null);
4056
+ // not complete"), through the node itself (`blocked`). An input changed
4057
+ // from outside the guess's hold asks a new question: the frame that
4058
+ // changed it is held with that hold (D-34, #4336) — it shows beside the
4059
+ // guess's truth, which reveals at that hold's commit (F-1). A `refresh()`
4060
+ // re-asks the same question: its frame is its own.
4061
+ // (No guess written, no arm.)
4062
+ if (guessing && status === STATUS_PENDING && el._config & CONFIG_GUESS) {
4063
+ if (passLane === null) joinFuture(el._flags & REACTIVE_REASK ? null : txOf(el)._parent);
3914
4064
  return;
3915
4065
  }
3916
4066
  const elNotify = statusNotifierOf(el);
@@ -3926,8 +4076,7 @@ function notifyStatus(el, status, error) {
3926
4076
  * propagates the derive's status from each leaf to the leaf's readers
3927
4077
  * (store/projection.ts `wakeFamily`), exactly as a memo's would reach its. */
3928
4078
  function propagateStatus(el, status, error) {
3929
- const pendingSource =
3930
- status === STATUS_PENDING && error instanceof NotReadyError ? error.source : undefined;
4079
+ const pendingSource = status === STATUS_PENDING && error instanceof NotReadyError && error.source;
3931
4080
  forEachDependent(el, (sub, link) => {
3932
4081
  sub._time = clock;
3933
4082
  // A pending mark on a kept-tail link re-derives the subscriber instead of
@@ -3949,8 +4098,15 @@ function propagateStatus(el, status, error) {
3949
4098
  // Inheriting the error would make a probe's reader errored. A boundary's
3950
4099
  // output (CONFIG_REDERIVE, boundaries.ts) the same: content or fallback
3951
4100
  // is its pass's call.
4101
+ // Not one D-25 makes the hold's work (verdict.ts `holdWork`): an async
4102
+ // memo with no verdict window of its own reads a verdict memo's value,
4103
+ // and that source going pending changes none of its reads — re-derived,
4104
+ // it is served the committed value it already derived from (A10) and
4105
+ // asks that question again. It waits on the source like any derivation
4106
+ // (F-9), and asks its next question once, at the landing (#4397).
3952
4107
  if (
3953
- sub._config & (CONFIG_REDERIVE | CONFIG_VERDICT) ||
4108
+ sub._config & CONFIG_REDERIVE ||
4109
+ (holdWorkHook && sub._config & CONFIG_VERDICT && !(pendingSource && holdWorkHook(sub, el))) ||
3954
4110
  (status === STATUS_PENDING && link._gen !== sub._depGen)
3955
4111
  ) {
3956
4112
  enqueueSub(sub);
@@ -4183,19 +4339,19 @@ function recompute(el, create = false) {
4183
4339
  // Restored at the end, after this pass's staging and runs have been
4184
4340
  // routed.
4185
4341
  // Lanes exist only with the lane engine installed (`laneStageHook`): a
4186
- // bundle without it seats every pass on mainline, and folds the seat away.
4342
+ // bundle without it seats every pass on mainline (`passLane` is never set)
4343
+ // and folds the seat, and its restores, away.
4187
4344
  const prevLane = passLane;
4188
4345
  // The mount a first pass belongs to: its creator's lane, or the one its
4189
4346
  // creator's own mount came from — a render effect a verdict lane's mount's
4190
4347
  // content creates is that mount's too (#4166). Lanes only, like the seat.
4191
4348
  const prevMount = laneRoot;
4192
4349
  const mount = laneStageHook && (laneRoot = create && (prevLane || prevMount));
4193
- setPassLane(
4194
- !laneStageHook
4195
- ? null
4196
- : (el._flags & REACTIVE_LANE_DIRTY ||
4197
- (el._config & (CONFIG_OVERRIDE | CONFIG_GUESS)) === CONFIG_OVERRIDE) &&
4198
- el._x?._transaction?._lane
4350
+ if (laneStageHook)
4351
+ setPassLane(
4352
+ (el._flags & REACTIVE_LANE_DIRTY ||
4353
+ (el._config & (CONFIG_OVERRIDE | CONFIG_GUESS)) === CONFIG_OVERRIDE) &&
4354
+ el._x?._transaction?._lane
4199
4355
  ? el._x._transaction
4200
4356
  : // Loose `!=`: false with no creator lane (undefined == null).
4201
4357
  create &&
@@ -4203,7 +4359,7 @@ function recompute(el, create = false) {
4203
4359
  creatorPass(context)?._flags & REACTIVE_RECOMPUTING_DEPS
4204
4360
  ? prevLane
4205
4361
  : null
4206
- );
4362
+ );
4207
4363
  // Attribution hook: fired before this run touches the dep list — `_deps`
4208
4364
  // still holds the previous run's links (the subscriptions that could have
4209
4365
  // triggered this run, and the baseline for the engine's subscription diff).
@@ -4214,9 +4370,35 @@ function recompute(el, create = false) {
4214
4370
  }
4215
4371
  // CARVE 2: lane posture resolution (OPTIMISTIC_DIRTY, derived-override
4216
4372
  // re-derivation, lane adoption through deps) went with the optimistic engine.
4373
+ // An error a pass staged, which this pass replaces (`valueChanged`).
4374
+ const replaced = el._config & CONFIG_STAGED_ERROR;
4217
4375
  if (!create) {
4218
4376
  deleteFromHeap(el, dirtyQueue);
4377
+ // A pending flight is the transition's own work (#3884), and so is a
4378
+ // held async memo's re-run with no verdict window of its own (D-25,
4379
+ // `holdWork`): a pass that takes no verdict role this time. A probe on
4380
+ // an earlier pass left CONFIG_VERDICT, and a frame read here would serve
4381
+ // the committed screen, settle the flight and drop the hold — a
4382
+ // superseding write's verdict would depend on that probe. Cleared for
4383
+ // this pass, before the flight it judges by is superseded; a probe in
4384
+ // the body re-stamps it after the plain reads. Not an effect: it has no
4385
+ // flight of its own (its pending is a source's), and a verdict memo's
4386
+ // change may have just made it a verdict reader (`laneDirty`). Only a
4387
+ // build with the lane engine (`laneStageHook`; verdict.ts imports it)
4388
+ // ever sets CONFIG_VERDICT: without it the line folds away.
4389
+ if (
4390
+ laneStageHook &&
4391
+ !isEffect &&
4392
+ (el._statusFlags & STATUS_PENDING ||
4393
+ (el._config & (CONFIG_ASYNC | CONFIG_HELD)) === (CONFIG_ASYNC | CONFIG_HELD))
4394
+ )
4395
+ el._config &= ~CONFIG_VERDICT;
4219
4396
  if (el._x !== null) {
4397
+ // The seam's re-derive runs with the parked flight still current; any
4398
+ // later pass asks its own question (`_parked`; only the lane engine
4399
+ // parks one).
4400
+ const p = laneStageHook && el._x._parked;
4401
+ if (p && p !== el._x._inFlight) el._x._parked = undefined;
4220
4402
  el._x._inFlight = null;
4221
4403
  // Supersede is where an iterator flight dies (#3122): close it now.
4222
4404
  // Idempotent with the cleanup-channel close.
@@ -4235,12 +4417,6 @@ function recompute(el, create = false) {
4235
4417
  // A).
4236
4418
  // A boundary's structure (CONFIG_FRAME) is the frame like a render
4237
4419
  // effect: it publishes mainline outside its transaction's flush (#4283).
4238
- // A pending flight is the transition's own work (#3884). A probe on an
4239
- // earlier pass left CONFIG_VERDICT, and a frame read here would serve the
4240
- // committed screen, settle the flight and drop the hold — a superseding
4241
- // write's verdict would depend on that probe. Cleared for this pass; a
4242
- // probe in the body re-stamps it after the plain reads.
4243
- if (el._statusFlags & STATUS_PENDING) el._config &= ~CONFIG_VERDICT;
4244
4420
  if (el._config & CONFIG_HELD) {
4245
4421
  const tx = txOf(el);
4246
4422
  // Held by a blocked lane: the frame joins nothing — a lane never holds
@@ -4264,7 +4440,11 @@ function recompute(el, create = false) {
4264
4440
  // children die below (`disposeChildren`). The landing re-derives a
4265
4441
  // fresh held frame for the reveal (`_reruns`).
4266
4442
  // Not held any more, whichever frame reader it is: its readers read
4267
- // the value this pass publishes.
4443
+ // the value this pass publishes — those that read the staging it
4444
+ // supersedes re-derive, even when it publishes the committed value
4445
+ // back (no change to notify them by): their held runs carry the
4446
+ // superseded frame, and the landing would apply them (#4378).
4447
+ if (el._pendingValue !== NOT_PENDING) insertSubs(el);
4268
4448
  el._pendingValue = NOT_PENDING;
4269
4449
  el._config &= ~CONFIG_HELD;
4270
4450
  }
@@ -4296,9 +4476,9 @@ function recompute(el, create = false) {
4296
4476
  // Capture both error and pending status before the compute clears them.
4297
4477
  // A conditional can drop its pending source and recover to an unchanged
4298
4478
  // value, leaving blocked dependents outside that source’s settle walk.
4299
- const outgoingError = el._statusFlags & STATUS_ERROR ? el._x?._error : undefined;
4479
+ const outgoingError = el._statusFlags & STATUS_ERROR && el._x?._error;
4300
4480
  const wasPending = (el._statusFlags & STATUS_PENDING) !== 0;
4301
- const outgoingPendingSources = wasPending ? el._x?._pendingSources : undefined;
4481
+ const outgoingPendingSources = wasPending && el._x?._pendingSources;
4302
4482
  // Pending SOURCE-hood, captured before the compute clears status: a node
4303
4483
  // whose own flight parked dependents self-registers in _pendingSources
4304
4484
  // (notifyStatus, isSource). If this recompute supersedes that flight and
@@ -4311,9 +4491,15 @@ function recompute(el, create = false) {
4311
4491
  // A19 exc. 2: a `refresh()` re-asks the question already answered — quiet
4312
4492
  // for the verdict — unless the question in flight is a new one, which the
4313
4493
  // re-ask does not launder (a poll during a quiet confirm stays quiet).
4494
+ // Nor a guess's new question whose answer is still held (#4336: an
4495
+ // action's refresh after an outside input change has not committed it).
4314
4496
  const reask =
4315
4497
  (el._flags & REACTIVE_REASK) !== 0 &&
4316
- !(el._statusFlags & STATUS_PENDING && el._x !== null && !el._x._reask);
4498
+ !(
4499
+ (el._statusFlags & STATUS_PENDING || (guessing && el._pendingValue !== NOT_PENDING)) &&
4500
+ el._x !== null &&
4501
+ !el._x._reask
4502
+ );
4317
4503
  const oldcontext = context;
4318
4504
  context = el;
4319
4505
  el._depsTail = null;
@@ -4343,6 +4529,7 @@ function recompute(el, create = false) {
4343
4529
  prevStrictRead = strictRead;
4344
4530
  strictRead = false;
4345
4531
  }
4532
+ const prevCopy = enterCopy();
4346
4533
  tracking = true;
4347
4534
  try {
4348
4535
  if (!true && el._config & CONFIG_SYNC);
@@ -4356,7 +4543,10 @@ function recompute(el, create = false) {
4356
4543
  const selfRegistered = el._x?._inFlight !== prevInFlight;
4357
4544
  value = selfRegistered || !isAsyncResult ? fnResult : handleAsync(el, fnResult);
4358
4545
  if (!selfRegistered && !isAsyncResult) {
4359
- if (el._x !== null) el._x._inFlight = null;
4546
+ if (el._x !== null) {
4547
+ el._x._inFlight = null;
4548
+ if (holdWorkHook) el._config &= ~CONFIG_ASYNC;
4549
+ }
4360
4550
  // A sync (non-object) return is the first real answer; async-shaped
4361
4551
  // results clear inside handleAsync at their own landing points, and a
4362
4552
  // self-registered flight clears when its own handleAsync lands.
@@ -4370,6 +4560,10 @@ function recompute(el, create = false) {
4370
4560
  if (el._statusFlags !== 0 || el._x !== null) clearStatus(el, create);
4371
4561
  } catch (e) {
4372
4562
  const notReady = e instanceof NotReadyError;
4563
+ // An error answers, and so does a loading window's unready read (D-25,
4564
+ // verdict.ts `holdWork`): no flight is its own. A pass left pending
4565
+ // keeps the last answer's. (Kept by the verdict engine alone.)
4566
+ if (holdWorkHook && (!notReady || el._loading)) el._config &= ~CONFIG_ASYNC;
4373
4567
  if (notReady && el._loading) {
4374
4568
  // Loading window with an unready sync dependency: register for the
4375
4569
  // source's settle (the settlePendingSource walk runs off
@@ -4406,7 +4600,10 @@ function recompute(el, create = false) {
4406
4600
  } finally {
4407
4601
  tracking = prevTracking;
4408
4602
  if (laneStageHook) laneRoot = prevMount;
4409
- strictRead = prevStrictRead;
4603
+ {
4604
+ strictRead = prevStrictRead;
4605
+ exitCopy(prevCopy);
4606
+ }
4410
4607
  // Consume the missed-wake latch (#3037, set by insertSubs): a dep write
4411
4608
  // landed beneath this pass on a link it had already validated. The wipe
4412
4609
  // below must not key off DIRTY/CHECK — the read-time pull protocol
@@ -4415,17 +4612,24 @@ function recompute(el, create = false) {
4415
4612
  missedWake = (el._flags & REACTIVE_MISSED_WAKE) !== 0;
4416
4613
  // A mount's staged read joins it — except a nested derivation left
4417
4614
  // pending: no render effect reaches it yet, so it holds nothing and
4418
- // reaches no boundary (Reader, #4203). A reader that does is caught.
4615
+ // reaches no boundary (Reader, #4203), whatever it read — a memo the
4616
+ // mount bore held over the staging included, at any depth (#4340). A
4617
+ // reader that does is caught.
4419
4618
  joined = !!(
4420
4619
  el._flags &
4421
- (mount && (prevLane || isEffect || !(el._statusFlags & STATUS_PENDING))
4422
- ? REACTIVE_JOINED | REACTIVE_STAGED_READ
4620
+ (mount
4621
+ ? prevLane || isEffect || !(el._statusFlags & STATUS_PENDING)
4622
+ ? REACTIVE_JOINED | REACTIVE_STAGED_READ
4623
+ : 0
4423
4624
  : REACTIVE_JOINED)
4424
4625
  );
4425
4626
  // A verdict reader is one for as long as it probes, or reads a memo
4426
4627
  // that does (`read`): a pass that did neither (REACTIVE_PROBED, the same
4427
- // bit) is an ordinary derivation again.
4428
- el._config = (el._config & ~CONFIG_VERDICT) | (el._flags & REACTIVE_PROBED);
4628
+ // bit) is an ordinary derivation again. Its async posture is the
4629
+ // verdict engine's to keep (CONFIG_ASYNC, verdict.ts `holdWork`, D-25).
4630
+ // (Only the verdict engine probes: without it the line folds away.)
4631
+ if (observeFlightHook)
4632
+ el._config = (el._config & ~CONFIG_VERDICT) | (el._flags & REACTIVE_PROBED);
4429
4633
  // REACTIVE_DISPOSED survives too (#3621): the pass may have disposed its
4430
4634
  // own owner (a memo calling its root's `dispose()`, a cleanup doing so
4431
4635
  // #3601/#3606), and `disposeChildren` set the flag on this node
@@ -4465,7 +4669,7 @@ function recompute(el, create = false) {
4465
4669
  clearDeps(el);
4466
4670
  if (el._x !== null) el._x._inFlight = null;
4467
4671
  if (attrHooks !== null) attrHooks.recomputeEnd(el, create, false, false, false, false);
4468
- setPassLane(prevLane);
4672
+ if (laneStageHook) setPassLane(prevLane);
4469
4673
  return;
4470
4674
  }
4471
4675
  // The lane this pass is work of, if any: its seat (the node's, its
@@ -4509,8 +4713,15 @@ function recompute(el, create = false) {
4509
4713
  ? el._x._lane
4510
4714
  : el._value;
4511
4715
  // A throwing comparator is contained inside `isSame` (A3, #2837; #4111).
4716
+ // A pass replacing an error this node staged is a new answer, even the
4717
+ // committed value (D-41: a recovery equal to the last good value is a
4718
+ // change): the slot it publishes to held the error, and the readers that
4719
+ // derived from it — `latest()` throws it — re-derive (#4403).
4512
4720
  const valueChanged =
4513
- (!isEffect && wasUninitialized) || !!el._x?._shownError || !isSame(el, compareValue, value);
4721
+ (!isEffect && wasUninitialized) ||
4722
+ !!el._x?._shownError ||
4723
+ !!replaced ||
4724
+ !isSame(el, compareValue, value);
4514
4725
  // A committed derived change becomes a cause for this node's subscribers,
4515
4726
  // chaining their attribution through this node to the root write.
4516
4727
  if (attrHooks !== null) {
@@ -4607,6 +4818,10 @@ function recompute(el, create = false) {
4607
4818
  el._value = value;
4608
4819
  } else {
4609
4820
  el._pendingValue = value;
4821
+ // D-39: a staged recovery keeps the error it replaces the display
4822
+ // until it commits (`commitPendingNode`), as a write over one does
4823
+ // (`setMemo`).
4824
+ if (outgoingError) el._x._shownError = outgoingError;
4610
4825
  // Born held: in the transaction from birth, so a read of it this
4611
4826
  // tick is a read of its world (the mount-during-a-hold case), not
4612
4827
  // only after the seam parks it.
@@ -4643,7 +4858,7 @@ function recompute(el, create = false) {
4643
4858
  // recoveries ride insertSubs above; a comparator throw re-errored the node
4644
4859
  // (el._x?._error re-set), so this only runs on a genuinely clean recovery.
4645
4860
  if (!valueChanged && !el._x?._error) {
4646
- if (outgoingError !== undefined) settleErroredDependents(el, outgoingError);
4861
+ if (outgoingError) settleErroredDependents(el, outgoingError);
4647
4862
  // Self-registration (this node's own superseded flight) is the #3181
4648
4863
  // sweep's business below — retiring it here too would walk twice.
4649
4864
  if (outgoingPendingSources)
@@ -4715,7 +4930,7 @@ function recompute(el, create = false) {
4715
4930
  // Lane work is the lane's to reveal (its seam), not this flush's commit.
4716
4931
  if (lane === null) queuePendingNode(el);
4717
4932
  }
4718
- setPassLane(prevLane);
4933
+ if (laneStageHook) setPassLane(prevLane);
4719
4934
  // Missed-wake reschedule (see the finally above): values this pass read
4720
4935
  // before the nested commit are stale, so run again now that the heap will
4721
4936
  // accept the node. Equality gates stop same-value landings from cascading,
@@ -4858,7 +5073,7 @@ function ext(el) {
4858
5073
  _q: 0,
4859
5074
  _lane: NOT_PENDING,
4860
5075
  _marks: 0,
4861
- _shownError: undefined
5076
+ _shownError: null
4862
5077
  });
4863
5078
  }
4864
5079
  /** A tracked read of a held node (the reader derives from its transaction's
@@ -4943,6 +5158,9 @@ function frameRead(c, el) {
4943
5158
  // head), and the pass may then read a guess and become the lane's.
4944
5159
  if ((t === flushTransaction && !lane && !verdict) || bornInto(c, t)) return false;
4945
5160
  staleReader(c, t);
5161
+ // Lane work or a verdict reader no longer waits on `el`'s flight (lanes.ts
5162
+ // `laneServed`, A15).
5163
+ if (lane || verdict) laneServedHook(c, el);
4946
5164
  return true;
4947
5165
  }
4948
5166
  /** A node born into transaction `t`: held by it and never committed (A29,
@@ -4962,6 +5180,15 @@ function bornInto(c, t) {
4962
5180
  function creatorPass(owner) {
4963
5181
  return owner !== null && owner._root ? owner._parentComputed : owner;
4964
5182
  }
5183
+ /** What `born` and the passes still running around `p` read — its
5184
+ * creator's, theirs — of the future or a staging (REACTIVE_JOINED |
5185
+ * REACTIVE_STAGED_READ): a pass created by one that did has no screen; its
5186
+ * branch exists only in that world (#4341). */
5187
+ function creatorReads(p, born) {
5188
+ return (p = creatorPass(p._parent))?._flags & REACTIVE_RECOMPUTING_DEPS
5189
+ ? creatorReads(p, born | p._flags)
5190
+ : born & (REACTIVE_JOINED | REACTIVE_STAGED_READ);
5191
+ }
4965
5192
  /** L2 — park the previous pass's frame for this node's commit: the children
4966
5193
  * (already allocated — nothing new) and the `_disposal` list move to the
4967
5194
  * extension, keep reacting from the heap, and die when the commit publishes
@@ -5426,8 +5653,15 @@ function pullComputed(owner, c) {
5426
5653
  // parent check is shallow, might need to be recursive
5427
5654
  if (height >= c._height && owner._parent !== c) c._height = height + 1;
5428
5655
  }
5429
- function read(el) {
5656
+ /**
5657
+ * Dev only: a read's diagnostics — the devtools read hook, the foreign-read
5658
+ * check and the post-await untracked-read check. Every caller guards with
5659
+ * `true`: `read` here, and a store leaf's read that is served without
5660
+ * `read` (`nodeValue`'s first landing), so no read goes unseen in dev.
5661
+ */
5662
+ function devRead(el) {
5430
5663
  DEV.hooks.onRead?.(el);
5664
+ if (!tracking) checkForeignRead();
5431
5665
  if (asyncTailFlights !== 0 && !tracking && untrackDepth === 0)
5432
5666
  checkPostAwaitRead(
5433
5667
  el,
@@ -5437,6 +5671,9 @@ function read(el) {
5437
5671
  (el._statusFlags & (STATUS_PENDING | STATUS_UNINITIALIZED)) ===
5438
5672
  (STATUS_PENDING | STATUS_UNINITIALIZED)
5439
5673
  );
5674
+ }
5675
+ function read(el) {
5676
+ devRead(el);
5440
5677
  let c = context;
5441
5678
  if (c?._root) c = c._parentComputed;
5442
5679
  const computed = el;
@@ -5445,10 +5682,11 @@ function read(el) {
5445
5682
  // as the `||` expression was: a signal's `_statusFlags`/`_height` read as
5446
5683
  // undefined and mask to 0 on every site below.)
5447
5684
  const owner = el;
5448
- if (typeof computed._fn === "function") prepareComputed(el);
5449
5685
  // The verdict windows (`latest`, `isPending`; verdict.ts): one cold path,
5450
- // off this one, present only while a window is open.
5686
+ // off this one, present only while a window is open. It wakes a lazy or
5687
+ // dormant memo itself, outside the window (#4366).
5451
5688
  if (verdict !== null) return verdict(el, c);
5689
+ if (typeof computed._fn === "function") prepareComputed(el);
5452
5690
  if (
5453
5691
  !computed._fn &&
5454
5692
  el._x?._snapshotValue === undefined &&
@@ -5507,7 +5745,8 @@ function read(el) {
5507
5745
  // it probed inline: it reads held values as the screen and shows the
5508
5746
  // verdict now. Not an effect: a render effect is a frame reader
5509
5747
  // already, and a user effect's posture is its own reads'.
5510
- if (!c._type) c._flags |= owner._config & CONFIG_VERDICT;
5748
+ // (A verdict memo exists only with the verdict engine installed.)
5749
+ if (observeFlightHook && !c._type) c._flags |= owner._config & CONFIG_VERDICT;
5511
5750
  }
5512
5751
  }
5513
5752
  // L2, as in the fast block above (tracked or not). A node born into the
@@ -5566,7 +5805,10 @@ function read(el) {
5566
5805
  // — unless the flight committed beneath inputs already on screen
5567
5806
  // (`observeFlight`), or the reader was born into the flight's transaction
5568
5807
  // (#4010): it has no screen, and waits on the flight with its frame.
5808
+ // (A lane, and a verdict reader, exist only with their engines installed —
5809
+ // the verdict engine imports the lane engine: without it the test folds.)
5569
5810
  if (
5811
+ laneStageHook &&
5570
5812
  owner._statusFlags & STATUS_PENDING &&
5571
5813
  !committed &&
5572
5814
  !(el._config & CONFIG_OVERRIDE) &&
@@ -5574,11 +5816,8 @@ function read(el) {
5574
5816
  ) {
5575
5817
  // A reader with no committed value yet sees the flight pending: a
5576
5818
  // boundary a lane mounts shows its fallback (A29's boundary exemption).
5577
- // (A lane, and a verdict reader, exist only with their engines installed.)
5578
- if (laneStageHook && passLane !== null && !(c?._statusFlags & STATUS_UNINITIALIZED))
5579
- committed = true;
5580
- else if (observeFlightHook && c !== null && c._config & CONFIG_VERDICT)
5581
- committed = observeFlightHook(c, owner);
5819
+ if (passLane !== null && !(c?._statusFlags & STATUS_UNINITIALIZED)) committed = true;
5820
+ else if (c !== null && c._config & CONFIG_VERDICT) committed = observeFlightHook(c, owner);
5582
5821
  }
5583
5822
  if (owner._statusFlags & STATUS_PENDING && !committed) {
5584
5823
  // A reader landing on a pending node throws; an untracked read of an
@@ -5626,13 +5865,21 @@ function read(el) {
5626
5865
  if (tracking && owner._time < clock) {
5627
5866
  recompute(owner);
5628
5867
  return read(el);
5629
- } else throw owner._x?._error;
5630
- }
5631
- if (committed) return el._value;
5632
- // D-41: an untracked read reads the screen, and a shown error is on it
5633
- // until the answer that replaces it commits.
5634
- if (!c && el._x?._shownError) throw el._x._shownError;
5635
- const value = serve(el, c);
5868
+ }
5869
+ // D-41: a staged error replacing the shown one is this flush's answer
5870
+ // (`stageError`): a reader served the staging (`serve`) throws it, as it
5871
+ // would read a staged value, and stages its own error with it; one
5872
+ // served the screen throws the shown error. Thrown the shown error, a
5873
+ // reader born this flush published it, and its readers' stale error
5874
+ // committed after the new one (#4404).
5875
+ if (!committed && el._config & CONFIG_STAGED_ERROR) serve(el, c);
5876
+ throw owner._x?._error;
5877
+ }
5878
+ // D-41: an untracked read reads the screen, and so does a frame reader
5879
+ // served the committed value — `serve` hands it that value as it does an
5880
+ // untracked read — a shown error included (`throwShown`).
5881
+ if (committed || !c) throwShown(el);
5882
+ const value = serve(el, committed ? null : c);
5636
5883
  if (
5637
5884
  !c &&
5638
5885
  typeof computed._fn === "function" &&
@@ -5651,6 +5898,12 @@ function read(el) {
5651
5898
  }
5652
5899
  return value;
5653
5900
  }
5901
+ /** D-41: a reader served the screen — an untracked read, a frame reader
5902
+ * served the committed value, a verdict reader (`serve`) — reads a shown
5903
+ * error there until the answer that replaces it commits. */
5904
+ function throwShown(el) {
5905
+ if (el._x?._shownError) throw el._x._shownError;
5906
+ }
5654
5907
  /**
5655
5908
  * Rule 1, the one slow implementation (DESIGN-CONSOLIDATION move 3b, step
5656
5909
  * 6c): the value a reader `c` (null = untracked, no pass) is served from
@@ -5677,12 +5930,13 @@ function serve(el, c) {
5677
5930
  markLateLinker(c);
5678
5931
  return u;
5679
5932
  }
5680
- if (readerSeesCommitted(el, c)) return el._value;
5681
5933
  if (
5682
- observeFlightHook &&
5683
- c._config & CONFIG_VERDICT &&
5684
- !(el._statusFlags & STATUS_UNINITIALIZED) &&
5685
- stagedScreen(c)
5934
+ readerSeesCommitted(el, c) ||
5935
+ (observeFlightHook &&
5936
+ c._config & CONFIG_VERDICT &&
5937
+ !(el._statusFlags & STATUS_UNINITIALIZED) &&
5938
+ stagedScreen(c) &&
5939
+ !throwShown(el))
5686
5940
  )
5687
5941
  return el._value;
5688
5942
  stagedRead(c, el);
@@ -5708,7 +5962,14 @@ function serve(el, c) {
5708
5962
  function stagedRead(c, el) {
5709
5963
  if (!(el?._statusFlags & STATUS_UNINITIALIZED)) {
5710
5964
  c._flags |= REACTIVE_STAGED_READ;
5711
- if (laneSeamsHook && passLane !== null) stagedReaders.push(c);
5965
+ // A verdict reader's too when it is an effect, before the frame has a
5966
+ // transaction (after, `stagedScreen` serves it the screen): the staging
5967
+ // is the frame's read, and the pass re-derives once as a verdict reader
5968
+ // if the flush parks (D-22; D-25: the probe decides the posture in either
5969
+ // read order). A memo's is the frame's derivation either way: a memo is
5970
+ // not a leaf (`frameRead`).
5971
+ if (laneSeamsHook && (passLane !== null || (c._type && c._config & CONFIG_VERDICT)))
5972
+ stagedReaders.push(c);
5712
5973
  }
5713
5974
  }
5714
5975
  /** A10 for a staged node: a verdict reader (the pass entered a window) that
@@ -5722,6 +5983,21 @@ function stagedRead(c, el) {
5722
5983
  * (`bornInto`, #4010): its branch exists only there. */
5723
5984
  function stagedScreen(c) {
5724
5985
  if (flushTransaction === null || bornInto(c, flushTransaction)) return false;
5986
+ // A transaction only a `latest()` read opened holds nothing: the staging
5987
+ // is the frame's read for a pass in the frame's seat, as it is before the
5988
+ // frame has one, and the seam re-derives the pass once only if the flush
5989
+ // parks (D-22, F-10). Lane work sees the screen (D-37). Not a pass with no
5990
+ // value yet: its first answer is nobody's hold (D-6) and would commit the
5991
+ // staging whether or not the frame parks — it reads the screen, and runs
5992
+ // again at the landing.
5993
+ if (
5994
+ observeFlightHook &&
5995
+ flushTransaction === latestOpened &&
5996
+ passLane === null &&
5997
+ c._config & CONFIG_VERDICT &&
5998
+ !(c._statusFlags & STATUS_UNINITIALIZED)
5999
+ )
6000
+ return !stagedReaders.push(c);
5725
6001
  staleReader(c, flushTransaction);
5726
6002
  return true;
5727
6003
  }
@@ -5900,7 +6176,7 @@ function setMemo(el, v) {
5900
6176
  // The error stays the display until the write commits (D-41).
5901
6177
  if (el._statusFlags & STATUS_ERROR) {
5902
6178
  el._x._shownError = el._x._error;
5903
- notifyStatus(el, STATUS_NONE, undefined);
6179
+ notifyStatus(el, STATUS_NONE);
5904
6180
  }
5905
6181
  v = setSignal(el, v);
5906
6182
  // A write replaces an error a pass staged (D-41): it is a value again.
@@ -6014,203 +6290,216 @@ function markRefresh(node) {
6014
6290
  }
6015
6291
 
6016
6292
  export {
6017
- merge as $,
6018
- tracking as A,
6019
- CONFIG_CHILDREN_FORBIDDEN as B,
6293
+ sameLane as $,
6294
+ REACTIVE_STAGED_READ as A,
6295
+ REACTIVE_DISPOSED as B,
6020
6296
  CONFIG_GUESS as C,
6021
- passLane as D,
6022
- EFFECT_RENDER as E,
6023
- setPassLane as F,
6297
+ CONFIG_VERDICT as D,
6298
+ REACTIVE_PROBE_UNANSWERED as E,
6299
+ blocked as F,
6024
6300
  GlobalQueue as G,
6025
- sameLane as H,
6026
- flushTransaction as I,
6027
- REACTIVE_SCREEN_READ as J,
6028
- REACTIVE_FRAME_READ as K,
6029
- attrHooks as L,
6030
- commitPendingNode as M,
6301
+ bornInto as H,
6302
+ REACTIVE_LANE_DIRTY as I,
6303
+ isThenable as J,
6304
+ CONFIG_HELD as K,
6305
+ clock as L,
6306
+ insertSubs as M,
6031
6307
  NOT_PENDING as N,
6032
- REACTIVE_IN_HEAP as O,
6033
- REACTIVE_DIRTY as P,
6034
- REACTIVE_CHECK as Q,
6035
- REACTIVE_DISPOSED as R,
6308
+ laneDirty as O,
6309
+ REACTIVE_LANE_READ as P,
6310
+ flushTransaction as Q,
6311
+ REACTIVE_DIRTY as R,
6036
6312
  STATUS_UNINITIALIZED as S,
6037
- STATUS_PENDING as T,
6038
- CONFIG_INPUTS_PUBLISHED as U,
6039
- releaseQueues as V,
6040
- CONFIG_SLOT_NODE as W,
6041
- holdNode as X,
6042
- joinFuture as Y,
6043
- reruns as Z,
6044
- CONFIG_STAGED as _,
6313
+ REACTIVE_RECOMPUTING_DEPS as T,
6314
+ queuePendingNode as U,
6315
+ STATUS_ERROR as V,
6316
+ tracking as W,
6317
+ CONFIG_CHILDREN_FORBIDDEN as X,
6318
+ passLane as Y,
6319
+ EFFECT_RENDER as Z,
6320
+ setPassLane as _,
6045
6321
  NotReadyError as a,
6046
- resetUnhandledAsync as a$,
6047
- REACTIVE_JOINED as a0,
6048
- REACTIVE_STAGED_READ as a1,
6049
- stagedRead as a2,
6050
- setVerdict as a3,
6051
- context as a4,
6052
- installVerdictHooks as a5,
6053
- REACTIVE_PROBED as a6,
6054
- verdict as a7,
6055
- link as a8,
6056
- pullComputed as a9,
6057
- signal as aA,
6058
- setSignal as aB,
6059
- cleanup as aC,
6060
- read as aD,
6061
- DEV as aE,
6062
- CONFIG_TRANSPARENT as aF,
6063
- NoOwnerError as aG,
6064
- ContextNotFoundError as aH,
6065
- createEffectNode as aI,
6066
- recompute as aJ,
6067
- EFFECT_USER as aK,
6068
- reportDiagnostic as aL,
6069
- computed as aM,
6070
- staleValues as aN,
6071
- CONFIG_AUTO_DISPOSE as aO,
6072
- EFFECT_TRACKED as aP,
6073
- heldRunHook as aQ,
6074
- unwrapStatusError as aR,
6075
- setStrictRead as aS,
6076
- queueEvent as aT,
6077
- StatusError as aU,
6078
- trimStaleDeps as aV,
6079
- haltReactivityAt as aW,
6080
- strictReadEvent as aX,
6081
- setTrackedQueueCallback as aY,
6082
- setEffectStatusNotify as aZ,
6083
- _hitUnhandledAsync as a_,
6084
- strictRead as aa,
6085
- strictReadActive as ab,
6086
- warnStrictReadUntracked as ac,
6087
- staleReader as ad,
6088
- globalQueue as ae,
6089
- joinPass as af,
6090
- markLateLinker as ag,
6091
- spectating as ah,
6092
- unflushedValue as ai,
6093
- CONFIG_STAGED_ERROR as aj,
6094
- stagedReaders as ak,
6095
- bornInto as al,
6096
- enterAction as am,
6097
- setEffectCallback as an,
6098
- exitAction as ao,
6099
- isThenable as ap,
6100
- actionDepth as aq,
6101
- flush as ar,
6102
- setQuestion as as,
6103
- emitDiagnostic as at,
6104
- enterCallback as au,
6105
- exitCallback as av,
6106
- getOwner as aw,
6107
- inEffectCallback as ax,
6108
- effectCallbackEvent as ay,
6109
- setExternalHooks as az,
6322
+ heldRunHook as a$,
6323
+ REACTIVE_SCREEN_READ as a0,
6324
+ REACTIVE_FRAME_READ as a1,
6325
+ attrHooks as a2,
6326
+ CONFIG_INPUTS_PUBLISHED as a3,
6327
+ releaseQueues as a4,
6328
+ holdNode as a5,
6329
+ reruns as a6,
6330
+ CONFIG_STAGED as a7,
6331
+ merge as a8,
6332
+ creatorReads as a9,
6333
+ exitAction as aA,
6334
+ actionDepth as aB,
6335
+ flush as aC,
6336
+ setQuestion as aD,
6337
+ emitDiagnostic as aE,
6338
+ enterCallback as aF,
6339
+ exitCallback as aG,
6340
+ getOwner as aH,
6341
+ inEffectCallback as aI,
6342
+ effectCallbackEvent as aJ,
6343
+ setExternalHooks as aK,
6344
+ signal as aL,
6345
+ setSignal as aM,
6346
+ cleanup as aN,
6347
+ read as aO,
6348
+ DEV as aP,
6349
+ CONFIG_TRANSPARENT as aQ,
6350
+ NoOwnerError as aR,
6351
+ ContextNotFoundError as aS,
6352
+ createEffectNode as aT,
6353
+ recompute as aU,
6354
+ EFFECT_USER as aV,
6355
+ reportDiagnostic as aW,
6356
+ computed as aX,
6357
+ staleValues as aY,
6358
+ CONFIG_AUTO_DISPOSE as aZ,
6359
+ EFFECT_TRACKED as a_,
6360
+ REACTIVE_JOINED as aa,
6361
+ stagedRead as ab,
6362
+ setVerdict as ac,
6363
+ context as ad,
6364
+ installVerdictHooks as ae,
6365
+ creatorPass as af,
6366
+ CONFIG_ASYNC as ag,
6367
+ REACTIVE_PROBED as ah,
6368
+ REACTIVE_WINDOW as ai,
6369
+ verdict as aj,
6370
+ prepareComputed as ak,
6371
+ link as al,
6372
+ pullComputed as am,
6373
+ strictRead as an,
6374
+ strictReadActive as ao,
6375
+ warnStrictReadUntracked as ap,
6376
+ globalQueue as aq,
6377
+ joinFuture as ar,
6378
+ joinPass as as,
6379
+ markLateLinker as at,
6380
+ spectating as au,
6381
+ unflushedValue as av,
6382
+ CONFIG_STAGED_ERROR as aw,
6383
+ REACTIVE_REASK as ax,
6384
+ enterAction as ay,
6385
+ setEffectCallback as az,
6110
6386
  CONFIG_AUTHORITATIVE as b,
6111
- peekNextChildId as b$,
6112
- ROOT_ERROR_HOOK as b0,
6113
- registerGraph as b1,
6114
- runWithOwner as b2,
6115
- setMemo as b3,
6116
- $REFRESH as b4,
6117
- markRefresh as b5,
6118
- createRoot as b6,
6119
- getObserver as b7,
6120
- dispose as b8,
6121
- TimeoutError as b9,
6122
- handleAsync as bA,
6123
- addPendingSource as bB,
6124
- propagateStatus as bC,
6125
- forEachDependent as bD,
6126
- settlePendingSource as bE,
6127
- joinPassAt as bF,
6128
- notifyEpoch as bG,
6129
- SUPPORTS_PROXY as bH,
6130
- CONFIG_KEY_BAG as bI,
6131
- releaseMarksHook as bJ,
6132
- installMarkHooks as bK,
6133
- CONFIG_LAGS as bL,
6134
- REACTIVE_REASK as bM,
6135
- onSupersede as bN,
6136
- haltReactivity as bO,
6137
- CONFIG_FRAME as bP,
6138
- heldUnder as bQ,
6139
- unhold as bR,
6140
- laneStageHook as bS,
6141
- REACTIVE_ZOMBIE as bT,
6142
- installBoundaryHooks as bU,
6143
- CAUGHT_REARMED as bV,
6144
- OBSERVE as bW,
6145
- clearSnapshots as bX,
6146
- enforceLoadingBoundary as bY,
6147
- getNextChildId as bZ,
6148
- markSnapshotScope as b_,
6149
- untrack as ba,
6150
- dirtyQueue as bb,
6151
- CONFIG_REDERIVE as bc,
6152
- passTx as bd,
6153
- CONFIG_FRESH_READ as be,
6154
- createOwner as bf,
6155
- writeHook as bg,
6156
- REACTIVE_WRITE_IN_OWNED_SCOPE_SIGNAL_MESSAGE as bh,
6157
- ownedScopeWriteMessage as bi,
6158
- isEqual as bj,
6159
- CONFIG_MANUAL_WRITE as bk,
6160
- snapshotCaptureActive as bl,
6161
- captureWriteSnapshot as bm,
6162
- notePromotedWrite as bn,
6163
- slotSignal as bo,
6164
- stagedScreen as bp,
6165
- CONFIG_PROMOTED as bq,
6166
- setSlotUnobserved as br,
6167
- installStoreHooks as bs,
6168
- joinPassTx as bt,
6169
- asyncTailFlights as bu,
6170
- untrackDepth as bv,
6171
- checkPostAwaitRead as bw,
6172
- disposalDepth as bx,
6173
- insertIntoHeap as by,
6174
- isDisposed as bz,
6387
+ heldUnder as b$,
6388
+ unwrapStatusError as b0,
6389
+ setStrictRead as b1,
6390
+ queueEvent as b2,
6391
+ StatusError as b3,
6392
+ trimStaleDeps as b4,
6393
+ haltReactivityAt as b5,
6394
+ strictReadEvent as b6,
6395
+ setTrackedQueueCallback as b7,
6396
+ setEffectStatusNotify as b8,
6397
+ _hitUnhandledAsync as b9,
6398
+ slotSignal as bA,
6399
+ stagedScreen as bB,
6400
+ devRead as bC,
6401
+ CONFIG_PROMOTED as bD,
6402
+ setSlotUnobserved as bE,
6403
+ installStoreHooks as bF,
6404
+ joinPassTx as bG,
6405
+ asyncTailFlights as bH,
6406
+ untrackDepth as bI,
6407
+ checkPostAwaitRead as bJ,
6408
+ disposalDepth as bK,
6409
+ insertIntoHeap as bL,
6410
+ isDisposed as bM,
6411
+ addPendingSource as bN,
6412
+ propagateStatus as bO,
6413
+ forEachDependent as bP,
6414
+ settlePendingSource as bQ,
6415
+ joinPassAt as bR,
6416
+ notifyEpoch as bS,
6417
+ SUPPORTS_PROXY as bT,
6418
+ CONFIG_KEY_BAG as bU,
6419
+ releaseMarksHook as bV,
6420
+ installMarkHooks as bW,
6421
+ CONFIG_LAGS as bX,
6422
+ onSupersede as bY,
6423
+ haltReactivity as bZ,
6424
+ CONFIG_FRAME as b_,
6425
+ resetUnhandledAsync as ba,
6426
+ ROOT_ERROR_HOOK as bb,
6427
+ registerGraph as bc,
6428
+ runWithOwner as bd,
6429
+ setMemo as be,
6430
+ $REFRESH as bf,
6431
+ markRefresh as bg,
6432
+ createRoot as bh,
6433
+ getObserver as bi,
6434
+ dispose as bj,
6435
+ TimeoutError as bk,
6436
+ untrack as bl,
6437
+ dirtyQueue as bm,
6438
+ CONFIG_REDERIVE as bn,
6439
+ passTx as bo,
6440
+ CONFIG_FRESH_READ as bp,
6441
+ createOwner as bq,
6442
+ writeHook as br,
6443
+ REACTIVE_WRITE_IN_OWNED_SCOPE_SIGNAL_MESSAGE as bs,
6444
+ ownedScopeWriteMessage as bt,
6445
+ isEqual as bu,
6446
+ CONFIG_MANUAL_WRITE as bv,
6447
+ checkForeignRead as bw,
6448
+ snapshotCaptureActive as bx,
6449
+ captureWriteSnapshot as by,
6450
+ notePromotedWrite as bz,
6175
6451
  CONFIG_OVERRIDE as c,
6176
- releaseSnapshotScope as c0,
6177
- resetErrorHalt as c1,
6178
- setConsoleFooter as c2,
6179
- setSnapshotCapture as c3,
6180
- setWriteHook as c4,
6181
- setAttributionHooks as c5,
6182
- liveRootOwners as c6,
6183
- ownerPath as c7,
6184
- isSuppressed as c8,
6185
- blockersOf as c9,
6186
- CONFIG_PLUMBING as ca,
6187
- anyExcluded as cb,
6188
- isExcluded as cc,
6189
- CONFIG_WIDE as cd,
6190
- GRAPH_SIZE_WARN_AT as ce,
6191
- noteFanOut as cf,
6192
- CONFIG_SUPERSEDES as cg,
6193
- newTransaction as d,
6452
+ unhold as c0,
6453
+ laneStageHook as c1,
6454
+ REACTIVE_ZOMBIE as c2,
6455
+ installBoundaryHooks as c3,
6456
+ CAUGHT_REARMED as c4,
6457
+ OBSERVE as c5,
6458
+ checkForeignOwnerless as c6,
6459
+ clearSnapshots as c7,
6460
+ enforceLoadingBoundary as c8,
6461
+ getNextChildId as c9,
6462
+ markSnapshotScope as ca,
6463
+ peekNextChildId as cb,
6464
+ releaseSnapshotScope as cc,
6465
+ resetErrorHalt as cd,
6466
+ runAsSolidCopy as ce,
6467
+ setConsoleFooter as cf,
6468
+ setSnapshotCapture as cg,
6469
+ setWriteHook as ch,
6470
+ setAttributionHooks as ci,
6471
+ liveRootOwners as cj,
6472
+ ownerPath as ck,
6473
+ isSuppressed as cl,
6474
+ blockersOf as cm,
6475
+ CONFIG_PLUMBING as cn,
6476
+ anyExcluded as co,
6477
+ isExcluded as cp,
6478
+ CONFIG_WIDE as cq,
6479
+ GRAPH_SIZE_WARN_AT as cr,
6480
+ noteFanOut as cs,
6481
+ CONFIG_SUPERSEDES as ct,
6482
+ armGuessing as d,
6194
6483
  enqueueSub as e,
6195
- CONFIG_VERDICT as f,
6196
- REACTIVE_PROBE_UNANSWERED as g,
6197
- blocked as h,
6484
+ newTransaction as f,
6485
+ staleReader as g,
6486
+ removePendingSource as h,
6198
6487
  installLaneHooks as i,
6199
- REACTIVE_LANE_DIRTY as j,
6200
- CONFIG_HELD as k,
6201
- clock as l,
6202
- insertSubs as m,
6488
+ REACTIVE_CHECK as j,
6489
+ REACTIVE_IN_HEAP as k,
6490
+ list as l,
6491
+ releaseFlightTeardown as m,
6203
6492
  nextQuestion as n,
6204
- ownFlights as o,
6205
- laneDirty as p,
6493
+ handleAsync as o,
6494
+ notifyStatus as p,
6206
6495
  question as q,
6207
6496
  resolveTx as r,
6208
6497
  schedule as s,
6209
- txOf as t,
6210
- REACTIVE_LANE_READ as u,
6498
+ STATUS_PENDING as t,
6499
+ CONFIG_SLOT_NODE as u,
6211
6500
  ext as v,
6212
- REACTIVE_RECOMPUTING_DEPS as w,
6213
- queuePendingNode as x,
6214
- list as y,
6215
- STATUS_ERROR as z
6501
+ txOf as w,
6502
+ commitPendingNode as x,
6503
+ ownFlights as y,
6504
+ stagedReaders as z
6216
6505
  };