diff --git a/packages/studio/src/player/components/TimelineTrackHeader.test.tsx b/packages/studio/src/player/components/TimelineTrackHeader.test.tsx index 5758c58e3..b13321670 100644 --- a/packages/studio/src/player/components/TimelineTrackHeader.test.tsx +++ b/packages/studio/src/player/components/TimelineTrackHeader.test.tsx @@ -891,7 +891,11 @@ describe("TimelineTrackHeader", () => { act(() => view.root.unmount()); }); - it("keeps the group pointer on the control line, not a third row", () => { + // The header is ONE line: name, clip count, then every control anchored to + // the right edge. It was two — a name line and a control line — which is + // what let a stray third child overflow the 48px box; now there is a single + // row and the controls share one right-aligned group. + it("keeps the name and every control on one line, controls to the right", () => { enabledCanaries.add("audio-fx-rack"); enabledCanaries.add("audio-groups"); const view = renderHeader({ @@ -903,14 +907,20 @@ describe("TimelineTrackHeader", () => { isAudioTrack: true, }); const header = view.host.querySelector('[role="rowheader"]'); - // One TRACK_H-tall wrapper holding exactly the two lines. + // One TRACK_H-tall wrapper holding one line. const lines = header?.children[0]; - expect(lines?.children).toHaveLength(2); - // And it is on the second line, beside the visibility control. - const controlLine = lines?.children[1]; + expect(lines?.children).toHaveLength(1); + // The controls live in a right-anchored group at the end of that line, + // after the name and the clip count — `ml-auto` is what holds the edge. + const line = lines?.children[0]; + const controls = line?.lastElementChild as HTMLElement | null; + expect(controls?.className).toContain("ml-auto"); expect( - controlLine?.querySelector('button[aria-label="Effects — group these clips first"]'), + controls?.querySelector('button[aria-label="Effects — group these clips first"]'), ).not.toBeNull(); + // And the clip count is beside the name, not out with the controls. + expect(controls?.querySelector('[aria-label="2 clips"]')).toBeNull(); + expect(line?.querySelector('[aria-label="2 clips"]')).not.toBeNull(); act(() => view.root.unmount()); }); }); diff --git a/packages/studio/src/player/components/TimelineTrackPlainHeader.tsx b/packages/studio/src/player/components/TimelineTrackPlainHeader.tsx index 44fae3b0d..d29c97906 100644 --- a/packages/studio/src/player/components/TimelineTrackPlainHeader.tsx +++ b/packages/studio/src/player/components/TimelineTrackPlainHeader.tsx @@ -80,43 +80,52 @@ export function PlainTrackHeader({ }) { return ( <> - {/* Line one: what the row IS. Line two (below) is what you can do to it — - the same split the group header uses, for the same reason: a name and - four controls sharing 232px truncated the name to a few characters. */} + {/* One line: the name, then every control pushed to the right edge. The + two-line split this replaced existed to stop four controls truncating + the name — but the name already truncates on its own (`min-w-0` plus + `truncate`), and the controls are `shrink-0`, so they hold the edge + and the name gives way instead. */}
{isAudioTrack && (
-
- {/* Not on an audio track. The control is the old visibility eye, and on - audio it silences rather than hides — but a row that already says what - it is with a speaker does not also need the hide affordance sitting in - the eye's slot. `visible={false}` rather than omitting the element, so - the spacer keeps every row's control columns aligned. + {/* `ml-auto` is what anchors the group right: it absorbs the slack the + truncating name leaves, so the controls sit on the edge whatever the + name's length. */} +
+ {/* Not on an audio track. The control is the old visibility eye, and + on audio it silences rather than hides — but a row that already says + what it is with a speaker does not also need the hide affordance + sitting in the eye's slot. `visible={false}` rather than omitting the + element, so the spacer keeps every row's control columns aligned. - EXCEPT when the audio track is ALREADY hidden. Withholding the control - unconditionally withheld the only way back: `data-hidden` silences the - clip in preview and drops it from the render, the panel's "Muted" is - the unrelated HTML `muted` attribute, and nothing else writes it — so a - track hidden before this rule (or by "Hide all", or by hand) was - silent with no control anywhere to restore it. Offering the eye only - in that state keeps the affordance off a normal audio row while - leaving the door open from the inside. */} -
);