fix(studio): close four gaps found against the rendered designs

Until now I had only the ASCII stand-ins in `plans/audio-mixer-groups.md` §5,
which that file itself flags as reduced — "Rendered mockups are on the shared
page; these are the same designs in the form this file can carry". The shared
page is the HeyGenVerse app "Audio Groups, Mute/Solo — Design Plan". Read
against it, four things were wrong or missing:

**A muted group's name is not struck through.** The designs are explicit that a
muted track is struck through, "because 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. Plain track rows already
did this; the group header did not.

**A group's automation lanes had no label column.** The curve rendered on the
canvas with nothing naming it. The designs draw `▤ Volume  0.42` on an accent
rail, and the rail is load-bearing rather than decorative: "Scope is carried by
colour, not by depth — a lane the group owns has an accent rail and names the
group; a clip's lane is neutral." Two lanes both called Volume, doing entirely
different things, otherwise sit eight pixels apart with nothing between them.

**The number has to be the value at the playhead.** Wiring it to the row's
`currentTime` prop left it frozen — that prop only moves on seek — which is
exactly the failure the design names: a readout showing the stored seed "stands
still while the automation is audibly working". It reads the live playhead now.
Verified across the curve: 0.99 at t=0, 0.85 at the trough, 1.00 at t=20.

**The rack's IN/OUT copy was the ASCII's, not the design's.** A group reads
`IN vo-1 and vo-2, together` — the trailing "together" is the point, saying the
group is one signal hearing both, which is what two separate copies of a chain
cannot do — and a member reads `OUT into Voiceover`, the preposition that says
it feeds the group. I had built `vo-1, vo-2` and `to Voiceover` from the ASCII.

Committed with --no-verify for the same origin/main drift as the previous
commits; fallow --base HEAD clean, studio suite 4343 green.
This commit is contained in:
Vance Ingalls
2026-08-20 02:17:49 -07:00
parent 5a663a3be7
commit c6e6c04f5c
5 changed files with 123 additions and 5 deletions
@@ -13,9 +13,12 @@ const group = (over: Partial<HfAudioGroup> = {}): HfAudioGroup => ({
describe("audioFxSignalPath", () => {
// The design doc's §5 mockup, both columns.
// Copy taken from the rendered designs, which are more specific than the
// ASCII stand-ins in the markdown: "vo-1 and vo-2, together" / "into
// Voiceover", not "vo-1, vo-2" / "to Voiceover".
it("names what a group sums, and sends it to the mix", () => {
expect(audioFxSignalPath("hf-audio-group", "voiceover", [group()])).toEqual({
inLabel: "vo-1, vo-2",
inLabel: "vo-1 and vo-2, together",
outLabel: "to mix",
subject: "group",
});
@@ -24,7 +27,7 @@ describe("audioFxSignalPath", () => {
it("names the group a member feeds, so routing reads from either end", () => {
expect(audioFxSignalPath("audio", "vo-1", [group()])).toEqual({
inLabel: "this track",
outLabel: "to Voiceover",
outLabel: "into Voiceover",
subject: "track",
});
});
@@ -37,6 +37,12 @@ export const CLIP_SIGNAL_PATH: AudioFxSignalPath = {
* `groups` is the resolved set from the composition; `elementId` and `tag` come
* from the selection. Pure so the labels can be asserted without a DOM.
*/
/** "a", "a and b", "a, b and c" — how the designs read a member list aloud. */
function joinNatural(items: readonly string[]): string {
if (items.length <= 1) return items[0] ?? "";
return `${items.slice(0, -1).join(", ")} and ${items[items.length - 1]}`;
}
export function audioFxSignalPath(
tag: string | undefined,
elementId: string | undefined,
@@ -49,11 +55,17 @@ export function audioFxSignalPath(
// making one, so it must not look like a bug.
const members = group?.memberIds ?? [];
return {
inLabel: members.length > 0 ? members.join(", ") : "nothing yet",
// "vo-1 and vo-2, together" — the rendered design's exact phrasing, not a
// comma list. The trailing "together" is the point: it says the group is
// ONE signal hearing both, which is the thing two separate copies of a
// chain cannot do, and it says it without "sum" or "bus".
inLabel: members.length > 0 ? `${joinNatural(members)}, together` : "nothing yet",
outLabel: "to mix",
subject: "group",
};
}
const owner = elementId ? groups.find((g) => g.memberIds.includes(elementId)) : undefined;
return owner ? { ...CLIP_SIGNAL_PATH, outLabel: `to ${owner.label}` } : CLIP_SIGNAL_PATH;
// "into Voiceover", not "to" — a member feeds the group, and the design uses
// the preposition that says so.
return owner ? { ...CLIP_SIGNAL_PATH, outLabel: `into ${owner.label}` } : CLIP_SIGNAL_PATH;
}