feat(studio,lint): carve targets voiceover groups — always, when plural - #3288
Open
vanceingalls wants to merge 1 commit into
Open
feat(studio,lint): carve targets voiceover groups — always, when plural#3288vanceingalls wants to merge 1 commit into
vanceingalls wants to merge 1 commit into
Conversation
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>
This was referenced Aug 16, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements B6 from
plans/audio-execution/B6-carve-groups.md: plural voiceover carve always targets a group instead of naming each clip.resolveCarveSourceIds(coreaudioGroups.ts) expands a group id to its current members at analysis time, so a clip added to the group later is covered without touchingsources. DSP untouched —mixCarveSourcesalready sums multiple sources correctly.autoSourceIds.mintGroupId, de-duped against every id in the document) and writesdata-audio-groupon each picked clip atomically, one undo entry (createAudioGroupAndAssignMembers, copiessetElementsHidden's multi-target write shape).audio_carve_ungrouped_sources(alongsideaudio_volume_double_automation): warns whendata-fx-carve'ssourcesnames two or more plain clip ids instead of a group./hyperframes-audiostates the same rule as an invariant with the grouped-narration HTML example from the design doc.Notable fix mid-implementation
resolveNextCarveSettingsis deliberately not anasync function— wrapping it in one forces a microtask on every call, including the synchronous branch, which is the exact bugwithAutoGroupedSources's sync-when-possible contract exists to avoid. Caught this viapropertyPanelAudioFxGroup.test.tsx(10 failures) during a complexity-driven refactor, fixed back to a plain function the caller conditionally awaits.Housekeeping
Extracted
useEffectiveTimelineDuration(App.tsx) anduseRemoveBackground(StudioRightPanel.tsx) — both pushed past the 600-line studio filesize cap from an added prop wire (onAutoGroupCarveSources). DecomposeduseFxCarve.ts's picker IIFE intouseFxCarveGrouping.tsto clear fallow's complexity gate.Stacks on and depends on:
Test plan
bun run buildcleanpackages/corefull suite: 2364/2364packages/studiofull suite: 4253/4253packages/lintfull suite: 524/524 (39 inmedia.test.ts, incl. 4 newaudio_carve_ungrouped_sourcescases)audioFx.ts)hyperframes preview(needs a running dev server / browser)🤖 Generated with Claude Code