mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 12:54:29 +00:00
fix(studio): half-lit group solo compares bare DOM ids too
A fifth face of the id-space mismatch, not in the review's list. TimelineGroupRow built its member list from `el.key ?? el.id` and handed it to isGroupHalfLitUnderSolo, which compares against the soloed set — bare DOM ids now that the header pushes them that way. Soloing a member lit nothing on its group, the one signal that says "some of what is under here still plays". Found by auditing every consumer of `soloed` rather than trusting the handoff's claim that this call site "happens to pass the bare id" — it passes a bare id for the GROUP and store keys for its MEMBERS. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
5850a0c978
commit
a8d27820ba
@@ -1,6 +1,7 @@
|
||||
import type { TimelineElement } from "../store/playerStore";
|
||||
import { usePlayerStore } from "../store/playerStore";
|
||||
import { isGroupHalfLitUnderSolo } from "../store/audioSoloSlice";
|
||||
import { runtimeAudioId } from "../lib/timelineElementHelpers";
|
||||
import {
|
||||
HF_AUDIO_FX_ATTR,
|
||||
serializeAudioFxChain,
|
||||
@@ -65,7 +66,10 @@ export function TimelineGroupRow({
|
||||
const domEditActions = useDomEditActionsContextOptional();
|
||||
const soloed = usePlayerStore((s) => s.soloed);
|
||||
const toggleSolo = usePlayerStore((s) => s.toggleSolo);
|
||||
const memberIds = memberElements.map((el) => el.key ?? el.id);
|
||||
// Bare DOM ids: this list is compared against the `soloed` set, which the
|
||||
// runtime matches on `el.id` (see `runtimeAudioId`). Store keys here made the
|
||||
// half-lit state unreachable — soloing a member lit nothing on its group.
|
||||
const memberIds = memberElements.map(runtimeAudioId).filter((id): id is string => id !== null);
|
||||
const writeGroupFxChain = (next: HfAudioFxChain, live: boolean) => {
|
||||
const value = next.nodes.length ? serializeAudioFxChain(next) : null;
|
||||
if (live) onSetAudioGroupAttributeLive?.(group.id, HF_AUDIO_FX_ATTR, value);
|
||||
|
||||
@@ -15,6 +15,7 @@ import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
audioGroupOf,
|
||||
isAudibleUnderSolo,
|
||||
isGroupHalfLitUnderSolo,
|
||||
resolveAudioGroups,
|
||||
} from "@hyperframes/core/audio-groups";
|
||||
import { parseTimelineFromDOM } from "./timelineDOM";
|
||||
@@ -95,6 +96,24 @@ describe("group membership ids cross into the runtime", () => {
|
||||
for (const id of memberIds) expect(clipIds).toContain(id);
|
||||
});
|
||||
|
||||
// TimelineGroupRow's half-lit state ("some of what's under here still
|
||||
// plays") compares its member list against the same soloed set. Built from
|
||||
// store keys it never matched, so soloing a member lit nothing on its group.
|
||||
it("half-lights the group when one member is soloed", () => {
|
||||
const doc = docWith(COMPOSITION);
|
||||
const members = parseTimelineFromDOM(doc, 30).filter((el) => el.audioGroup === "voiceover");
|
||||
const memberIds = members.map(runtimeAudioId).filter((id): id is string => id !== null);
|
||||
expect(memberIds).toEqual(["voice-1", "voice-2"]);
|
||||
|
||||
const soloed = new Set(["voice-1"]);
|
||||
expect(isGroupHalfLitUnderSolo(soloed, "voiceover", memberIds)).toBe(true);
|
||||
// Store keys are the shape that silently failed.
|
||||
const storeKeys = members.map((el) => el.key ?? el.id);
|
||||
expect(isGroupHalfLitUnderSolo(soloed, "voiceover", storeKeys)).toBe(false);
|
||||
// Soloing the group itself is lit, not half-lit.
|
||||
expect(isGroupHalfLitUnderSolo(new Set(["voiceover"]), "voiceover", memberIds)).toBe(false);
|
||||
});
|
||||
|
||||
it("an element with no DOM id is not groupable or soloable", () => {
|
||||
const doc = docWith(`
|
||||
<div data-composition-id="root" data-duration="10"></div>
|
||||
|
||||
Reference in New Issue
Block a user