Repository navigation
fix(signals): seat a first pass in its creator's lane, never a verdict lane (#3835, #3851) - #3869
Conversation
…ement (#3835) A first pass created by a guess's lane is the lane's frame (ruling A) and recompute's tail already routed its result and run there, but its head reset `passLane` for every fresh node, so the pass read as the action's own pass in the action's flush: served the held staging and joined the transaction. Inside a memo the lane created, the binding was born held while the memo's element showed through the lane (`<div>Hello</div>` with no style until the action landed); directly under the lane pass it showed the held value early. The head now seats a first pass in its creator's guess lane, so the binding is the lane's stale reader of the held write: committed value now, re-derived at the landing (frameRead, 2026-10-01: a lane sees the screen plus its own guesses). Verdict lanes are not inherited: verdict work reads the staged world and a mount it makes stays a mainline mount (A29's boundary exemption; the #3540 keyed-Show-over-latest rows). Co-authored-by: Claude via Cursor <noreply@cursor.com>
Inside an action, `<Show when={latest(x) > 0}>` opens at once, and a
`<Loading>` it mounted showed `content 1` beside the committed `x = 0`.
Three gaps let the proposal through:
- #3835's seat excluded a verdict lane only at recompute's head; the tail
still routed the first pass to the creator's lane (`passLane ??
creatorLane`), so the content was staged as verdict-lane work. The
creator's lane is now filtered once, for both.
- Mainline, the content's first pass ran in the action's own flush and
read `x`'s staging before the seam held it: neither joined nor created
by a joined pass, it published directly. A first pass that read a
staging of a flush with a transaction is now born held like one that
read a held node (A29) — inside a flush only: an action body's read of
an unflushed write is served committed (A28, posture-store-parity S6).
- The boundary's output, re-derived at the seam, entered the transaction;
the verdict-lane render effect read its staging in that flush and its
run was released with the lane. Verdict-lane work that read the frame's
stagings now re-derives on the committed world when the frame stays
parked (`stagedReaders`), as guess-lane work does; a verdict reader
still answers for itself (#3322, #3540), and a transaction landing at
the same seam needs no second run (pending-gated-landing-replay).
Co-authored-by: Claude via Cursor <noreply@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
#3835's seat (ae59c76) made a guess lane's first pass lane work from its start, so a `Loading` the lane mounts over a flight read it as lane work: A31 served the flight's committed value (`data 1`) instead of the boundary's fallback. It passed on `next`. Lane work with no committed value yet now reads a pending flight as a mainline mount's memo does — it enters and throws, and the boundary catches it (A29's boundary exemption, #3540); render effects keep rule 3's stale read. Taken from #3843 (brenelz): the `frameRead` and A31 arms, the tail's `let lane = passLane` (a seated pass never loses its lane mid-pass), and optimistic-mount-nested-memo-3835.test.ts verbatim. Co-authored-by: Brenley Dueck <184235+brenelz@users.noreply.github.com> Co-authored-by: Claude via Cursor <noreply@cursor.com> Co-authored-by: Cursor <cursoragent@cursor.com>
…ass-lane Co-authored-by: Cursor <cursoragent@cursor.com> # Conflicts: # packages/signals/docs/RULES-INDEX.md # packages/signals/src/core/core.ts
…ead (#3851) Maintainer ruling (2026-10-07): a transition holds on any unready read, but a new memo does not join it by being created. The staged-read born- held clause applied to every first pass inside a flush with a transaction, so a Loading that a held memo mounted had its content born held and reported pending while hidden (#3824's pinned isPending(view) === false). It now applies only to a mount made under a verdict lane (the #3851 shape); other mounts publish and re-derive if the flush parks. An action body never runs under a verdict lane, so the `globalQueue._running` guard (A28: a body's read of an unflushed write is served committed) is no longer needed: -3 B minified against the previous clause. Spec: dated amendments next to A29 (the ruling, quoted) and on the L2 staging-read statement (verdict-lane work re-runs against committed values when the transaction stays parked). Co-authored-by: Claude via Cursor <noreply@cursor.com> Co-authored-by: Cursor <cursoragent@cursor.com>
🦋 Changeset detectedLatest commit: 95bae2f The changes in this PR will be included in the next version bump. This PR includes changesets to release 12 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)
Bundled with Rolldown (what Vite ships), brotli q11, decimal KB. A scenario fails only when it is over its brotli cap and its minified size is more than 20 B over the minified recorded with the cap; over the cap within that allowance is brotli layout noise and passes with a warning. Caps and their recorded minified in |
Raises nine caps to CI brotli + 10 B with their recorded minified, from Size run 37642512669 against next @ 53ef0e6 — the frozen core, simple-app and page floors among them. Accepted by the maintainer 2026-10-07. Co-authored-by: Claude via Cursor <noreply@cursor.com> Co-authored-by: Cursor <cursoragent@cursor.com>
Merging this PR will regress 1 benchmark
Warning Please fix the performance issues or acknowledge them on CodSpeed. Performance Changes
Tip Investigate this regression by commenting Comparing |
Co-authored-by: Claude via Cursor <noreply@cursor.com> Co-authored-by: Cursor <cursoragent@cursor.com>
Coverage Report for CI Build 37655687712Coverage remained the same at 75.927%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsNo coverage regressions found. Coverage Stats
💛 - Coveralls |
recompute's head asked creatorPass(context) for every first pass before checking for a creator lane. The lane comparison now goes first, with a loose != that is false when there is no lane (undefined == null), so the common path never calls it. Same result; -1 B minified. Co-authored-by: Claude via Cursor <noreply@cursor.com> Co-authored-by: Cursor <cursoragent@cursor.com>
…swer into it (#3800) (#3881) * fix(signals): a new async memo created over a hold lands its first answer into it (#3800) A memo created while a transaction holds a value, whose first pass reads that value and returns a promise, was a loading source the seam does not hold (plan sec. 15.2), so its answer landed as mainline commit #0 and its readers showed the held future beside the committed page. The same memo with a synchronous first answer is born held (A29). The first pass now records the transaction it was born into under the born-held arm's conditions (a joined read, or a verdict lane's mount reading a staging), and its first landing is staged into that transaction while it is live. The hold never waits for the first load (the direction rule, #3820). Maintainer clarification (2026-10-07): the #3869 ruling covers new memos that don't read async; a new memo that reads the held world is held, async first answer included. Co-authored-by: Claude via Cursor <noreply@cursor.com> Co-authored-by: Cursor <cursoragent@cursor.com> * chore(size): size exception for a new async memo's first landing into its hold (#3800) Raises seventeen caps to CI brotli + 10 B with their recorded minified, from Size run 37684843282 against next @ dafad1d — the frozen core, simple-app, hydrating and live-page floors among them. Accepted by the maintainer 2026-10-07 on the condition hello world stays under 10 KB (CI: 9,914 B). Co-authored-by: Claude via Cursor <noreply@cursor.com> Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Claude via Cursor <noreply@cursor.com> Co-authored-by: Cursor <cursoragent@cursor.com>
… the lane seam (#3882) Since #3869, verdict-lane work that read a staging of the flush is re-derived on the committed world while the frame stays parked (`stagedReaders`, the lane seam). A node born into the parked frame has no committed value: the re-run reads the same staging, registers again, and the seam re-queues it every round until flush throws "Potential Infinite Loop Detected" (semantic fuzzer, mount-under-hold: a `latest()`-gated Show mounting a fresh Loading under an action hold, 29 cases across seeds 3289 and 91501). `stagedRead` keeps REACTIVE_STAGED_READ for such a read but no longer lists the pass for re-derivation. The frames return to pre-#3869's (the content shows the held value while held; A29's fallback frame is still open, pinned as test.fails). Co-authored-by: Claude via Cursor <noreply@cursor.com> Co-authored-by: Cursor <cursoragent@cursor.com>
Fixes #3835
Fixes #3851
A first pass is seated in its creator's lane when that lane is a guess's, and never in a verdict lane. Three commits on top of the #3835 seat, plus the maintainer's 2026-10-07 ruling on born held.
Credit: the
frameReadgate for uninitialized lane memos, the A31 skip, and theoptimistic-mount-nested-memo-3835tests come from @brenelz's #3843 (carried verbatim,Co-authored-byon that commit, named in its changeset). His diagnosis —passLanecleared at the start of the creation pass — is what the #3835 fix builds on.Fixes
#3835: a binding an optimistic mount creates lands with its element
recompute's head resetpassLanefor every fresh node, so the pass read as the action's own. It was served the held staging and joined the transaction. Inside a memo the lane created, the binding was born held while the element showed through the lane (<div>Hello</div>without itsstyleuntil the action landed).optimistic-mount-binding-3835.test.ts,packages/web/test/optimistic-mount-style-3835.spec.tsx.A
Loadinga lane mounts over a flight shows its fallback (from #3843)Loadingthe lane mounts over a flight read it through A31 and was served the flight's committed value. Lane work with no committed value yet now reads a pending flight as a mainline mount's memo does: it enters and throws, and the boundary catches it (A29's boundary exemption). Render effects keep rule 3's stale read.optimistic-mount-nested-memo-3835.test.ts(brenelz, verbatim).#3851: a verdict reader's mount stays mainline
Inside an action,
<Show when={latest(x) > 0}>opens at once, and a<Loading>it mounted showedcontent 1beside the committedx = 0.x's staging before the seam holds it. A mount made under a verdict lane whose first pass read a staging of a flush with a transaction is now born held, so the boundary shows its fallback andcontent 1appears at the landing.stagedReaders), as guess-lane work does. A verdict reader still answers for itself (2.0.0-rc.7 render effect depending on non async values from two concurrent updates commit with the wrong data #3322, Loadingonshould behave as a keyed <Show> around the boundary; a boundary mounted under a hold must not be born held (A29) #3540). A transaction landing at the same seam needs no second run.verdict-mount-first-pass-3851.test.ts.Rulings (dated amendments in
SPEC-ASYNC-SEMANTICS.md)stagedReadcomment is updated.Loadingmounted by a held memo, and the hidden boundary reported pending (fix(signals): a Loading mounted under a hold shows its fallback (L2 regression of #3540) #3824 pinsisPending(view) === false). The shipped form applies the hold only to a verdict lane's mount:prevLane && el._flags & REACTIVE_STAGED_READ. It also replaces theglobalQueue._runningguard, because an action body never runs under a verdict lane. That makes it 3 B smaller (minified) than the first version.Tests
@solidjs/signalssuite: 5052 passed, 26 expected failures, 2 skipped.packages/web/test/optimistic-mount-style-3835.spec.tsxandloading-fallback-in-flush-3540.spec.tsxpass.next,optimistic-mount-binding-3835(2/2),verdict-mount-first-pass-3851(2/2) and the web Optimistically mounted component appears before its dynamic style is applied #3835 spec (3/3) fail.optimistic-mount-nested-memo-3835has 2 that fail onnextand 2 that pass there; those two guard against the regression the seat caused (theLoadingover a flight).Size
CI Size run 37642512669, against
next@53ef0e69e(after #3853, #3855, #3865). The approved budget was this branch's numbers before the 2026-10-07 ruling: about +36 B minified on the floors and +62 B onisPending/latest. The ruled form comes in 3 B smaller than that on every affected scenario.Every raised cap is CI brotli + 10 B (rounded up to 0.01 KB) with its recorded minified set to the head's, and has a dated ledger note in
scripts/size/scenarios.js(floor-caps.jsonfor the frozen ones). Most of those scenarios were already over their brotli cap on layout and within the minified allowance; this PR's ~+33 B minified goes past it. The observe tiers, frames, and server scenarios are unchanged or under their caps.Public API changes
None. Internal
recompute/read/frameReadand lane-seam logic only.Size-Exception: first-pass lane seating for #3835/#3851 (about +33 B minified on the signals core, +59 B on
signals: + isPending/latest), accepted by the maintainer 2026-10-07, so the frozen signals core floor, simple-app floor and both page floors move.