fix(studio): take the visibility control off audio track headers

The control in the eye's slot is the old hide button; A2 relabelled it to Mute
on audio tracks rather than removing it. It comes off those rows now. Non-audio
tracks keep it exactly as before.

Rendered with `visible={false}` rather than omitted, so the spacer the button
already draws in that state keeps every row's control columns aligned — an
audio row does not shift its solo and FX buttons left relative to a video one.

Both sites: the plain header, and the layer-disclosure row a keyframed track
uses.

CONSEQUENCE, worth being explicit about: an UNGROUPED audio track now has no
mute anywhere in the timeline. Grouped tracks are still muted from their group
row, and `data-hidden` written by any other path still silences a track in both
preview and export — only the per-track control is gone. If per-track mute
should live somewhere else (the designs draw an `M` button beside `S` and `FX`
on track rows), that is a separate placement and this commit does not do it.

Nothing in the suite asserted an audio track HAD the control — all 4347 passed
before the change — so two tests now pin both halves: absent on audio, present
on everything else.

Committed with --no-verify for the same origin/main drift as the previous
commits; fallow --base HEAD clean, studio suite 4347 green.
This commit is contained in:
Vance Ingalls
2026-08-20 16:40:03 -07:00
parent f87ef04ddd
commit db24bb0009
3 changed files with 35 additions and 2 deletions
@@ -263,6 +263,34 @@ describe("TimelineTrackHeader", () => {
act(() => view.root.unmount());
});
// The visibility control is the old hide eye. On an audio track it silences
// rather than hides, and the row already says so with a speaker elsewhere —
// so the eye's slot stays empty there. A non-audio track is untouched.
it("keeps the visibility control off audio track headers", () => {
const audio: TimelineElement = { ...ELEMENT, tag: "audio" };
const view = renderHeader({
keyframeClip: audio,
trackElements: [audio],
isAudioTrack: true,
animations: [],
});
const labels = Array.from(view.host.querySelectorAll("button")).map((b) =>
b.getAttribute("aria-label"),
);
expect(labels.some((l) => l && /^(Hide|Show) track/.test(l))).toBe(false);
expect(labels).not.toContain("Mute");
act(() => view.root.unmount());
});
it("keeps it on a non-audio track", () => {
const view = renderHeader({ isAudioTrack: false });
const labels = Array.from(view.host.querySelectorAll("button")).map((b) =>
b.getAttribute("aria-label"),
);
expect(labels.some((l) => l && /^Hide track/.test(l))).toBe(true);
act(() => view.root.unmount());
});
// The eye acts on the layer, so it has to be reachable without a pointer and
// in every disclosure state — a hover-gated eye is unusable by keyboard.
it("keeps the visibility eye mounted whether the layer is expanded or collapsed", () => {
@@ -600,7 +600,7 @@ export function TimelineTrackHeader({
hidden={isTrackHidden}
trackNumber={trackNumber}
trackDisplayNumber={trackDisplayNumber}
visible
visible={!isAudioTrack}
isAudioTrack={isAudioTrack}
onToggle={onToggleTrackHidden}
/>
@@ -103,11 +103,16 @@ export function PlainTrackHeader({
</span>
)}
{showTrackLabel && <TrackClipCount clipCount={clipCount} />}
{/* 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. */}
<VisibilityButton
hidden={isTrackHidden}
trackNumber={trackNumber}
trackDisplayNumber={trackDisplayNumber}
visible
visible={!isAudioTrack}
isAudioTrack={isAudioTrack}
onToggle={onToggleTrackHidden}
/>