Files
hyperframes/packages/core
Vance Ingalls c12854b47c feat(core): ensureHfIds node-id pass + shared hf- mint helper (R1) (#1269)
## What

Adds `ensureHfIds(html: string): string` in `packages/core/src/parsers/hfIds.ts`. Single DOM pass (via linkedom) that mints a `data-hf-id` attribute on every eligible element before the caller sees the markup.

**Id derivation:** FNV-1a 32-bit hash of `tagName | sorted-attrs(\x00/\x01 separated) | ownText`, last 4 chars of base-36, `hf-` prefix. Collision resolution appends a sibling counter and re-hashes. Preserves existing ids (elements with `data-hf-id` already set are skipped). Excludes non-visual tags: `script`, `style`, `template`, `meta`, `link`, `noscript`, `base`.

**Fragment handling:** detects bare HTML fragments (no `<!doctype` / `<html`) and wraps in a full document shell before parsing, then returns `body.innerHTML` — matching the pattern used by `parseSourceDocument` in `sourceMutation`.

## Why

Counter-based ids (`element-0`, `element-1`, …) are positional. Inserting a new layer at position 0 shifts every id below it. The R1 milestone requires content-based, stable ids so that targeting operations (split, patch, probe) stay valid across re-parses and element insertions. T2 spec (`stableIds.test.ts`) defines the contract: same content → same id, adding a sibling doesn't change other ids, format matches `/^hf-[a-z0-9]{4}$/`.

## How

- `toHfId(hash)` — `slice(-4)` of `hash.toString(36)` for better distribution across the suffix space
- `data-hf-id` is excluded from the hash input (prevents circular dependency on the attribute being set)
- Already-assigned ids tracked in a `Set`; duplicates get a counter suffix before re-hashing
- Fragment detection: `/<!doctype|<html[\s>]/i.test(html)` — if bare, wrap→parse→`body.innerHTML`

## Test plan

- [x] T2 spec: `packages/core/src/parsers/stableIds.test.ts` — 7 tests pass (3 were `.fails` stubs targeting R1; 4 were pre-existing baselines that must not regress)
- [x] No changes to existing htmlParser tests needed at this layer (wiring is PR #1270)
2026-06-08 23:28:25 -07:00
..
2026-06-09 01:30:37 +00:00
2026-03-21 22:43:56 -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