mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-04 07:19:52 +00:00
A narration clip inside a Voiceover group had a carve pointed at that group — a member ducking the bus it feeds. Three faults, each sufficient on its own. **No bed-eligibility rule.** `couldBeCarveSource` has said since it was written that music and sfx cannot be sources, and it is called from nowhere — exported, tested, dead. Nothing ever asked the near-end question: can this track be the BED. `showCarve` only asked "is anything already carving against me, and is there anything to listen to", so a voice track was offered the control like any other. Added `couldBeCarveBed` beside its sibling and wired it in. **Offering is not applying.** A bed with exactly one candidate carves itself unasked, which is right for a track named `music-bed` and wrong for one named `a1` — a decision taken off a name that said nothing is how a carve appears that nobody remembers configuring. `isNamedCarveBed` gates self-application on a name that positively reads as a bed; the picker stays looser, the same split the source side already makes between `sourceOptions` and `autoSourceIds`. **A bed was offered its own group.** The candidate scan excluded exactly one element, the bed itself. Its siblings survived that filter and rolled up into the very group the bed belongs to, which came back as a candidate — and being the only one, was applied. The mirror case too: a group bed's id matches no <audio> id, so nothing stopped a group carving against itself. `collectCarveCandidates` now takes the bed's id and drops both it and its group. An existing carve still shows its module (`carve !== null`), so nothing already configured becomes unreachable — only newly offered and self-applied ones are refused. The bed/relationship predicates moved to `useFxCarveGrouping.ts`, next to the source-eligibility rules they belong with. That is also what puts `useFxCarve.ts` back under the 600-line ceiling it crossed here. Five tests, each mutation-checked against the pre-fix code. Verified live: selecting `vo-2` renders no carve module; `music-bed` still gets one, listening to `Voiceover (4)`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
56 lines
2.3 KiB
TypeScript
56 lines
2.3 KiB
TypeScript
// @vitest-environment happy-dom
|
|
import { describe, expect, it } from "vitest";
|
|
import { collectCarveCandidates } from "./useFxCarveGrouping";
|
|
|
|
function previewDoc(html: string): Document {
|
|
const doc = document.implementation.createHTMLDocument("preview");
|
|
doc.body.innerHTML = html;
|
|
return doc;
|
|
}
|
|
|
|
/** What the panel would offer as sources for `bedId`, the way useFxCarve asks. */
|
|
function candidatesFor(doc: Document, bedId: string) {
|
|
const others = Array.from(doc.querySelectorAll<HTMLAudioElement>("audio[id]")).filter(
|
|
(a) => a.id !== bedId,
|
|
);
|
|
return collectCarveCandidates(doc, others, () => true, bedId).map((c) => c.id);
|
|
}
|
|
|
|
const GROUPED_VOICES = `
|
|
<hf-audio-group id="voiceover" data-label="Voiceover"></hf-audio-group>
|
|
<audio id="vo-1" data-audio-group="voiceover"></audio>
|
|
<audio id="vo-2" data-audio-group="voiceover"></audio>
|
|
<audio id="music-bed"></audio>
|
|
`;
|
|
|
|
describe("collectCarveCandidates", () => {
|
|
// The observed bug: selecting vo-2 offered "Voiceover (2)" — the group vo-2 is
|
|
// itself a member of. The caller filters out the bed element, but vo-1 survives
|
|
// that filter and rolls up into exactly that group. Being the only candidate, it
|
|
// was then applied without the author asking: a member ducking the bus it feeds.
|
|
it("never offers a member the group it belongs to", () => {
|
|
expect(candidatesFor(previewDoc(GROUPED_VOICES), "vo-2")).toEqual(["music-bed"]);
|
|
});
|
|
|
|
// And the mirror: a group bed's own id matches no <audio> id, so nothing
|
|
// excluded it. Its members rolled up and handed the group back to itself.
|
|
it("never offers a group itself", () => {
|
|
const doc = previewDoc(GROUPED_VOICES);
|
|
expect(candidatesFor(doc, "voiceover")).toEqual(["music-bed"]);
|
|
});
|
|
|
|
it("still offers a group the bed has nothing to do with", () => {
|
|
const doc = previewDoc(`
|
|
${GROUPED_VOICES}
|
|
<hf-audio-group id="sfx" data-label="SFX"></hf-audio-group>
|
|
<audio id="sfx-click" data-audio-group="sfx"></audio>
|
|
`);
|
|
expect(candidatesFor(doc, "music-bed")).toEqual(["voiceover", "sfx"]);
|
|
});
|
|
|
|
it("leaves an ungrouped bed's candidates alone", () => {
|
|
const doc = previewDoc(`<audio id="music-bed"></audio><audio id="vo-1"></audio>`);
|
|
expect(candidatesFor(doc, "music-bed")).toEqual(["vo-1"]);
|
|
});
|
|
});
|