Files
Miguel Ángel 3739e6e32a refactor(core): route both assembly paths through one owner, and settle four divergences
Rendering a composition and mounting one now derive root discovery, scope
identity, asset sources and order, hoisted links, variable carriers and
nested-host enumeration from the same module. Each keeps its own I/O, which is
where they genuinely differ. The compiler's local depth cap and root lookup and
the runtime's three pre-filtered head parameters are gone; the runtime hands over
the head node and lets the module decide what comes out of it.

Four divergences are settled here. Each changes behaviour, so each is stated with
what actually differs rather than with a description of the edit.

Inline head scripts. The compiler looped head scripts with a src branch and no
else, so an inline one was silently discarded on render while the runtime ran it.
The compiler was losing code, not holding a convention. Head and content scripts
now run through one loop, head first, order preserved. A non-templated
sub-composition with an inline head script went from zero collected scripts to
one, wrapped, with its body intact.

Link hoisting. Conditional on render, unconditional on mount, so a templated
sub-composition's webfont link was dropped in video and kept in preview. Hoisting
is the superset and matches what the author declared, so hoisting wins. A
templated composition with a stylesheet link in its head went from no external
links to that link. The parity fixture that previously recorded this shape as a
known exclusion now gates it instead.

Anonymous hosts. With a host naming no id, the compiler fell back to the first
declared composition and scoped to it; the mount left the content unflattened and
injected its stylesheet into the host head unscoped, so a composition's CSS leaked
into whatever mounted it. The compiler's answer wins. The mount now flattens like
every other mount and restores the declared id afterwards. The injected rule went
from a bare class selector to one scoped to the composition.

This flips an assertion that documented the old behaviour as intentional. Its
premise no longer holds. What that test actually cared about, the root and its
content being present under the host, still holds and is still asserted; the
claim that nothing was flattened is now false and the test asserts the scoping
instead.

Scope ids. The compiler splits the CSS scope id from the script composition id;
they differ only when a host names an id the content does not declare, and there
the scripts follow the declared id so their self-referencing queries resolve. The
runtime used one for both. The split wins: a host naming captions-comp over
content declaring captions now emits scripts bound to captions while its CSS
still scopes to captions-comp.

Left alone deliberately: the variable-carrier divergence and its TODO, recursion
on the mount path, and the three copies each of the flattened-root helper and the
id assignment. The first two are behaviour changes with their own units. The third
looks mergeable and is not cheaply, and it touches the instancing contract the
pixel harness guards.

Verified: core 1694 passing, producer 573 passing, the parity contract now nine
tests with fixtures gating the two divergences it can observe. Lint clean,
typecheck:runtime and the runtime preview guards clean, package cycles unchanged.

A trap worth recording: the parity test's compiler arms import core's built dist
while the mount arm imports source, so core must be rebuilt before that lane means
anything after a compiler change. Skipping it produces a phantom divergence.
2026-08-07 21:30:26 +00:00
..
2026-08-07 15:31:57 +00: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