From e07f1405f6be5b9bcf0f38060fb27d6333a43b44 Mon Sep 17 00:00:00 2001 From: Vance Ingalls Date: Tue, 14 Jul 2026 12:20:47 -0700 Subject: [PATCH] fix(studio): version-scope Grade persist, flush pending edits via effect cleanup not render MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes three of the adversarial findings from the third #2416 tip re-review: - Grade rollback was identity-scoped but not attempt-scoped: two edits on the SAME element (e.g. drag Exposure, then Contrast, before Exposure's persist settles) could have the earlier edit's late completion stamp confirmedGradingRef with its now-superseded value, or revert `grading` out from under the newer optimistic edit. Added a monotonic per-commit version via the existing bumpDomEditCommitVersion primitive (the same one the DOM-attribute commit runner uses for the identical race) — persistColorGradingValue now checks both identity AND "is this still the latest attempt for this element" before applying any effect. - The render-phase identity-reset block consumed shared mutable state (clearing the pending-persist timer, reading and nulling pendingPersistValueRef) directly during render. Adjusting STATE during render this way is React's documented pattern and safe to repeat, but consuming a ref this way is not: if React discarded/interrupted that specific render before it committed, the timer would already be cancelled and the pending value already nulled, with no corresponding effect ever running to compensate, silently losing the edit. Replaced with the idiomatic pattern for "clean up a per-identity resource when it changes" — a useEffect keyed on identityKey whose CLEANUP performs the cancellation/flush. A cleanup only ever runs for the effect instance that actually committed, closing the gap entirely. The render-phase block now only performs pure, idempotent state resets. - FlatSelectRow's Preset row passes label="" (the visible "Preset" text is a sibling span, to avoid rendering it twice) which left the underlying + * unnamed. Falls back to `label` when omitted. */ + ariaLabel?: string; value: string; options: Array; tier: PropertyValueTier; @@ -515,7 +532,7 @@ export function FlatSelectRow({