You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
2.0.0-rc.3 | latest() does not work on projected value createProjection() #3075
import{render,untrack,memo}from'@solidjs/web';import{createSignal,createProjection,latest,createMemo}from'solid-js';exportdefaultfunctionApp(){const[count,setCount]=createSignal(0);const[derived,setDerived]=createSignal(()=>{returncount()})constm=createMemo(()=>{returncount()})constp=createProjection(()=>{return{a: count()}},{})setTimeout(()=>{untrack(()=>{setCount(v=>v+5)console.log("count",count(),latest(count))// 0 5 latest on source (success)console.log("p.a",p.a,latest(()=>p.a))// 0 0 latest on projected count (Fail)console.log(derived(),latest(derived))// 0 5 latest on derived signal (success)console.log(m(),latest(m))// 0 5 latest on memo signal (success)})})return(<divclass="p-2">
latest does not work for projection, but works for derived signal, or memo
</div>);}if(typeofdocument!=='undefined'){render(()=><App/>,document.getElementById('root')!);}``
Fixed in 0e37f90 (same commit as #3074 — two directions of one isolation gap).
Why it never worked: latest() is implemented in core read(), which routes the read through a companion computed that re-evaluates speculatively against pending values — that's what makes it work for signals, memos, and derived signals. But store property reads go through the proxy traps, which only reach core read() when there's a tracking observer; an untracked latest(() => p.a) served the committed backing straight from the trap, so the latest window was invisible to stores. (The rc.0 → rc.1 regression window you bisected is the store rewrite — the legacy store happened to route these reads through core.)
Now the get trap recognizes the latest window: it pulls the projection computed up to date first (so the derive re-runs against the in-flight source values), then serves the in-flight derivation. The speculative recompute this triggers is staged, not committed — a plain p.a read right after still shows the committed value until flush(), matching count() vs latest(count) exactly.
Your playground's four log lines now all pair up: count 0 5, p.a 0 5, derived 0 5, memo 0 5.
Activity
fix(signals): projection transition isolation and latest() parity (#3074
ryansolid commentedon Aug 27, 2026
Fixed in 0e37f90 (same commit as #3074 — two directions of one isolation gap).
Why it never worked:
latest()is implemented in coreread(), which routes the read through a companion computed that re-evaluates speculatively against pending values — that's what makes it work for signals, memos, and derived signals. But store property reads go through the proxy traps, which only reach coreread()when there's a tracking observer; an untrackedlatest(() => p.a)served the committed backing straight from the trap, so the latest window was invisible to stores. (The rc.0 → rc.1 regression window you bisected is the store rewrite — the legacy store happened to route these reads through core.)Now the get trap recognizes the latest window: it pulls the projection computed up to date first (so the derive re-runs against the in-flight source values), then serves the in-flight derivation. The speculative recompute this triggers is staged, not committed — a plain
p.aread right after still shows the committed value untilflush(), matchingcount()vslatest(count)exactly.Your playground's four log lines now all pair up:
count 0 5,p.a 0 5,derived 0 5,memo 0 5.fix: restore next work reverted by the stage-2 replay (pre-merge sweep)
fix: restore next work reverted by the stage-2 replay (pre-merge sweep)
Stage 2: store patch channel + patch-mode list driver (dormant by def…
refactor(signals): store node reads select by readerSeesCommitted (Ru…
refactor(signals): serve() — Rule 1's one slow selection for signals …