## Summary
This PR ended up covering the full HDR Docker/docs follow-through plus the producer/engine work needed to make HDR still images render and regress correctly in CI.
The branch now does four things:
- forwards `--hdr` through the Docker render path in the CLI
- adds and expands HDR documentation across the docs site
- adds first-class HDR still-image support to the engine/producer pipeline
- adds targeted HDR regression coverage, including a CI-safe fallback for PNG HDR metadata detection when `ffprobe` does not expose PNG color tags
## What changed
### CLI and docs
- `hyperframes render --docker --hdr` now preserves `--hdr` when invoking the in-container CLI
- added a dedicated HDR guide and linked it from CLI, producer, engine, rendering, and common-mistakes docs
- documented HDR constraints and verification flow: HDR source requirements, MP4/H.265 Main10 output, PQ/HLG handling, Docker usage, and common SDR fallback causes
### Engine and producer HDR image support
- added `ImageElement` support to the engine composition model and parsing path
- threaded image elements through producer compilation and orchestration
- probed image sources for HDR color spaces so image-only compositions can trigger HDR output without requiring an HDR video source
- included HDR image start times in stacking queries so the layered compositor can place images correctly in z-order
- integrated HDR image compositing into the layered HDR render loop alongside native HDR video layers and SDR DOM overlays
- forced screenshot mode for HDR layered compositing where required to keep DOM/HDR layer composition deterministic
- skipped readiness waiting for natively extracted HDR videos in the engine path where it was unnecessary and could block layered HDR flows
### HDR metadata robustness
- added a fallback in `extractVideoMetadata()` to read PNG `cICP` metadata directly when `ffprobe` omits color-space fields for PNGs
- this specifically fixes CI/Docker detection for the `hdr-image-only` fixture, where the render was falling back to SDR because the PNG was not being recognized as BT.2020 PQ
### Regression coverage and fixture cleanup
- added `hdr-image-only`, a regression fixture that validates HDR still-image rendering end to end
- added `hdr-pq`, a focused HDR PQ regression fixture for the video path
- updated regression CI to run an `hdr` shard with `--sequential hdr-pq hdr-image-only`
- removed the older larger `hdr-regression/*` fixture set in favor of the smaller targeted regressions used by CI
- added the necessary fixture generation/readme material and checked-in golden outputs for the new HDR tests
## Why
The original PR description only covered the CLI flag forwarding and docs work. Since then, the branch also picked up the missing runtime support needed for HDR still images and the regression coverage to keep that path from breaking.
The practical issue this closes is:
- local host runs could pass while CI failed `hdr-image-only`
- the failure was a full-frame visual mismatch caused by SDR fallback, not unstable rendering
- root cause was PNG HDR metadata not being surfaced by `ffprobe` in the CI Docker environment
- parsing the PNG `cICP` chunk directly makes HDR detection deterministic across environments
## Test plan
### Local targeted checks
```bash
bunx oxlint packages/engine/src/utils/ffprobe.ts packages/engine/src/utils/ffprobe.test.ts
bunx oxfmt packages/engine/src/utils/ffprobe.ts packages/engine/src/utils/ffprobe.test.ts
bun --cwd packages/engine test src/utils/ffprobe.test.ts src/utils/hdr.test.ts
```
### Producer regression runs on host
```bash
bun run --cwd packages/core build:hyperframes-runtime:modular
bun --cwd packages/producer test -- --sequential --exclude-tags slow,render-compat,hdr
bun --cwd packages/producer test -- --sequential hdr-pq hdr-image-only
```
Observed result:
- `fast` shard: 7 passed, 0 failed
- `hdr` shard: 2 passed, 0 failed
### CI-equivalent Docker verification
```bash
docker build -f Dockerfile.test -t hyperframes-producer:test .
docker run --rm \
--security-opt seccomp=unconfined \
--shm-size=4g \
-v "$PWD/packages/producer/tests:/app/packages/producer/tests" \
hyperframes-producer:test \
--sequential hdr-pq hdr-image-only
```
Observed result:
- `hdr-image-only`: passed
- `hdr-pq`: passed
- shard summary: 2 passed, 0 failed
### Specific regression fixed
Before the PNG `cICP` fallback, the Docker/CI run failed `hdr-image-only` with:
- missing `"[Render] HDR source detected — output: PQ ..."` log line
- full-frame visual mismatch across all 100 checkpoints
- PSNR ~17 on every frame, indicating a consistent SDR-vs-HDR pipeline mismatch
After the fallback, the same Docker path recognizes the PNG as HDR and the shard passes.
* fix(core): drive adapter seeks when composition has no GSAP timeline
renderSeek returned early when deps.getTimeline() was null, skipping the
onDeterministicSeek call that drives all frame adapters (CSS, WAAPI,
Lottie, Three.js). That meant compositions using any non-GSAP animation
primitive froze on their initial frame during capture.
Now we still quantize the seek time and fire onDeterministicSeek even
without a timeline, so each adapter gets a chance to advance.
GSAP compositions are unaffected — timeline-driven seek still takes the
same path it did before.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(producer): auto-fallback screenshot capture for raf and iframes
Co-Authored-By: Codex <codex@openai.com>
* test(producer): add render compatibility regression fixtures
Co-Authored-By: Codex <codex@openai.com>
* fix(core): scrub CSS animations via WAAPI currentTime
Co-Authored-By: Codex <codex@openai.com>
* test(producer): cover css keyframe renders
Co-Authored-By: Codex <codex@openai.com>
* fix(producer): propagate virtual time into iframe documents
Co-Authored-By: Codex <codex@openai.com>
* test(producer): refresh iframe docker golden
Co-Authored-By: Codex <codex@openai.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Codex <codex@openai.com>
* feat(docs): add template gallery page with visual previews
* fix(docs): remove invalid MDX heading anchors
* chore: retrigger CI
* feat(docs): merge gallery into templates page with hover-to-play video previews
- Consolidated gallery.mdx and templates.mdx into single templates.mdx
- Moved templates page to Getting Started section
- Added MP4 video previews rendered by hyperframes (hover to play)
- Custom JS for hover-to-play behavior (Mintlify strips JSX event handlers)
- 2-column grid for landscape, 3-column for portrait
- Remotion-style cards with gradient overlay labels
* fix(docs): update broken links after templates page move
* ci(regression): remove scripts/ from regression trigger paths
scripts/ contains dev utilities (lint, versioning, preview generation)
that don't affect the rendering engine.
## What
Updated the GitHub Actions regression workflow to monitor specific package directories instead of the entire packages folder.
## Why
This change provides more granular control over when regression tests are triggered, allowing the workflow to run only when changes are made to the core, producer, or engine packages rather than any package in the repository.
## How
Modified the path filters in the regression workflow to explicitly list the three critical package directories (`packages/core/**`, `packages/producer/**`, `packages/engine/**`) instead of using the broad `packages/**` pattern.
## Test plan
How was this tested?
- [ ] Unit tests added/updated
- [ ] Manual testing performed
- [ ] Documentation updated (if applicable)
* fix(ci): update publish workflow to use bun install
pnpm-lock.yaml was removed in the bun migration but publish.yml
still referenced it. Use bun for install/build, keep pnpm for
publish (publishConfig overrides + --provenance).
* docs: update stale pnpm references to bun across docs and scripts
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The CI and regression path filters included their own workflow files,
which meant any PR that changed CI config would trigger the full
build/test/regression suite. Workflow file changes don't need code
validation — they need a test run of the workflow itself, which
happens automatically.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The repo has a ruleset requiring these checks: Build, Typecheck,
Test: core, Test: engine, Test: runtime contract, regression.
With paths-ignore, docs-only PRs would never report these checks,
blocking merge forever.
Fix: add a `changes` job using dorny/paths-filter that detects
whether code files changed. Each job uses `if: needs.changes.outputs.code == 'true'`
which causes GitHub to report the job as "skipped" (counts as passing)
rather than "never started" (counts as pending).
The regression summary job explicitly handles the no-code-changes case
by checking the filter output before evaluating shard results.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- New docs.yml: runs `mint validate` and `mint broken-links` on docs/** changes
- ci.yml: paths-ignore docs/**, *.md so build/typecheck/tests don't run on docs-only PRs
- regression.yml: same paths-ignore to skip Docker regression tests on docs-only PRs
No branch protection is configured, so paths-ignore won't block merges.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Split 21 style tests into 6 shards (3 each, last has 2) to reduce
max wall time from ~38min to ~25min. Each test takes ~7-8min plus
~5min Docker overhead per shard.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All style regression tests passed on first run — promote them from
optional (continue-on-error) to required. Rebalanced into 4 style
shards + 1 fast shard, all gated by the summary job.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Port the regression test infrastructure from the internal repo to OSS.
Runs golden-baseline visual/audio comparisons inside Docker for deterministic results.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>