366 Commits
Author SHA1 Message Date
James 2dc2531cf7 chore: release v0.6.25 2026-05-19 02:33:31 +00:00
James Russo 17f47f30dd fix(distributed): gate per-worker SwiftShader probe to worker 0 only (#956)
After #916 moved `assertSwiftShader` from `renderChunk()`'s eager probe
session into `executeWorkerTask`, every parallel worker began running its
own `chrome://gpu` / canvas-WebGL probe. At `chunkWorkerCount=6` (texture
launch at chunks=3) that's 6 concurrent CDP page-loads per chunk × 3
chunks = 18 simultaneous probes. Bench data on dev (12 producer pods × 22
vCPU) showed c=3 worst-case wall-clock at 67.3s, 24.7s above c=6 worst
(42.6s) — pod_total inflates 100s → 147s uniformly across all three
chunks per slow iter, the signature of cluster-level CDP contention
rather than within-pod contention.

Workers within a chunk share the same Chrome binary, flags, and OS/driver
state on a single pod, so worker 0's success is representative for the
rest. Gate the probe via `shouldVerifyWorkerGpu(workerId, config)` so
only worker 0 navigates to the probe page; workers 1..N-1 skip it. The
fail-fast contract still holds at the chunk level (worker 0 still aborts
the chunk if SwiftShader didn't load) — just without the concurrent CDP
traffic.

Expected wall-clock impact: c=3 worst drops from ~67s to in line with
c=6 worst (~42-44s). c=6 (3 workers/pod) and c=8 (2 workers/pod) should
see smaller wins; c=12 (1 worker/pod, sequential branch) is unaffected.

Closes #955.
2026-05-18 22:30:13 -04:00
Miguel Ángel 7354d61371 chore: release v0.6.24 2026-05-18 22:00:56 -04:00
Miguel Ángel 72a18a0116 Merge pull request #947 from heygen-com/feat/studio-blocks-panel
feat(studio): full Blocks panel — browse, search, add, drag-and-drop registry items
2026-05-19 03:58:25 +02:00
Miguel Ángel ffbc18ad31 feat(studio): full Blocks panel — browse, search, add, drag-and-drop registry items
Adds a Blocks tab to the Studio left sidebar with the full 78-item registry
catalog (58 blocks + 20 components). Users can browse by category, search by
title/description, preview CDN-hosted poster thumbnails with video-on-hover,
and install items on-demand with one click or drag-to-timeline.

Core changes:
- BlockCategory type + resolveBlockCategory() for 7 categories (Captions, VFX,
  Transitions, Effects, Social, Data, Scenes)
- Registry API routes: GET /api/registry/blocks (catalog) + POST install
- StudioApiAdapter extended with listRegistryCatalog + installRegistryBlock
- Vite adapter reads from disk; CLI adapter fetches from GitHub (24h cache)
- BlockParam interface + params on 6 blocks for future parameter controls

Studio UI:
- 4th sidebar tab "Blocks" with responsive grid, category pills, search bar
- BlockCard: CDN poster thumbnail, video autoplay on hover, duration + WebGL badges
- On-demand install: blocks append as sub-compositions on timeline; components
  overlay at start=0 spanning full duration with transparent background patching
- TIMELINE_BLOCK_MIME drag-and-drop to timeline
- BlockParamsPanel (Phase 3 scaffold) auto-opens for parameterized blocks

Registry manifests:
- All 58 blocks backfilled with preview: { video, poster } CDN URLs
- All 20 components normalized to object format + poster URLs added
- 6 blocks annotated with params (Liquid Glass/Background, Portal, Chart,
  Logo Outro, Magnetic)
- flowchart-vertical preview generated and uploaded to CDN
2026-05-18 21:15:15 -04:00
James 7e0a447325 refactor: drop unused exports detected by fallow auto-fix
Run `fallow fix --auto-fixable` to remove `export` keywords from symbols
fallow's reachability analysis identifies as unused. Keeps only the cases
where the symbol is still referenced internally in its own file (so
removing `export` doesn't surface a new oxlint `no-unused-vars` error).

Result: fallow dead-code findings drop from 276 → 208 (68 fewer unused
exports), with no behavior change — each symbol is still defined and used
exactly the same way within its file.

Reverted ~20 files where fallow's auto-fix would have created cascading
"declared but never used" lint errors — those are cases where the symbol
isn't used at all, and properly cleaning them up means deleting the
declaration, not just dropping `export`. Better to land that as a
separate, narrower PR rather than mixing it into a mechanical de-export.

Also reverted four false positives where fallow missed real consumers:
- `captureCost.ts` (renderOrchestrator has two separate import blocks
  from the same module; fallow only saw the first)
- `propertyPanelHelpers.ts`, `domEditingLayers.ts` (real internal uses
  fallow's reachability missed)
- `render.ts` (functions imported via `await import()` dynamic import,
  which fallow's static analysis doesn't follow)

Test plan: bun run --filter '*' typecheck (clean), oxlint + oxfmt clean,
cli/core/studio/engine vitest suites pass (335 + 917 + 576 + 605 tests).
2026-05-19 00:51:56 +00:00
Miguel Ángel d873364746 test(producer): regenerate 7 stale regression baselines (#946)
## Summary

- Regenerate golden baselines for 7 regression tests that were deterministically failing due to stale baselines from before the recent renderer fixes
- Root cause: renderer changes (revert flattenInnerRoot, late-bind polling removal, conditional rebind, sub-comp timeline readiness polling) changed visual output, but baselines were never regenerated
- CI's `Detect changes` job was skipping shards on non-engine commits, masking the failures — making it look like flaky tests when in reality they failed every time shards ran

## Tests regenerated (all in Docker with pinned Chrome 148.0.7778.167)

| Test | Previously failed frames | Baseline age |
|---|---|---|
| `gsap-letters-render-compat` | 86/100 | old |
| `typegpu-adapter` | 76/100 | May 13 |
| `style-7-prod` | 60/100 | PR #368 (very old) |
| `style-15-prod` | 59/100 | May 17 (pre-renderer-fixes) |
| `style-18-prod` | 19/100 | old |
| `style-3-prod` | 7/100 | May 17 (pre-renderer-fixes) |
| `style-8-prod` | 1/100 | borderline |

## Test plan

- [x] All 7 tests pass locally in Docker with 0 failed frames
- [x] Verified deterministic — ran style-7-prod twice, same result both times
- [x] All 8 CI regression shards green
- [x] Other tests (style-1, 2, 4, 5, 6, 9, 10, 11, 12, 13, 16, 17, overlay-montage, sub-composition-video, vignelli-stacking, etc.) still pass

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-05-19 02:38:07 +02:00
Miguel ÁngelandClaude Sonnet 4.6 13846554f5 test(producer): regenerate gsap-letters-render-compat baseline
Missed in the initial batch — 86/100 frames were failing. Regenerated
in Docker with the same pinned Chrome build. Now passes with 0 failures.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 23:43:13 +00:00
Miguel ÁngelandClaude Sonnet 4.6 5a6c3dd45f test(producer): regenerate 6 stale regression baselines in Docker
Renderer fixes (revert flattenInnerRoot, late-bind polling removal,
conditional rebind, sub-comp timeline readiness polling) changed visual
output but baselines were never regenerated. CI's Detect changes job
was skipping shards on non-engine commits, masking the failures.

Regenerated in Docker (Dockerfile.test) with pinned Chrome 148.0.7778.167:
- style-7-prod (60/100 frames failed, baseline from PR #368)
- style-15-prod (59/100 frames failed)
- style-18-prod (19/100 frames failed)
- style-3-prod (7/100 frames failed)
- style-8-prod (1/100 frames failed, borderline)
- typegpu-adapter (76/100 frames failed)

All 6 now pass locally with 0 failed frames.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 22:28:10 +00:00
James Russo 2fd161b943 Merge pull request #939 from heygen-com/feat/auto-size-chunk-size-when-undefined
feat(producer): auto-size chunkSize from maxParallelChunks when undefined
2026-05-18 17:25:08 -04:00
JamesandClaude Opus 4.7 a8499632c6 test(producer): preserve single-chunk-path coverage + add auto-size integration
Address PR review feedback on #939:

- Pin chunkSize=240 on the golden planDir layout test so the 1-chunk path
  through plan() stays exercised after the auto-sizer change. Assert
  chunkCount === 1 explicitly (previously just >= 1).
- Add an integration test that runs plan() with chunkSize=undefined and
  asserts the auto-sizer produces multi-chunk output end-to-end
  (chunkCount=3, encoder.gopSize=10, encoder.chunkSize=10) for the same
  30-frame fixture.
- Document the GOP/file-size trade-off on the chunkSize docstring so
  adopters who optimize for output bytes know to pin chunkSize.
- Update the resolveChunkPlan docstring formula to reference the operative
  variable (resolvedChunkSize) instead of the now-ambiguous chunkSize.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 21:09:31 +00:00
James Russo 1edea8afe4 Merge pull request #938 from heygen-com/chore/fallow-config-and-cleanup
chore: add fallow config and fix high-signal findings
2026-05-18 16:52:18 -04:00
James Russo cc7622cc42 Merge pull request #926 from heygen-com/fix/ci-chrome-pin-and-psnr-harness
fix(ci): pin chrome-headless-shell + clamp PSNR checkpoint to a valid frame
2026-05-18 16:51:29 -04:00
JamesandClaude Opus 4.7 fd3fce9955 refactor(producer): tighten resolveChunkPlan assertion + trim comments
Address self-review findings:
- assertPositiveInteger now only runs on the caller-supplied path so the
  error message names `configChunkSize` only when the caller actually
  passed one. Previously, the assertion fired against `resolvedChunkSize`
  on both paths and would have lied about the offending input.
- Drop the call-site comment that narrated the diff/history; the
  function docstring already covers the contract.
- Drop the internal-track name and date from the MIN_CHUNK_SIZE rationale
  and the test block header.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 20:37:25 +00:00
JamesandClaude Opus 4.7 e79a8faa42 feat(producer): auto-size chunkSize from maxParallelChunks when undefined
Previously, plan() defaulted chunkSize to 240 on a `?? DEFAULT_CHUNK_SIZE`
line, so a 660-frame composition with maxParallelChunks=16 ended up at 3
chunks (ceil(660/240)) regardless of the caller's fan-out intent.

When config.chunkSize is undefined, auto-size from maxParallelChunks:
  effectiveChunkSize = max(MIN_CHUNK_SIZE, ceil(totalFrames / maxParallelChunks))

MIN_CHUNK_SIZE=10 keeps per-chunk fixed overhead from swamping the
parallelism gain on tiny renders. Explicit numbers, including 240, take
precedence over the auto-sizer — no behavior change for callers that set
chunkSize explicitly.

Surfaced by the lever-1 chunk-scaling benchmark on 2026-05-17.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 20:29:34 +00:00
James 2087d5dab2 chore: add fallow config and fix high-signal findings
Configure fallow via .fallowrc.jsonc so its analysis reflects this repo's
real entry surface, then fix the genuine issues it found.

Fallow noise reduction (601 → 276 dead-code findings):
- Ignore docs/, test fixtures, skill test-corpora, registry/, examples/
- Declare worker entry points loaded dynamically by file path
  (pngDecodeBlitWorker.ts, shaderTransitionWorker.ts)
- Declare runtime IIFE entry (core/src/runtime/entry.ts) built outside the
  import graph by build-hyperframes-runtime-artifact.ts
- Declare bun:test files in producer + aws-lambda as test entries
- Ignore dynamically-resolved deps: tsup external (puppeteer-core, esbuild,
  giget), peer/static-file (gsap in player perf tests), workspace deps
  hoisted by bun (happy-dom, @hyperframes/*), and @fontsource/* packages
  read via readFileSync in generate-font-data.ts

Extract inline build:fonts scripts:
- packages/{cli,producer}/package.json had multi-line `node -e ...` blobs
  containing braces that fallow mis-parsed as glob alternate groups. Moved
  to dedicated build-fonts.mjs scripts.

Fix duplicate exports:
- Remove dead FileIcon alias in studio/SystemIcons.tsx (FileTreeIcons.tsx
  has the real, used one)
- Consolidate ValidationResult: drop the identical duplicate in
  gsapParser.ts; both parsers now import from core.types
- Suppress intentional namespace patterns (per-namespace ML manager
  exports; CLI per-command 'examples' convention; fileServer.ts test-only
  isPathInside which has different symlink semantics from utils/paths.ts)

Break circular dep (studio/components/editor):
- manualEditsDom.ts re-exported clearStudioPathOffset / clearStudioRotation
  / clearStudioBoxSize from manualEditsSnapshot.ts, which imports four
  helpers from manualEditsDom.ts — back-edge cycle
- Re-export moved to manualEdits.ts (the package-public barrel) where the
  rest of the snapshot re-exports already live; underlying files now form
  a clean DAG

Remove genuinely unused deps:
- studio: motion (no imports anywhere), codemirror (umbrella package; the
  @codemirror/* sub-packages are used directly)
- cli: mime-types (plus its only consumer src/utils/mime.ts, which was a
  hardcoded mime table that didn't use the package), and its now-stale
  tsup external entry

Verified: typecheck across core/cli/producer/studio is clean, oxlint
+ oxfmt pass, manualEdits.test.ts (18 tests) and core parser tests (69
tests) still pass.

Deferred follow-ups (real findings, separate PRs):
- 8 circular deps in producer/services/render/stages/ — renderOrchestrator
  ↔ captureHdr* / captureStage / extractVideosStage form a hub cycle
- ~14 unused files in producer/src/services/ that look like dead
  re-export shims to @hyperframes/engine, but aren't in the public
  exports map — need to confirm no deep-import consumers before deletion
- waveform.ts complexity hotspot
2026-05-18 18:57:21 +00:00
Miguel Ángel 27efcd0f80 chore: release v0.6.22 2026-05-18 14:23:43 -04:00
Miguel Ángel 8163f38077 chore: release v0.6.21 2026-05-18 11:29:01 -04:00
Miguel Ángel 4bf2fa5cd1 fix: revert flattenInnerRoot, use host-level authored-id + count-based rebind
Three changes to fix regression failures without breaking baselines:

1. Revert flattenInnerRoot in producer — use the original innerHTML
   inlining that preserves the existing DOM structure. Instead, set
   data-hf-authored-id on the HOST element so the scoped proxy can
   still rewrite #id selectors for sub-composition scripts.

2. Revert compiled.html baselines to main (no DOM structure changes).

3. Use timeline-count comparison instead of poll duration to decide
   whether to call __hfForceTimelineRebind. Compare timeline count
   before vs after the poll — rebind only when new timelines appeared
   during polling. This correctly identifies async compositions
   regardless of fetch speed, while leaving sync compositions
   untouched.
2026-05-18 10:20:17 -04:00
Miguel Ángel 04c35ce24c fix: regression fixes — conditional rebind + updated compilation baselines
1. Only call __hfForceTimelineRebind() when the timeline poll actually
   had to wait (pollDuration > 2 intervals). For compositions with
   synchronous timeline registration, the rebind was unnecessary and
   shifted render timing, causing PSNR regressions in chat and
   gsap-letters-render-compat.

2. Regenerate compiled.html baselines for missing-host-comp-id and
   overlay-montage-prod to match the new flattenInnerRoot behavior
   (data-composition-id stripped from inlined inner roots, replaced
   with data-hf-authored-id).

3. Add late-bind polling to runtime init.ts — after external
   compositions load, poll for 5s to detect async timelines that
   register after initial binding (e.g. from fetch callbacks).
2026-05-18 03:46:58 -04:00
Miguel Ángel 0f1c64dcae fix: address review feedback — observability, dedup, query-strip, catalog
Review items addressed:

1. Mirror video-failure warning in beginFrame path (was screenshot-only)
2. Fix resolveProjectRelativeSrc escape-fallback to use query-stripped
   cleanSrc instead of raw src for the normalize/strip arm
3. Export prepareFlattenedInnerRoot from @hyperframes/core/compiler and
   consume in the producer instead of duplicating the implementation
4. Use typed Window cast instead of (window as any) for __hfForceTimelineRebind
5. Regenerate docs/public/catalog-index.json with all 6 map blocks
6. Restore Maps nav group in docs.json (catalog generator had merged
   them into Data)
2026-05-18 01:41:15 -04:00
Miguel Ángel cf2addfb92 chore: remove accidentally committed producer node_modules 2026-05-18 01:28:31 -04:00
Miguel Ángel 4b9abaaf6f fix: deterministic stagger, lint importmap/module, cache-bust previews
- Replace from:"random" with from:"center" stagger in us-map,
  world-map, spain-map — random stagger is non-deterministic across
  parallel render workers, causing visual jumps at chunk boundaries.

- Exempt type="importmap" and type="module" inline scripts from the
  invalid_inline_script_syntax lint rule. The rule used new Function()
  to parse, which rejects import statements and JSON import maps.
  Closes #929.

- Cache-bust all map MDX preview video URLs after re-rendering with
  the deterministic stagger fix.
2026-05-18 01:18:24 -04:00
Miguel Ángel 5aff328a9b fix(producer): preserve inner root element when inlining sub-compositions
The producer's inlineSubCompositions was not passing flattenInnerRoot,
causing sub-composition inner root elements to be unwrapped during
compilation. Scripts using #id selectors (rewritten to
[data-hf-authored-id] by the scoping proxy) could not find their DOM
root because the authored-id attribute was never set.

The core bundler (bundleToSingleHtml) already passed flattenInnerRoot
correctly. This aligns the producer's render compilation with the
same behavior: clone the inner root, strip timing/composition attrs,
replace id with data-hf-authored-id, and mark with
data-hf-inner-root.
2026-05-18 00:27:20 -04:00
Miguel Ángel 5beeb4227e Merge pull request #927 from heygen-com/fix/caption-polish
fix(registry): polish caption timing and add highlight style
2026-05-18 03:06:57 +02:00
Miguel Ángel c8e8fdcf6d fix(producer): fetch missing font weights from Google Fonts at render time
The deterministic font system now supplements its embedded font bundle
with Google Fonts fetches for any weights not in the pre-bundled set.
This fixes compositions that request font weights (e.g. Montserrat 300)
not included in the CANONICAL_FONTS faces array — previously those
weights were silently dropped, causing invisible text in renders.

Also replaces caption-glitch-rgb and caption-weight-shift with improved
versions from avatar preview compositions, adapted to 1920x1080 with
standard demo transcript.
2026-05-17 20:41:30 -04:00
James 9a063e3fe0 Merge remote-tracking branch 'origin/main' into fix/ci-chrome-pin-and-psnr-harness 2026-05-18 00:18:55 +00:00
James Russo 4f274d3605 Merge pull request #920 from heygen-com/chore/aws-lambda-publish-ready
chore(lambda): publish-readiness for @hyperframes/aws-lambda
2026-05-17 19:41:04 -04:00
James 187dfd5b6c fix(producer): indirect lambda-local import path so tsc can't resolve it
The tsconfig `exclude` list isn't enough to keep producer's tsc emit pass
from pulling `regression-harness-lambda-local.ts` (and its
`@hyperframes/aws-lambda` static imports) into the program — tsc still
statically resolves the path in
`await import("./regression-harness-lambda-local.js")` from
`regression-harness.ts`, walks into the excluded file, and fails on
the missing aws-lambda type declarations.

Reproduction (clean workspace, no aws-lambda dist yet, mirrors CI):

  rm -rf packages/{aws-lambda,producer,core}/dist
  bun run build
  # @hyperframes/producer build: src/regression-harness-lambda-local.ts(36,70):
  # error TS2307: Cannot find module '@hyperframes/aws-lambda' or its
  # corresponding type declarations.

Fix: route the dynamic import path through a top-level string constant
so tsc can't statically resolve the target. tsc keeps the type-only
imports (`RunLambdaLocalRender` from the no-aws-lambda types file) and
treats the dynamic-import target as opaque. `tsx` resolves the path
normally at runtime, so `--mode=lambda-local` is unchanged.
2026-05-17 23:11:45 +00:00
Miguel Ángel 20a6ff6505 Merge pull request #925 from heygen-com/fix/regenerate-style-baselines
test: regenerate style-1-prod and style-12-prod baselines
2026-05-18 01:08:38 +02:00
James 070d40fecb fix(ci): pin chrome-headless-shell + clamp PSNR checkpoint to a valid frame
Two narrow fixes to keep the regression suite green and reproducible.
Stale baselines from the sub-composition refactor (PR #918) are being
regenerated separately in PR #925; this PR is just the structural
fixes that PR can't make on its own.

1. **Pin `chrome-headless-shell` in `Dockerfile.test`** to
   `148.0.7778.167` instead of `@stable`. `@stable` is a moving tag;
   every Chrome stable promotion shifts pixel output enough to fail
   PSNR on the golden baselines, so the regression suite silently
   broke whenever Docker.test rebuilt against a freshly-promoted
   stable. Pinning to the version `@stable` currently resolves to
   (matching what main's regenerated baselines were captured under)
   makes Chrome bumps an explicit, batched-with-baseline-regen
   action. Comment on the `RUN` line spells out the bump procedure.

2. **Clamp the last PSNR checkpoint to a frame the video stream
   actually contains.** `runTestSuite` samples 100 checkpoints across
   `min(rendered, snapshot)` container duration. Container duration
   includes audio padding past the last video frame — many-cuts is
   5.654s container vs 5.6s of video at 30fps = 168 frames. At i=99
   the raw container duration mapped to time 5.59746s → frame index
   168 (round(5.59746 × 30)), one past the last frame the stream
   contains. ffmpeg's `psnr` filter emits no `average:` line for a
   non-existent frame, so the harness crashed with `Unable to parse
   PSNR output at 5.59746s` — pre-existing on plain `origin/main`,
   which PR #918 admin-merged through on shard-2. Miguel's regen via
   `--update` didn't catch it because `--update` only writes the
   snapshot; it doesn't validate. Subtracting one frame interval
   from the sampling duration guarantees the last checkpoint always
   lands on a real frame.

Verified locally inside `Dockerfile.test`:

  bun run --cwd packages/producer docker:build:test
  bun run --cwd packages/producer docker:test many-cuts   #  green
  bun run --cwd packages/producer docker:test style-3-prod \
    style-5-prod sub-composition-video                    #  green
2026-05-17 22:41:55 +00:00
Miguel Ángel 6c533c0b0f chore: release v0.6.20 2026-05-17 18:07:59 -04:00
Miguel ÁngelandClaude Sonnet 4.6 4d403b0d73 test: regenerate style-1-prod and style-12-prod baselines in Docker
Sub-comp visibility fix (PR #918) changed rendered output for these two
tests but the baselines on main were stale. Regenerated inside
Dockerfile.test to match CI's Chrome + ffmpeg build.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 22:03:17 +00:00
Miguel Ángel 04aa6a644f chore: release v0.6.19 2026-05-17 17:55:32 -04:00
James a18fc04bf4 fix(producer): break aws-lambda↔producer build cycle for lambda-local harness
The aws-lambda publish-readiness changes earlier in this PR moved
aws-lambda's types from `./src/index.ts` → `./dist/index.d.ts`. That
flipped producer's emit pass from "always works" to "needs aws-lambda
built first," because producer's `regression-harness-lambda-local.ts`
imports `@hyperframes/aws-lambda{,/handler}`. aws-lambda's own emit
pass in turn imports `@hyperframes/producer{,/distributed}` → circular
build dependency, so neither side can emit declarations in a single
pass. CI's first run on this PR failed Build, Typecheck, CLI smoke,
windows tests, and every perf job for this reason.

Fix: extract the public surface of `regression-harness-lambda-local.ts`
into a new types-only file that has no aws-lambda imports, point
`regression-harness.ts` at that types file, and exclude
`regression-harness-lambda-local.ts` from producer's tsconfig
`include`. The implementation file is still loaded at runtime via
`tsx` (lambda-local mode runs the harness through tsx, not from
producer's dist/), so the runtime contract is unchanged — producer's
tsc just no longer has to type-check it.

  - `regression-harness-lambda-local-types.ts` (new): exports
    `RunLambdaLocalInput` + `RunLambdaLocalRender` with zero
    aws-lambda imports.
  - `regression-harness-lambda-local.ts`: re-exports
    `RunLambdaLocalInput` from the new types file (so the public
    name stays stable for any future direct imports).
  - `regression-harness.ts`: drops the `typeof import("...")` trick
    and uses the explicit `RunLambdaLocalRender` signature for the
    dynamically-loaded function.
  - `tsconfig.json`: excludes `src/regression-harness-lambda-local.ts`
    so producer's emit pass never resolves `@hyperframes/aws-lambda`.

Verified:

  bun run build                   # full root build green
  bun run verify:packed-manifests # all packages publish-safe
2026-05-17 21:43:46 +00:00
Miguel Ángel 1ac70e8968 Merge pull request #922 from heygen-com/fix/css-import-inlining
fix(core): resolve CSS @import and rebase url() paths during bundling
2026-05-17 23:28:36 +02:00
Miguel Ángel aa2f59c1d2 chore: untrack packages/producer/dist build artifacts
The root .gitignore already has `dist/` but these files were
force-added at some point. No other package has dist/ tracked.
2026-05-17 17:24:20 -04:00
Miguel Ángel 7a1e8769a5 Merge pull request #918 from heygen-com/refactor/unify-subcomp-inlining
fix: hold external sub-compositions in render mode + regenerate baseline
2026-05-17 20:22:21 +02:00
James Russo b05a22e69e feat(producer): add --mode=lambda-local to the regression harness (#913)
* feat(producer): add --mode=lambda-local to the regression harness

Third harness mode that drives the OSS @hyperframes/aws-lambda handler
through the exact event sequence Step Functions produces in
production:

  handler({Action: "plan"})             → planDir tarball on fake S3
  handler({Action: "renderChunk"}) × N  → chunk artifacts on fake S3
  handler({Action: "assemble"})         → final mp4/mov/png-sequence

The S3 client is a filesystem-backed fake (every s3://<bucket>/<key>
URI maps to <tempRoot>/s3/<key>), so the harness exercises the
handler's event-parsing + tar/S3 conventions + dispatch logic on top
of the underlying producer primitives. Regressions in event JSON
shape, S3 key layout, or plan-hash boundary checks now surface in
the same CI run as the in-process and distributed-simulated modes
without paying for a real AWS round-trip.

Deliberately NOT a Docker/RIE invocation — that would gate the
producer test suite on Docker-in-Docker support which most CI
runners lack. Real-ZIP-via-RIE tests live in
packages/aws-lambda/scripts/ (probe:beginframe) and the
maintainer-run smoke.sh.

Wired up via:
  - HarnessMode union extended to include "lambda-local"
  - parseHarnessModeFlag accepts --mode=lambda-local
  - regression-harness.ts dispatches to runLambdaLocalRender for
    the new mode, sharing the distributed-support gate +
    pathology-floor threshold with distributed-simulated mode
  - package.json scripts: test:lambda-local + docker:test:lambda-local
  - producer.devDependencies += @hyperframes/aws-lambda (workspace)
  - producer/tsconfig.json gains path mappings to self so the type
    cycle through aws-lambda's source resolves at typecheck time
    without needing producer to be pre-built

Tests: 3 new unit tests on parseHarnessModeFlag + resolveMinPsnrForMode
cover the new mode. End-to-end PSNR contract still runs through
Dockerfile.test (manual + CI).

* refactor(producer): /simplify pass on lambda-local harness imports

Three small cleanups on top of the lambda-local harness:

  - Drop the unused createReadStream import + its `void` workaround
    comment. The aws-lambda handler's tar / S3 transport pulls
    createReadStream from its own imports; this file never references
    it directly.

  - Hoist the dynamic `await import("node:fs")` calls for
    writeFileSync out of FilesystemBackedFakeS3.send into the static
    import block. Repeated PutObject calls don't need to repay the
    dynamic-import cost.

  - Hoist the dynamic `await import("@hyperframes/aws-lambda")` call
    for untarDirectory similarly. Drops the now-redundant duplicate
    aws-lambda import statement.

The PutObject body branch also collapses: `body instanceof Buffer`
and `typeof body === "string"` both call writeFileSync identically,
so they share one branch.

No behavior changes.

* fix(producer): lazy-import lambda-local harness module

The static import of regression-harness-lambda-local.ts pulled
@hyperframes/aws-lambda (and its @aws-sdk/* + @sparticuz/chromium
transitive deps) at module-load time. Dockerfile.test only copies
the producer's own files into the container, so aws-lambda's src
isn't present at runtime — and even `--mode=in-process` failed:

  Error [ERR_MODULE_NOT_FOUND]: Cannot find module
  '/app/packages/producer/node_modules/@hyperframes/aws-lambda/src/index.ts'
  imported from /app/packages/producer/src/regression-harness-lambda-local.ts

Load the module on demand instead. `--mode=lambda-local` callers
pay the import cost; the existing in-process and distributed-
simulated modes don't.

* fix(producer): address PR review on lambda-local harness

Three review items from Vai:

  - `Config.width`/`Config.height` are now plumbed through
    RunLambdaLocalInput rather than hardcoded inside
    runLambdaLocalRender. Lambda-local's whole point is to catch
    event-shape drift; if the handler ever starts honouring
    Config.width/height (e.g. for canvas sizing), having those
    values flow from the caller means the harness sees what the
    fixture authored. The interface change makes the eventual
    upgrade-to-real-fixture-resolution a one-line dispatch swap.

  - Drop the dead `export type { Fps }` and its unused import
    from @hyperframes/core. The module never re-exports it.

  - The dispatch site in regression-harness.ts now passes 1920×1080
    explicitly with a comment marking it as a placeholder until
    the harness compiles the composition HTML up-front to surface
    the authored data-width/data-height. distributed-simulated
    mode uses the same placeholder internally, kept for parity.

No behavior change in the existing modes; lambda-local now has a
clear extension point for honouring fixture dimensions.
2026-05-17 14:04:49 -04:00
Miguel ÁngelandClaude Sonnet 4.6 54afeccde7 test: regenerate all affected baselines in Docker
Regenerated baselines for all regression tests with sub-compositions
in the cancelled shards: style-3-prod, style-5-prod, style-9-prod,
style-15-prod, style-16-prod, style-17-prod, style-18-prod,
sub-composition-video, many-cuts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 17:47:25 +00:00
Miguel ÁngelandClaude Sonnet 4.6 819b00b9fe test: regenerate style-5-prod baseline
Sub-composition visibility fix changes output for compositions
with external sub-compositions. Baseline regenerated in Docker.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 17:37:37 +00:00
Miguel ÁngelandClaude Sonnet 4.6 d242c73948 test: regenerate style-1-prod baseline after linkedom fragment fix
The sub-composition inlining now correctly preserves composition IDs
when the host data-composition-id differs from the inner root's
(e.g., host "captions-comp" with inner root "captions"). The captions
layer renders with proper scoping, changing visual output.

Baseline regenerated inside Docker per CLAUDE.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 17:13:52 +00:00
Miguel ÁngelandClaude Sonnet 4.6 581e7a7eda refactor: extract shared inlineSubCompositions from bundler and producer
Both the core bundler (htmlBundler.ts) and the producer (htmlCompiler.ts)
had parallel ~200-line implementations of sub-composition inlining. This
divergence caused bug #911 (producer didn't set data-composition-file).

Extract the shared logic into core/compiler/inlineSubCompositions.ts:
- Single function handles: template/body extraction, CSS/script scoping,
  asset path rewriting, data-composition-file attribution, content injection
- Callers provide environment-specific callbacks (HTML resolution, parsing,
  variable handling, inner root flattening)
- Core bundler passes its advanced features (runtime IDs, variables,
  inline style rewriting, inner root flattening)
- Producer passes a simpler resolver (map + filesystem fallback) and
  adds pixel sizing post-hoc

Net: -215 lines, one source of truth for sub-comp inlining.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 16:28:20 +00:00
Miguel ÁngelandClaude Sonnet 4.6 b9bdc80db7 test: regenerate style-12-prod baseline after sub-comp duration hold fix
The sub-composition visibility fix (2b46565c) correctly holds external
compositions through their authored data-duration. This changes
style-12-prod output from t=8.26s onward: the mondrian-colors
sub-composition now stays visible instead of going black when its GSAP
timeline ends.

Baseline regenerated inside Docker per CLAUDE.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 16:14:43 +00:00
Miguel ÁngelandClaude Sonnet 4.6 78fce8bd8a chore: release v0.6.18
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 15:32:53 +00:00
Miguel ÁngelandClaude Sonnet 4.6 2b46565c65 fix(runtime): hold external sub-compositions in render mode
PR #917 fixed visibility clamping for external sub-compositions in
preview mode by checking data-composition-src. However, the producer's
htmlCompiler strips that attribute during inlining without setting the
data-composition-file marker that the core bundler sets. This caused
the runtime to still clamp duration to Math.min(authored, live) in
rendered output.

Two fixes:
- Runtime: also check data-composition-file (set by the core bundler
  after inlining)
- Producer: set data-composition-file before removing
  data-composition-src, matching the core bundler's behavior

Closes #911

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 15:30:23 +00:00
Miguel ÁngelandClaude Sonnet 4.6 3f976d454c chore: release v0.6.17
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 15:15:54 +00:00
James b30fd29695 chore: release v0.6.16 2026-05-17 08:20:08 +00:00
James Russo f01fccb0ea perf(distributed): skip eager probe session when chunkWorkerCount > 1 (#916) 2026-05-17 04:18:47 -04:00
James Russo 34d1f0e1d0 feat(lambda): add TypeScript SDK and CDK construct (#909)
* feat(lambda): add TypeScript SDK and CDK construct

Adds the client-side surface on top of the Phase 6a Lambda handler so
adopters can drive a deployed stack from Node without writing AWS-SDK
boilerplate:

- renderToLambda(opts) starts a Step Functions execution and returns a
  handle. Does NOT poll.
- getRenderProgress({ executionArn }) returns a snapshot of progress,
  frames rendered, cost (Lambda GB-seconds + SFN transitions), errors,
  and the final output object once Assemble completes.
- deploySite({ projectDir, bucketName }) content-addresses the project
  tree, tar.gzs it, and uploads to S3 with a HeadObject short-circuit so
  re-renders of the same tree skip the tar+PUT.
- validateDistributedRenderConfig throws a typed InvalidConfigError
  before StartExecution, so shape errors surface synchronously.
- computeRenderCost is exposed for callers who want to format cost out
  of band.

Also ships HyperframesRenderStack, an aws-cdk-lib L2 construct that
emits the same topology as examples/aws-lambda/template.yaml. Lives on
the ./cdk subpath export so SDK-only consumers don't pull aws-cdk-lib
into their runtime graph (declared as an optional peer dependency).

Tests: 24 new unit tests across the SDK plus 9 CDK synth / contract /
snapshot tests. All 83 tests in packages/aws-lambda/src pass.

* refactor(lambda): /simplify pass on the SDK + CDK PR

Pulls shared logic out so the SDK doesn't re-invent things the handler
and the producer already have:

- `formatExtension` extracted to packages/aws-lambda/src/formatExtension.ts.
  handler.ts and renderToLambda.ts both used identical 12-line copies of
  this switch.
- `PLAN_PROJECT_DIR_SKIP_SEGMENTS` is now exported from
  @hyperframes/producer/distributed. deploySite consumes it instead of
  its own duplicate SKIP_TOP_LEVEL set; the two lists were trivially
  identical and would have drifted silently.
- `FakeS3` + `drainBody` factored out of the two SDK test files into
  src/sdk/__fixtures__/fakeS3.ts. Drops ~110 lines of test-file
  duplication and gives future SDK tests a one-line FakeS3 import.
- S3 URI building in deploySite and renderToLambda routes through the
  existing `formatS3Uri` helper instead of inline `s3://...`
  concatenation; matches the convention already in handler.ts.

Net -133 lines across the touched files. All 83 aws-lambda tests still
pass; all 60 producer distributed tests still pass.

* fix(lambda): bump CDK test timeouts for CI cold-start synth

The bun:test default 5s timeout tripped the first CDK snapshot test
in CI when the cold-start `Template.fromStack(stack)` synth took ~5-8s
on the slowest GitHub Actions runner. Locally on a warm shell the
synth measures <1s, so the failure didn't reproduce until PR #909 hit
CI.

Two changes:

  - Both CDK test files cache one synth in `beforeAll(..., 30000)` and
    reuse the result across every test that uses the default props.
    Each individual test now runs in microseconds (pure assertions
    against the already-synthed template), so the 5s timeout no longer
    applies on the hot path.

  - The two contract tests that exercise non-default props
    (reservedConcurrency, projectName) still synth fresh per-test; they
    get a per-test `it(..., 30000)` timeout.

No behavior changes.

* fix(lambda): address PR review on SDK + CDK construct

Three correctness + ergonomics fixes raised in Vai's review:

  - getRenderProgress over-counted SFN transitions by 3-5×. Step
    Functions Standard Workflows bill per state-entry, not per
    history event. Each Task produces ~5-7 history events
    (Scheduled / Started / Succeeded / TaskStateExited / …);
    counting `events.length` reported the runaway. Switch to
    counting `*StateEntered` events explicitly.

  - assembleComplete + outputFile detection was coupled to the
    Lambda payload's `Action` field. Move both signals onto the
    enclosing state name (`StateExited.name === "Assemble"`), which
    is the state-machine identity rather than the Lambda event
    contract. framesRendered increment moves to the same boundary
    (RenderChunk state).

  - SiteHandle now carries `bucketName` directly so README + CLI
    callers don't have to re-parse `projectS3Uri.split("/")[2]`.

Test updates: getRenderProgress tests wrap renderChunk/assemble
events in matching StateEntered + StateExited pairs so the new
state-name-driven dispatch is exercised end-to-end. SiteHandle
fixture in renderToLambda.test.ts gets the new bucketName field.

All 83 aws-lambda tests still pass.
2026-05-17 03:03:51 -04:00