Files
heygen-com__hyperframes/skills-manifest.json
T
Vance Ingalls 485c037dcf feat(studio,lint): carve targets voiceover groups — always, when plural (#3288)
* feat(core): route grouped audio through a group bus in preview

An audio element carrying `data-audio-group` no longer lands its gain on
the master bus directly — it feeds a per-group `GainNode` (built lazily on
first use, one per group id) which itself feeds master, so members of the
same group sum before the ear, ready for a group-level FX chain and
volume/mute in later steps. An id with no matching `<hf-audio-group>`
element still gets a plain, unprocessed bus rather than losing the track.

The group's own chain and volume lane are wired through the same
`attachElementFxChain`/`scheduleVolumeLane` every element already uses,
against the group's clock — composition time (design doc §1.3), since a
group has no `data-start` and a missing one parses as 0. The bus persists
across `stopAll()` (mirroring `_masterGain`'s own lifecycle) so replaying a
group does not rebuild its chain; only `destroy()` disposes it.

Render is untouched — stays flat until B4; `audio-groups` is still a 0%
canary so nothing ships this to a real composition without hand-authoring
`data-audio-group`.

Also: `audioGroupOf` (B1) crashed on any element lacking a real `tagName`/
`getAttribute` — exactly the shape of most `HTMLMediaElement` test doubles
in this suite, including this file's own `mockEl`. Made it tolerant, same
style as `readChain`'s existing guard in `runtime/audioFx.ts`.

`schedulePlayback` was already 110 lines pre-existing before this diff;
extracted `resolveDestination` and `handleSourceEnded` to shrink it to 92,
then suppressed the remainder (inherently sequential graph wiring, not a
decision tree) per the same precedent B2 used on `TimelineLogicalRow`.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* feat(studio,lint): carve targets voiceover groups — always, when plural

Plural voiceover carve now targets a group instead of naming each clip:
`resolveCarveSourceIds` (core `audioGroups.ts`) expands a group id to its
current members at analysis time, so a clip added to the group later is
covered without touching `sources`. The picker (`useFxCarve.ts`) offers a
grouped voice as one option instead of one row per member, tests overlap
as a union of member spans (a group overlaps the bed if ANY member does),
and prefers a qualifying group over its individual members in
`autoSourceIds`.

Picking two or more ungrouped voice clips in the carve flow now mints a
group behind them (`mintGroupId`, de-duped against every id in the
document) and writes `data-audio-group` on each picked clip atomically,
one undo entry — `createAudioGroupAndAssignMembers` in
`timelineTrackVisibility.ts` copies `setElementsHidden`'s multi-target
write shape. The DSP is untouched: `mixCarveSources` already sums
multiple sources correctly (verified in the design doc's own
investigation) — this only fixes the picker.

New lint rule `audio_carve_ungrouped_sources` (`packages/lint/src/rules/
media.ts`, alongside `audio_volume_double_automation`) warns when a
`data-fx-carve`'s `sources` names two or more plain clip ids instead of a
group — the shape that silently rots when a clip is added. `/hyperframes-
audio` states the same rule as an invariant, not a tip, with the grouped-
narration HTML example from the design doc.

The group-matching and auto-group logic (`withAutoGroupedSources`,
`collectCarveCandidates`) is split into `useFxCarveGrouping.ts` —
`useFxCarve.ts` was pushing past the 600-line cap. `resolveNextCarveSettings`
is deliberately NOT an `async function`: wrapping it in one would force a
microtask on every call, including the synchronous branch — the exact bug
`withAutoGroupedSources`'s own sync-when-possible contract exists to avoid,
and one caught via `propertyPanelAudioFxGroup.test.tsx` (10 failures)
before fixing it back to a plain function the caller conditionally awaits.

Also extracted `useEffectiveTimelineDuration` out of `App.tsx` and
`useRemoveBackground` out of `StudioRightPanel.tsx` (both pushed past 600
lines from an added prop wire), and decomposed `useFxCarve.ts`'s picker
IIFE to clear fallow's complexity gate.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 09:42:16 -07:00

86 lines
1.7 KiB
JSON

{
"source": "heygen-com/hyperframes",
"skills": {
"embedded-captions": {
"hash": "ab099eb22321e865",
"files": 138
},
"faceless-explainer": {
"hash": "5de02925f2431645",
"files": 24
},
"figma": {
"hash": "642cce1b5e211182",
"files": 2
},
"general-video": {
"hash": "16898c02677d09bc",
"files": 4
},
"hyperframes": {
"hash": "d64583e4aa8011f7",
"files": 17
},
"hyperframes-animation": {
"hash": "67d23c9507ebb1e8",
"files": 121
},
"hyperframes-audio": {
"hash": "819ab4e70d0f1cc9",
"files": 6
},
"hyperframes-cli": {
"hash": "3fa884269c43d7df",
"files": 11
},
"hyperframes-core": {
"hash": "74b9841f63c8f405",
"files": 20
},
"hyperframes-creative": {
"hash": "87bc09ec744313c8",
"files": 78
},
"hyperframes-keyframes": {
"hash": "6bc62531ecea9a52",
"files": 3
},
"hyperframes-registry": {
"hash": "d6dfcc8ceb7c8178",
"files": 12
},
"media-use": {
"hash": "1b0ce647f5c7df95",
"files": 152
},
"motion-graphics": {
"hash": "853ac75cbab69036",
"files": 23
},
"music-to-video": {
"hash": "39282be90a90f89f",
"files": 132
},
"pr-to-video": {
"hash": "337d781bd7517d2c",
"files": 30
},
"product-launch-video": {
"hash": "6d691b3db92e2c39",
"files": 29
},
"remotion-to-hyperframes": {
"hash": "79fcf8e9641d126e",
"files": 70
},
"slideshow": {
"hash": "c68fb51d48b80802",
"files": 2
},
"talking-head-recut": {
"hash": "2eaa83d6889537a5",
"files": 28
}
}
}