fix(signals): optimistic lanes release when nothing authoritative remains; shared render effects don't entangle unrelated writes (#3426, #3427, #3407) - #3434
Merged
Conversation
…ains; shared render effects don't entangle unrelated writes (#3426, #3427, #3407) Three of gabbev's optimistic/transaction reports, one theme: the pure graph does not wait where it is not forced to, and a hold is a fact about what a pass observed — never about which reader happened to exist. - #3426: the last async reader of an optimistic value unmounting mid-action releases the frame. `reporterBlocksSource` is now `sourceObserved`, shared by the settle verdict, the lane hold (`waitingTransition`) and the landing: the hold check prunes dead reporters itself. A live action parks its transaction without a verdict, so this is the only prune such a frame ever gets — the lane used to wait for a flight nobody observed. - #3427: the action body ending starts the correction (`endOptimism`, called from `flush` after the heap and before the verdict). With the bodies over (`_acted`, no live `_actions`) and nothing authoritative up — no override node's own source in flight, no held plain load the action asked for — each override's truth supersedes it as an arriving differing truth does (A18): the graph re-derives now and the transaction settles when THAT lands. Before: wait for the obsolete lane-derived flight, flash the obsolete optimistic frame, revert, re-ask — a waterfall. Display is unchanged (override on screen until commit). A co-written pending flag stays through the action's own load (attribution-holds pin); optimistic store edits keep settle-then-revert; companions snap at settlement. - #3407: a render effect's pass belongs to whatever dirtied it. `recompute` re-entered a stamped effect's transaction whenever another was active, so a sync `action` write to a signal that merely shared a hole with a held async merged into the async's transaction and waited (0:0 → 2:1) while the same plain write passed through. The stamp re-entry is memo-only now; the re-entry's one legitimate job — completing every reveal that discovered a flight — moves to the landing (`settleTransition` → `enterWaiting`). A15 amended in place: a shared reader entangles nothing by itself; only a pass that observes a flight pending joins it. Two independent flights read in one hole now land at their own times. Docs: SPEC A15 shared-hole corollary, A18 body-end corollary; INTERNALS §2/§3 mechanism notes; RULES-INDEX regenerated. Floor 23,058 → 23,195 (+78 B #3426/#3427, +59 B #3407); size-limit caps ratcheted with notes. Co-authored-by: Claude via Cursor <noreply@cursor.com> Co-authored-by: Cursor <cursoragent@cursor.com>
🦋 Changeset detectedLatest commit: 9e21d80 The changes in this PR will be included in the next version bump. This PR includes changesets to release 11 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 |
This was referenced Sep 14, 2026
Coverage Report for CI Build 34885888106Coverage remained the same at 71.842%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsNo coverage regressions found. Coverage Stats
💛 - Coveralls |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #3426, fixes #3427, fixes #3407.
One theme: the pure graph does not wait where it is not forced to, and a hold is a fact about what a pass observed — never about which reader happened to exist.
#3426 — the last async reader unmounting mid-action releases the frame
The lane held
Value: 1on theDetailsreader observingdetailspending. A live action parks its transaction without a settle verdict, so that reader's registration was never pruned when it died; the lane waited for a flight nobody observed.Rule: the hold check prunes as it reads.
reporterBlocksSourceis nowsourceObserved— the live-reporter test shared by the settle verdict (transitionComplete), the lane hold (waitingTransition) and the landing. The frame reveals at the unmount and reverts when the action ends, like a plain write would.#3427 — the action body ending starts the correction
The settle waited for
details(1)to land (3000:Value: 1 | Details: 1flashed), then reverted and re-askeddetails(0)(4000) — a waterfall with a flash, for a guess the action had stopped justifying at 2500.Rule (A18 body-end corollary): once the bodies have ended and nothing authoritative is in flight — no override node's own source (
transitionBlocked), no held flight that does not derive from an override — each override's truth supersedes it, exactly as an arriving differing truth does (#3331): the graph re-derives from the truth as the transaction's held work and settles when that lands (3500, nothing visible changes). Display is untouched: the override stays on screen until commit. Carve-outs, all existing rulings: a co-written pending flag stays through the action's own plain load (setSaving(true); setPage(2)— theattribution-holdssaving-indicator pin depends on this); optimistic store edits keep the settle-then-revert order; companions snap at settlement. The typical refresh-inside-the-action pattern never hits the hook — the refresh is the transaction's own authoritative work and holds through.Mechanism:
Transition._acted(set byaction, merged),GlobalQueue._endOptimismcalled fromflushafter the heap and before the verdict; a supersession re-runs the heap in the same pass.#3407 — a shared render effect no longer entangles an unrelated sync write
A plain
setBpublished1:0beside the pendingdetailsA; the same write inside anactionmerged into the async's transaction and waited (0:0 → 2:1).recompute's head re-entered a stamped effect's transaction whenever any other transaction was active — before the pass ran, before anything could have been observed.Rule (A15 shared-hole corollary): a render effect's pass belongs to whatever dirtied it. The stamp re-entry is memo-only (a memo's value is its transaction's work); a stamped effect dirtied by another writer runs that writer's pass, reads the held flight as a stale reader (committed value, coherent with the flight's committed inputs) and publishes with the writer. Only a pass that actually observes a pending flight — the reveal carve-out refused — joins it. The re-entry's one legitimate job, completing every reveal that discovered a flight, moves to the landing:
settleTransition→enterWaitingfolds in every transaction still waiting on the node. Consequence beyond the issue: two independent flights read in one hole now land at their own times (next: one unit).Verification
@solidjs/signals: 169 files / 1794 tests green. No existing expectation moved; the A15 reveal-completion pins (spec-async-semantics,reveal-carve-out,stale-read-uninitialized-cross-transition) are the regression net for the landing fold.tests/optimistic-lane-release.test.ts(2.0.0-rc.8 optimistic update keeps waiting after its last async reader unmounts #3426, 2.0.0-rc.8 optimistic update first reveals after its action body has finished #3427 ×2, co-written flag),tests/shared-effect-no-entangle.test.ts(2.0.0-rc.8 shared render effect entangles a synchronous action with an unrelated pending update #3407 plain vs action, carve-out plain vs action).treeshake.test.ts; six brotli caps ratcheted with notes inscripts/size/.size-limit.js.SPEC-ASYNC-SEMANTICS.mdA15 amended in place (shared-hole corollary), A18 body-end corollary;INTERNALS-ASYNC-STATE.md§2/§3 mechanism notes;RULES-INDEX.mdregenerated (A15 → amended). Changeset included.Based on
nextafter #3432.Co-authored-by: Claude via Cursor noreply@cursor.com