mirror of
https://github.com/penpot/penpot.git
synced 2026-08-08 13:58:35 +00:00
The sidebar measures panel numeric inputs (X, Y, width, height, rotation) emitted one full apply-modifiers commit per DOM event with no throttle: every arrow key-repeat, wheel tick and scrub pointermove became update-positions / update-dimensions / increase-rotation. A sustained gesture starved the React renderer and crashed the workspace with error #185 (Maximum update depth exceeded). Coalesce those bursts at the data layer (potok), following the update-position-data debounce pattern in texts.cljs: - update-positions is now burst-coalesced in place (its only caller is the measures panel); new update-dimensions-coalesced and increase-rotation-coalesced variants are used by the measures panel, while the immediate events keep serving plugins, variants and token application (including the delta? rotation path). - The first event of a burst commits immediately (leading edge, so single edits stay synchronous); further ticks commit at most once per 50 ms (throttle); a trailing debounced flush guarantees the exact final value lands. All payloads are absolute values, so keeping the latest queued value per shape/attribute is lossless. - Pending payloads are drained atomically and stale shape ids (deleted mid-burst) are skipped. The drain stream lives until the workspace is finalized, so bursts reuse a single subscription. - Fewer commits per burst also means fewer undo entries; scrub drags still produce a single entry via the input's outer transaction. Tests: new frontend-tests.logic.sidebar-transform-coalescing-test (8 tests, legacy SVG and WASM renderer branches) guards the invariant that a 20-event burst commits the exact final value in a handful of commits. The previously unregistered update-position-test is wired into the runner with WASM mock fixtures (it fails in full-suite context without them due to a pre-existing global mock-state issue). AI-assisted-by: kimi-k3