diff --git a/packages/studio/src/player/components/TimelineAutomationLane.tsx b/packages/studio/src/player/components/TimelineAutomationLane.tsx
index 250cbc28a..7b423963e 100644
--- a/packages/studio/src/player/components/TimelineAutomationLane.tsx
+++ b/packages/studio/src/player/components/TimelineAutomationLane.tsx
@@ -616,8 +616,8 @@ export interface TimelineAutomationLaneSlotProps {
/** Keyframe lanes already stacked above, which automation sits under. */
laneCount: number;
/** Exact y for the first lane, overriding `laneCount`. A group's lanes sit
- * under its bus strip, and TRACK_H + STRIP_H is not a whole number of
- * keyframe lanes, so it cannot be said in `laneCount`. */
+ * directly under its header row rather than under a stack of keyframe
+ * lanes, so it cannot be said in `laneCount`. */
topOffset?: number;
accentColor: string;
/** Composition-time playhead; the slot converts it to clip-local. */
diff --git a/packages/studio/src/player/components/TimelineGroupBusStrip.test.tsx b/packages/studio/src/player/components/TimelineGroupBusStrip.test.tsx
deleted file mode 100644
index 1508855f9..000000000
--- a/packages/studio/src/player/components/TimelineGroupBusStrip.test.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-// @vitest-environment happy-dom
-import React, { act } from "react";
-import { createRoot, type Root } from "react-dom/client";
-import { afterEach, describe, expect, it } from "vitest";
-import { TimelineGroupBusStrip } from "./TimelineGroupBusStrip";
-
-(globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
-
-let container: HTMLDivElement;
-let root: Root;
-
-afterEach(() => {
- act(() => root.unmount());
- container.remove();
-});
-
-function renderStrip(memberLabels: readonly string[]) {
- container = document.createElement("div");
- document.body.append(container);
- root = createRoot(container);
- act(() => root.render());
-}
-
-describe("TimelineGroupBusStrip", () => {
- // "vo-1 and vo-2", the designs' own phrasing. A comma list reads as data;
- // this line is a sentence about what the group holds.
- it("joins the member labels as a sentence", () => {
- renderStrip(["vo-1", "vo-2"]);
- expect(container.textContent).toContain("Holdsvo-1 and vo-2");
- });
-
- it("keeps the commas beyond two names", () => {
- renderStrip(["vo-1", "vo-2", "vo-3"]);
- expect(container.textContent).toContain("vo-1, vo-2 and vo-3");
- });
-
- it("says so when a group holds nothing yet", () => {
- renderStrip([]);
- expect(container.textContent).toContain("Holdsnothing yet");
- });
-
- // The volume slider and the level meter were removed with mute and solo.
- // `data-volume` is still honoured by the preview bus and the render — there
- // is just no control for it here, and no level read back out of the graph.
- it("offers no volume control and no meter", () => {
- renderStrip(["vo-1"]);
- expect(container.querySelector("input")).toBeNull();
- expect(container.textContent).not.toMatch(/dB|Too loud/i);
- });
-});
diff --git a/packages/studio/src/player/components/TimelineGroupBusStrip.tsx b/packages/studio/src/player/components/TimelineGroupBusStrip.tsx
deleted file mode 100644
index 81e3fc79e..000000000
--- a/packages/studio/src/player/components/TimelineGroupBusStrip.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-/**
- * What a group's `∿` area says about the group itself: which tracks it holds.
- *
- * B7 also put a volume slider and a live level meter here — both removed. The
- * group's `data-volume` is still honoured by the preview bus and by the render;
- * there is simply no control for it on this row, and nothing reads a level back
- * out of the graph any more.
- */
-import { STRIP_H, TRACK_H } from "./timelineLayout";
-
-interface TimelineGroupBusStripProps {
- memberLabels: readonly string[];
-}
-
-export function TimelineGroupBusStrip({ memberLabels }: TimelineGroupBusStripProps) {
- // "vo-1 and vo-2", the designs' own phrasing — a comma list reads as data,
- // and this line is a sentence about what the group is holding.
- const holds =
- memberLabels.length > 1
- ? `${memberLabels.slice(0, -1).join(", ")} and ${memberLabels[memberLabels.length - 1]}`
- : (memberLabels[0] ?? "nothing yet");
-
- return (
-
- {/* Label and value, as the designs split them: "Holds" is chrome, the
- member list is the answer. */}
- Holds
-
- {holds}
-
-
- );
-}
diff --git a/packages/studio/src/player/components/TimelineGroupRow.tsx b/packages/studio/src/player/components/TimelineGroupRow.tsx
index c9102ef21..1d27189da 100644
--- a/packages/studio/src/player/components/TimelineGroupRow.tsx
+++ b/packages/studio/src/player/components/TimelineGroupRow.tsx
@@ -8,12 +8,11 @@ import type { TimelineTrackGroupInfo } from "./useTimelineTrackDerivations";
import type { TimelineLogicalRow } from "./timelineKeyboardNavigation";
import { TimelineTrackRow } from "./TimelineTrackRow";
import { TimelineGroupHeader } from "./TimelineGroupHeader";
-import { TimelineGroupBusStrip } from "./TimelineGroupBusStrip";
import { groupAutomationLanes } from "./automationLaneData";
import { groupAutomationElement } from "./groupAutomationElement";
import { TimelineAutomationLaneSlot } from "./TimelineAutomationLane";
import { TimelineGroupLaneLabels } from "./TimelineGroupLaneLabels";
-import { STRIP_H, TRACK_H } from "./timelineLayout";
+import { TRACK_H } from "./timelineLayout";
import type { UseAutomationLanesResult } from "./useAutomationLanes";
import { useDomEditSelectionContextOptional } from "../../contexts/DomEditContext";
@@ -86,10 +85,6 @@ export function TimelineGroupRow({
// its name in the header does.
const domSelection = useDomEditSelectionContextOptional()?.domEditSelection ?? null;
const isGroupSelected = domSelection?.id === group.id;
- const memberLabels = group.memberTracks.map((track, i) => {
- const owner = memberElements.find((el) => el.track === track && el.audioGroup);
- return owner?.label ?? owner?.id ?? `track ${i + 1}`;
- });
const isLaneOpen = expandedLaneOwnerIds.has(group.id);
// Optional, like every sibling row: Timeline renders outside the edit
// provider in read-only hosts (Timeline.test.ts asserts it), and the throwing
@@ -152,7 +147,6 @@ export function TimelineGroupRow({
columnWidth={contentOrigin >= LABEL_COL_W ? LABEL_COL_W : contentOrigin}
theme={theme}
/>
- {isLaneOpen && }
{/* The group's OWN curves, under the strip. Selected-gated exactly like a
clip's: the binder writes through the dom-edit selection, so a lane is
editable once the group is selected — which clicking its name does. */}
@@ -163,7 +157,7 @@ export function TimelineGroupRow({
= LABEL_COL_W ? LABEL_COL_W : contentOrigin}
gutterBackground={theme.gutterBackground}
accentColor={GROUP_LANE_ACCENT}
@@ -187,7 +181,7 @@ export function TimelineGroupRow({
pps={pps}
// Below the strip, which sits directly under the header row.
laneCount={0}
- topOffset={TRACK_H + STRIP_H}
+ topOffset={TRACK_H}
accentColor={GROUP_LANE_ACCENT}
currentTime={currentTime}
beatTimes={beatTimes}
diff --git a/packages/studio/src/player/components/timelineLayout.ts b/packages/studio/src/player/components/timelineLayout.ts
index 782e45cc8..74d201feb 100644
--- a/packages/studio/src/player/components/timelineLayout.ts
+++ b/packages/studio/src/player/components/timelineLayout.ts
@@ -7,7 +7,6 @@ export const GUTTER = 32;
export const LABEL_COL_W = 232;
export const TRACK_H = 48;
export const LANE_H = 28;
-export const STRIP_H = 40; // group bus strip (B7) — one fixed block, not per-property like LANE_H
export const RULER_H = 24;
export const CLIP_Y = 3;
export const CLIP_HANDLE_W = 18;
diff --git a/packages/studio/src/player/components/useTimelineTrackLayout.test.ts b/packages/studio/src/player/components/useTimelineTrackLayout.test.ts
index 2a8da0ab0..2b8587123 100644
--- a/packages/studio/src/player/components/useTimelineTrackLayout.test.ts
+++ b/packages/studio/src/player/components/useTimelineTrackLayout.test.ts
@@ -5,7 +5,7 @@ import { createRoot } from "react-dom/client";
import type { GsapAnimation } from "@hyperframes/core/gsap-parser";
import { afterEach, describe, expect, it, vi } from "vitest";
import { usePlayerStore, type TimelineElement } from "../store/playerStore";
-import { LANE_H, STRIP_H, TRACK_H } from "./timelineLayout";
+import { LANE_H, TRACK_H } from "./timelineLayout";
import { AUTOMATION_LANE_H } from "./automationLaneHeight";
import { getTimelinePropertyLanes } from "./TimelinePropertyLanes";
import { resolveTrackKeyframeClip, useTimelineTrackLayout } from "./useTimelineTrackLayout";
@@ -74,10 +74,10 @@ describe("collapsed audio groups", () => {
return { layout, unmount: () => act(() => root.unmount()) };
}
- // The `∿` area holds the bus strip (B7) AND the group's own automation rows
- // (B2). Sized for only the strip, every lane the count had just promised was
- // clipped out of the row — which is what "expanding automation on a group
- // doesn't show the automation" looked like from outside.
+ // The `∿` area holds the group's own automation rows. Sized without them,
+ // every lane the count had just promised was clipped out of the row — which
+ // is what "expanding automation on a group doesn't show the automation"
+ // looked like from outside.
it("reserves room for the group's own automation rows, not just the strip", () => {
enabledCanaries.add("audio-groups");
const automation = JSON.stringify({
@@ -110,8 +110,8 @@ describe("collapsed audio groups", () => {
const anchorIndex = layout!.tracks.findIndex(([track]) => track === -0.5);
expect(anchorIndex).toBeGreaterThanOrEqual(0);
const openHeight = layout!.rowHeights[anchorIndex];
- // One lane of headroom beyond header + strip.
- expect(openHeight).toBe(TRACK_H + STRIP_H + AUTOMATION_LANE_H);
+ // One lane of headroom beyond the header row itself.
+ expect(openHeight).toBe(TRACK_H + AUTOMATION_LANE_H);
act(() => root.unmount());
});
diff --git a/packages/studio/src/player/components/useTimelineTrackLayout.ts b/packages/studio/src/player/components/useTimelineTrackLayout.ts
index 6597f561f..46b4edeaa 100644
--- a/packages/studio/src/player/components/useTimelineTrackLayout.ts
+++ b/packages/studio/src/player/components/useTimelineTrackLayout.ts
@@ -7,7 +7,6 @@ import { usePlayerStore, type TimelineElement } from "../store/playerStore";
import type { DraggedClipState } from "./timelineClipDragTypes";
import { useTimelineTrackDerivations } from "./useTimelineTrackDerivations";
import {
- STRIP_H,
TRACK_H,
createTimelineRowGeometry,
type TimelineRowGeometry,
@@ -139,8 +138,8 @@ function computeLaneCounts(
/** Group anchor rows have no elements of their own (`groupTimelineTracks`
* pushes them as `[anchorKey, []]`), so `trackHeights` — which only ever
* looks at a row's clips — always gives them TRACK_H. Override those
- * specific rows post-hoc: TRACK_H while collapsed, +STRIP_H and the group's
- * own automation rows once its `∿` is open. */
+ * specific rows post-hoc: TRACK_H while collapsed, plus the group's own
+ * automation rows once its `∿` is open. */
function applyGroupStripHeights(
tracks: readonly (readonly [number, readonly TimelineElement[]])[],
rowHeights: number[],
@@ -152,10 +151,9 @@ function applyGroupStripHeights(
return tracks.map(([track], index) => {
const group = groupByAnchor.get(track);
if (!group || !expandedLaneOwnerIds.has(group.id)) return rowHeights[index] ?? TRACK_H;
- // The strip AND the group's own automation rows: `∿` discloses both (B7 put
- // the bus strip in this area, B2 put the lanes here), so a row sized for
- // only the strip clipped every lane it had just promised in the count.
- return TRACK_H + STRIP_H + groupOwnLaneCount(group) * AUTOMATION_LANE_H;
+ // The group's own automation rows, which its `∿` discloses. A row sized
+ // without them clipped every lane it had just promised in the count.
+ return TRACK_H + groupOwnLaneCount(group) * AUTOMATION_LANE_H;
});
}