Commit Graph
287 Commits
Author SHA1 Message Date
Miguel ÁngelandClaude Opus 4.6 ef63bb9cde chore(renderer): remove brainstorming docs, QA pages, and lockfile from PR
- Remove docs/superpowers/plans and specs (brainstorming artifacts)
- Remove packages/renderer/qa/ (dev testing pages)
- Remove packages/renderer/pnpm-lock.yaml (monorepo uses root lockfile)
- Remove run-parity-test.sh (superseded by regression-parity.ts)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 14:03:49 +02:00
Miguel ÁngelandClaude Opus 4.6 83436f3d36 fix(renderer): fix regression harness — async wait, upload server, asset copying
- Use async spawn for agent-browser wait so Node event loop stays active
  for the upload HTTP server to process browser POST requests
- Add CORS headers to upload server responses
- Add frameSource: 'snapdom' to test page (headless Chrome auto-detects
  tab-capture which hangs waiting for screen share permission)
- Copy source assets to output dir (CSS, fonts not inlined by compiler)
- Use compile-test.ts for compilation (injects runtime + __hf bridge)
- Write serve.json with cleanUrls:false to prevent .html→extensionless redirects
- Increase upload timeout from 10s to 300s for long compositions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 13:56:41 +02:00
Miguel ÁngelandClaude Opus 4.6 6d4ad2ecce fix(renderer): fix video frame drops, audio, animation jitter, and background rendering
- Discover media elements from DOM when __player bridge doesn't expose hf.media
  (scans both <video> and <audio> elements with data-start/data-duration)
- Resolve media src to absolute URLs so audio mixer fetch() works cross-origin
- Seek original video elements directly instead of clone+canvas overlay approach
  (SnapDOM natively captures <video> via drawImage)
- Force synchronous reflow after GSAP seek to prevent stale computed styles
- Add warmup phase: wait for fonts.ready + settle ticks before first capture
- Replace all requestAnimationFrame with setTimeout for background tab support
- Fix interleaved capture/encoding progress by deferring encoding reports

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 12:58:32 +02:00
Miguel ÁngelandClaude Opus 4.6 0b15403593 fix(studio): set concurrency=1 for browser render to prevent UI lockup
Single-iframe capture keeps Chrome responsive during long renders.
With 8 parallel iframes, SnapDOM overwhelms the main thread and locks
the browser. Concurrency=1 trades speed for reliability.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 04:23:24 +02:00
Miguel ÁngelandClaude Opus 4.6 7d36ab38e3 fix(renderer): add yields between frames to prevent main thread lockup
- iframe-pool: setTimeout(0) yield after each captured frame
- SnapDOM source: requestAnimationFrame after seek() before capture
  to ensure layout/paint is processed

This prevents Chrome from becoming unresponsive during long renders,
keeping CDP, DevTools, and UI interactions functional.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 04:01:26 +02:00
Miguel ÁngelandClaude Opus 4.6 09363f1898 fix(renderer): bridge __player→__hf in SnapDOM source, fix studio worker URL
- SnapDOM frame source now detects window.__player (studio runtime) in
  addition to window.__hf and creates a bridge automatically. This lets
  the renderer work with studio preview URLs directly.
- Studio useBrowserRender hook: force snapdom frame source and provide
  explicit worker URL for Vite dev compatibility.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 05:43:56 +02:00
Miguel ÁngelandClaude Opus 4.6 e924c76c10 feat(studio): add browser-side export button using @hyperframes/renderer
Adds a "Browser" export button next to the existing server-side Export
button in the RenderQueue panel. Uses the @hyperframes/renderer package
to render compositions entirely client-side via WebCodecs, with progress
tracked in the existing job list and automatic file download on completion.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 05:24:46 +02:00
Miguel ÁngelandClaude Opus 4.6 f9489afb98 feat(renderer): add drawElementImage + tab-capture frame sources, regression harness
drawElementImage: pixel-perfect capture via html-in-canvas API using
<canvas layoutsubtree> with iframe child. Achieves 42 dB PSNR vs 30 dB
for SnapDOM. Requires Chrome --enable-blink-features=CanvasDrawElement.

Tab-capture: experimental getDisplayMedia-based capture.

