Repository navigation
fix(signals): an async memo over a verdict memo asks each question once - #4397
Conversation
A memo that reads a verdict memo is a verdict reader (F-6, #4050), and propagateStatus re-derived every verdict reader when its source went pending. Re-derived, an async one was served the committed value it had already derived from (A10) and asked that question again; that flight held the verdict lane, and the source's held landing re-ran it as lane work onto the same committed value, a third ask. Only the landing's re-derive asked the new question. Now an async memo that is a verdict reader only by reading a verdict memo (it opened no isPending/latest window of its own: REACTIVE_WINDOW, set per pass by markVerdictReader) drops CONFIG_VERDICT when that memo goes pending and takes the plain pending mark: it is pending through the flight (F-9) and the hold's work until it lands, reading plainly (D-25, #3884), and asks once at the landing. Sync readers re-derive as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2
🦋 Changeset detectedLatest commit: afec0a7 The changes in this PR will be included in the next version bump. This PR includes changesets to release 15 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Size (brotli, eager entry chunk)
|
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2
Coverage Report for CI Build 40844824351Coverage remained the same at 78.516%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsNo coverage regressions found. Coverage Stats
💛 - Coveralls |
Merging this PR will improve performance by 9.78%2 warnings
Performance Changes
Tip Curious why performance improved? Comment Comparing Footnotes
|
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2
…o verdict role #4397 review: with two verdict sources (`m1 = ask(1 + m0() + m0b())` over `m0 = ask(1 + latest(s) + s())` and `m0b = ask(10 * (latest(t) + t()))`), the grouped `[isPending(m1), m1()]` reader asked `m1:5` a second time on stale inputs after both sources landed. m1 lost CONFIG_VERDICT when its first source went pending, but a pass that threw NotReady at that source's read picked it back up (F-6, `read`). The settle walk then re-ran m1 as a verdict reader, and the committed values a hold had moved past were served to it (A10). The demotion is now per pass, in `recompute`. An async memo (new CONFIG_ASYNC: its last answering pass registered a flight) whose pass opened no isPending/latest window of its own (REACTIVE_WINDOW) does not become a verdict reader by reading a verdict memo. It waits on its sources like any derivation (F-9), reads them plainly (D-25), and asks each question once. The side-effecting clear in `propagateStatus` is gone, since that path no longer sees such a memo as a verdict reader. Pinned: two verdict memos into one async memo, grouped and split, with no question asked twice and equal final screens. A `test.fails` pin records that the grouped shape still skips the t lane's m1:15/m1:16 asks that split makes: whether a later write merges into an open transaction or opens its own lane depends on grouping (#4398). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2
|
The size Generated by Claude Code |
|
The CodSpeed regression on Generated by Claude Code |
A reader that groups isPending with the value over a held write (`[isPending(m1), m1()]`) never showed m1's lane answer, though a plain value reader did. Two things kept it out: - A pass served a held node's committed value (frameRead, a stale reader of the hold) still kept the node's propagated pending sources, so when it went pending on another source it never woke when that source settled. frameRead now drops what the served node propagated onto it (A15): the landing re-derives it. - A lane's reveal (reruns on a lane) cleared REACTIVE_FRAME_READ, the mark the held transaction's landing needed to re-run the reader. Only the transaction's own reruns clear it now. CONFIG_ASYNC is worked out once, at the end of each pass, from the pass's `_inFlight` (registered a flight: async; answered sync: not; pending or errored without a flight: the last answer's), so a plain sync recompute does no extra work and handleAsync no longer touches it. refresh(m0) mid-hold now shows the lane's answer for the refreshed question in every reader shape. Pins for the held write, an equal landing, and refresh after and mid-hold cover the value, pair and split readers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2
…2' into claude/project-thread-x1ore2-4365 The held-shapes pins from #4397 ask the same questions here, each once, and show the same values; with `latest()`'s pass the frame's (R5), the held transaction asks `m0:5` at the write, before the lane's `m0:4`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2
…-x1ore2-lane-reask Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2
…r (D-25) Review of #4397: the strict rule (an async memo with no `latest`/`isPending` window of its own took no verdict role at all) overrode D-25 ("one that reads a verdict reader's value is ... a verdict reader itself"; "a memo computes under its own posture"). It caused two regressions against next: 1. Memo-extracted `latest()` (`v = createMemo(() => latest(s))`, `m1` async over `v()` and `s()`): m1's pass at the write read the transaction's staging and asked m1:220, the lane re-derived it (m1:120), and the landing asked m1:220 again. It now asks m1:120 then m1:220, once each, as the inline form does (D-22, F-9 exc. 2), showing v120 then v220 (F-6). 2. A reader disposed and remounted while the lane answer was in flight: `[isPending(m1), m1()]` in one effect showed true,4 -> true,5 -> false,6 while `m1()` alone and split showed 4 -> 6. All shapes now show the screen, then the landing (F-6: grouped holds like a lone read; D-26). The strict rule is gone. In its place, D-25's "a node whose fetch a hold is waiting on is that hold's work" (#3884), kept by the verdict engine (verdict.ts `holdWork`, behind `holdWorkHook`, like `observeFlightHook`): - A verdict source going pending does not re-derive an async memo with no window of its own: none of its reads changed, and re-derived it was served the committed value it had already derived from and asked that question again (A10). It waits on the source like any derivation (F-9). Except when its own flight is up outside a lane on a new question of the source's: that re-run supersedes a flight asked of a value the source has moved past (a `refresh()` re-ask is the same question). - A held re-run of such a memo reads plainly, as a pending one already did (#3884): the write that re-ran it is its hold's. - Its async posture (CONFIG_ASYNC) is kept at each pass end in verdict.ts, not computed in core. Kept from the PR, as review asked: the frame read's drop of a served flight's pending (A15; now in lanes.ts `laneServed`, behind a hook, run only for lane work and verdict readers, with no closure), and `reruns` leaving the stale-reader mark to the transaction at a lane's reveal. Pins (tests/verdict-memo-flight-no-reask.test.ts): the memo-extracted and inline forms ask m1:120, m1:220 once and show v110, v120, v220 in every reader shape (D-22, D-25, F-6); a reader remounted mid-flight shows the screen, then the landing, grouped as alone or split (F-6, D-26). Both fail on the previous head. Size: the floors are +14 B minified over next (was +126). The verdict scenarios carry the rule: + isPending/latest +339 B. Ledger lines and the frozen live-server-components floor are re-recorded to the measured sizes (Size-Exception: floor-caps.json 115,423 -> 115,646 B minified, measured 38,197 B brotli; the maintainer relaxed the size rule for this fix). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2
CI ran the "remounted mid-flight, then a write" pin slower than local runs: the landing's `m1:6` was asked before the remount in the grouped run and after the write in the lone run, so comparing one shape's asks against another's compared two timings, not two shapes. Run at a later remount locally, every shape asks `m1:6` alike, and none diverges. The pin now states what holds at any timing, per shape: no question asked twice (F-9), the lane's `m1:5` never shown to a reader mounted mid-hold (D-26), and the grouped `[isPending(m1), m1()]` ending on the landing like the lone read (F-6). The no-write case still fails on the strict rule's head, where the grouped remount showed `true,5`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2
…ad-x1ore2-lane-reask RULES-INDEX.md regenerated with scripts/rules-index.mjs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2
CI's coverage run (slower than a plain run) let m1's 10 ms lane flight land before the pin's 2 ms sleep ended, so the later write came after the landing and the run asked m1:220 where the pin expects m1:125. The pin compared two timings, not two behaviours. m1's flights now land only when the test lands them, so the later write always comes while the write's flights are up. The pin's expectations are unchanged (D-25, D-22, F-6, F-9), and it still fails on ee0b535's source in all three reader shapes (the m1:220, m1:120, m1:220 triple ask). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2
|
…rk (D-25) Review of #4397: REACTIVE_WINDOW was wiped at the pass tail, and both of holdWork's consumers (the pass head and propagateStatus) run between passes, so `own` was CONFIG_ASYNC alone. The rule hit every async verdict memo, including one that calls latest()/isPending() itself, and changed screens against next on the canonical F-6 shape: an async memo over `${isPending(m0)}:${latest(m0)}` never showed the lane's true:2, and one over latest(m0) read through [isPending(m1), m1()] skipped l1 and showed true,l1. CONFIG_ASYNC is now set at the pass end, while the pass's REACTIVE_WINDOW is still up, only for a pass that registered a flight and opened no window; a pass that opened one clears it. A memo that asks is a verdict reader whoever dirties it (D-25, ruled 2026-10-28), unchanged from next. The held re-run arm applies only to memos that never asked; removing it reasks m1:5/m1:104/m1:15 in five pins, so it stays, documented against D-25. Pins: the two own-window shapes, as next shows them (D-25, F-6). The pin file takes the hand-landed flights from the split prepared for review, so no pin races a timer. Ledger re-records dropped: the size ledger is re-recorded once on next after the batch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2
CONFIG_ASYNC is now set where a flight registers (handleAsync) and cleared by a verdict window (markVerdictReader) or a sync answer, instead of a holdWork call at every pass's end. The pass-head check is inline. holdWork only answers propagateStatus. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2
A pass that throws a real error answers with no flight of its own, so a verdict source going pending re-derives the memo instead of holding it on its pre-error value (F-1). Pinned, held and unheld. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2
…-x1ore2-lane-reask # Conflicts: # packages/signals/docs/RULES-INDEX.md # packages/signals/src/core/core.ts
Merging #4397 into #4393 asked three questions twice: - An async memo over `latest()` of a flight (the own-window pins) took R5's frame pass and then a verdict-lane pass that asked the same question again. `latest` of a flight serves the committed value in either world (D-22), so the seam has nothing to decide. The reader joins the verdict lane at once, as a plain read of a flight makes it (`observeFlight`). `latest` of a staging stays the seam's (#4365). - An async memo over a `latest()` memo (D-22 extracted): R5 parked only the reader's own flight. The memo's frame flight was superseded by its lane pass, and the landing asked `m1:220` again. The seam now parks the frame flights of a sync reader's async readers held by the frame, and the landing adopts them as it does inline (F-9). The remount and extracted pins take R5's order: the frame's question comes first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2
Conflicts resolved as follows: - recompute's pass-head CONFIG_VERDICT clear takes next's form: laneStageHook && !isEffect && (pending || ASYNC|HELD). - laneSeams takes next's signature, (t), which reads stagedReaders directly. It also takes next's `l` for an unheld staged reader and its bornInto/blocked(l) arm. It keeps #4393's heldBy lane arm, its R5 parking and adoptParked, and the hook. - provisionalVerdict keeps #4393's R5 shape: latest() opens no transaction. #4401's joinFuture/openedByLatest path breaks 25 R5 pins, so `latestOpened` is now never set. - verdictValue keeps #4393's NOT_PENDING display path for a first load (#4363). #4401's first-load throw is not taken. - verdict-memo-flight-no-reask keeps #4393's R5 expectations and adds next's "error, then refresh(m0)" pins. - The async-gen baseline and RULES-INDEX are taken from next and then regenerated. Still failing: latest-memo-inline-parity "inline, plain-first" and "inline, latest-first" (and the web spec "inline"), which come from R5 against #4401, and latest-two-readers "beside". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2
…at carve (F-6, D-26) With #4397 merged, m1 is a verdict reader between its flights (D-25) and m0's lane pass re-derives it into the s lane early, so the t transaction lands while m1:1122 is in flight. Telling that pass apart as the hold's work needs a per-pass check; it goes to the carve. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2
…undefined (#4362, #4363, #4376) (#4383) * fix(signals): latest() of a memo born in a lane throws NotReady instead of undefined (#4362, #4363, #4376) A memo whose compute reads latest(s) is re-asked as the write's verdict lane work, so it becomes a lane derivation. While its first pass is in flight, nothing is in the lane, and laneValueOf/display fell back to the committed value, which an uninitialized node does not have: - latest(m)/isPending(m) served undefined and probed it pending (#4363); - the lane's end committed that undefined as the node's answer and cleared STATUS_UNINITIALIZED with the flight still up, which tripped the flush loop guard for [latest(m), isPending(m)] (#4362); - in hydration, m2 = fetchC(1 + latest(m1)) asked fetchC(NaN) instead of adopting the streamed answer (#4376). laneValueOf/display now return NOT_PENDING for such a node, so every read throws NotReady (A7; A19 exc. 1) and the lane's end leaves the node in flight. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * fix(signals): keep a verdict reader's lane routing and own-error recovery (async-gen #119, #1238) - verdictValue: read the lane value before routing the reader into the verdict lane, so a node born in the lane with nothing in it yet is read as any uninitialized node (NotReady) without moving its reader (#1238). - reruns: a stale reader whose error is its own (thrown by its pass over the committed value it was served) re-derives at the landing; only an error from an errored source is skipped (#3945). With #4363 no longer committing `undefined`, async-gen case 119 reached this stuck error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * chore: re-measure sizes over next @ 40cde2c, record async-gen improvements - scripts/size: ledger lines for `signals: + isPending/latest`, the compiled live page and live + router (+46/+46/+47 B minified over next); this PR's store-app line re-measured (97,019 B). - async-gen baseline: 15 recorded failures no longer reproduce (10 undefined-value, 4 final-screen, 1 unhandled-rejection). - RULES-INDEX regenerated; changeset notes the error recovery. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * fix(signals): re-derive a self-errored stale reader at the lane's end, not in reruns The verdict reader's own error from a served committed value (async-gen #119) now re-derives from `dissolveLane` (lane engine only) instead of a widened `reruns` condition, so the floors pay nothing (0 B; the lane scenarios +20 B over the previous form). `reruns` is next's again. Ledger re-measured; async-gen baseline unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * fix(signals): the lane's end re-derives a render effect's own stale error too The re-enqueue of a self-errored stale reader at the lane's end sat inside the non-effect branch, so a render effect whose display-ahead pass threw over a held write's screen value kept its error after the landing (found by the #4365 thread). It now applies to both. Byte-neutral (0 B minified in every scenario); floors 0 B. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * fix(signals): an async memo over a verdict memo asks each question once A memo that reads a verdict memo is a verdict reader (F-6, #4050), and propagateStatus re-derived every verdict reader when its source went pending. Re-derived, an async one was served the committed value it had already derived from (A10) and asked that question again; that flight held the verdict lane, and the source's held landing re-ran it as lane work onto the same committed value, a third ask. Only the landing's re-derive asked the new question. Now an async memo that is a verdict reader only by reading a verdict memo (it opened no isPending/latest window of its own: REACTIVE_WINDOW, set per pass by markVerdictReader) drops CONFIG_VERDICT when that memo goes pending and takes the plain pending mark: it is pending through the flight (F-9) and the hold's work until it lands, reading plainly (D-25, #3884), and asks once at the landing. Sync readers re-derive as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * test(signals): with #4383, isPending of the unresolved m1 holds its Loading, so the unheld latest case asks like the held one Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * docs(signals): regenerate RULES-INDEX Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * fix(signals): an async memo with no verdict window of its own takes no verdict role #4397 review: with two verdict sources (`m1 = ask(1 + m0() + m0b())` over `m0 = ask(1 + latest(s) + s())` and `m0b = ask(10 * (latest(t) + t()))`), the grouped `[isPending(m1), m1()]` reader asked `m1:5` a second time on stale inputs after both sources landed. m1 lost CONFIG_VERDICT when its first source went pending, but a pass that threw NotReady at that source's read picked it back up (F-6, `read`). The settle walk then re-ran m1 as a verdict reader, and the committed values a hold had moved past were served to it (A10). The demotion is now per pass, in `recompute`. An async memo (new CONFIG_ASYNC: its last answering pass registered a flight) whose pass opened no isPending/latest window of its own (REACTIVE_WINDOW) does not become a verdict reader by reading a verdict memo. It waits on its sources like any derivation (F-9), reads them plainly (D-25), and asks each question once. The side-effecting clear in `propagateStatus` is gone, since that path no longer sees such a memo as a verdict reader. Pinned: two verdict memos into one async memo, grouped and split, with no question asked twice and equal final screens. A `test.fails` pin records that the grouped shape still skips the t lane's m1:15/m1:16 asks that split makes: whether a later write merges into an open transaction or opens its own lane depends on grouping (#4398). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * fix(signals): a grouped reader over a held write shows the lane's answer A reader that groups isPending with the value over a held write (`[isPending(m1), m1()]`) never showed m1's lane answer, though a plain value reader did. Two things kept it out: - A pass served a held node's committed value (frameRead, a stale reader of the hold) still kept the node's propagated pending sources, so when it went pending on another source it never woke when that source settled. frameRead now drops what the served node propagated onto it (A15): the landing re-derives it. - A lane's reveal (reruns on a lane) cleared REACTIVE_FRAME_READ, the mark the held transaction's landing needed to re-run the reader. Only the transaction's own reruns clear it now. CONFIG_ASYNC is worked out once, at the end of each pass, from the pass's `_inFlight` (registered a flight: async; answered sync: not; pending or errored without a flight: the last answer's), so a plain sync recompute does no extra work and handleAsync no longer touches it. refresh(m0) mid-hold now shows the lane's answer for the refreshed question in every reader shape. Pins for the held write, an equal landing, and refresh after and mid-hold cover the value, pair and split readers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * test(signals): name frameRead in the held-shapes pin comment Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * chore(size): re-record this PR's four sizes over next @ 7ebd4a3 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * fix(signals): an async memo over a verdict memo stays a verdict reader (D-25) Review of #4397: the strict rule (an async memo with no `latest`/`isPending` window of its own took no verdict role at all) overrode D-25 ("one that reads a verdict reader's value is ... a verdict reader itself"; "a memo computes under its own posture"). It caused two regressions against next: 1. Memo-extracted `latest()` (`v = createMemo(() => latest(s))`, `m1` async over `v()` and `s()`): m1's pass at the write read the transaction's staging and asked m1:220, the lane re-derived it (m1:120), and the landing asked m1:220 again. It now asks m1:120 then m1:220, once each, as the inline form does (D-22, F-9 exc. 2), showing v120 then v220 (F-6). 2. A reader disposed and remounted while the lane answer was in flight: `[isPending(m1), m1()]` in one effect showed true,4 -> true,5 -> false,6 while `m1()` alone and split showed 4 -> 6. All shapes now show the screen, then the landing (F-6: grouped holds like a lone read; D-26). The strict rule is gone. In its place, D-25's "a node whose fetch a hold is waiting on is that hold's work" (#3884), kept by the verdict engine (verdict.ts `holdWork`, behind `holdWorkHook`, like `observeFlightHook`): - A verdict source going pending does not re-derive an async memo with no window of its own: none of its reads changed, and re-derived it was served the committed value it had already derived from and asked that question again (A10). It waits on the source like any derivation (F-9). Except when its own flight is up outside a lane on a new question of the source's: that re-run supersedes a flight asked of a value the source has moved past (a `refresh()` re-ask is the same question). - A held re-run of such a memo reads plainly, as a pending one already did (#3884): the write that re-ran it is its hold's. - Its async posture (CONFIG_ASYNC) is kept at each pass end in verdict.ts, not computed in core. Kept from the PR, as review asked: the frame read's drop of a served flight's pending (A15; now in lanes.ts `laneServed`, behind a hook, run only for lane work and verdict readers, with no closure), and `reruns` leaving the stale-reader mark to the transaction at a lane's reveal. Pins (tests/verdict-memo-flight-no-reask.test.ts): the memo-extracted and inline forms ask m1:120, m1:220 once and show v110, v120, v220 in every reader shape (D-22, D-25, F-6); a reader remounted mid-flight shows the screen, then the landing, grouped as alone or split (F-6, D-26). Both fail on the previous head. Size: the floors are +14 B minified over next (was +126). The verdict scenarios carry the rule: + isPending/latest +339 B. Ledger lines and the frozen live-server-components floor are re-recorded to the measured sizes (Size-Exception: floor-caps.json 115,423 -> 115,646 B minified, measured 38,197 B brotli; the maintainer relaxed the size rule for this fix). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * test(signals): pin the remount shapes on what holds whatever the timing CI ran the "remounted mid-flight, then a write" pin slower than local runs: the landing's `m1:6` was asked before the remount in the grouped run and after the write in the lone run, so comparing one shape's asks against another's compared two timings, not two shapes. Run at a later remount locally, every shape asks `m1:6` alike, and none diverges. The pin now states what holds at any timing, per shape: no question asked twice (F-9), the lane's `m1:5` never shown to a reader mounted mid-hold (D-26), and the grouped `[isPending(m1), m1()]` ending on the landing like the lone read (F-6). The no-write case still fails on the strict rule's head, where the grouped remount showed `true,5`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * test(signals): land the extracted pin's flights by hand, not by timer CI's coverage run (slower than a plain run) let m1's 10 ms lane flight land before the pin's 2 ms sleep ended, so the later write came after the landing and the run asked m1:220 where the pin expects m1:125. The pin compared two timings, not two behaviours. m1's flights now land only when the test lands them, so the later write always comes while the write's flights are up. The pin's expectations are unchanged (D-25, D-22, F-6, F-9), and it still fails on ee0b535's source in all three reader shapes (the m1:220, m1:120, m1:220 triple ask). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * fix(signals): only an async memo with no window of its own is hold work (D-25) Review of #4397: REACTIVE_WINDOW was wiped at the pass tail, and both of holdWork's consumers (the pass head and propagateStatus) run between passes, so `own` was CONFIG_ASYNC alone. The rule hit every async verdict memo, including one that calls latest()/isPending() itself, and changed screens against next on the canonical F-6 shape: an async memo over `${isPending(m0)}:${latest(m0)}` never showed the lane's true:2, and one over latest(m0) read through [isPending(m1), m1()] skipped l1 and showed true,l1. CONFIG_ASYNC is now set at the pass end, while the pass's REACTIVE_WINDOW is still up, only for a pass that registered a flight and opened no window; a pass that opened one clears it. A memo that asks is a verdict reader whoever dirties it (D-25, ruled 2026-10-28), unchanged from next. The held re-run arm applies only to memos that never asked; removing it reasks m1:5/m1:104/m1:15 in five pins, so it stays, documented against D-25. Pins: the two own-window shapes, as next shows them (D-25, F-6). The pin file takes the hand-landed flights from the split prepared for review, so no pin races a timer. Ledger re-records dropped: the size ledger is re-recorded once on next after the batch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * perf(signals): keep D-25's async posture off recompute's hot path CONFIG_ASYNC is now set where a flight registers (handleAsync) and cleared by a verdict window (markVerdictReader) or a sync answer, instead of a holdWork call at every pass's end. The pass-head check is inline. holdWork only answers propagateStatus. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * fix(signals): an error clears D-25's async posture A pass that throws a real error answers with no flight of its own, so a verdict source going pending re-derives the memo instead of holding it on its pre-error value (F-1). Pinned, held and unheld. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * fix(signals): a stale reader's own error re-derives at any landing, not only a lane's end With #4383 the lane's end no longer commits `undefined` for a node that has produced nothing (#4363), so a sync memo that errored before its first value (async-gen 91501 #1158: m1 = m0 + 1 throws at m0 = 1) stays uninitialized through the next lane's end. Its next pass, under the transaction a `latest()` read opened, is "a pass with no value yet": `stagedScreen` serves it the screen (m0 = 1, not the staged 4) and lists it as a stale reader to run again at the landing. That pass throws its own error, and `reruns` skipped every errored reader whose error source is errored -- its own included -- so it never ran again and the root stayed on [ROOT-E]. On next the memo had been initialized as `undefined`, so it read the staging instead and never hit this. `reruns` now re-derives a reader whose error is its own (A15; D-39: the landing is the new answer), as #4383 first did (1e270cd). That also covers the lane's node case, so the `dissolveLane` re-enqueue (3fc3ef1, 15b9419) goes: +11 B minified on the floors, -27 B on the lane scenarios. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 --------- Co-authored-by: Claude <noreply@anthropic.com>
…er watches the seam (#4365) (#4393) * fix(signals): latest() of a memo born in a lane throws NotReady instead of undefined (#4362, #4363, #4376) A memo whose compute reads latest(s) is re-asked as the write's verdict lane work, so it becomes a lane derivation. While its first pass is in flight, nothing is in the lane, and laneValueOf/display fell back to the committed value, which an uninitialized node does not have: - latest(m)/isPending(m) served undefined and probed it pending (#4363); - the lane's end committed that undefined as the node's answer and cleared STATUS_UNINITIALIZED with the flight still up, which tripped the flush loop guard for [latest(m), isPending(m)] (#4362); - in hydration, m2 = fetchC(1 + latest(m1)) asked fetchC(NaN) instead of adopting the streamed answer (#4376). laneValueOf/display now return NOT_PENDING for such a node, so every read throws NotReady (A7; A19 exc. 1) and the lane's end leaves the node in flight. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * fix(signals): latest(a) beside b() computes one pass on the frame's world (#4365) A memo or render effect that reads `latest(a)` beside `b()` computed a torn pass on a batched write. The `latest()` read of a's staging opened the flush's transaction for its verdict (verdict.ts `provisionalVerdict`). The plain `b()` read after it then took that transaction for a hold (`stagedScreen`): it served b's committed value and made the reader a stale reader of the transaction. Nothing held the flush, so that torn pass was one of the flush's frames. Through a memo, a render effect ran `[2,1]` before `[2,2]`. A throw from the pass stuck, because the landing skips errored readers. A pass seated in the verdict lane only by its own `latest()` read (REACTIVE_PROVISIONAL) now reads a staging as the frame does: it leaves the lane and is recorded in `stagedReaders`. If the frame commits, the pass was the frame's one pass. If the flush parks, the lane seam re-derives it on the committed world as display-ahead work. A reader whose own stale pass threw now re-derives at the landing. `stagedScreen` moves into lanes.ts behind a hook, so the core floor does not carry it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * fix(signals): keep a verdict reader's lane routing and own-error recovery (async-gen #119, #1238) - verdictValue: read the lane value before routing the reader into the verdict lane, so a node born in the lane with nothing in it yet is read as any uninitialized node (NotReady) without moving its reader (#1238). - reruns: a stale reader whose error is its own (thrown by its pass over the committed value it was served) re-derives at the landing; only an error from an errored source is skipped (#3945). With #4363 no longer committing `undefined`, async-gen case 119 reached this stuck error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * chore: re-measure sizes over next @ 40cde2c, record async-gen improvements - scripts/size: ledger lines for `signals: + isPending/latest`, the compiled live page and live + router (+46/+46/+47 B minified over next); this PR's store-app line re-measured (97,019 B). - async-gen baseline: 15 recorded failures no longer reproduce (10 undefined-value, 4 final-screen, 1 unhandled-rejection). - RULES-INDEX regenerated; changeset notes the error recovery. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * fix(signals): re-derive a self-errored stale reader at the lane's end, not in reruns The verdict reader's own error from a served committed value (async-gen #119) now re-derives from `dissolveLane` (lane engine only) instead of a widened `reruns` condition, so the floors pay nothing (0 B; the lane scenarios +20 B over the previous form). `reruns` is next's again. Ledger re-measured; async-gen baseline unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * fix(signals): latest() opens no transaction for its verdict; its reader watches the seam (#4365) Rework #4365 to the ruled shape (R5): in provisionalVerdict, latest() no longer calls joinFuture(null). Like a probe read before anything has joined, it goes through watchVerdict. Its pass is the frame's. If the flush parks, it re-derives once in the verdict lane: it is marked unanswered, so the seam does not take the transaction it joined as its answer. A flush that has already joined a transaction (Vl1), and reads outside a flush (Vl3), are unchanged. This drops the stagedScreen variant (REACTIVE_PROVISIONAL, the stagedScreen hook move and the laneSeams change). It also drops this branch's `reruns` change: #4383 handles a self-errored stale reader in lanes.ts dissolveLane, and that branch merges next. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * test(signals, web): re-pin the keyed Show over latest() to two mounts at a park (R-5, #4365) Under design ruling R-5, latest() opens no transaction for its verdict. The keyed condition's first pass is the frame's, and Show mounts A there. The parked flush re-runs the latest() reader once in the verdict lane, and Show mounts A again. The lane's mount is the one shown (the early fallback, still asserted); the held frame's mount is discarded at the lane's reveal. mounts goes [1,2,2] -> [1,3,3] for the static and callback forms over latest(count). The committed-key and `on` rows are unchanged. This re-pin waits on Ryan's ruling. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * fix(signals): the lane's end re-derives a render effect's own stale error too The re-enqueue of a self-errored stale reader at the lane's end sat inside the non-effect branch, so a render effect whose display-ahead pass threw over a held write's screen value kept its error after the landing (found by the #4365 thread). It now applies to both. Byte-neutral (0 B minified in every scenario); floors 0 B. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * test(signals, web): note why the keyed Show over latest() mounts 3 at a park (D-2, A10) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * fix(signals): an async memo over a verdict memo asks each question once A memo that reads a verdict memo is a verdict reader (F-6, #4050), and propagateStatus re-derived every verdict reader when its source went pending. Re-derived, an async one was served the committed value it had already derived from (A10) and asked that question again; that flight held the verdict lane, and the source's held landing re-ran it as lane work onto the same committed value, a third ask. Only the landing's re-derive asked the new question. Now an async memo that is a verdict reader only by reading a verdict memo (it opened no isPending/latest window of its own: REACTIVE_WINDOW, set per pass by markVerdictReader) drops CONFIG_VERDICT when that memo goes pending and takes the plain pending mark: it is pending through the flight (F-9) and the hold's work until it lands, reading plainly (D-25, #3884), and asks once at the landing. Sync readers re-derive as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * test(signals): with #4383, isPending of the unresolved m1 holds its Loading, so the unheld latest case asks like the held one Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * docs(signals): regenerate RULES-INDEX Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * fix(signals): a latest() reader's frame flight is adopted at the landing, not asked again (#4365) Under R5 a latest() reader's first pass is the frame's: an async one asks the frame's question there. When the flush parks, the seam re-derives it in the verdict lane (F-6: the display-ahead view's own async), and that re-derive superseded the frame's flight. The transaction's landing then re-ran the reader as a stale reader and asked the frame's question a second time on the same input. The seam now parks the frame pass's flight (`_parked`) when it re-derives an async unanswered reader outside a lane, and the landing's `reruns` adopts it (`adoptFlight`: registered as if the re-derive had returned it, through a promise of its own so the frame pass's listener cannot land it twice) instead of re-running the reader. That happens whether the flight landed before the transaction or is still in the air. Any later pass drops the parked flight. Asks for m0 = ask(1 + latest(s) + s()) and m1 = ask(1 + m0()) after setS(2): m0:5, m0:4, m1:5, m0:5, m1:6 become m0:5, m0:4, m1:5, m1:6 (F-9: a re-ask of the same question is no new question). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * test(web): #4375 hydration pins the asks over latest(s) under R5's adopted frame flight Over `latest(s)` the write asks m0's frame question first (R5), the verdict lane's intermediate next, and the landing adopts the frame's flight: `m0:5, m0:4, m1:5, m1:6`, each question asked once. The control keeps its `m0:5, m1:6` tail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * fix(signals): an async memo with no verdict window of its own takes no verdict role #4397 review: with two verdict sources (`m1 = ask(1 + m0() + m0b())` over `m0 = ask(1 + latest(s) + s())` and `m0b = ask(10 * (latest(t) + t()))`), the grouped `[isPending(m1), m1()]` reader asked `m1:5` a second time on stale inputs after both sources landed. m1 lost CONFIG_VERDICT when its first source went pending, but a pass that threw NotReady at that source's read picked it back up (F-6, `read`). The settle walk then re-ran m1 as a verdict reader, and the committed values a hold had moved past were served to it (A10). The demotion is now per pass, in `recompute`. An async memo (new CONFIG_ASYNC: its last answering pass registered a flight) whose pass opened no isPending/latest window of its own (REACTIVE_WINDOW) does not become a verdict reader by reading a verdict memo. It waits on its sources like any derivation (F-9), reads them plainly (D-25), and asks each question once. The side-effecting clear in `propagateStatus` is gone, since that path no longer sees such a memo as a verdict reader. Pinned: two verdict memos into one async memo, grouped and split, with no question asked twice and equal final screens. A `test.fails` pin records that the grouped shape still skips the t lane's m1:15/m1:16 asks that split makes: whether a later write merges into an open transaction or opens its own lane depends on grouping (#4398). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * fix(signals): two latest() readers under one async memo ask each question once - A latest() reader whose frame lands at the seam that would re-derive it in the verdict lane keeps its flight: the pass was the landing's (it asked the frame question twice in one flush). The check moves from core's `_update` into lanes.ts `laneSeams`. - Verdict-lane work that read a staging re-derives on the committed world only while the transaction holding the staging stays parked: judged on the flush's transaction, not the lane's parent, and not when the pass's own render effect is all that keeps it parked (`heldBy`). The re-derive asked a question already asked and showed it over inputs the landing replaced. - The landing's adopt of a parked frame flight is a lane hook (`laneAdoptHook`): scheduler.ts no longer imports `adoptFlight`. - #4398's grouped/split pin passes here: grouped and split ask the same questions with latest()'s pass the frame's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * fix(signals): a grouped reader over a held write shows the lane's answer A reader that groups isPending with the value over a held write (`[isPending(m1), m1()]`) never showed m1's lane answer, though a plain value reader did. Two things kept it out: - A pass served a held node's committed value (frameRead, a stale reader of the hold) still kept the node's propagated pending sources, so when it went pending on another source it never woke when that source settled. frameRead now drops what the served node propagated onto it (A15): the landing re-derives it. - A lane's reveal (reruns on a lane) cleared REACTIVE_FRAME_READ, the mark the held transaction's landing needed to re-run the reader. Only the transaction's own reruns clear it now. CONFIG_ASYNC is worked out once, at the end of each pass, from the pass's `_inFlight` (registered a flight: async; answered sync: not; pending or errored without a flight: the last answer's), so a plain sync recompute does no extra work and handleAsync no longer touches it. refresh(m0) mid-hold now shows the lane's answer for the refreshed question in every reader shape. Pins for the held write, an equal landing, and refresh after and mid-hold cover the value, pair and split readers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * test(signals): name frameRead in the held-shapes pin comment Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * chore(size): re-record this PR's four sizes over next @ 7ebd4a3 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * fix(signals): a latest() reader's lane answer reaches the screen whatever the grouping Three review cases on R5, decided from F-6 (display-ahead readers show the held change now, beside the held screen, in their own lane; the lane reveals at its own landing, ahead of the parent's) and D-22/D-23: - An isPending(m0) effect beside `v${m0()}`, with an unrelated async keeping the flush parked: the verdict lane revealed at the write, before R5's lane pass of m0 joined it, so m0's reader read m0 outside the shown lane and waited for the unrelated async. The verdict lane a re-derived latest() reader joins no longer reveals at that seam (laneSeams). - An async memo over latest(s) held only by its own flight: the lane asked m1:120 and its parent landed at the same seam, adopting the frame flight before the lane's reader re-ran, so only v220 showed. A parent whose lane reveals with readers to re-derive now lands a round later (`revealing`), so they show the lane's answer first: v120, then v220, inline and with latest(s) extracted into a memo (F-6; Ryan, 2026-10-29, after #4284). - A sync latest() memo's `[isPending(m0), m0()]` reader under an unrelated hold showed the frame pass's `true,12` before the lane's `false,22`: the same early reveal. It now shows `false,22` directly, as next does (D-22: the pass re-derives once as a verdict reader; D-23). `heldBy` now counts a lane under `t` that only `r`'s own flight keeps blocked: with the parent landing a round later, a lane pass's flight was the only thing parking it, and the seam re-derived the pass and asked its question again. Size: adoptFlight folds into adoptParked (-16 B) and adopts through `parked.then()` (-4 B). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * docs(signals): changeset for the latest() lane-answer fixes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * fix(signals): an async memo over a verdict memo stays a verdict reader (D-25) Review of #4397: the strict rule (an async memo with no `latest`/`isPending` window of its own took no verdict role at all) overrode D-25 ("one that reads a verdict reader's value is ... a verdict reader itself"; "a memo computes under its own posture"). It caused two regressions against next: 1. Memo-extracted `latest()` (`v = createMemo(() => latest(s))`, `m1` async over `v()` and `s()`): m1's pass at the write read the transaction's staging and asked m1:220, the lane re-derived it (m1:120), and the landing asked m1:220 again. It now asks m1:120 then m1:220, once each, as the inline form does (D-22, F-9 exc. 2), showing v120 then v220 (F-6). 2. A reader disposed and remounted while the lane answer was in flight: `[isPending(m1), m1()]` in one effect showed true,4 -> true,5 -> false,6 while `m1()` alone and split showed 4 -> 6. All shapes now show the screen, then the landing (F-6: grouped holds like a lone read; D-26). The strict rule is gone. In its place, D-25's "a node whose fetch a hold is waiting on is that hold's work" (#3884), kept by the verdict engine (verdict.ts `holdWork`, behind `holdWorkHook`, like `observeFlightHook`): - A verdict source going pending does not re-derive an async memo with no window of its own: none of its reads changed, and re-derived it was served the committed value it had already derived from and asked that question again (A10). It waits on the source like any derivation (F-9). Except when its own flight is up outside a lane on a new question of the source's: that re-run supersedes a flight asked of a value the source has moved past (a `refresh()` re-ask is the same question). - A held re-run of such a memo reads plainly, as a pending one already did (#3884): the write that re-ran it is its hold's. - Its async posture (CONFIG_ASYNC) is kept at each pass end in verdict.ts, not computed in core. Kept from the PR, as review asked: the frame read's drop of a served flight's pending (A15; now in lanes.ts `laneServed`, behind a hook, run only for lane work and verdict readers, with no closure), and `reruns` leaving the stale-reader mark to the transaction at a lane's reveal. Pins (tests/verdict-memo-flight-no-reask.test.ts): the memo-extracted and inline forms ask m1:120, m1:220 once and show v110, v120, v220 in every reader shape (D-22, D-25, F-6); a reader remounted mid-flight shows the screen, then the landing, grouped as alone or split (F-6, D-26). Both fail on the previous head. Size: the floors are +14 B minified over next (was +126). The verdict scenarios carry the rule: + isPending/latest +339 B. Ledger lines and the frozen live-server-components floor are re-recorded to the measured sizes (Size-Exception: floor-caps.json 115,423 -> 115,646 B minified, measured 38,197 B brotli; the maintainer relaxed the size rule for this fix). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * test(signals): pin the remount shapes on what holds whatever the timing CI ran the "remounted mid-flight, then a write" pin slower than local runs: the landing's `m1:6` was asked before the remount in the grouped run and after the write in the lone run, so comparing one shape's asks against another's compared two timings, not two shapes. Run at a later remount locally, every shape asks `m1:6` alike, and none diverges. The pin now states what holds at any timing, per shape: no question asked twice (F-9), the lane's `m1:5` never shown to a reader mounted mid-hold (D-26), and the grouped `[isPending(m1), m1()]` ending on the landing like the lone read (F-6). The no-write case still fails on the strict rule's head, where the grouped remount showed `true,5`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * test(signals): land the extracted pin's flights by hand, not by timer CI's coverage run (slower than a plain run) let m1's 10 ms lane flight land before the pin's 2 ms sleep ended, so the later write came after the landing and the run asked m1:220 where the pin expects m1:125. The pin compared two timings, not two behaviours. m1's flights now land only when the test lands them, so the later write always comes while the write's flights are up. The pin's expectations are unchanged (D-25, D-22, F-6, F-9), and it still fails on ee0b535's source in all three reader shapes (the m1:220, m1:120, m1:220 triple ask). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * fix(signals): only an async memo with no window of its own is hold work (D-25) Review of #4397: REACTIVE_WINDOW was wiped at the pass tail, and both of holdWork's consumers (the pass head and propagateStatus) run between passes, so `own` was CONFIG_ASYNC alone. The rule hit every async verdict memo, including one that calls latest()/isPending() itself, and changed screens against next on the canonical F-6 shape: an async memo over `${isPending(m0)}:${latest(m0)}` never showed the lane's true:2, and one over latest(m0) read through [isPending(m1), m1()] skipped l1 and showed true,l1. CONFIG_ASYNC is now set at the pass end, while the pass's REACTIVE_WINDOW is still up, only for a pass that registered a flight and opened no window; a pass that opened one clears it. A memo that asks is a verdict reader whoever dirties it (D-25, ruled 2026-10-28), unchanged from next. The held re-run arm applies only to memos that never asked; removing it reasks m1:5/m1:104/m1:15 in five pins, so it stays, documented against D-25. Pins: the two own-window shapes, as next shows them (D-25, F-6). The pin file takes the hand-landed flights from the split prepared for review, so no pin races a timer. Ledger re-records dropped: the size ledger is re-recorded once on next after the batch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * perf(signals): keep D-25's async posture off recompute's hot path CONFIG_ASYNC is now set where a flight registers (handleAsync) and cleared by a verdict window (markVerdictReader) or a sync answer, instead of a holdWork call at every pass's end. The pass-head check is inline. holdWork only answers propagateStatus. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * fix(signals): R5 with #4397 asks each question once Merging #4397 into #4393 asked three questions twice: - An async memo over `latest()` of a flight (the own-window pins) took R5's frame pass and then a verdict-lane pass that asked the same question again. `latest` of a flight serves the committed value in either world (D-22), so the seam has nothing to decide. The reader joins the verdict lane at once, as a plain read of a flight makes it (`observeFlight`). `latest` of a staging stays the seam's (#4365). - An async memo over a `latest()` memo (D-22 extracted): R5 parked only the reader's own flight. The memo's frame flight was superseded by its lane pass, and the landing asked `m1:220` again. The seam now parks the frame flights of a sync reader's async readers held by the frame, and the landing adopts them as it does inline (F-9). The remount and extracted pins take R5's order: the frame's question comes first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * fix(signals): a verdict effect's staged read before the frame's transaction re-derives once if the flush parks (D-22) In R5, latest() opens no transaction. A render effect that is a verdict reader (`v:${s()} a:${A()} l:${latest(s)}`) read s's staging before the frame had a transaction, so it stayed the frame's pass. Its held run then showed `l:0` until the landing. stagedRead now also lists such an effect for the seam, which re-derives it once as a verdict reader when the flush parks, as D-22 and D-25 require: it shows `v:0 a:0 l:1`. This is limited to effects. A memo is not a leaf, and with async memos the re-run asked its question again. With every memo, the async-gen case e1-3289-1831 ended on [ROOT-E]. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * test(signals): pin #4393's beside shape as it.fails for the reader-seat carve (F-6, D-26) With #4397 merged, m1 is a verdict reader between its flights (D-25) and m0's lane pass re-derives it into the s lane early, so the t transaction lands while m1:1122 is in flight. Telling that pass apart as the hold's work needs a per-pass check; it goes to the carve. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * test(signals): land twoVerdicts' flights by hand The grouped-vs-split ask order raced its 10 ms flights against a 12 ms sleep on a slow runner (m0b:20 before m1:5). The t write now comes after the s write's first flights land, while m1's question is up. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 * perf(signals): keep R5's parked flight off the floors The recompute clear runs only with the lane engine, and ext() no longer seeds the field, so builds without lanes drop both (-39 B minified on every floor). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2 --------- Co-authored-by: Claude <noreply@anthropic.com>
The differential proves the table subsumes it: with the mechanism gone the signals, solid and web suites (incl. #4397's and #4393's pins) stay green, the 2k × 3 generator screens match next except the two known closed cases, and the shadow reports 0 classes. The stale-reader mark the same rows fire (ST → _reruns) re-derives the reader at the landing, and holdWork keeps the #4397 wait; the pending-source drop had nothing left to change. The oracle drops the mark with it. + isPending/latest −104 B min; live page −106. Co-authored-by: Claude via Cursor <noreply@cursor.com> Co-authored-by: Cursor <cursoragent@cursor.com>
…efresh (#4576, #4577) (#4585) * fix(signals): a lane pass served past a refresh retires the pending it carried (#4576) A derivation that is lane work (it reads an optimistic guess) and is served the committed value of a refreshing store drops that refresh from its own pending sources (`laneServed`, #4397). The render effect the refresh's pending had propagated onto through it kept the mark: the refresh's landing walk stopped at the derivation, which no longer carried the source, so the effect stayed pending on a flight that had already landed. A filter change held with it never committed once the second async memo answered last. The drop now retires the source from the dependents that carried it through the derivation (`settlePendingSource`, which keeps it where another path still reaches it). A hold lasts only while an on-screen reader observes a flight it waits on (D-11), so the change commits once every ask has answered (F-2), settling as one with what shares the screen (D-9) and whole on the way (F-1). Lanes engine only; no core-path change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0117BoWNSSgtemsSYsaX2hXE * fix(signals): lane work re-based over a parked frame is not re-derived on the committed world (#4577) Re-derived at the seam, a re-based lane pass paired the frame's new rows with the old filter, and the landing then showed that beside the new filter (F-1, D-34 ¶3). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0117BoWNSSgtemsSYsaX2hXE * fix(signals): a re-based lane reads its frame's world in every flush, through merges (#4577) A second async change merged into the parked filter frame mid-hold; the lane's later passes then read the screen and re-asked over the old filter, and the landing committed that beside the new one (F-1, D-9). This also covers #4576, so laneServed is back to its next form. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0117BoWNSSgtemsSYsaX2hXE * chore: drop an accidentally committed node_modules symlink Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0117BoWNSSgtemsSYsaX2hXE * fix(signals): restore laneServed retiring the carried pending (#4576) Without it a nested action's refresh answers were lost (the store ended [2, 5] instead of [5]). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0117BoWNSSgtemsSYsaX2hXE --------- Co-authored-by: Claude <noreply@anthropic.com>
Summary
Before (next): with
m0 = createMemo(() => ask("m0", 1 + latest(s) + s()))andm1 = createMemo(() => ask("m1", 1 + m0())),setS(2)under a held frame asked["m0:4","m1:5","m0:5","m1:5","m1:5","m1:6"].m1askedm1:5three times.After: the same case asks
["m0:4","m1:5","m0:5","m1:6"].m1:5is the verdict lane's one answer overm0's display-ahead intermediate (F-6). The landing then asks each memo once (F-9: a re-ask of the same question is no new question).m1is a verdict reader only because it reads a verdict memo (D-25, #4050). It was re-derived wheneverm0went pending and was served the committed value it had already answered from (A10), so it asked the same question again. That extra flight held the verdict lane, and the held landing re-ranm1a third time.A grouped reader over a held write now also shows the lane's answer.
[isPending(m1), m1()]showed103 → 105, while a plainm1()reader shows103 → 104 → 105. Value, pair and split readers now agree.An async memo that calls
latest()/isPending()itself is unchanged from next. It stays a verdict reader and shows the lane's answer ahead (D-25, F-6).How
The verdict role (
CONFIG_VERDICTfromREACTIVE_PROBED) is unchanged from next. This PR adds D-25's other clause, a node whose fetch a hold is waiting on is that hold's work (#3884), for an async memo that is a verdict reader only through what it reads. The decision lives in the verdict engine behind one hook (holdWorkHook, installed byverdict.tslikeobserveFlightHook), called only frompropagateStatus. Nothing new runs per pass inrecompute.CONFIG_ASYNCmarks an async memo with no window of its own:handleAsyncsets it where a flight registers, unless the pass'sREACTIVE_WINDOWis up;markVerdictReader(a window), a sync answer or a thrown error clears it;CONFIG_VERDICTfor the pass in two cases. One is when the node is pending (isPending/latest on a superseded transition depend on whether the parked async memo also reads them #3884, as on next). The other is when it is bothCONFIG_ASYNCandCONFIG_HELD: the write that re-ran it is its hold's, so it reads plainly. That second case covers only memos that never asked. D-25's "a verdict posture outlives the pass that asked" covers passes that calledlatest/isPending, and those clearCONFIG_ASYNC. Without the second case, five pins ask twice again.propagateStatus), aCONFIG_ASYNCsub is not re-derived if it isn't pending, sits in a lane, or the source's pending is arefresh()re-ask. It waits on the source like any derivation (F-9). If it is pending outside a lane on a new question of the source's, it is still re-derived.frameReaddrops what a served node propagated (A15,lanes.tslaneServedbehindlaneServedHook). Lane work or a verdict reader served a held node's committed value stops waiting on that node's flight.scheduler.tsreruns:if (!u._lane)), so the transaction's landing re-runs the reader.Review fix (bb06dc7).
REACTIVE_WINDOWwas wiped at the pass tail, and both consumers run between passes. So the rule applied to every async verdict memo, including ones with their own window, and changed screens against next:${isPending(m0)}:${latest(m0)}never showed the lane'strue:2;latest(m0)read through[isPending(m1), m1()]skippedl1.The window is now folded into
CONFIG_ASYNC, and both shapes are pinned as next shows them.Perf fix (b9b9800). bb06dc7's
holdWorkcall at the end of every pass cost update-partial 7-14% (CodSpeed -6.8%).CONFIG_ASYNCupkeep moved tohandleAsync,markVerdictReaderand the sync-answer branches. In 12 alternating local runs against next, update-partial was +0.3% and dynamic-tag +1.1%, both within noise.Review fix (0d6ca5a). After b9b9800, a pass that threw a real error kept
CONFIG_ASYNC. Sorefresh(m0)to the same erroring answer showedm1's pre-error value as final (F-1). An error now clears the bit, pinned held and unheld.Pins
All pins are in
packages/signals/tests/verdict-memo-flight-no-reask.test.ts: 20 pass and 1 isit.fails. Flights in the held-write, remount, extracted and error pins land when the test lands them. The 80 ms holder pins, the two "waits on it, pending" pins andtwoVerdictsstill sleep.{latest,plain} {held,unheld}: an async memo over a verdict memo asks each question once{m1(), [isPending(m1), m1()]}: a memo over a re-asking verdict memo waits on it, pendingtwo verdict memos into one async memo: no question asked twice, grouped or splittwo verdict memos …: grouped asks the t lane's questions as split does(it.fails, #4398)held write/landing equal/refresh after it settles/refresh mid-hold{value,pair,split}: an async memo over a latest() memo asks each question once, as inlineremounted mid-flight(with and without a later write)own window: an async memo over isPending() and latest() …/… over latest() …, read with isPending() beside iterror, then refresh(m0) to the same answer: {held,unheld}, the error stays"latest unheld" keeps next's shape
[m0:4, m0:5, m1:6]. #4383 changes it to the held shape.How did you test this change?
@solidjs/webtest-types clean;--checkclean.Size
The PR doesn't re-record the ledger. It is re-recorded once on
nextafter the batch lands, so the sizecheckis expected to report the stale ledger. Each floor is +143 B minified (+40 to +55 B brotli) over next, andisPending/latestis about +403 B minified.Size-Exception: the frozen live-server-components floor grows about +412 B minified over next for this correctness fix; the maintainer relaxed the size rule for it on 2026-10-29 (size matters only if hello world passes 20 KB brotli).
🤖 Generated with Claude Code
https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2