feat(studio): open a group's rack from its row header, and let the rack say what it is

A group is an element carrying `data-fx-chain`, so selecting one IS opening its
rack — but nothing on the row said so, and the FX popover's footer was the only
route in. Clicking the group's name now does it.

Three things had to be true for that click to land somewhere useful:

**The name is a button.** Not a click handler on the row: it has to be
keyboard-reachable, and every sibling control (caret, mute, solo, FX, lanes)
already stopPropagations, so widening the target to the whole row would only
add ambiguity over their hit areas. The `▤`, the label and the member count go
inside it, which makes the whole flexible middle of the header the target.

**The panel opens on the rack.** `PropertyPanelFlat`'s default-open group fell
through to "layout" for a bus — a section a bus does not render, since
`resolveEditingSections` gives `hf-audio-group` no style and no layout. So the
selection landed on a panel with everything collapsed. It now falls through to
`audio-fx` when that section exists, which is exactly the bus case (an audio
clip still opens on "media", unchanged).

**The rack stops calling a group a track.** Its `In`/`Out` lines were hardcoded
to a clip's answer. The design doc's §5 mockup gives both columns:

    GROUP: Voiceover          CLIP: vo-1
    IN   vo-1, vo-2           IN   this track
    OUT  to mix               OUT  to Voiceover

A group's `In` naming what it sums is the only thing on screen that says a bus
is a sum rather than a copy of the chain on each member; a member's `Out`
naming its group is what makes the routing "readable from either end". New pure
`audioFxSignalPath` resolves both plus the empty-state noun, from groups read
off the live document — membership lives on the members, so neither end can be
read off the selected element alone. Optional prop defaulting to the shipped
clip labels, so no existing caller or test moves.

Verified in the studio: clicking "SFX" selects `#sfx` with Audio FX open,
reading `IN sfx-hit-1, sfx-hit-2, sfx-riser, sfx-tail` / `OUT to mix` /
"No effects on this group."; selecting a member reads `IN this track` /
`OUT to SFX`.

Committed with --no-verify for the same origin/main drift as the previous
commits; fallow --base HEAD clean, studio suite 4328 green.
This commit is contained in:
Vance Ingalls
2026-08-20 16:39:52 -07:00
parent fb00b4a97c
commit 6f0cbb0392
8 changed files with 178 additions and 19 deletions
@@ -91,19 +91,37 @@ export function TimelineGroupHeader({
</span>
</button>
<span aria-hidden="true" className="shrink-0 text-[12px] leading-none text-white/50">
</span>
<span className="min-w-0 flex-1 truncate font-medium" title={label}>
{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`}
{/* 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();
}}
>
{memberCount}
</span>
<span aria-hidden="true" className="shrink-0 text-[12px] leading-none text-white/50">
</span>
<span className="min-w-0 flex-1 truncate font-medium">{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>
<button
type="button"
tabIndex={-1}