Conversation
- README with hero section, quick start, HTML schema example, package overview, and Remotion comparison - MIT LICENSE (copyright HeyGen) - CONTRIBUTING.md with dev setup, commit conventions, and project structure - GitHub issue templates (bug report, feature request) and PR template - .gitignore for Node.js/TypeScript projects Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
| *.mp4 | ||
| *.webm | ||
| *.mov |
There was a problem hiding this comment.
we may have some regression tests with videos in them using git lfs something we may want to allow
|
|
||
| - [Node.js](https://nodejs.org/) 22+ | ||
| - [pnpm](https://pnpm.io/) 9+ | ||
| - [FFmpeg](https://ffmpeg.org/) (for rendering) |
There was a problem hiding this comment.
we need a version here probably ffmpeg, can we check what version
| 3. Install dependencies: `pnpm install` | ||
| 4. Create a branch: `git checkout -b my-feature` | ||
|
|
||
| ## Development Setup |
There was a problem hiding this comment.
should we wait to add in this info/readme now or wait till we have more code in here?
| ## Packages | ||
|
|
||
| | Package | Description | | ||
| |---------|-------------| | ||
| | `@hyperframes/core` | Types, schema, parsers, compiler, runtime, frame adapters | | ||
| | `@hyperframes/cli` | `npx hyperframes dev \| render \| validate \| init` | | ||
| | `@hyperframes/producer` | Local rendering engine (Node.js + Puppeteer + FFmpeg) | | ||
| | `@hyperframes/studio` | Browser-based preview/editor | | ||
| | `@hyperframes/mcp` | MCP server for AI agent integration | | ||
| | `create-hyperframe` | Project scaffolding (`npx create-hyperframe`) | |
There was a problem hiding this comment.
maybe we don't add this yet ? and have WIP/reminder to do this later
|
|
||
| ## Documentation | ||
|
|
||
| Visit [hyperframes.dev](https://hyperframes.dev) for full documentation, guides, and API reference. |
- .gitignore: remove blanket video file ignores (may need LFS for regression test fixtures) - CONTRIBUTING.md: strip dev setup details until packages are ported (leave TODO) - README.md: strip packages table, comparison, requirements, docs link (leave TODO) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
vanceingalls
left a comment
There was a problem hiding this comment.
Overall this looks good — clean scaffolding, second commit addressed the earlier review comments well. A few things to consider before going public:
1. Repo URL hardcoded to heygen-com — CONTRIBUTING.md references https://github.com/heygen-com/hyperframes/issues. The launch plan listed GitHub org name as an open decision (hyperframes, hyperframes-dev, or under HeyGen org). Not a blocker for merging into a private repo, but worth deciding before launch.
2. .gitignore gaps — Missing .debug/ (producer's parity harness writes to .debug/parity-harness-ci) and *.tgz (npm pack artifacts). Minor.
3. No pnpm-workspace.yaml stub — Plan calls for a pnpm monorepo. Including the workspace config now (even with empty packages list) would make the next PR cleaner.
4. Code of Conduct — "Be respectful. We're building something together." is fine for now, but GitHub's community profile will flag it. Consider adding Contributor Covenant before going public.
5. SECURITY.md — Not needed yet, but expected for any serious OSS project before launch. How to report vulnerabilities, responsible disclosure, etc.
None of these are merge blockers — they're all "before flipping to public" items. 👍
- CODE_OF_CONDUCT.md (Contributor Covenant v2.1) - SECURITY.md (responsible disclosure policy) - pnpm-workspace.yaml stub for monorepo - .gitignore: add .debug/ and *.tgz - CONTRIBUTING.md: link to Code of Conduct Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
bugfixes for hyperframes producer
- Eliminate redundant extractGsapWindows() call (was parsing each script twice, now once) - Fix window shadowing global — renamed to win, consistent with line 655 - Split classAttr once instead of twice - Named ClipInfo type for the selector map - Moved clip map construction before the script loop (computed once) - Removed orphan block scope and #1.5 numbering Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Eliminate redundant extractGsapWindows() call (was parsing each script twice, now once) - Fix window shadowing global — renamed to win, consistent with line 655 - Split classAttr once instead of twice - Named ClipInfo type for the selector map - Moved clip map construction before the script loop (computed once) - Removed orphan block scope and #1.5 numbering Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Eliminate redundant extractGsapWindows() call (was parsing each script twice, now once) - Fix window shadowing global — renamed to win, consistent with line 655 - Split classAttr once instead of twice - Named ClipInfo type for the selector map - Moved clip map construction before the script loop (computed once) - Removed orphan block scope and #1.5 numbering Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Eliminate redundant extractGsapWindows() call (was parsing each script twice, now once) - Fix window shadowing global — renamed to win, consistent with line 655 - Split classAttr once instead of twice - Named ClipInfo type for the selector map - Moved clip map construction before the script loop (computed once) - Removed orphan block scope and #1.5 numbering
…eadless-shell The engine assumed any binary passed via PRODUCER_HEADLESS_SHELL_PATH supported the HeadlessExperimental.beginFrame CDP command. When the CLI resolved system Chrome (e.g. /usr/bin/google-chrome) instead of chrome-headless-shell, the render would silently hang for 120s then timeout — the #1 new-user friction point. Now checks the binary path for "chrome-headless-shell" before selecting beginframe capture mode. System Chrome falls back to screenshot mode which works universally. Reproducer: # On a machine with system Chrome but no chrome-headless-shell cached npx hyperframes init test --template blank --non-interactive cd test && npx hyperframes render --output out.mp4 # Was: 120s hang, then "Timed out after waiting 120000ms" # Now: renders successfully via screenshot mode
…eadless-shell The engine assumed any binary passed via PRODUCER_HEADLESS_SHELL_PATH supported the HeadlessExperimental.beginFrame CDP command. When the CLI resolved system Chrome (e.g. /usr/bin/google-chrome) instead of chrome-headless-shell, the render would silently hang for 120s then timeout — the #1 new-user friction point. Now checks the binary path for "chrome-headless-shell" before selecting beginframe capture mode. System Chrome falls back to screenshot mode which works universally. Reproducer: # On a machine with system Chrome but no chrome-headless-shell cached npx hyperframes init test --template blank --non-interactive cd test && npx hyperframes render --output out.mp4 # Was: 120s hang, then "Timed out after waiting 120000ms" # Now: renders successfully via screenshot mode
Only one cursor may be visible at a time. Multiple cursors on screen looks broken. Every other cursor must be cursor-hide. Promoted to rule #1 in the cursor section. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(skills): add gsap-effects skill with typewriter pattern
Distills typewriter text animation into a reusable reference:
basic typewriter, blinking cursor, word rotation, appending words,
and a characters-per-second timing guide. Uses GSAP TextPlugin.
Also references the new skill from compose-video.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(skills): emphasize cursor must always blink when idle and sit flush
Two key rules added to the typewriter skill:
1. Cursor must blink in every idle state (after typing, after clearing,
during hold pauses) — a solid idle cursor looks broken.
2. No whitespace between text and cursor elements in HTML — any gap
between the last character and the caret looks wrong.
Also adds cursor-hide state for multi-line handoffs and updates word
rotation example to include cursor state management.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(skills): backspace must delete from end, not front
TextPlugin's text:{value:""} removes characters from the front,
which looks wrong. Added a backspace helper that steps through
substrings from right to left using tl.call(). Updated word
rotation example to use it.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(skills): handoffs must blink before typing, use margin for spacing
Two lessons from testing:
1. Cursor handoffs need a blink pause — going hide→solid directly
skips the idle state. Pattern: hide→blink→pause→solid→type→blink.
2. Use margin-left on a wrapper span for spacing between static and
dynamic text. Flex gap spaces the cursor away, trailing spaces
collapse.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(skills): enforce single visible cursor as a hard rule
Only one cursor may be visible at a time. Multiple cursors on
screen looks broken. Every other cursor must be cursor-hide.
Promoted to rule #1 in the cursor section.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…209) ## Summary Two independent initiatives that improve agent DX and expand HyperFrames' reach. ### Initiative 1: Fix the Clip Animation Footgun - `gsap_animates_clip_element` lint rule now uses smart detection — only errors when GSAP animates `visibility` or `display` on a clip element - All other properties (opacity, transform, x, y, scale, etc.) are allowed silently - This was the #1 agent failure in QA (10/10 agents hit it on v0.2.1) ### Initiative 2: `<hyperframes-player>` Web Component - New `@hyperframes/player` package — zero dependencies, 3.3KB gzipped - Iframe-based web component with Shadow DOM for perfect isolation - Video-like API: `play()`, `pause()`, `seek()`, `currentTime`, `duration`, events - Controls overlay with play/pause, scrubber (mouse + touch), time display, auto-hide - Full docs page at `docs/packages/player.mdx` ## Before / After ### Clip animation lint **Before (10/10 agents hit this):** ``` ✗ gsap_animates_clip_element: GSAP animation targets a clip element. Selector "#title" resolves to element <div id="title" class="clip">. The framework manages clip visibility — animate an inner wrapper instead. Fix: Wrap content in a child <div> and target that with GSAP. ``` **After (only errors on actual conflicts):** ``` # This passes lint — no error: tl.from("#title", { opacity: 0, y: -50, scale: 0.8 }, 0); # This still errors — actual conflict with runtime: tl.to("#title", { visibility: "hidden" }, 3); ✗ gsap_animates_clip_element: GSAP animation sets visibility on a clip element. Fix: Remove the visibility/display tween. Use opacity for fade effects. ``` ### Embeddable player **Before:** No way to embed a composition in a web page. **After:** ```html <script src="https://cdn.jsdelivr.net/npm/@hyperframes/player"></script> <hyperframes-player src="./composition/index.html" controls></hyperframes-player> ``` ```js const player = document.querySelector('hyperframes-player'); player.play(); player.pause(); player.seek(2.5); player.addEventListener('ready', (e) => console.log('Duration:', e.detail.duration)); ``` ## Test plan - [x] 427 core tests pass (20 GSAP lint tests with smart detection) - [x] 7 player tests pass (formatTime + element registration) - [x] TypeScript compiles cleanly (core + player) - [x] Lint: GSAP animating clip with safe props → 0 errors - [x] Lint: GSAP animating clip with `visibility` → 1 error (correct) - [x] Player builds to 3.3KB gzipped ESM - [x] Lockfile updated for CI - [x] Docs page added at `docs/packages/player.mdx`
…ogger test Adds a standalone smoke harness for the HDR encode pipeline plus a guard against the silent-decode-failure regression in blitHdrVideoLayer. - packages/producer/scripts/hdr-smoke.ts: end-to-end harness that renders the hdr-pq and mixed-sdr-hdr regression fixtures, then ffprobes both stream-level and frame-level side-data so we can catch missing MasteringDisplay / MaxCLL SEI in CI without hand-running ffprobe. Frame probe uses -show_frames -read_intervals %+#1 since x265 emits HDR metadata as in-band SEI prefix NAL units, not container-level boxes. - renderOrchestrator.test.ts: new "logs decode errors via the supplied logger" test pinning blitHdrVideoLayer's failure path to log.warn (matches production behaviour — silent failures were the original PR-314 review concern). - renderOrchestrator.ts: doc-only — clarify that blitHdrVideoLayer is exported so the time→frame math, last-frame freeze, border- radius detection, and affine-vs-region branch can be unit tested without spinning up the full producer. - .gitignore: ignore packages/producer/tests/hdr-regression/_renders and the generated hdr-full-demo workdir so smoke runs don't pollute git status.
…ogger test Adds a standalone smoke harness for the HDR encode pipeline plus a guard against the silent-decode-failure regression in blitHdrVideoLayer. - packages/producer/scripts/hdr-smoke.ts: end-to-end harness that renders the hdr-pq and mixed-sdr-hdr regression fixtures, then ffprobes both stream-level and frame-level side-data so we can catch missing MasteringDisplay / MaxCLL SEI in CI without hand-running ffprobe. Frame probe uses -show_frames -read_intervals %+#1 since x265 emits HDR metadata as in-band SEI prefix NAL units, not container-level boxes. - renderOrchestrator.test.ts: new "logs decode errors via the supplied logger" test pinning blitHdrVideoLayer's failure path to log.warn (matches production behaviour — silent failures were the original PR-314 review concern). - renderOrchestrator.ts: doc-only — clarify that blitHdrVideoLayer is exported so the time→frame math, last-frame freeze, border- radius detection, and affine-vs-region branch can be unit tested without spinning up the full producer. - .gitignore: ignore packages/producer/tests/hdr-regression/_renders and the generated hdr-full-demo workdir so smoke runs don't pollute git status.
…ogger test Adds a standalone smoke harness for the HDR encode pipeline plus a guard against the silent-decode-failure regression in blitHdrVideoLayer. - packages/producer/scripts/hdr-smoke.ts: end-to-end harness that renders the hdr-pq and mixed-sdr-hdr regression fixtures, then ffprobes both stream-level and frame-level side-data so we can catch missing MasteringDisplay / MaxCLL SEI in CI without hand-running ffprobe. Frame probe uses -show_frames -read_intervals %+#1 since x265 emits HDR metadata as in-band SEI prefix NAL units, not container-level boxes. - renderOrchestrator.test.ts: new "logs decode errors via the supplied logger" test pinning blitHdrVideoLayer's failure path to log.warn (matches production behaviour — silent failures were the original PR-314 review concern). - renderOrchestrator.ts: doc-only — clarify that blitHdrVideoLayer is exported so the time→frame math, last-frame freeze, border- radius detection, and affine-vs-region branch can be unit tested without spinning up the full producer. - .gitignore: ignore packages/producer/tests/hdr-regression/_renders and the generated hdr-full-demo workdir so smoke runs don't pollute git status.
…ogger test Adds a standalone smoke harness for the HDR encode pipeline plus a guard against the silent-decode-failure regression in blitHdrVideoLayer. - packages/producer/scripts/hdr-smoke.ts: end-to-end harness that renders the hdr-pq and mixed-sdr-hdr regression fixtures, then ffprobes both stream-level and frame-level side-data so we can catch missing MasteringDisplay / MaxCLL SEI in CI without hand-running ffprobe. Frame probe uses -show_frames -read_intervals %+#1 since x265 emits HDR metadata as in-band SEI prefix NAL units, not container-level boxes. - renderOrchestrator.test.ts: new "logs decode errors via the supplied logger" test pinning blitHdrVideoLayer's failure path to log.warn (matches production behaviour — silent failures were the original PR-314 review concern). - renderOrchestrator.ts: doc-only — clarify that blitHdrVideoLayer is exported so the time→frame math, last-frame freeze, border- radius detection, and affine-vs-region branch can be unit tested without spinning up the full producer. - .gitignore: ignore packages/producer/tests/hdr-regression/_renders and the generated hdr-full-demo workdir so smoke runs don't pollute git status.
…ogger test Adds a standalone smoke harness for the HDR encode pipeline plus a guard against the silent-decode-failure regression in blitHdrVideoLayer. - packages/producer/scripts/hdr-smoke.ts: end-to-end harness that renders the hdr-pq and mixed-sdr-hdr regression fixtures, then ffprobes both stream-level and frame-level side-data so we can catch missing MasteringDisplay / MaxCLL SEI in CI without hand-running ffprobe. Frame probe uses -show_frames -read_intervals %+#1 since x265 emits HDR metadata as in-band SEI prefix NAL units, not container-level boxes. - renderOrchestrator.test.ts: new "logs decode errors via the supplied logger" test pinning blitHdrVideoLayer's failure path to log.warn (matches production behaviour — silent failures were the original PR-314 review concern). - renderOrchestrator.ts: doc-only — clarify that blitHdrVideoLayer is exported so the time→frame math, last-frame freeze, border- radius detection, and affine-vs-region branch can be unit tested without spinning up the full producer. - .gitignore: ignore packages/producer/tests/hdr-regression/_renders and the generated hdr-full-demo workdir so smoke runs don't pollute git status.
…ness (#314) * feat(hdr): add HDR image support — probe, extract, and composite ImageElement type and parseImageElements() parser for <img> elements. When --hdr is set, images are probed alongside videos for HDR color space. HDR images get single-frame 16-bit PNG extraction and route through the existing blitHdrVideoLayer path. SDR images unchanged. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(hdr): address review feedback across stack - Tolerate per-image HDR probe failures; warn on missing paths. - Warn when HDR <img> source is animated (stills-only for now). - Namespace HDR image frame dirs as hdr_img_<id> to avoid collisions. - Drop image id from HDR layer set when extraction fails. - Promote HDR frame extraction failure from info to warn. - Rename hdrVideoStartTimes → hdrLayerStartTimes. - Make CompiledComposition.images optional. - Fix NaN data-duration slipping through parseImageElements. - Remove dead setAttribute mutation in parseImageElements. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: restore .gitignore entries removed during rebase Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(producer): add HDR image decode cache + promote blit failure to warn Implements the decode-once cache claimed in the PR review reply: HDR image layers now reuse decoded rgb48le buffers across the frames they're visible. Cache is render-scoped (cleared per job) and keyed by framePath::sourceTransfer::targetTransfer because convertTransfer mutates the buffer in-place. Only image layers receive the cache. Video layers would bloat memory (every frame has a unique path: ~37 MB × 300 frames at 1080p ≈ 11 GB). Images decode once and are blitted on every visible frame. Also promotes the blitHdrVideoLayer catch block from log.debug to log.warn — a blit failure means a missing/dropped HDR layer, which is user-visible and shouldn't be silent at the default log level. Made-with: Cursor * test(engine): expand parseImageElements coverage Adds the test cases the PR #314 review flagged as missing: - empty image list / no <img> elements - duration="0", negative, NaN, and Infinity rejection - missing data-start defaults to 0 - duplicate ids are preserved (documents current contract) Made-with: Cursor * fix(engine): handle stdin EINVAL on streaming encoder pipe close Writes to ffmpeg's stdin pipe that race with ffmpeg's exit emit EINVAL or EPIPE as unhandled 'error' events on the Socket, crashing the process. Add a no-op error handler on stdin — the exit handler already captures the failure via the result object. Surfaced during HDR regression renders where the last frame's write coincides with ffmpeg finishing input processing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test(producer): add hdr-smoke regression script + blitHdrVideoLayer logger test Adds a standalone smoke harness for the HDR encode pipeline plus a guard against the silent-decode-failure regression in blitHdrVideoLayer. - packages/producer/scripts/hdr-smoke.ts: end-to-end harness that renders the hdr-pq and mixed-sdr-hdr regression fixtures, then ffprobes both stream-level and frame-level side-data so we can catch missing MasteringDisplay / MaxCLL SEI in CI without hand-running ffprobe. Frame probe uses -show_frames -read_intervals %+#1 since x265 emits HDR metadata as in-band SEI prefix NAL units, not container-level boxes. - renderOrchestrator.test.ts: new "logs decode errors via the supplied logger" test pinning blitHdrVideoLayer's failure path to log.warn (matches production behaviour — silent failures were the original PR-314 review concern). - renderOrchestrator.ts: doc-only — clarify that blitHdrVideoLayer is exported so the time→frame math, last-frame freeze, border- radius detection, and affine-vs-region branch can be unit tested without spinning up the full producer. - .gitignore: ignore packages/producer/tests/hdr-regression/_renders and the generated hdr-full-demo workdir so smoke runs don't pollute git status. * fix(engine): convert sRGB DOM overlays to BT.2020 primaries before HDR composite DOM overlays composited onto HDR video frames were oversaturated because `blitRgba8OverRgb48le` mapped sRGB 8-bit values directly through an HDR OETF (HLG or PQ) without first converting from BT.709 to BT.2020 color primaries. Treating sRGB values as if they already lived in the much wider BT.2020 gamut pushed saturated colors well past the designer's intent — e.g. sRGB pure blue (0,0,255) landed on BT.2020 blue, which is far more vivid than what was specified. Replace `buildSrgbToHdrLut` with the full pipeline: sRGB 8-bit → linear BT.709 (sRGB EOTF, 256-entry LUT) → linear BT.2020 (3×3 BT.2087-0 primary matrix) → HDR signal 16-bit (HLG/PQ OETF, 4096-entry LUT) The matrix rows sum to 1.0, so neutral content (R=G=B) is invariant — text and grayscale UI render identically. Chromatic content (icons, accent colors, progress bars) is now color-accurate against BT.2020 HDR video. PQ scales relative to 203 nits SDR white per BT.2408 so SDR overlays sit at conventional brightness inside the HDR frame. Verified end-to-end with hdr-smoke (sdr-baseline, hdr-pq, mixed-sdr-hdr): sampled red overlay pixels (#C1121F) in the rendered PQ output match the calculated post-conversion 16-bit values (~30837/18894/15630), versus the old buggy pipeline's ~33820/10529/13663 (visibly oversaturated). Removes the `getSrgbToHdrLut` public export — the LUT is now an internal pipeline stage, not a single-step conversion. Made-with: Cursor * feat(hdr): inject mdcv/clli MP4 container boxes for HDR10 outputs x265 emits HDR10 mastering display + content light level metadata as in-band HEVC SEI messages, but FFmpeg's `mov` muxer doesn't extract those into the container-level `mdcv` (Mastering Display Color Volume) and `clli` (Content Light Level Info) boxes that ingest pipelines read. Without them, YouTube, Apple AirPlay, and most HDR TVs see only stream-level color tagging (`colr`) and treat the file as SDR BT.2020, silently tone-mapping the output. Add `mp4HdrBoxes.ts`, which surgically inserts `mdcv` + `clli` boxes inside the HEVC sample entry (`hvc1`/`hev1`), bumps every parent box's size, and rewrites every `stco`/`co64` chunk offset that points past the insertion site so the file stays decodable. Reference: ISO/IEC 14496-15 (NAL-structured video) and ISO/IEC 23001-8 (CICP). Wire the injection in two places: - `streamingEncoder.ts`: post-encode for direct HDR renders, so standalone calls into the engine emit YouTube-ready files. - `renderOrchestrator.ts`: post-mux for the producer pipeline, because FFmpeg's mp4 muxer rebuilds the container during mux/faststart and drops the boxes we injected into the intermediate video-only file. Failures degrade to a warning — the file is still playable; only HDR recognition on strict ingests is affected. Covered by 50+ unit tests in `mp4HdrBoxes.test.ts` (parser fuzzing, box layout, stco/co64 rewriting, malformed-input rejection). Made-with: Cursor * fix(engine): install __name shim before page scripts run `page.evaluate` callbacks with nested `function` declarations crashed with `ReferenceError: __name is not defined` whenever the host was bundled by tsx/bun. esbuild's `keepNames` mode wraps every function declaration — including ones inside the body of an evaluate callback — with a `__name(fn, "name")` call to preserve `Function.prototype.name`. The helper is injected into the host bundle but never serialized into the function string Puppeteer ships to the browser, so the browser context sees a free reference and throws. Install a no-op identity `globalThis.__name` shim via `page.evaluateOnNewDocument` during session init. We pass a string literal (not a function) because esbuild does not transform string contents — defining the shim inline would itself get wrapped with `__name(...)` and produce a use-before-define cycle. Running it via `evaluateOnNewDocument` guarantees the shim is in place before any page script (including subsequent `page.evaluate` callbacks) executes. Document the constraint at the call site in `videoFrameInjector.ts` so future edits don't try to redefine the shim inside an evaluate callback. Made-with: Cursor * test(producer): add hdr-feature-stack regression fixture A six-scene composition that exercises every native HDR compositing feature in one render so regressions in any one path get caught by the existing `bun run hdr-smoke` flow: - scene 1: pure HDR video + sRGB DOM badge overlay - scene 2: HDR background + SDR picture-in-picture + sRGB DOM headline - scene 3: 16-bit HDR PNG image + sRGB DOM caption - scene 4: two HDR videos masked by border-radius (circle + rounded) - scene 5: three z-ordered overlapping cards (HDR, SDR, HDR) with sRGB tags - scene 6: three transformed clips (translate/rotate, scale/skew, opacity/translateY) — exercises per-element compositor layers The five inter-scene transitions cover four shader programs (domain-warp, cross-warp-morph, flash-through-white, gravitational-lens) so the engine-render-mode init path in hyper-shader.ts gets coverage too. Asset symlinks reuse source media that hdr-pq and sdr-baseline already ship, so this does not add any new binary blobs to the repo. The vendored shader-transitions.js is a local build of @hyperframes/shader-transitions@0.4.6 — the published CDN bundle at this version is missing the window.__hf.transitions write the engine needs for native HDR compositing. Once a fixed build ships the vendor file goes away and the HTML can pull from CDN again. Wires the fixture into hdr-smoke.ts with the same probe expectations as the other HDR fixtures (yuv420p10le, smpte2084, bt2020, requireHdrSideData), and adds **/vendor/ to the oxlint ignore patterns so vendored library bundles don't trip lint. Made-with: Cursor * test(producer): add opacity-mixed-fade HDR regression fixture Adds a focused 6-second composition that runs the same opacity timeline on an HDR clip and an SDR clip side-by-side: a `tl.set` to opacity 0, an entry fade-in (0→1), then an opacity yoyo (1↔0.15). Both wrappers use the same selectors and share the same tween, so any divergence between the HDR (compositor) path and the SDR (DOM-injected <img>) path is immediately visible as a left/right asymmetry. This fixture would have caught the SDR-opacity regression fixed in the previous commit — without it, the issue only surfaced as a vague "opacity isn't doing anything" report on the larger hdr-feature-stack composition. The fixture is also wired into hdr-smoke so CI exercises the same code path on every change. Assets are symlinked to the existing hdr-feature-stack videos to avoid duplicating ~25 MB of binary content. Made-with: Cursor * test(producer): refresh hdr-feature-stack with real HDR/SDR clips Expands the regression composition to better cover the SDR-opacity fix and to read more clearly when reviewing the rendered output: * Replace the previous 36-byte placeholder media (Git LFS pointers that were never resolved on disk) with real BT.2020 PQ clips drawn from a YouTube HDR demo (M8hv1Oah2uQ) and BT.709 SDR clips drawn from a YouTube SDR sample (SnUBb-FAlCY). Total ~40 MB of binaries — kept inline rather than tracked through LFS so the fixture matches the rest of the hdr-regression suite. * Add `data-start` / `data-duration` to every HUD slate, caption, and overlay element so they actually reach the layered compositor during their owning scene window. Without these the elements were computed off-stage by the renderer and the slates / pip captions in scenes 1 and 2 never appeared in the final video. * Paint scene 6 on a white field so an opacity yoyo on a framed clip is visible — on the default near-black root the dip-to-translucent reads as no change at all. * Drop opacity from the scene-6 transform yoyo on `#s6-f3`. Earlier scenes already cover the opacity-yoyo path; scene 6 is now a dedicated transform showcase (entry opacity fade-in stays so the clip enters cleanly). Made-with: Cursor * fix(producer): align hdr layer start times reference to renamed variable Commit eac396a renamed hdrVideoStartTimes → hdrLayerStartTimes across the file, but it predated the diagnostic-block typo fix on feat/hdr-layered-compositing (4f6e967). After the restack, line 1470 ended up using the (now non-existent) hdrVideoStartTimes name and broke the producer typecheck/build on this branch. Restore the rename so the diagnostic block matches the local declaration. Made-with: Cursor * docs(hdr): document HDR regression fixtures and testing gaps Add a colocated README describing the five HDR regression fixtures, the three layers of HDR coverage we have today (engine vitests, the hdr-smoke metadata script, and the partial visual harness integration), and the known gaps - notably that hdr-smoke is not in CI and there are no committed pixel goldens, which is why the SDR opacity yoyo bug on <video>-backed clips slipped through initially. Add a pointer from CONTRIBUTING.md so HDR-touching contributors find it from the standard "Running Tests" section. Made-with: Cursor --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
No code yet — just the repo scaffolding for review before we start porting packages.
Test plan
🤖 Generated with Claude Code