Also adds regression parity scripts for compiling test compositions and
comparing PSNR against golden reference renders.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 05:24:45 +02:00
Miguel ÁngelandClaude Opus 4.6 f10cd056f7 fix(renderer): fix viewport capture, empty audio, cancel propagation, progress ETA
- Capture doc.body instead of doc.documentElement in SnapDOM to fix 75% viewport mismatch
- Make audio track conditional (hasAudio flag) to prevent corrupt empty audio tracks
- Add publishConfig for npm publishing
- Wire ProgressTracker for estimatedTimeRemaining and captureRate in progress callbacks
- Recompute video overlay canvas position per-frame for GSAP-animated elements
- Add AbortSignal to captureAll for cancel propagation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 05:24:45 +02:00
Miguel ÁngelandClaude Opus 4.6 76727306c9 fix(renderer): fix iframe polling, add workerUrl config, add QA test page
- Replace requestAnimationFrame with setTimeout in __hf protocol polling
  (rAF doesn't fire for off-screen iframes)
- Add workerUrl option to EncoderOptions and RenderConfig for configuring
  the encoding worker URL (needed for bundled deployments)
- Add QA test page with self-contained CSS-animated composition

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 05:24:45 +02:00
Miguel ÁngelandClaude Sonnet 4.6 23b5d834a1 fix(renderer): address code review issues
- Fix audio format: replace interleaveChannels with concatPlanarChannels
- Fix MIME type: derive video/mp4 vs video/webm from outputFormat in finalize
- Add isSupported() guard at top of render() for early failure
- Fix durationMs to report wall-clock render time (totalMs)
- Fix audio filter to use strict equality (hasAudio === true)
- Remove unused progress type from WorkerOutMessage union

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 05:24:45 +02:00
Miguel ÁngelandClaude Opus 4.6 18d6a3e9e2 feat(renderer): add progress tracking and ETA estimation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 05:24:45 +02:00
Miguel ÁngelandClaude Opus 4.6 725d9e71b1 feat(renderer): integrate video frame injector into SnapDOM frame source
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 05:24:45 +02:00
Miguel ÁngelandClaude Opus 4.6 826179e44f test(renderer): add end-to-end integration test with basic composition
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 05:24:44 +02:00
Miguel ÁngelandClaude Opus 4.6 e79f6b02b0 feat(renderer): add main orchestrator and public API (render, createRenderer)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 05:24:44 +02:00
Miguel ÁngelandClaude Sonnet 4.6 5019fc09b4 feat(renderer): add parallel iframe pool for frame capture
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 05:24:44 +02:00
Miguel ÁngelandClaude Sonnet 4.6 fc823c572f feat(renderer): add video frame injector for <video> element capture
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 05:24:44 +02:00
Miguel ÁngelandClaude Sonnet 4.6 05ee085c36 feat(renderer): add OfflineAudioContext-based audio mixer
Implements mixAudio() with TDD: decodes audio sources, applies volume/timing
via GainNode, and renders a mixed PCM AudioBuffer using OfflineAudioContext.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 05:24:44 +02:00
Miguel ÁngelandClaude Sonnet 4.6 4b1e46294e feat(renderer): add WebCodecs encoding worker and main-thread wrapper
Implements the encoding pipeline for @hyperframes/renderer: worker message
protocol types, Web Worker entry point (VideoEncoder + MediaBunny muxer),
and the main-thread Encoder class that manages the worker lifecycle.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 05:24:44 +02:00
Miguel ÁngelandClaude Sonnet 4.6 3112bc8035 feat(renderer): add SnapDOM frame source implementation
Implements SnapdomFrameSource class that loads compositions in hidden iframes,
seeks via window.__hf.seek(), and captures DOM frames using @zumer/snapdom.
Adds jsdom dev dependency for the test environment.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 05:24:43 +02:00
Miguel ÁngelandClaude Sonnet 4.6 408d69b1a0 feat(renderer): add frame timing and distribution utilities
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 05:24:43 +02:00
Miguel ÁngelandClaude Sonnet 4.6 a00930d952 feat(renderer): scaffold package with types and compat detection
Adds the @hyperframes/renderer package skeleton: package.json, tsconfig.json,
src/types.ts (all pipeline interfaces), src/compat.ts (WebCodecs feature
detection), and src/index.ts (re-exports). Typecheck passes with zero errors.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 05:24:43 +02:00
Miguel ÁngelandClaude Opus 4.6 b8c56195c0 docs: add client-side renderer implementation plan
13-task plan covering package scaffold, SnapDOM frame source, WebCodecs
encoding worker, MediaBunny muxer, OfflineAudioContext audio mixer,
parallel iframe capture, video frame injector, and main orchestrator.
56 total steps with TDD approach.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 05:24:43 +02:00
Miguel ÁngelandClaude Opus 4.6 e58bff5756 docs: add client-side renderer design spec
Design spec for @hyperframes/renderer — a new browser-only package that
renders compositions to MP4 using WebCodecs + MediaBunny + OffscreenCanvas
workers, with SnapDOM as interim frame source and html-in-canvas as the
future pixel-perfect path. Replaces the need for Puppeteer + FFmpeg.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 05:24:43 +02:00
Miguel Ángel 1c61a0b25a chore: release v0.2.3-alpha.2 v0.2.3-alpha.2 2026-04-08 03:19:08 +02:00
Miguel Ángel 43e9252065 feat: add MOV (ProRes 4444) as transparent video output format (#224)
## Summary

- Adds `--format mov` to the render CLI for ProRes 4444 transparent video output
- ProRes 4444 with alpha is the industry standard for transparent video overlays, supported by CapCut, Final Cut, Premiere, DaVinci, and After Effects
- WebM VP9 alpha technically works but is ignored by all major video editors — only browsers decode it
- Adds MOV to the studio export dropdown alongside MP4 and WebM

## Transparency format comparison

| Format | Codec | Alpha | Video editors | Browsers | File size |
| --- | --- | --- | --- | --- | --- |
| **MOV** | ProRes 4444 | Yes | CapCut, Final Cut, Premiere, DaVinci, After Effects | No (won't play in browser) | Large (~5-40 MB) |
| **WebM** | VP9 | Yes | None (shows black) | Chrome, Firefox | Small (~200 KB) |
| **MP4** | H.264 | No | All | All | Small |

> **Note:** ProRes MOV files do not play in Chromium browsers — they are an intermediate/editing format, not a delivery format. Use [rotato.app/tools/transparent-video](https://rotato.app/tools/transparent-video) to verify transparency works correctly.

## Changes

- **CLI**: Add `mov` to `--format` validation, examples, and output path logic
- **Engine**: `getEncoderPreset()` returns ProRes 4444 (`yuva444p10le`) for `mov` format; handle `.mov` in `applyFaststart` and `muxVideoWithAudio`; add `pix_fmt` to streaming encoder ProRes path
- **Producer**: Treat `mov` like `webm` for alpha capture (PNG frames, screenshot mode, `forceScreenshot`)
- **Studio**: Add MOV option to export format dropdown and render queue hook
- **Core**: Add `mov` to studio API types, render route, and mime helpers
- **Tests**: Add encoder preset tests for mov format (42 total, all passing)

## Usage

```bash
hyperframes render --format mov --output overlay.mov
```

## Test plan

- [x] `pnpm build` passes
- [x] `pnpm --filter @hyperframes/engine test` — 42 tests pass (2 new for MOV)
- [x] `oxlint` and `oxfmt` clean on all 12 changed files
- [x] End-to-end local render produces ProRes 4444 (`yuva444p12le`) with working alpha
- [x] Docker render with `--format mov` — ProRes 4444 confirmed via ffprobe
- [x] Studio dropdown shows MOV option in built JS
- [x] Transparency verified with [rotato.app/tools/transparent-video](https://rotato.app/tools/transparent-video)
2026-04-08 03:11:37 +02:00
Vance Ingalls 85a76c0043 feat(cli): implement Docker rendering for deterministic output (#215)
## Summary

- **The `--docker` flag was a no-op stub** — `renderDocker` called the same local `executeRenderJob` as `renderLocal`, no container was ever launched
- Now `renderDocker` generates a Dockerfile, builds a versioned `hyperframes-renderer:<version>` image with Chrome/FFmpeg/fonts/chrome-headless-shell, and runs the render inside a container
- Image is cached per CLI version — first render builds (~2 min), subsequent renders reuse it
- Forces `linux/amd64` platform since chrome-headless-shell has no ARM Linux binary
- Uses `execFileSync` (array form) throughout to prevent shell injection
- Forwards `--quiet`, `--gpu`, and render config flags into the container
- `Dockerfile.render` added as a reference for manual builds

## Test plan

- [x] `hyperframes render --docker` builds image and produces valid MP4
- [x] Second run reuses cached image (no rebuild)
- [x] `--quiet` suppresses container output while keeping stderr for errors
- [x] Typecheck, lint, format all pass
- [ ] Verify `--gpu` with `--docker` on a machine with GPU access

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-04-07 16:38:53 -07:00
James Russo b491679c71 fix(engine): add bt709 color space + range conversion to encoding (#223)
## Description

Adds proper BT.709 color space metadata and full→limited range conversion to H.264/H.265 encoding. Chrome captures frames in full-range sRGB (BT.709 primaries), but without explicit color tagging, players guess the wrong color space and range — causing color shifts across iOS/Android/desktop and crushed dark values that compound the gradient banding issue fixed in #222.

**What changed:**

| Setting | Before | After |
|---------|--------|-------|
| `color_space` | `bt470bg` (guessed) | `bt709` (explicit) |
| `color_primaries` | `unknown` | `bt709` |
| `color_transfer` | `unknown` | `bt709` |
| `color_range` | `pc` (full, wrong for H.264) | `tv` (limited, correct) |
| `time_base` | `1/15360` (varies by platform) | `1/90000` (fixed) |

**Approach:**
- BT.709 VUI params embedded via x264-params/x265-params (`colorprim=bt709:transfer=bt709:colormatrix=bt709`) — ensures the bitstream itself carries color info
- FFmpeg-level metadata flags (`-colorspace:v bt709`, etc.) — belt-and-suspenders
- `scale=in_range=pc:out_range=tv` filter converts Chrome's full-range output to TV/limited range
- VAAPI path chains the range filter with existing `format=nv12,hwupload`
- `-video_track_timescale 90000` for consistent cross-platform A/V timing (same as Remotion)
- VP9 and ProRes encoding unaffected

## Testing

- Verified via ffprobe: all 5 color metadata fields now correct
- Directly tested FFmpeg args produce expected output
- 40 engine tests pass (8 new: color metadata h264/h265, range filter CPU, VAAPI filter chain, GPU skip, VP9 skip, timescale)
- Builds cleanly, lint + format pass
2026-04-07 10:40:18 -07:00
James RussoandClaude Opus 4.6 7bd8939143 fix(engine): add anti-banding x264/x265 params for dark gradients (#222)
Add aq-mode=3 (auto-variance adaptive quantization) to CPU H.264/H.265
encoding. This redistributes bits from bright/textured areas to dark flat
areas where color banding is most visible in 8-bit yuv420p output.

- standard/high presets: aq-mode=3 + aq-strength=0.8 + deblock=1,1
- draft (ultrafast): aq-mode=3 only (deblock too slow for ultrafast)
- GPU and VP9 encoders unaffected (have their own AQ implementations)

Adds 6 regression tests verifying the params are emitted correctly.

Fixes color banding on dark gradients (eval issue #3, prompts 3,5,10,14).

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 09:49:52 -07:00
Miguel Ángel 25f34f7f93 fix(cli): fix preview opening wrong project in dev mode (#221)
## Summary

One-character fix: removes leading `/` from the preview URL hash.

Dev mode opened `#/project/<name>` but the studio expects `#project/<name>`. The route regex in `App.tsx` (`/^#project\/([^/]+)/`) never matched the slash-prefixed hash, so it fell through to auto-selecting the first project from `/api/projects` — ignoring the project path you passed on the CLI.

## Root cause

```
// preview.ts dev mode (line 176) — WRONG
`${frontendUrl}#/project/${pName}`

// App.tsx route parser (line 34) — expects this format
window.location.hash.match(/^#project\/([^/]+)/)
```

Local-studio mode and embedded mode already used the correct `#project/` format.

## Test plan

- [x] Typecheck passes
- [x] `npx tsx cli preview /path/to/project` opens the correct project in the studio
2026-04-07 17:40:24 +02:00
Miguel Ángel ef2f646c90 fix(producer): extract head assets from non-template sub-comps + fix postcss ESM (#220)
## Summary

Two fixes in the producer:

1. **Head styles/scripts extraction**: mirrors the runtime fix from PR #219. The producer's `inlineSubCompositions()` parsed only `bodyEl.innerHTML` from non-template sub-compositions, discarding all `<head>` content.
2. **Externalize postcss**: postcss is a CJS module with `require("path")` — bundling it into ESM output caused "Dynamic require of path is not supported" at runtime, breaking `npx tsx cli render` and `npx tsx cli preview` from the local dev build.

## Verified

Re-rendered the iris-wipe composition (eval prompt #25, previously scored 1.0/5 — entirely black):

| Frame | Before fix | After fix |
| --- | --- | --- |
| 0\.5s | Black | Red background + "HELLO" text |
| File size | 16\.9 KB (all black) | 64\.8 KB (actual content) |

Scene 1 now renders correctly. Scene 2's clip-path animation has a separate GSAP issue (the lint already warns about it via `scene_layer_missing_visibility_kill`).

## Test plan

- [x] `pnpm --filter @hyperframes/producer build` succeeds
- [x] `node --input-type=module -e "import './dist/index.js'"` loads without error
- [x] Re-render iris-wipe produces visible content (64.8 KB vs 16.9 KB)
- [x] Frame extraction confirms red "HELLO" scene renders correctly
2026-04-07 17:37:26 +02:00
Miguel Ángel f56b4c8620 fix(core): load head styles/scripts from non-template sub-compositions (#219)
## Summary

Fixes a bug where non-template sub-compositions (full HTML documents loaded via `data-composition-src`) lost all `<head>` styles and scripts. This affected **three code paths**:

1. **Runtime** (`compositionLoader.ts`) — browser preview via iframe fetch
2. **Bundler** (`htmlBundler.ts`) — studio preview HTML bundling (**this was causing the black preview**)
3. Producer fix is in PR #220

## What it fixes

**Eval prompt #25** (iris-wipe) renders entirely black in both the studio preview and rendered video because scene backgrounds (`#EF4444` red, `#3B82F6` blue), positioning, and the GSAP CDN script were all in `<head>` and silently dropped.

### Verified

Rebuilt core, started studio preview, fetched the bundled HTML from `/api/projects/iris-wipe/preview` — confirmed `#scene1 { background: #EF4444 }` and `.scene { position: absolute }` are now present in the output.

## Root cause

All three code paths did the same thing:
```js
const contentHtml = template ? template.innerHTML : bodyEl.innerHTML;
// ^ <head> content is already lost here
```

## Test plan

- [x] All 429 core tests pass
- [x] Studio preview endpoint returns correct bundled HTML with head styles included
- [x] `pnpm --filter @hyperframes/core build` succeeds

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-04-07 17:17:25 +02:00
Miguel Ángel 0d33238381 fix(core): add lint rule for infinite GSAP repeat (#218)
## Summary

Adds a new lint rule `gsap_infinite_repeat` that flags `repeat: -1` in GSAP timelines as an error. This is a hard enforcement of the skill guardrail added in PR #217.

## What it fixes

The deterministic capture engine (`HeadlessExperimental.beginFrame`) seeks to exact frame times on a paused GSAP timeline. When a timeline contains `repeat: -1`, the timeline duration is infinite, which causes the capture engine to produce incorrect/blurry output.

**Eval prompt #20** (loading-spinner, scored 2.0/5) used `repeat: -1` on a dots animation cycle, producing "a highly compressed and blurry loading animation lacking visual clarity and professional polish."

## Changes

- `packages/core/src/lint/rules/gsap.ts` — new `gsap_infinite_repeat` rule (regex scan for `repeat: -1`)
- `packages/core/src/lint/rules/gsap.test.ts` — 2 new tests (detects infinite repeat, allows finite repeat)

## Test plan

- [x] `pnpm --filter @hyperframes/core test` — all 429 tests pass
- [x] Rule catches `repeat: -1` and reports as error with fix hint
- [x] Rule does not flag `repeat: 4` (finite repeats)
2026-04-07 16:53:50 +02:00
Miguel Ángel 883bd8273e fix(skills): add rendering guardrails to hyperframes skill (#217)
## Summary

Adds critical rendering constraints to the `hyperframes` skill discovered from eval analysis of 27 agent-generated compositions. These guardrails prevent agents from producing compositions that technically work but render poorly.

## What it fixes

| Rule Added | Eval Prompts Affected | Issue |
| --- | --- | --- |
| Ban `repeat: -1` | #20 loading-spinner (2.0/5) | Infinite timeline broke capture engine |
| Ban async timeline construction | #16 particle-logo (2.6/5) | Timeline empty at capture time |
| Min font size 16px (labels), 20px (body) | #7, #8, #13, #14, #15, #19 | Illegible text after encoding |
| Ban full-screen dark linear gradients | #3, #5, #10, #14 | H.264 color banding |
| `<link>` fonts over CSS `@import` | #7, #24 | Font loading race conditions |

## Changes

- **Rules section**: Added `repeat: -1` ban, async timeline ban, items 8-9 to "Never do" list
- **Typography section**: Expanded font size guidance with specific minimums per text role (headlines, body, labels)
- **New "Backgrounds and Color" section**: Guidance on avoiding gradient banding
- **Output Checklist**: 5 new items covering all new constraints

## Test plan

- [ ] Run eval with updated skill and compare avg quality scores
- [x] Skill renders correctly in `/hyperframes` invocation
2026-04-07 03:38:59 +02:00
Vance IngallsandClaude Opus 4.6 569513145b feat(skills): add WebGL shader transitions and restructure catalog (#213)
* feat(skills): add WebGL shader transitions and restructure catalog

Add 14 WebGL fragment shader transitions to the transitions skill:
domain warp dissolve, ridged burn, whip pan, SDF iris, ripple waves,
gravitational lens, cinematic zoom, chromatic radial split, glitch,
swirl vortex, thermal distortion, flash through white, cross-warp
morph, and light leak (shader).

Restructure catalog.md from a 1045-line monolith into a 105-line
routing layer with 15 reference files. SKILL.md loads at 101 lines,
catalog.md loads at 105 lines — reference files loaded on demand
only for the transition type being implemented.

Key additions:
- Full WebGL setup boilerplate with media capture (images, video,
  object-fit: cover, live video re-upload during transitions)
- Hard rules for shader transitions capturing all bugs found during
  development (Y-flip, preserveDrawingBuffer, fwidth, boomerang,
  tween proxy reuse, tl.call vs onComplete)
- CSS vs Shader decision guide in SKILL.md
- Visual pattern warning against repeating geometric patterns
- Shader transitions slotted into mood/energy mapping tables
- Noise libraries: quintic C2, ridged, erosion FBM, cosine palette

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(skills): fold transitions into hyperframes skill

Move transitions from a standalone skill (4th top-level) into
hyperframes/references/transitions/, aligning with the consolidation
in #211 that reduced 15 skills to 3. Fewer standalone skills means
higher trigger reliability for multi-skill tasks.

Also removes stale text-burn-dom.html reference from css-destruction.md.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 14:50:13 -07:00
James RussoandClaude Opus 4.6 0a0d5d3654 refactor(skills): consolidate 15 skills into 3 (#211)
* refactor(skills): consolidate 15 skills into 3 for better trigger reliability

Merge 9 GSAP skills (core, timeline, scrolltrigger, plugins, utils, react,
frameworks, performance, effects) and 6 HyperFrames skills (compose, captions,
tts, audio-reactive, marker-highlight, cli) into 3 consolidated skills:

- `gsap` — core API + timelines + performance in SKILL.md; scrolltrigger,
  plugins, utils, react, frameworks, effects in references/
- `hyperframes` — composition authoring rules in SKILL.md; captions, tts,
  audio-reactive, marker-highlight in references/
- `hyperframes-cli` — CLI commands (init, lint, preview, render, etc.)

Why: With 15 separate skills, agents must correctly trigger the right subset
for any task. "Create an animated video with captions" needed 6+ skills to
fire — each with ~90% trigger accuracy means ~53% chance of getting all of
them. With 3 skills, that same task needs just `hyperframes` + `gsap` (~90%
both fire). Progressive disclosure still works via references/ files loaded
on demand.

Also fixes: CLAUDE.md referenced `window.__GSAP_TIMELINE` (incorrect) —
corrected to `window.__timelines`.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(cli): add --skip-skills flag to init command

Allow skipping the AI coding skills installation prompt during
`hyperframes init` with `--skip-skills`. Useful when skills are
already installed or when the user wants to scaffold without them.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(skills): address code review feedback on consolidation

Restore content lost during over-compression:

- captions: fix overflow to `visible` (not hidden — clips glow effects),
  add container pattern warning, scale headroom formula, and self-lint
  placement guidance
- audio-reactive: restore sampling frequency pattern (per-frame tl.call
  loop vs single tween) and textShadow-on-container gotcha
- effects/typewriter: restore word rotation, appending words, spacing
  with static text, and multi-line cursor handoff patterns
- effects/audio-visualizer: restore spatial mapping conventions, fetch vs
  inline loading, WebGL/DOM rendering approaches, and canvas layering
- hyperframes-cli: restore --strict-all flag in render flags table

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(cli): update build:copy and template for consolidated skill names

- build:copy: reference skills/hyperframes, skills/hyperframes-cli,
  skills/gsap instead of the old 15 skill directory names
- _shared/CLAUDE.md template: update skill table to consolidated names

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 11:21:49 -07:00
Miguel Ángel 5655dabff6 feat: allow clip animation + ship <hyperframes-player> web component (#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`
2026-04-06 19:59:39 +02:00
Miguel Ángel baa3d813be fix: address QA report P0-P2 issues for 10/10 agent experience (#208)
## Summary

Addresses all 8 issues from the QA report to improve agent and user experience.

### P0 — Must Fix

- **Blank template broken captions**: Removed `compositions/captions.html` and its reference from the blank template. Every agent (10/10) hit 404 errors during render.
- **Inner-wrapper example**: Added a clear structural comment in the blank template showing the correct `class="clip"` + inner wrapper pattern.
- **Sub-composition introspection**: `hyperframes compositions` now reads external HTML files referenced via `data-composition-src` and shows their real duration/element count instead of `0.0s / 0 elements`.

### P1 — Fix Soon

- **Font mapping warnings**: Now lists all mapped fonts, suggests alternatives (use a mapped font, add @font-face, install locally), and links to docs.
- **Browser 404s**: Non-font "Failed to load resource" 404s now prefixed with `[non-blocking]` instead of `[Browser:ERROR]`.
- **Render concurrency**: Default workers increased from `cores/2` (max 4) to `cores*3/4` (max 6). Added `--concurrency` alias.

### P2 — Nice to Have

- **Transform conflict fix suggestion**: `gsap_css_transform_conflict` now suggests exact GSAP property replacements (e.g., `xPercent: -50, yPercent: -50`).
- **Upgrade --yes**: Now actually runs the install instead of just printing the command.

## Before / After

### Font mapping warning

**Before:**

```
[Compiler] No deterministic font mapping for: DM Sans
```

**After:**

```
[Compiler] No deterministic font mapping for: DM Sans
  Mapped fonts: arial → inter, courier → jetbrains-mono, ...
  To fix, pick one:
    1. Use a mapped font name instead (see list above)
    2. Add a @font-face block in your HTML with a local or hosted font file
    3. Install the font locally on the render machine (Docker: add to Dockerfile)
    4. Add an alias to FONT_ALIASES in deterministicFonts.ts (for contributors)
```

### Browser 404s during render

**Before:** `[Browser:ERROR] Failed to load resource: the server responded with a status of 404`
**After:** `[non-blocking] Failed to load resource: the server responded with a status of 404`

### Transform conflict lint

**Before:** `Fix: Remove the transform from CSS and use tl.fromTo...`
**After:** `Fix: Remove transform: translate(-50%, -50%) from CSS and replace with GSAP properties: xPercent: -50, yPercent: -50`

### Compositions command

**Before:**

```
overlay   0.0s   1920×1080   0 elements
```

**After:**

```
overlay   8.0s   1920×1080   2 elements ← compositions/overlay.html
```

## Test plan

- [x] All 422 core tests pass
- [x] TypeScript compiles cleanly (all 4 packages)
- [x] Full monorepo build succeeds
- [x] `hyperframes init --template blank` ships without broken captions reference
2026-04-06 18:56:39 +02:00
Vance IngallsandClaude Opus 4.6 c47e710ffc feat(skills): add scene transitions skill with 35-type catalog (#212)
## What

A scene transition selection framework and implementation catalog covering 35 transition types across 8 categories.

### transitions skill

**SKILL.md** — Selection framework:
- Energy → transition mapping (calm/medium/high)
- Mood → transition mapping (warm, cold, editorial, tech, edgy, playful, dramatic, premium, retro)
- Narrative position guidance (opening, between sections, climax, outro)
- Blur intensity scaling by energy level
- Configuration presets (snappy, smooth, gentle, dramatic, instant, luxe)

**catalog.md** — Implementation reference:
- GSAP code for all 35 transitions
- Hard rules from real bugs (scene visibility, iframe compatibility, VHS clone pattern, z-index, overlay sizing)
- Scene template

### Categories

| Category | Transitions |
|----------|------------|
| Content-transforming | Push slide, vertical push, elastic push, squeeze, zoom through, zoom out, gravity drop, 3D flip |
| Reveal/mask | Circle iris, diamond iris, diagonal split, clock wipe, shutter |
| Dissolve | Crossfade, blur crossfade, focus pull, color dip |
| Cover | Staggered blocks, horizontal blinds, vertical blinds |
| Light | Light leak, overexposure burn, film burn |
| Distortion | Glitch, chromatic aberration, ripple, VHS tape |
| Pattern | Grid dissolve |
| Instant | Flash cut, morph circle |

## Why

Agents building multi-scene compositions were using the same opacity crossfade for every scene change regardless of video mood/energy. The transitions skill provides context-aware selection so a wellness video gets blur crossfades while a sports promo gets flash cuts and a cyberpunk event gets VHS distortion.

## How

- SKILL.md follows writing-skills guide: description uses "Use when..." triggers, no workflow summary, under 500 words
- catalog.md is heavy reference with table of contents
- Hard rules consolidated from real rendering bugs discovered during 16 A/B eval comparisons
- Mood mappings designed from a motion design perspective

## Test plan

- [x] 16 A/B eval compositions comparing with/without skill across moods
- [x] 39-scene transition catalog composition demoing every type
- [x] Skill audit against writing-skills guide
- [x] All transition types mapped to at least one mood
- [x] Lint passes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 20:29:23 -07:00
Vance Ingalls d8bffd41f9 feat(lint,skills): add caption/audio-reactive lint rules and skill guidance (#207)
## What

Bumped all package versions to `0.2.2-alpha.4` and added five new lint rules for caption and GSAP animation quality checks.

## Why

The new lint rules address common issues in HyperFrames compositions:
- Caption overflow clipping when emphasis words are scaled above 1.0x
- Text shadow artifacts on caption group containers with semi-transparent children
- Mismatch between fitText maxWidth and scaled word dimensions
- Imperceptible audio reactivity from single tweens instead of time-sampled animations
- Scene layer visibility conflicts when relying only on opacity tweens

## How

Added three new caption-specific lint rules in `captions.ts`:
- `caption_overflow_clips_scaled_words` - detects `overflow: hidden` on caption containers when scripts scale words above 1.0x
- `caption_textshadow_on_group_container` - flags textShadow tweens applied to group containers instead of individual words
- `caption_fittext_scale_mismatch` - calculates effective width from fitText maxWidth × max scale factor and warns when it exceeds safe bounds

Added two new GSAP lint rules in `gsap.ts`:
- `audio_reactive_single_tween_per_group` - identifies audio-reactive captions using peak values instead of time-sampled loops
- `scene_layer_missing_visibility_kill` - detects multi-scene compositions missing hard visibility kills after opacity exit tweens

Enhanced documentation with new mask reveals guide and updated existing skills with overflow handling, scene management, and audio reactivity best practices.

## Test plan

- [x] Lint rules tested against existing composition patterns
- [x] Documentation updated with new techniques and constraints
- [x] Version bumps applied consistently across all packages
2026-04-03 10:52:06 -07:00
James RussoandClaude Opus 4.6 29541aefaa feat(cli): prompt to install skills during init (#206)
Replace the static "Tip" message at the end of `hyperframes init` with an
interactive prompt that offers to install AI coding skills. When the user
accepts, the skills command runs `npx skills add` with `--all` and
`stdio: "inherit"` so the native installer output is visible.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 07:36:08 -07:00
Miguel Ángel e2c8ed5d83 refactor(core): replace cheerio with linkedom to drop deprecated whatwg-encoding (#187)
## Summary

- `cheerio` pulls `encoding-sniffer` → `whatwg-encoding@3.1.1` (deprecated), causing a warning on every `npm install -g hyperframes`
- `linkedom` was already bundled into the CLI via tsup `noExternal` and has zero deprecated transitive deps
- Rewrote `htmlBundler.ts` and `subComposition.ts` to use standard DOM APIs via `linkedom`
- Added a `parseHTMLContent` helper that wraps HTML fragments in a full document structure (required for `linkedom` to populate `document.body`)
- Removed `cheerio` from `cli` dependencies and tsup `external` list
- Replaced `cheerio` with `linkedom` in `core` `optionalDependencies`

## Test plan

- [x] All 411 tests pass (`bun run test` in `packages/core`)
- [x] Full monorepo build succeeds (`bun run build`)
- [x] TypeScript typecheck passes
2026-04-03 16:06:30 +02:00
James 06e3da9ad3 chore: release v0.2.2 v0.2.2 2026-04-03 04:36:19 +00:00
Miguel ÁngelandClaude Opus 4.6 49e333e9de feat(cli): add skills command that installs all without selection (#192)
* feat(cli): add skills command that installs all without selection

Wraps `npx skills add --all -g` so users don't need to manually
select skills or targets. Just run `hyperframes skills` and everything
gets installed to all supported AI tools.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(cli): fix typecheck errors in skills command

Use spawn instead of execFile to avoid stdio type mismatch.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 15:11:56 -07:00
Vance IngallsandClaude Opus 4.6 e9c2e6f772 fix(cli): resolve SyntaxError in bundled ESM output (#203)
Two issues prevented `npx hyperframes` from running:

1. The tsup banner declared `const __filename` which collided with
   esbuild's CJS-to-ESM `var __filename` shim. ESM strict mode
   rejects const+var redeclaration. Changed to `var` so both
   declarations coexist.

2. postcss (producer dependency) was not resolvable during bundling
   due to bun's isolated module layout. Added postcss as an external
   dependency of the CLI package.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 14:52:37 -07:00
Miguel Ángel 44fcc8e8b2 fix(templates): resolve all lint errors and warnings in bundled templates (#197)
## What

Added GSAP CDN imports to all template compositions and improved project linting to skip template placeholders.

## Why

Templates were missing GSAP script imports, causing JavaScript errors when GSAP animations tried to execute. The linter was also incorrectly flagging template placeholder values like `__VIDEO_SRC__` as missing audio sources.

## How

- Added `<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>` to all composition HTML files
- Updated `lintAudioSrcNotFound` function to skip template placeholders matching the pattern `__[A-Z_]+__`
- Fixed GSAP animation properties by adding `overwrite: "auto"` to prevent conflicts
- Replaced CSS transforms with GSAP `gsap.set()` calls for better animation control
- Moved video elements outside timed containers in some templates to avoid nesting issues
- Standardized transcript data format to use double quotes for JSON consistency
- Added proper timeline initialization with `window.__timelines = window.__timelines || {}`
- Enhanced caption styling with overflow handling and max-width constraints

## Test plan

- [x] Manual testing performed
- [x] Verified GSAP animations work correctly in all templates
- [x] Confirmed linter no longer flags template placeholders as errors
- [x] Tested video playback and audio synchronization
2026-04-02 14:38:24 -07:00
Miguel ÁngelandClaude Opus 4.6 7294803fbc feat(fonts): add Playfair Display, Noto Sans JP, Roboto, and 4 more to deterministic font database (#196)
* fix(engine): suppress font-loading 404 noise in render console output

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(engine): downgrade resource 404s to buffer-only instead of suppressing

Address review feedback: instead of silently dropping "Failed to load
resource" errors (which could hide real asset failures), keep them in
browserConsoleBuffer for diagnostics but don't print to stdout. Real
asset 404s are still caught by the file server's own logging.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(engine): narrow 404 filter to font CDN domains and woff2 files only

Address review: filter was too broad and could suppress real asset
failures. Now only suppresses 404s matching fonts.googleapis,
fonts.gstatic, or .woff2 file extensions. Missing images, scripts,
and videos will still surface as [Browser:ERROR] in render output.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(fonts): add Playfair Display, Noto Sans JP, Roboto, and 4 more to deterministic font database

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 14:32:08 -07:00
Miguel ÁngelandClaude Opus 4.6 38aadc2a25 fix(engine): suppress font-loading 404 noise in render console output (#195)
* fix(engine): suppress font-loading 404 noise in render console output

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(engine): downgrade resource 404s to buffer-only instead of suppressing

Address review feedback: instead of silently dropping "Failed to load
resource" errors (which could hide real asset failures), keep them in
browserConsoleBuffer for diagnostics but don't print to stdout. Real
asset 404s are still caught by the file server's own logging.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(engine): narrow 404 filter to font CDN domains and woff2 files only

Address review: filter was too broad and could suppress real asset
failures. Now only suppresses 404s matching fonts.googleapis,
fonts.gstatic, or .woff2 file extensions. Missing images, scripts,
and videos will still surface as [Browser:ERROR] in render output.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 14:30:35 -07:00
Miguel Ángel 7f4e4333e5 fix(cli): read actual composition dimensions in info and fix semver update check (#194)
## Summary

- **`info`** **command** now reads actual `data-width`/`data-height` from the root composition element instead of hardcoding 1920x1080 or 1080x1920 based on a parser heuristic that defaults to "portrait"
- **Update check** now uses proper semver comparison instead of string inequality (`!== VERSION`). Previously reported "update available" when installed `0.2.1` and npm had `0.2.0`

**Part 2 of 5** in a stacked PR series fixing E2E test findings.

## Test plan

- [x] `npx hyperframes info` on a 1920x1080 project shows "1920x1080" (not "1080x1920")
- [x] `npx hyperframes info --json` returns correct width/height fields
- [x] Version check no longer reports downgrade as available update
2026-04-02 14:12:48 -07:00
James RussoandClaude Opus 4.6 5e8ff36675 refactor(cli): colocate --help examples in command files (#202)
Move per-command examples from the centralized `help.ts` record into
each command file as `export const examples: Example[]`. help.ts now
dynamically imports them at --help time.

This means adding a new command and its examples happens in one file
instead of two, reducing the chance of forgetting examples.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 14:11:38 -07:00