Two sibling call paths still answered from GSAP's 0.5s default while the
toggle path had moved to the clip-wide fallback.
isPlayheadWithinTween now takes the selection, so the toolbar stops
promising "extends animation" on a duration-less tween whose click actually
toggles an interior keyframe. The arc drag commit resolves its replacement
duration the same way its click-path sibling does, instead of authoring
duration 0.5 and collapsing the arc window.
The flat text section drops its two marker-clearing effects: the autofocus
marker is a ref now, read and cleared by the render that consumes it.
6px between rows left little clearance above a row's value for the
promote-to-variable badge (now positioned above the row). Widen the
row gap to 10px.
Every editable value in the flat inspector (FlatRow's CommitField, the
Motion Timing row's Start/End/Duration cells, and every raw <select> —
Style/Text dropdowns, Grade's Custom LUT and Copy-grade-to scope) rendered
its underline/border only on hover (`border-transparent
group-hover:border-...` or no border at all). At rest a value looked like
plain static text, with nothing distinguishing it from a label — testers
reported not being able to tell which fields were editable.
Give each a dim-but-visible resting border (`border-panel-border-input/50`,
or `border-panel-accent/30` for the explicitCustom tier) that brightens on
hover/focus, instead of a fully transparent one. Purely visual — no
behavior change.
Full studio suite (2641 tests) green; typecheck/oxlint/oxfmt clean.
Fixes issues raised in the Deepwork re-review of #2120-#2190 that weren't
covered by #2225's earlier fix pass:
- useColorGradingController: reset grading/compare/mediaMetadata state (and
cancel pending persist/status timers) when selection changes to a
different element — this hook is called unconditionally on every render
(unlike legacy ColorGradingSection, remounted via a selectionIdentityKey
React key), so switching selection reused the previous element's state.
- useColorGradingController: stop permanently caching a non-OK
/media/metadata response as null — a transient server error poisoned the
HDR banner for that asset for the whole page lifetime.
- FlatSelectRow: preserve a valid authored value outside the preset list
(e.g. mix-blend-mode: difference, an arbitrary object-position) instead of
silently misrepresenting it as the first preset — touching the control
would overwrite real persisted state.
- FlatSlider: the throttled trailing commit now reads onCommit through a
ref updated every render instead of closing over it at schedule time — a
caller whose onCommit spreads other current state (Grade's per-detail
commits) could otherwise have a delayed commit revert whatever the user
changed on a different control in the same 40ms window.
- FlatSlider: flush a still-queued trailing commit on unmount instead of
dropping it, and disable the reset button when the slider itself is
disabled.
- FlatSlider: add touch-action: none to the track so touch drags don't
compete with page scroll.
- FlatColorGradingAccessory: clean up the compare-hold's window listeners
on unmount, not only on release — switching selection mid-hold used to
leak them.
- Align (flat Text): re-clicking the option already visually active for a
logical start/end value no longer rewrites it to the physical left/right,
preserving RTL semantics.
- FlatSegmentedRow: give every option an accessible name and aria-pressed
state — two visually-identical glyph buttons (upright/italic "A") had no
way to be told apart by assistive tech.
- PropertyPanelFlat: the panel body falls back to its own scroll when the
collapsed group headers alone exceed the available height, so groups
can't become permanently unreachable in a short pane.
New regression tests for all of the above; full studio suite at the known
pre-existing baseline (55 failures unrelated to this stack).
FlatTextSection's multi-field branch (textFields.length > 1) now renders
FlatTextLayerList (Task 5) + the existing single-field FlatTextFieldEditor
for the active field, tracked via new local activeFieldKey state that
resyncs (useEffect) when the active field disappears from props. This
retires the legacy TextSection delegation entirely for that case; the
TextSection import is removed from propertyPanelFlatTextSection.tsx since
nothing else in the file referenced it.
Also updates propertyPanelSections.test.tsx and PropertyPanel.test.tsx,
which exercised/documented the old multi-field-falls-back-to-legacy-
TextSection behavior in comments and test titles — reworded to describe
the new flat path (assertions were already compatible and still pass).
Flag for reviewer: hideOwnHeading on the legacy TextSection component
(propertyPanelSections.tsx) was added in an earlier plan specifically for
this now-removed call site. It has no remaining consumer after this task
lands (PropertyPanel.tsx's legacy caller doesn't pass it). Left in place
per brief instruction — not deleting unilaterally, since that's a scope
decision for whoever reviews this task.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The flat inspector's Text FlatGroup rendered unconditionally, showing an
empty "Text" header for non-text elements (image, video, etc). Gate it on
isTextEditableSelection(element) so it disappears entirely when there's no
text to edit.
Also, the legacy multi-field TextSection (used as a fallback when an
element has 2+ text fields) rendered its own internal "Text" heading
nested inside the new flat Text FlatGroup, producing a doubled "Text"
heading. Add a hideOwnHeading prop to TextSection (default false, so its
other — legacy, non-flat — call site is unaffected) and pass it from
FlatTextSection's fallback path.