From db1b81357d1bc86d67ccd3d25a68c48df068a0b4 Mon Sep 17 00:00:00 2001 From: Vance Ingalls Date: Wed, 19 Aug 2026 20:12:46 -0700 Subject: [PATCH] feat(studio): one-line track header, controls right-aligned MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The name, the clip count and every control now share one line, with the controls anchored to the right edge: eye, then FX, then the automation toggle. The two-line split this replaces existed to stop four controls truncating the name to a few characters. It does not need a second line to do that: the name already truncates on its own, and the controls are `shrink-0`, so they hold the edge and the name gives way instead. `ml-auto` on the control group absorbs whatever slack the name leaves, which is what keeps the buttons on the edge at any name length. The clip count sits against the name rather than out with the controls. That took dropping `flex-1` from the name — with it, the name claimed all the free width and pushed the badge across the row to meet the buttons. Now the badge tracks the name's own width: measured 4px after it on every row, whatever the name's length. Also folds away the class of bug the last two commits fixed: with one line and one right-aligned group there is no second line for a control to be misfiled onto, and nothing to centre in a box that grows. Co-Authored-By: Claude Opus 5 (1M context) --- .../components/TimelineTrackHeader.test.tsx | 22 +++++-- .../components/TimelineTrackPlainHeader.tsx | 63 +++++++++++-------- 2 files changed, 52 insertions(+), 33 deletions(-) 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. */} -
);