Fixes real bugs from two independent re-reviews (#2225 @ 65954c3804,
#2416 @ beaf4ffbf6):
- FlatTimingRow's pinRange committed a pinned start+duration range through
TWO sequential onSetAttribute calls. Each resolves domEditSelection fresh
from current hook state, so a selection change between the two awaits
could misdirect the second write at the newly-selected element instead of
the one being edited, and a failure of just the second call left the pair
half-applied (inconsistent inferred/explicit state). Added
commitDataAttributes/handleDomAttributesCommit (mirroring
onCommitAnimatedProperties's same-shaped fix for GSAP property batches):
one PatchOperation[] persist call against an explicit, caller-supplied
selection — not the "current" one — threaded through as the new optional
onSetAttributes prop. pinRange uses it when provided, falls back to the
old sequential behavior otherwise.
- Hide All silently dropped nested sub-composition children: a selection
inside a sub-comp with no timeline-store entry of its own resolves to a
virtual `sourceFile#domId` key (the fallback branch exists so the
expansion hook can later resolve it via clipParentMap), but
toggleTimelineElementHidden only searched the RAW store list, which never
contains that key. useTimelineElementVisibilityEditing now resolves
against useExpandedTimelineElements() instead, matching the track-based
toggle's existing approach — the expanded list synthesizes a real,
patchable TimelineElement (matching key/domId/sourceFile) for each visible
child whenever its host is currently expanded.
- Two composition hosts importing the same sub-composition collapsed to
the first one: findMatchingTimelineElementId ORed domId/selector/
compositionSrc matches with equal priority in a single per-element scan,
so `.find()` could stop at an EARLIER, unrelated host that merely shared
the compositionSrc, before the scan ever reached the correct domId/
selector match further down the list. Restructured to try domId, then
selector, across the WHOLE list first; compositionSrc-only matching is
now a true last resort for when neither identifies a specific element.
- FlatSlider's native pointercancel handler (a platform-level gesture abort
— scroll/touch takeover, pen leaving range) manually duplicated the
pointer-capture release logic instead of calling cancelDrag, so it never
reverted to the pre-drag value — leaving whatever intermediate position
the pointer last reached committed, unlike the Escape/right-click paths
added in the previous round. Now calls cancelDrag directly.
- useColorGradingController's flushPendingPersist read identityKeyRef.current
fresh at flush time rather than a value snapshotted when the edit was
scheduled. Defensive fix: added pendingPersistIdentityRef, set alongside
pendingPersistValueRef in commitColorGrading, read by flushPendingPersist
instead of the live ref — closes the gap regardless of how unlikely the
actual race is given the identity-cleanup effect's existing eager-flush
behavior.
Two prior findings re-verified as already fixed further up this same
Graphite stack (not re-fixed here, per established stack-order handling):
metadata-cache negative-caching (267cdfce1) and cross-file
selectionIdentityKey (6f40e03a1), both landing after #2225's reviewed head.
StudioRightPanel.tsx crossed the 600-line file-size gate after wiring the
new onSetAttributes prop through; extracted the inspector split-pane resize
handlers (previously inlined) into their own useInspectorSplitResize hook.
New regression tests: repeated-composition-host resolution, atomic vs.
fallback pinRange commit paths, pointercancel revert. Full studio suite
still at the known pre-existing 55-failure baseline, zero new regressions.
Typecheck/oxlint/oxfmt clean.