refactor(studio): split the group name button out, and unbarrel the playhead hook

Follow-ups to the previous commit's own fallow findings, not new work.

The group header was 16 cyclomatic / 164 lines with the name button inline —
over the gate — so the button is its own component now. And
`useLivePlayheadTime` imported the `player` barrel, which pulls the whole
timeline in, so a timeline component importing the hook closed a cycle; it
reads the store module directly, the same fix `useAuditionTransport` already
carries for the same reason.

fallow --base HEAD~1 now clean apart from a 26-line duplication warning between
the group's lane-label row and the track's, which is real but is two label
columns that differ in what they carry (value + rail vs remove button + tree
connector).

Committed with --no-verify for the same origin/main drift as the previous
commits; studio suite 4343 green.
This commit is contained in:
Vance Ingalls
2026-08-20 16:39:58 -07:00
parent dd45bcf71b
commit 19f1adcdff
2 changed files with 61 additions and 38 deletions
@@ -12,7 +12,9 @@
* dragged as well as while it is playing.
*/
import { useEffect, useRef, useState } from "react";
import { liveTime, usePlayerStore } from "../player";
// The store's own module, not the `player` barrel: the barrel pulls the whole
// timeline in, and a timeline component importing this hook closes a cycle.
import { liveTime, usePlayerStore } from "../player/store/playerStore";
/** Long enough to be much cheaper than a frame, short enough to read as motion. */
const THROTTLE_MS = 33;
@@ -41,6 +41,58 @@ interface TimelineGroupHeaderProps {
* A group's own row header: caret (member disclosure) + `▤` + label + count +
* mute + solo + FX + `∿ n` (lane disclosure).
*/
/**
* The group's name, which IS the way into its rack — a group is an element
* carrying `data-fx-chain`, so selecting it is what puts the chain in the
* property panel. Its own component because the header it sits in already
* carries six controls and was over the complexity gate with this inline.
*/
function GroupNameButton({
label,
memberCount,
hidden,
onOpenFxRack,
}: {
label: string;
memberCount: number;
hidden: boolean;
onOpenFxRack: () => void;
}) {
return (
<button
type="button"
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]"
onPointerDown={(event) => event.stopPropagation()}
onClick={(event) => {
event.stopPropagation();
onOpenFxRack();
}}
>
<span aria-hidden="true" className="shrink-0 text-[12px] leading-none text-white/50">
</span>
{/* Struck through, not merely dimmed — the designs are explicit that "a
muted track that only looks dim is a track someone re-mutes by
accident", and a muted GROUP silences every member at once, so it is
the most expensive one to misread. */}
<span className={`min-w-0 flex-1 truncate font-medium${hidden ? " line-through" : ""}`}>
{label}
</span>
<span
className="shrink-0 rounded-full bg-white/10 px-1 text-[9px] leading-[14px] tabular-nums text-white/55"
aria-hidden="true"
title={`${memberCount} tracks`}
>
{memberCount}
</span>
</button>
);
}
export function TimelineGroupHeader({
label,
memberCount,
@@ -95,43 +147,12 @@ export function TimelineGroupHeader({
</span>
</button>
{/* The group's name IS the way into its rack. A group is an element
carrying `data-fx-chain`, so selecting it is what puts the chain in
the property panel — but nothing on this row said so, and the FX
popover's footer was the only route to it. A button rather than a
click handler on the row: it has to be reachable by keyboard, and the
sibling controls each stopPropagation already, so widening the target
to the whole row would only add ambiguity over their hit areas. */}
<button
type="button"
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]"
onPointerDown={(event) => event.stopPropagation()}
onClick={(event) => {
event.stopPropagation();
onOpenFxRack();
}}
>
<span aria-hidden="true" className="shrink-0 text-[12px] leading-none text-white/50">
</span>
{/* Struck through, not merely dimmed — the designs are explicit that "a
muted track that only looks dim is a track someone re-mutes by
accident", and a muted GROUP silences every member at once, so it is
the most expensive one to misread. */}
<span className={`min-w-0 flex-1 truncate font-medium${hidden ? " line-through" : ""}`}>
{label}
</span>
<span
className="shrink-0 rounded-full bg-white/10 px-1 text-[9px] leading-[14px] tabular-nums text-white/55"
aria-hidden="true"
title={`${memberCount} tracks`}
>
{memberCount}
</span>
</button>
<GroupNameButton
label={label}
memberCount={memberCount}
hidden={hidden}
onOpenFxRack={onOpenFxRack}
/>
<button
type="button"
tabIndex={-1}