The "Income this month" field could not be changed from 0. Typing zeros
appeared to work; typing any non-zero snapped the field back to a single 0.
mutate() defers its callback behind the store's serialization queue and an
IndexedDB write. React re-renders and resets a controlled input's DOM value
back to the last committed value well before that callback runs, so a handler
reading event.target.value INSIDE the callback read the reset value and
committed 0 — forever. Committing 0 matches what is displayed, which is why
typing zeros looked fine.
Six handlers had this shape (income, post name, post currency, standing-rule
kind, base currency, rate service URL). Each now reads the DOM value
synchronously and passes it in. Sites that already captured into a const first
— the FX rate and the standing-rule amount — were never affected, which is why
those fields worked.
Purchase, split and plan editors were never affected either: they write to
local component state synchronously, never through the deferred queue.
Adds a source-level regression guard, because the trigger is real-browser
render timing that happy-dom does not reproduce.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>