Repository navigation
[2.0 / next] laneRead's D-28 verdict-seat reset compares verdict lanes by identity; a merged transaction's lane reads as another hold's #4427
Description
Summary
laneRead's D-28 reset (#4275, #4284) — "a verdict lane's seat does not carry a leaf into another lane" — compares verdict lanes by object identity: passLane !== l && passLane._parent._verdict === passLane. After a transaction merge (A._into = B), A's verdict lane and B's verdict lane are two objects for one hold. A render effect seated as B's verdict reader (its latest() of a node B holds routed it to verdictLane(txOf(el)), resolved) then reads a derivation that still sits in A._verdict (its _x._transaction was stamped before the merge): the reset fires as if the leaf had crossed into another hold's lane, passLane is cleared, and the leaf is served the staging through the L7' arm (c held by the flush's transaction) instead of staying seated and reading the lane's value.
The hold is the same; only the lane object differs. The leaf leaves its own hold's verdict seat for the rest of the pass.
Reproduction
async-gen seed 3289 case #43 (packages/web/test/async-gen):
const sc = fc.sample(scenarioArb(), { seed: 3289, numRuns: 44 })[43];
await runScenario(sc);[s0=1 s1=2 m0=memo(latest(s1) !%4=3) m1=async(latest(s1) !%4=0)]
<span split[s1()]>s0()</span><Show when=m0()><span></span></Show><Loading><span fx[latest(s1),m1()]>m0()</span></Loading>
:: f A(s1=1) s0=1 s1=0 s0=3 s0=3 m S1 A(s0=3)
scenario JSON
{"signals":[1,2],"memos":[{"async":false,"deps":[{"src":{"k":"s","i":1},"how":"latest"}],"poison":3},{"async":true,"deps":[{"src":{"k":"s","i":1},"how":"latest"}],"poison":0}],"tree":{"t":"group","kids":[{"t":"leaf","text":[{"how":"plain","src":{"k":"s","i":0}}],"attrs":[{"how":"plain","src":{"k":"s","i":1}}],"grouped":false},{"t":"show","when":{"how":"plain","src":{"k":"m","i":0}},"child":{"t":"leaf","text":[],"attrs":[],"grouped":true},"fallback":false},{"t":"loading","child":{"t":"leaf","text":[{"how":"plain","src":{"k":"m","i":0}}],"attrs":[{"how":"latest","src":{"k":"s","i":1}},{"how":"plain","src":{"k":"m","i":1}}],"grouped":true}}]},"steps":[{"t":"flush"},{"t":"action","s":1,"v":1},{"t":"write","s":0,"v":1},{"t":"write","s":1,"v":0},{"t":"write","s":0,"v":3},{"t":"write","s":0,"v":3},{"t":"micro"},{"t":"settle","pick":1},{"t":"action","s":0,"v":3}]}The fx[latest(s1), m1()] render effect reads latest(s1) (held → seated as the hold's verdict reader), then m1 — a verdict-lane derivation whose lane's _parent has been merged (resolveTx(l._parent) !== l._parent) into the very hold the leaf is seated for. Observed: passLane reset, m1's staging served (L7'). The reader-seat table (#4395, row V6/L6) compares holds, not lane objects: no leave, the lane's value.
Found by the S1 reader-seat differential: class V6 expected Lv, got P — slot (V posture; s u d ip vlane parentMerged shown · cU cH cOtherTx t1 w2 vseat oldNoLane flushTx flushNotElTx) plus the downstream verdict seat at the pass's end (old none, new seated), 1 case in the 1500-case regression gate, none in the signals or web suites. The case's screens are all [ROOT-E] (the m0 poison), so nothing visible differs here; the mechanism is wrong independent of the screen.
Why it is a bug
- D-28 / [2.0 / next] A held optimistic lane holds per render effect:
latest(x)in its own JSX hole shows the guess beside held content (D-25) #4284: the leaf leaves the verdict seat for a lane that is not that hold's. A merged transaction's verdict lane is that hold's. - Lane identity surviving a merge is a Idea: let an unrelated write publish during a refresh by forking only the nodes it overlaps #3905-adjacent artifact (
verdictLane(t)is per transaction object; nodes stamped before the merge keep the old lane). Every other seat comparison in the engine resolves (txOf,resolveTx); this one does not.
Intended fix
The reader-seat rebuild S1 (#4395) decides the leave from the seat's hold (resolveTx(vhold) !== resolveTx(l._parent)), which fixes this by construction. Until the swap lands, the shadow carries this as a known class (shadowKnown, this issue). #3905 (transaction merging) is the structural fix for lanes keeping pre-merge identities and stays out of scope here.
Activity
seat(S1 M2a): exact marks in the slot table, compared against today's…
test(signals): reader seat — the slot table and shadow differential a…
chore(size): record #4427's cost on the live server-component pages; …
ryansolid commentedon Oct 30, 2026
Fixed on
nextby #4455 (131ceb8); it ships in the next RC.Generated by Claude Code
fix(signals): a merged transaction's verdict lane is the same hold's …
refactor(signals): drop #4455's held-hold comparison in laneRead; the…
Merge next into memo-form-tear
fix(signals): a held frame's leaf is the hold's own work, not its ver…