Files
hyperframes/packages/core
Vance IngallsandClaude Opus 5 e3a6ef3116 fix(studio,core): unify the audio id space, emit group elements, gate both canaries
Six of the fifteen findings from the max-effort review of the audio
groups / mute-solo / pitch-shift stack. Nothing in the stack is merged;
this sits on top of wa-24-timeline-fx.

The id space (findings 1-3). Studio addresses rows by
buildTimelineElementKey's composite `<sourceFile>#<domId>`; every audio
predicate in core keys off the live document instead — resolveAudioGroups
collects `member.id`, isAudibleUnderSolo compares `el.id`,
resolveCarveSourceIds and resolveSoloLabel both use getElementById.
Nobody checked the boundary, so:

  * solo put a composite key in the set the runtime matches against
    `el.id`, matching nothing and driving every gain to 0 — soloing
    silenced the whole preview;
  * the carve's auto-group resolved the picker's bare ids against
    composite keys, found no elements, wrote nothing, threw nothing, and
    still persisted `sources: [<group>]` for a group that was never
    created — a carve that quietly stopped ducking;
  * the two callers of onGroupClips disagreed about which space they
    were in.

Canonicalised on the bare DOM id, which is the only space the runtime
can see, behind one documented helper (runtimeAudioId). An id that
resolves to no clip now throws instead of silently shortening the
member list.

The group element (finding 4). Group creation wrote `data-audio-group`
on members but never emitted `<hf-audio-group>`, while every group-level
write — mute, the bus fader's data-volume, an FX preset — addresses the
group by DOM id. Groups the product created were exactly the groups
nothing could edit. Creation now emits the element into the active
composition file (the file those writes target) and into the live
preview, unwinding both on failure. Group ids are validated before being
interpolated into markup.

The canary leaks (findings 5-6). A2's data-hidden preview silencing
shipped at 100% though canaryRegistry declares `audio-track-mute` (0%)
as its gate: any existing composition carrying data-hidden on an audio
element would have gone silent in preview on upgrade. Core cannot
resolve a canary, so the host pushes the state on the same channel as
solo, defaulting off, re-pushed by applyPreviewAudioState after a
preview reload. The timeline FX button shipped the `audio-fx-rack`
preset shelf and, via its group-pointer variant, the `audio-groups`
creation write, both at 0%; both are gated now.

Tests. Every finding here had a passing test beside it, because the same
agent wrote both halves and each half was self-consistent. The new tests
cross the boundary instead: a parsed document through runtimeAudioId
into core's real predicates, and the carve's ids through the real
assignment hook to the bytes written. Each was mutation-checked against
the pre-fix code.

Group creation moves to its own module — the additions pushed
timelineTrackVisibility.ts past the 600-line ceiling. Also swaps two raw
NUL bytes in useFxCarve.ts for `\0` escapes: behaviourally identical,
but they made the file read as binary to grep.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 02:13:58 -07:00
..

@hyperframes/core

Types, parsers, generators, compiler, linter, runtime, and frame adapters for the Hyperframes video framework.

Install

npm install @hyperframes/core

Most users don't need to install core directly — the CLI, producer, and studio packages depend on it internally.

What's inside

Module Description
Types TimelineElement, CompositionSpec, Asset, canvas dimensions, defaults
Parsers parseHtml — extract timeline elements from HTML; parseGsapScript — parse GSAP animations
Generators generateHyperframesHtml — produce valid Hyperframes HTML from a composition spec
Compiler compileTimingAttrs — resolve data-start / data-duration into absolute times
Linter lintHyperframeHtml — validate Hyperframes HTML (missing attributes, overlapping tracks, etc.)
Runtime IIFE script injected into the browser — manages seek, media playback, and the window.__hf protocol
Frame Adapters Pluggable animation drivers (GSAP, Lottie, CSS, or custom)

Frame Adapters

A frame adapter tells the engine how to seek your animation to a specific frame:

import { createGSAPFrameAdapter } from "@hyperframes/core";

const adapter = createGSAPFrameAdapter({
  getTimeline: () => gsap.timeline(),
  compositionId: "my-video",
});

Implement FrameAdapter for custom animation runtimes:

import type { FrameAdapter } from "@hyperframes/core";

const myAdapter: FrameAdapter = {
  id: "my-adapter",
  getDurationFrames: () => 300,
  seekFrame: (frame) => {
    /* seek your animation */
  },
};

Parsing and generating HTML

import { parseHtml, generateHyperframesHtml } from "@hyperframes/core";

const { elements, metadata } = parseHtml(htmlString);
const html = generateHyperframesHtml(spec);

Linting

import { lintHyperframeHtml } from "@hyperframes/core/lint";

const result = lintHyperframeHtml(htmlString);
// result.findings: { severity, message, elementId }[]

Documentation

Full documentation: hyperframes.heygen.com/packages/core