From 3306dbcdc0201d636d3fa4b5715c76907540e932 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel?= Date: Fri, 18 Sep 2026 09:48:37 -0400 Subject: [PATCH] fix(studio): keep the volume slider editable while automation owns the level (#4066) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(studio): keep the volume slider editable while automation owns the level The Design panel's Volume slider disabled itself outright once a volume automation lane existed, with no alternative write path ever wired in — so turning automation on froze the control permanently. A commit now inserts or moves a keyframe at the playhead instead, reusing the exact merge logic the timeline automation lane's own double-click gesture already uses. The slider also now shows the envelope's live value at the playhead rather than the static, possibly stale attribute. * fix(studio): sample the live playhead for volume automation, not the stale store The store's currentTime only updates once at the end of playback, so a keyframe committed mid-transport landed at the last seek instead of the real playhead and could silently overwrite the lane's seed point. useLivePlayheadTime subscribes to the same live signal the timeline follows, matching how the audio FX panel already reads it. Also stops treating a video element's automation as invisible on the timeline: groupAutomationLanes, its paired row-height count, and the lane-drawing filter all gated on audio tag, so a volume lane written from the property panel never showed a track to expand or a keyframe to see. All three now key off "has lanes" (or audio-or-video where the element itself, not its attribute, has to gate a live edit), matching groupAutomationLanes and useTimelineTrackLayout's documented invariant that the row's reserved height and its drawn lanes count the same clips. * refactor(studio): simplify pass on the volume-automation fix Extracted mergeInsertPoint (automationLaneGeometry.ts) so the panel's withPointAt and the timeline lane's own double-click gesture share one merge/seed/sort rule instead of two copies of the same three lines. Fixed a real duplicate subscription: useVolumeAutomation was calling useLivePlayheadTime() itself even though its only caller already reads the same value via the prop PropertyPanel.tsx computes and passes down. Threads currentTime through as a parameter instead. Unified the three places that decide whether an element can draw an automation lane (groupAutomationLanes, automationLaneCountOf, TimelineAutomationLaneSlot's clip filter) behind one exported predicate, isAudioOrVideoTimelineElement, so the row's reserved height and its drawn lanes can't independently drift on what counts. * test(studio): pin the clip-local time translation for volume automation The adversarial reviewer mutated out `currentTime - elStart` and every existing test still passed — the duration clamp happened to mask its absence in every case that touched it. New case starts the clip at t=10 so the clamp can't hide a missing subtraction. --- .../components/editor/PropertyPanelFlat.tsx | 16 ++-- .../editor/propertyPanelAutomation.test.ts | 83 ++++++++++++++++- .../editor/propertyPanelAutomation.ts | 15 +++ .../propertyPanelFlatMediaSection.test.tsx | 35 +++++++ .../editor/propertyPanelFlatMediaSection.tsx | 32 +++++-- .../editor/useVolumeAutomation.test.tsx | 91 +++++++++++++++++-- .../components/editor/useVolumeAutomation.ts | 25 ++++- .../components/TimelineAutomationLaneSlot.tsx | 6 +- .../components/automationLaneData.test.ts | 14 ++- .../player/components/automationLaneData.ts | 9 +- .../components/automationLaneGeometry.ts | 15 +++ .../components/useAutomationLaneGestures.ts | 12 +-- .../components/useTimelineTrackLayout.ts | 13 +-- .../studio/src/utils/timelineInspector.ts | 10 ++ 14 files changed, 321 insertions(+), 55 deletions(-) diff --git a/packages/studio/src/components/editor/PropertyPanelFlat.tsx b/packages/studio/src/components/editor/PropertyPanelFlat.tsx index ebce5c3e1e..7ceb80a1e0 100644 --- a/packages/studio/src/components/editor/PropertyPanelFlat.tsx +++ b/packages/studio/src/components/editor/PropertyPanelFlat.tsx @@ -285,17 +285,17 @@ export function PropertyPanelFlat({ } : null; const audioSelection = isAudioDomElement(element.element); - // Handlers being wired is necessary but not sufficient: App.tsx always passes - // them, so this alone showed the tween editor for every selection — including - // an `