diff --git a/packages/studio/src/player/components/TimelineGroupHeader.tsx b/packages/studio/src/player/components/TimelineGroupHeader.tsx
index b36fdd7ea..64a1fa941 100644
--- a/packages/studio/src/player/components/TimelineGroupHeader.tsx
+++ b/packages/studio/src/player/components/TimelineGroupHeader.tsx
@@ -52,7 +52,10 @@ function GroupNameButton({
tabIndex={-1}
aria-label={`Open ${label} effects`}
title="Open effects"
- className="flex min-w-0 flex-1 items-center gap-1.5 rounded border-0 bg-transparent p-0 text-left text-[11px] text-white hover:text-[#3CE6AC] focus-visible:outline focus-visible:outline-1 focus-visible:outline-[#3CE6AC]"
+ // No `flex-1`: the row's control group owns the slack now (`ml-auto`), so
+ // claiming it here would push the controls off the right edge — and the
+ // count with them, since it rides inside this button.
+ className="flex h-6 min-w-0 items-center gap-1.5 rounded border-0 bg-transparent p-0 text-left text-[11px] text-white hover:text-[#3CE6AC] focus-visible:outline focus-visible:outline-1 focus-visible:outline-[#3CE6AC]"
onPointerDown={(event) => event.stopPropagation()}
onClick={(event) => {
event.stopPropagation();
@@ -70,10 +73,6 @@ function GroupNameButton({
>
{memberCount}
- {/* Eats the slack so the count sits beside the name rather than drifting
- to the far edge, while the button itself stays full width — the whole
- name line is the target that opens the rack. */}
-
);
}
@@ -98,7 +97,7 @@ export function TimelineGroupHeader({
- {/* Line one: what the row IS. The caret rides with the name because it
- discloses the name's contents. */}
-
-
- {/* Line two: what you can DO to it. Its own row so the name is not
- squeezed to a few characters by five controls sharing 232px. */}
-
+ {isExpanded ? "▾" : "▸"}
+
+
+ {/* `ml-auto` absorbs the slack the truncating name leaves, so the controls
+ sit on the edge whatever the name's length. */}
+
{
});
expect(laneToggle(automated.host)).toBeDefined();
});
+
+ // Same shape as a track header: caret and name on the left, every control in
+ // one right-anchored group. It was two lines — name, then controls — which is
+ // what let a stray child overflow the 48px box on the track side.
+ it("keeps the caret, the name and every control on one line", () => {
+ const { host } = renderRow({
+ fxChain: JSON.stringify({
+ version: 1,
+ nodes: [{ type: "peaking", id: "p1", params: { frequency: 1000, gain: -3, q: 1 } }],
+ }),
+ automation: JSON.stringify({
+ version: 1,
+ lanes: [{ target: "fx.p1.gain", points: [{ t: 0, v: 0 }] }],
+ }),
+ });
+ const header = host.querySelector('[role="rowheader"]');
+ // Caret, name, control group — no second line.
+ expect(header?.children).toHaveLength(3);
+ const controls = header?.lastElementChild as HTMLElement | null;
+ expect(controls?.className).toContain("ml-auto");
+ // Both controls live in that group, so they share the right edge.
+ expect(controls?.querySelectorAll("button")).toHaveLength(2);
+ // The member count rides with the name, not out with the controls.
+ expect(controls?.querySelector('[title="2 tracks"]')).toBeNull();
+ act(() => undefined);
+ });
});