Status: accepted (Phase 1)
When does signal.value = x reach the DOM? The answer has to be exact,
documented and defensible, because it determines input latency, the amount of
redundant work, and whether developers need await tick() in their code and
their tests.
n writes does n DOM passes.requestAnimationFrame batching. Aligns with paint and coalesces
aggressively, but adds up to a full frame of latency to every interaction and
makes synchronous reads of laid-out geometry impossible after a write.Option 1 plus explicit batch(). A write flushes the effect queue at the end of
the outermost write or batch. No microtask is ever scheduled by a write.
Framework-internal multi-writes (list reconciliation, props propagation, context swap) are batched internally, so the common “one user action, many derived changes” case does one DOM pass without the developer doing anything.
Documented semantics:
count.value = 1; // DOM is updated when this statement returns
el.offsetHeight; // reads post-update layout, no await needed
batch(() => { a.value = 1; b.value = 2; }); // one flush after the callback
effect(() => { ... }); // runs immediately on creation, then on change
Nested batches flush once, at the outermost exit. Writes performed during a
flush are appended to the same queue and processed in the same pass; a
re-entrancy depth limit converts runaway cycles into a thrown
FirsthandCycleError rather than a frozen tab.
rapid signal updates scenario, both batched and unbatched,
and both numbers are published.One module-level queue array, reused. No timers, no promise chains, no per-update scheduling objects.
await tick(); expect(el.textContent) works immediately.Microtask default (latency and un-observability), rAF default (a frame of latency), priority scheduling (solves a re-render problem this framework does not have, at real byte and complexity cost).