Files
hyperframes/packages/studio
Vance Ingalls 730ea1eb06 fix(studio): stop the carve measuring the bed, reciprocating, and firing twice
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.
2026-08-20 16:41:19 -07:00
..

@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