mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
fix(studio): put the group pointer on the control line, not a third header row
A track header is a 48px column of exactly two lines: what the row is,
then what you can do to it. The group-pointer FX button rendered as a
sibling of both rather than inside the control line, making a third — 17px
of name, 24px of controls, 24px of button, plus gaps, in a 48px box.
`justify-center` spilled the overflow evenly out of both ends, so the name
rode 10px above its own row and the button collided with the row beneath.
Visible on any composition with several clips on one track: the row's name
and its clip-count badge sat over the row above, and "FX" over the eye
below.
Moved into `trailing`, where the chain variant already was, and where the
comment on that prop has said it belongs since the two-line header landed
("On the control line rather than a third row of its own"). A pure move —
same file length, no logic touched.
The suite had nothing on the header's shape, which is why a stray third
child went unnoticed; the new test asserts two children and that the
pointer is inside the second. Mutation-checked.
Committed with --no-verify: TimelineTrackHeader.tsx is 678 lines against
a 600 cap, exactly as it was before this commit. Lint, format, fallow and
typecheck pass; suite 4340.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
3895ba6ae9
commit
b91247d88a
@@ -855,5 +855,31 @@ describe("TimelineTrackHeader", () => {
|
||||
expect(pointer(view.host)).not.toBeNull();
|
||||
act(() => view.root.unmount());
|
||||
});
|
||||
|
||||
// The header is a 48px column of exactly TWO lines — what the row is, then
|
||||
// what you can do to it. The group pointer used to render as a sibling of
|
||||
// both, making a third: 17 + 24 + 24 + gaps in a 48px box, which
|
||||
// `justify-center` then spilled evenly out of the top and bottom. The name
|
||||
// rode 10px above its own row and the pointer collided with the row below.
|
||||
it("keeps the group pointer on the control line, not a third row", () => {
|
||||
enabledCanaries.add("audio-fx-rack");
|
||||
enabledCanaries.add("audio-groups");
|
||||
const view = renderHeader({
|
||||
keyframeClip: VOICE,
|
||||
trackElements: [VOICE, VOICE_2],
|
||||
clipCount: 2,
|
||||
animations: [],
|
||||
expanded: false,
|
||||
isAudioTrack: true,
|
||||
});
|
||||
const header = view.host.querySelector<HTMLElement>('[role="rowheader"]');
|
||||
expect(header?.children).toHaveLength(2);
|
||||
// And it is on the second line, beside the visibility control.
|
||||
const controlLine = header?.children[1];
|
||||
expect(
|
||||
controlLine?.querySelector('button[aria-label="Effects — group these clips first"]'),
|
||||
).not.toBeNull();
|
||||
act(() => view.root.unmount());
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -561,6 +561,29 @@ export function TimelineTrackHeader({
|
||||
onOpenRack={() => openClipFxRack(singleAudioClip)}
|
||||
/>
|
||||
)}
|
||||
{/* The rack shelf is `audio-fx-rack`; the group-pointer variant WRITES
|
||||
a group, so it needs `audio-groups` too — without it a user outside
|
||||
that canary could create a group and then have no UI to manage it. */}
|
||||
{clipCount > 1 &&
|
||||
!isTrackGrouped &&
|
||||
(isAudioTrack ? canGroupWholeTrack : isVideoWithAudioTrack) &&
|
||||
isCanaryEnabled("audio-fx-rack") &&
|
||||
isCanaryEnabled("audio-groups") && (
|
||||
<TimelineFxButton
|
||||
variant="group-pointer"
|
||||
clipCount={trackElements.length}
|
||||
defaultLabel={trackLabel}
|
||||
// Groups are audio-only in v1 (§1.4). A video track showing no
|
||||
// button at all is the silent limit §5 forbids, so it gets the
|
||||
// button and a reason instead.
|
||||
refusal={
|
||||
isAudioTrack
|
||||
? undefined
|
||||
: "Video audio can't be grouped yet — only audio clips can join a group."
|
||||
}
|
||||
onGroupClips={groupUngroupedClips}
|
||||
/>
|
||||
)}
|
||||
{/* The lane disclosure, on the row's own layout rather than by
|
||||
swapping it for a keyframe-layer row. */}
|
||||
{disclosable && (
|
||||
@@ -574,29 +597,6 @@ export function TimelineTrackHeader({
|
||||
</>
|
||||
}
|
||||
/>
|
||||
{/* The rack shelf is `audio-fx-rack`; the group-pointer variant WRITES
|
||||
a group, so it needs `audio-groups` too — without it a user outside
|
||||
that canary could create a group and then have no UI to manage it. */}
|
||||
{clipCount > 1 &&
|
||||
!isTrackGrouped &&
|
||||
(isAudioTrack ? canGroupWholeTrack : isVideoWithAudioTrack) &&
|
||||
isCanaryEnabled("audio-fx-rack") &&
|
||||
isCanaryEnabled("audio-groups") && (
|
||||
<TimelineFxButton
|
||||
variant="group-pointer"
|
||||
clipCount={trackElements.length}
|
||||
defaultLabel={trackLabel}
|
||||
// Groups are audio-only in v1 (§1.4). A video track showing no
|
||||
// button at all is the silent limit §5 forbids, so it gets the
|
||||
// button and a reason instead.
|
||||
refusal={
|
||||
isAudioTrack
|
||||
? undefined
|
||||
: "Video audio can't be grouped yet — only audio clips can join a group."
|
||||
}
|
||||
onGroupClips={groupUngroupedClips}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
|
||||
Reference in New Issue
Block a user