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.0 | royal flush - inconsistent read/write in onSettled #3006
Thanks for the careful report — you found a genuine inconsistency, and it's now fixed on next (a7780c7).
What was happening:onSettled (and createTrackedEffect) callbacks ran with a tracked context internally, which made them observe their own staged writes immediately — unlike the effect half of createEffect and event handlers, which read committed values until the graph settles. That asymmetry was an accident of the read path, not a design choice.
The fix: all effect-phase scopes now share one contract:
Reads return the settled graph — a callback never observes its own unsettled write (same as setState in a React effect).
Writes are queued into the same flush's continuation, so dependent memos/effects still update before the flush returns — nothing is lost or deferred to another tick.
Functional setters still compose: set(v => v + 1) twice increments twice.
On flush(): it genuinely cannot run inside these callbacks — the flush that invokes them is already in progress, so it's inherently non-reentrant (React's flushSync has the identical restriction in effects). Dev now reports this loudly everywhere (onSettled/createTrackedEffect throw; createEffect callbacks warn instead of silently no-op'ing), and both messages point at the escape hatch: queueMicrotask(() => flush()). The JSDoc for onSettled and createTrackedEffect now documents all of this.
Activity
fix(signals): unify effect-phase write visibility — callbacks never o…
ryansolid commentedon Aug 17, 2026
Thanks for the careful report — you found a genuine inconsistency, and it's now fixed on
next(a7780c7).What was happening:
onSettled(andcreateTrackedEffect) callbacks ran with a tracked context internally, which made them observe their own staged writes immediately — unlike the effect half ofcreateEffectand event handlers, which read committed values until the graph settles. That asymmetry was an accident of the read path, not a design choice.The fix: all effect-phase scopes now share one contract:
setStatein a React effect).set(v => v + 1)twice increments twice.On
flush(): it genuinely cannot run inside these callbacks — the flush that invokes them is already in progress, so it's inherently non-reentrant (React'sflushSynchas the identical restriction in effects). Dev now reports this loudly everywhere (onSettled/createTrackedEffectthrow;createEffectcallbacks warn instead of silently no-op'ing), and both messages point at the escape hatch:queueMicrotask(() => flush()). The JSDoc foronSettledandcreateTrackedEffectnow documents all of this.chore(size): bump scenario limits for the rc batch (flatten 66accfb, #…
store rewrite: mirror core's #3006 committed-visibility rule
Store rewrite: single-home storage model replaces the legacy store (#…
Fix projection stores leaking unsettled writes to effect-phase readers (
fix(signals): wake tracked effects when a value they read stale commi…
fix(signals): tracked effect re-runs for a same-pass write (#4598) (#…
fix(signals): tracked effect re-runs for a memo's same-pass write and…