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; }); }