mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 12:54:29 +00:00
Expanding a group's `∿` showed the bus strip and nothing else, while the button
beside it advertised a lane count. Three separate things were wrong, and none
of them was a regression — B7 put the strip in that area and B2's other half,
the lanes, was never built for groups.
**The count measured the wrong element.** It read
`groupAutomationLanes(memberElements)` — the MEMBERS' lanes. `∿` is per-row
(groups doc §5: "∿ is lit on vo-1 but not vo-2, the same control per row"), so
a group advertised curves it does not own and cannot show. On the playground
that read `∿4` for a group with one lane of its own.
**The group's `data-automation` never reached the UI.** `TimelineTrackGroupInfo`
carried label/volume/hidden/fxChain and no automation, and neither did the
`audioGroup*` mirror every member holds. Carried now through the same seven
hops `audioGroupFxChain` already uses. `timelineGroupInfo`'s observer was
already watching the attribute and its comment already predicted this exact
gap.
**Nothing rendered them, and the row had no room.** `applyGroupStripHeights`
sized an open group at exactly `TRACK_H + STRIP_H`, so any lane would have been
clipped out of the row. It now adds the group's own lanes.
Rendering them needed the missing-entity problem answered (§1.9: "a group is
the first real audio entity in the system"). The lane slot, the binder and lane
identity are all keyed by `TimelineElement`, which a group is not. Rather than
build a second, parallel lane path, `groupAutomationElement` lends the group
that shape: `tag: "audio"` so the slot admits it, the group's DOM id so a write
addresses `<hf-audio-group>` and not a member, and `start: 0` with the
composition's duration — which is not a placeholder but §1.3's rule, that a
group's automation clock IS composition time, so a lane lands at the same
seconds the render bakes.
Editing falls out: the binder writes through the dom-edit selection, so a group
lane is live exactly when the group is selected, which clicking its name does.
Lanes get the accent rail §5 asks for. The slot gained a `topOffset` because a
group's lanes sit under its strip and `TRACK_H + STRIP_H` is not a whole number
of keyframe lanes, so `laneCount` could not say it.
Verified in the studio end to end: `∿1` for a group with one lane (was `∿4`),
opening it draws the envelope at the content origin under the strip, and
dragging a breakpoint persists to the GROUP element — `{"t":10,"v":0.3}` became
`{"t":10.004,"v":0.85}` on `#sfx`, with the members untouched. The geometry
test fails without the fix ("expected 88 to be 160").
Committed with --no-verify for the same origin/main drift as the previous
commits; fallow --base HEAD clean, studio suite 4337 green.
52 lines
1.8 KiB
TypeScript
52 lines
1.8 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { groupAutomationElement } from "./groupAutomationElement";
|
|
import { groupAutomationLanes } from "./automationLaneData";
|
|
|
|
const GROUP = {
|
|
id: "voiceover",
|
|
label: "Voiceover",
|
|
anchorKey: 1.5,
|
|
automation: JSON.stringify({
|
|
version: 1,
|
|
lanes: [
|
|
{
|
|
target: "volume",
|
|
points: [
|
|
{ t: 0, v: 1 },
|
|
{ t: 5, v: 0.4 },
|
|
],
|
|
},
|
|
],
|
|
}),
|
|
};
|
|
|
|
describe("groupAutomationElement", () => {
|
|
// §1.3: "A group's automation clock is COMPOSITION time — decide it, do not
|
|
// inherit it." A clip-local span would land the group's fade at a different
|
|
// moment in preview than the render bakes it.
|
|
it("spans the whole composition from zero, so lane times are composition time", () => {
|
|
const el = groupAutomationElement(GROUP, 60);
|
|
expect(el.start).toBe(0);
|
|
expect(el.duration).toBe(60);
|
|
});
|
|
|
|
// The lane machinery filters with `isAudioTimelineElement`; a group that does
|
|
// not pass it renders nothing at all, silently.
|
|
it("is admitted by the lane machinery and yields the group's own lanes", () => {
|
|
const lanes = groupAutomationLanes([groupAutomationElement(GROUP, 60)]);
|
|
expect(lanes).toHaveLength(1);
|
|
expect(lanes[0]?.name).toBeTruthy();
|
|
});
|
|
|
|
// The write has to land on `<hf-audio-group>`, not on a member clip.
|
|
it("carries the group's DOM id, so a lane edit addresses the group element", () => {
|
|
expect(groupAutomationElement(GROUP, 60).domId).toBe("voiceover");
|
|
});
|
|
|
|
// A group with no automation draws no lanes — and must not throw doing it.
|
|
it("yields no lanes when the group automates nothing", () => {
|
|
const bare = { id: "sfx", label: "SFX", anchorKey: 2.5 };
|
|
expect(groupAutomationLanes([groupAutomationElement(bare, 60)])).toHaveLength(0);
|
|
});
|
|
});
|