mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
Review findings 9, 10, 11 plus the prune-sentinel tail item. All four are the group-first carve shape catching up with code written when `sources` held clip ids. **9 — the far-end guard was defeated by the shape lint asks for.** `carvesAgainst` matched a carve's `sources` against raw clip ids and never expanded a group. A plural carve now names a GROUP (that is what `audio_carve_ungrouped_sources` exists to push authors toward), so `.includes(memberId)` stopped matching: `carverAgainst` returned null, the carve module was offered on a voice clip a bed is already ducking against, and switching it on wrote a reciprocal carve — each side measuring audio the other is already attenuating. Sources are expanded through `resolveCarveSourceIds` first now, which `useFxCarve` already imported for exactly this. **10 — the bed was measured as one of its own voices.** A group expands to its CURRENT members, so once the bed joins the group its own carve names — one timeline drag — `resolveCarveVoices` accepted it: peaking notches at the bed's own spectral peaks and a duck envelope that dips whenever the bed is loud, written to `data-fx-chain` / `data-automation` and baked into the export. The bed's id is excluded at the analysis entry, not inside core's generic resolver, because the exclusion is a fact about this analysis and not about resolution. `excludedFor` only guards the picker while it is offering options. **11 — the await opened a double-fire window the `<= 1` / `!== 1` split cannot see across.** `setCarve` awaits group creation, which live-patches `data-audio-group` and calls `updateElement` per member — a store notification. React re-renders mid-flight and before the carve attribute is written, so the candidate count collapses 2 → 1 while `carve` is still null and the sibling single-candidate effect fires a SECOND concurrent `setCarve`: two read-modify-write saves of `data-fx-carve` against one file (lost update) and two analyse() runs — two decodes, two FFT passes, two competing chain/automation writes. One in-flight latch now guards both effects, applied only to AUTO decisions: a manual change from the panel stays interruptible. **Tail — the prune never ran for a group bed.** Its "is the timeline loaded" sentinel was `present.has(element.id)`, and a group is not a timeline element, so a bus carrying its own carve always bailed. It now accepts either proof. `useFxCarve.ts` would have gone to 619 lines, so the compile half — `mintCarveNodes`, `measureCarve`, `carveLanes`, `carveLaneFor` — moved to `useFxCarveNodes.ts` first. 580 -> 442 + 153, both under the cap, and the split is the natural one: that half is pure, the hook keeps effects and persistence. Three tests for 9, each verified against a revert. studio: 390 files.
@hyperframes/studio
Browser-based composition editor UI for Hyperframes. Provides a visual timeline, code editor, and live preview for building video compositions.
Install
npm install @hyperframes/studio
What it does
The studio is a React application with:
- Visual timeline — drag, resize, and arrange elements on tracks
- Code editor — edit HTML and GSAP scripts with CodeMirror (syntax highlighting, autocomplete)
- Live preview — see changes in real time as you edit
- Composition inspector — view and modify element properties
Development
The studio is embedded in the hyperframes preview command. To develop the studio UI itself:
cd packages/studio
bun run dev # Start Vite dev server
bun run build # Build for production
bun run typecheck # Type-check
Tech stack
- React 18/19, Zustand (state management)
- CodeMirror 6 (editor)
- Tailwind CSS (styling)
- Vite (bundler)
- Phosphor Icons
Documentation
Full documentation: hyperframes.heygen.com/packages/studio
Related packages
@hyperframes/core— types and parsers used by the editorhyperframes— CLI that serves the studio viahyperframes preview