Commit Graph
100 Commits
Author SHA1 Message Date
James RussoandClaude Opus 4.7 807078c7cd docs(skills): make captions non-optional in changelog-video (#2729)
* docs(skills): make captions non-optional in changelog-video

The Jul 13-20 build shipped without captions because the pipeline had two soft
failure modes:

- Empty vo-words.json from the TTS provider was silently accepted, so
  align-captions.mjs had nothing to align. captions.json was never produced.
- Step 5 tolerated the master-skeleton's LINES array staying empty. The build
  lint-checked and validated clean, but the caption rail rendered no text.

This adds three hard gates on top of the existing scaffold:

- Step 4 adds a whisper forced-alignment fallback so a missing TTS timestamp
  payload no longer breaks the caption pipeline. Whisper only supplies timings;
  captions still use the DISPLAY layer from script-tokens.json.
- Step 5 flags an empty LINES array as a shipped bug and shows the exact
  IIFE-input shape it expects.
- Step 6 adds gate 5: sample 3-4 frames across the VO window and confirm visible
  caption text on each. If any spoken window renders no caption, the build is
  red.

Anti-patterns table gets two rows covering the empty-LINES and
missing-word-timings failure modes. .claude and .agents mirrors stay
byte-identical.

Follow-up to #2669 (pre-build gate); paired with the v5 rebuild that
retrofitted captions onto the shipped Jul 13-20 video.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs(skills): bump changelog caption size to 32px for phone-screen legibility

25px reads too small at 1080² when the video is viewed on a phone without
zoom. Bumped to 32px (font-size 25→32, top 1002→990, height 40→52, alpha
.92→.94) so captions carry across small-screen playback. Change is CSS-only
on the master-skeleton; existing per-scene chrome and layouts are untouched.

Retrofitted the Jul 13-20 v6 build with the same values (identical CSS
block) so the shipped video and the future default match. .claude and
.agents mirrors stay byte-identical.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs(skills): sync caption position refs to top: 990 across SKILL.md + build-spec

Miga caught: the master-skeleton bump to top: 990 / 32px / height: 52 was
not reflected in the SKILL.md prose or build-spec.md. All three files now
name the same values so the doc, gate, and code stay consistent. Also
clarifies build-spec.md's caption-rail line to point at SKILL.md step 5 as
the source of the LINES-populated requirement.

.claude and .agents mirrors byte-identical.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs(skills): sync script-voice.md caption rail to top: 990 / 32px

Third and final stale reference to top: 1002 / 25px — script-voice.md was
still stating the pre-bump values. Now matches master-skeleton.html,
build-spec.md, and SKILL.md. Also bumps the 'keep critical small text out'
guidance from ~80px to ~100px to reflect the slightly taller caption rail.

Full-directory sweep confirms zero remaining occurrences of 1002 or 25px
across .claude/ and .agents/ skill mirrors.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-21 20:38:46 -04:00
James Russo 2e97e5b1ef docs(changelog): weekly digest 2026-07-13–2026-07-20 (#2664)
* docs(changelog): weekly digest 2026-07-13–2026-07-20

* docs(changelog): embed the weekly video in the Jul 13-20 digest

Adds the Jul 13-20 changelog video (produced via the changelog-video
skill, hosted at static.heygen.ai) at the top of the Update block using
Mintlify's <Frame> component with a native <video controls>.

Video URL: https://static.heygen.ai/hyperframes/changelog-videos/weekly-changelog-jul13-20.mp4
Slack thread: C0ACCNHLG3U · 1784565857.520599
2026-07-21 20:36:27 -04:00
James 4b6bb8ffa9 chore: release v0.7.67 2026-07-21 23:51:05 +00:00
James Russo 465c9e7641 fix(producer): preserve runtime audio variables in distributed plans (#2725)
* fix(producer): preserve runtime audio variables in distributed plans

* fix(producer): prefer runtime duration for volume sampling
2026-07-21 19:49:39 -04:00
James RussoandMiao Yang 696cbdbbd0 chore(skills): package Codex plugin upload (#2668)
* chore(skills): package Codex plugin upload

* chore(skills): harden Codex plugin content

* fix(skills): satisfy plugin quality gates

* fix(skills): address plugin packaging review

* fix(plugin): simplify asset validation

* fix(skills): correct embedded-captions catalog count to 35 after nightcity removal

The nightcity theme removal left SKILL.md claiming 36 identities in
four places, including the frontmatter description the router reads.
The catalog now has 35 entries (10 classic + 25 themed).

---------

Co-authored-by: Miao Yang <miao.yang@heygen.com>
2026-07-22 00:41:40 +08:00
James 835fa899c7 chore: release v0.7.65 2026-07-21 04:45:59 +00:00
James Russo efab7a1b32 Merge pull request #2671 from heygen-com/fix/bound-static-dedup-cardinality
fix: bound invalid render durations
2026-07-21 00:37:12 -04:00
James 52da8d3cdb test: align distributed duration assertion 2026-07-21 03:51:33 +00:00
James 344d9c0a87 fix: bound invalid render durations 2026-07-21 03:05:53 +00:00
James Russo e4a30f627f Merge pull request #2669 from heygen-com/jerrai/changelog-video-skill-pre-build-gate
docs(skills): add pre-build gate to changelog-video SKILL.md
2026-07-20 21:13:59 -04:00
jrusso1020 7d312bd170 docs(skills): add pre-build gate to changelog-video SKILL.md
Adds an explicit Step 0 above the pipeline that spells out the concrete file
copies (fonts, bgm, ffmpeg-encoded bg-pattern, master-skeleton.html) before
any composition HTML is written. Extends the anti-patterns table with the
three failure modes James caught on the Jul 13-20 weekly run: starting from
a prior video's index.html, hand-crafting @font-face / WebGL shader / custom
BGM, and shipping without a CloudFront invalidation after the S3 replace.

Why: the previous SKILL.md said "follow references/build-spec.md exactly" in
step 5, but that lets an agent read the build-spec after already writing
composition HTML on the wrong scaffold. Ships of the Jul 13-20 video went
through three off-brand iterations before I finally started from
master-skeleton.html (Jake called v3 "trash" — the skill's assets, fonts,
and scaffold ARE the brand, not the SKILL.md prompt on its own).

The three added anti-patterns are the concrete traps: agents will keep
starting from prior-project templates unless the skill explicitly bans it.

Mirrors to .agents/skills/ (Codex CLI) and .claude/skills/ (Claude Code)
byte-identical, per the skill-mirror gate.
2026-07-21 01:05:53 +00:00
James ab8b50afd7 refactor(repo): resolve changed-code audit 2026-07-20 11:59:57 -07:00
James a03d40ef4c test(engine): isolate peer abort regression 2026-07-20 11:02:05 -07:00
James 3222f1f31d refactor(cli): decompose render phases 2026-07-20 10:34:06 -07:00
James a9338a4e0f fix(cli): await error telemetry before finalization 2026-07-20 09:45:54 -07:00
James 73d3b4e1f4 fix(cli): report command failures once 2026-07-20 09:04:03 -07:00
James 619406a23b fix(cli): complete process lifecycle migration 2026-07-20 09:04:03 -07:00
James e0bda7a171 fix(cli): keep post-render exit reset root-owned 2026-07-20 09:04:03 -07:00
James 00b88abeb7 test(cli): align timeout failure with result contract 2026-07-20 09:04:03 -07:00
James 3aa2404747 refactor(cli): centralize process lifecycle 2026-07-20 09:04:03 -07:00
James 942db16fcd fix(cloudrun): publish adapter contract 2026-07-18 20:17:01 -04:00
James 634cd719a2 fix(parsers): mark node-only subpaths 2026-07-18 18:50:26 -04:00
James 3454a80eb2 test(repo): execute packed subpaths 2026-07-18 18:27:44 -04:00
James 8d9d9c016e refactor(repo): centralize package subpaths 2026-07-18 15:58:23 -04:00
James 562544f68d refactor(core): own edit protocol contract 2026-07-18 13:24:57 -04:00
James f0956e5944 test(compiler): enforce preview render parity 2026-07-18 12:26:52 -04:00
James 38c2d06933 refactor(studio): gate SDK operation families 2026-07-18 11:34:22 -04:00
James 8dd7e3dfc5 fix(studio): keep atomic Acorn splits Recast-free 2026-07-18 09:36:00 -04:00
James 022e260ac3 fix(studio): read writer environment explicitly 2026-07-18 06:41:26 -04:00
James 710a8f1db2 fix(studio): select writer for atomic cuts 2026-07-18 06:41:26 -04:00
James 43788c6dda refactor(studio): gate Acorn writer migration 2026-07-18 06:41:26 -04:00
James a7d6f7f7dc fix(producer): retain streaming retry after OOM 2026-07-17 19:14:04 -04:00
James e9604270b2 test(producer): preserve parallel plan watchdog coverage 2026-07-17 19:14:04 -04:00
James 6a84e95c68 refactor(producer): make capture plans immutable 2026-07-17 19:14:04 -04:00
James e73304fb0e feat(cli): make cloud archives size-aware 2026-07-17 18:44:58 -04:00
James 1a66c881b8 fix(producer): preserve render request config contracts 2026-07-17 17:22:34 -04:00
James fc4e9a6c0e fix(producer): validate render request engine snapshots 2026-07-17 16:22:23 -04:00
James 25dd4cc8c0 fix(producer): omit absent render request options 2026-07-17 16:22:23 -04:00
James c251db02d0 fix(producer): validate render request wire contracts 2026-07-17 16:22:23 -04:00
James 65f2e2927c refactor(producer): unify render requests 2026-07-17 16:22:23 -04:00
James 5da9f7ab3d fix(engine): avoid polynomial capture failure regex 2026-07-17 05:22:53 -04:00
James acb3d81b99 refactor(engine): type capture failures 2026-07-17 04:10:30 -04:00
James Russo 57d3bf4960 refactor(engine): manage child process lifecycles (#2160)
* refactor(engine): manage child process lifecycles

* fix(engine): preserve child reaping after runtime errors

* fix(engine): untrack child processes on exit
2026-07-17 01:17:53 -04:00
James Russo 065f8c9c8d Revert "docs(changelog): weekly digest 2026-06-29–2026-07-06 (#1995)" (#2571)
This reverts commit 9dc7dcd7fb.
2026-07-16 17:34:47 -04:00
James RussoandClaude Opus 4.8 9dc7dcd7fb docs(changelog): weekly digest 2026-06-29–2026-07-06 (#1995)
Auto-generated weekly digest (bun run changelog:weekly), hand-cleaned to
publish quality. Grouped into Features / Fixes / Performance / Docs &
Examples with readable one-liners and commit + PR links.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 17:32:33 -04:00
James RussoandJake Moran e96ebd74de feat(skills): add changelog-video skill for repo-native CC + Codex discovery (#2552)
Packages Jake Moran's changelog-video pipeline (v1, validated end-to-end
by Home on the Jun 23-29 range) as a repo-native skill set that Claude
Code (.claude/skills/) and Codex CLI (.agents/skills/) auto-discover the
moment the repo is opened. No install step; run the skill against a
changelog markdown for a given git range and it produces a lint-clean,
seam-gate-green 1080x1080 MP4 (~45-60s, Annie VO, mock-UI visualizations,
caption rail) end-to-end.

Six skills added byte-identical in both mirror dirs:
- changelog-video (pipeline entry point)
- motion-doctrine (carries seam-stamp.mjs + seam-gate.mjs)
- cut-the-curve, captions-overlay, seam-craft, oversized-cursor

Layout:
- .claude/skills/  - Claude Code project-local auto-discover
- .agents/skills/  - Codex CLI project-local auto-discover (verified via
                     Magi's clean-home Codex 0.144.3 repro; NOT .codex/skills/)

Fonts, animated background (12 MB), house BGM (5 MB), lexicon, and
align-captions ship inside the skill dirs. .gitattributes routes only
.claude/skills/**/*.{mp4,mp3} + .agents/skills/**/*.{mp4,mp3} through
LFS — narrowly scoped so unrelated Player, Studio, registry, and
marketplace media stay put. HeyGen CLI auth is the one credential the
skill needs; Node >= 22, ffmpeg, and headless Chrome are documented
alongside in both READMEs.

.gitignore: rewrites .claude/ and .agents/ blocks to keep agent-installed
skill hygiene while re-including the six repo-native skill dirs plus
README.md.

CI:
- Extends changes.skills filter to match .claude/skills/**,
  .agents/skills/**, scripts/lint-skills.ts, and scripts/check-skill-mirror.mjs.
- New 'Skills: project-native lint + mirror' job runs the extended
  lint-skills.ts (schema-driven; required { name, description } + optional
  { license, allowed-tools, metadata }, name pattern check, description
  length check) plus a new check-skill-mirror.mjs byte-integrity script
  (24 mirrored files must match; README.md deliberately per-CLI).
- Wired into 'bun run lint' locally.

Frontmatter validator:
- Rejects unsupported top-level keys (catches category:-style drift).
- Requires name + description.
- Validates name pattern (^[a-z][a-z0-9-]{0,63}$) and description shape
  (non-empty, <=1024 chars).
- Missing frontmatter block itself is a first-class error.

Also strips unsupported top-level 'category:' frontmatter from Jake's
motion-doctrine and cut-the-curve SKILL.mds (both mirrors), rewrites the
TTS invocation from ~/.claude/skills/media-use/... to the tracked
skills/hyperframes-media/scripts/heygen-tts.mjs, swaps npx hyperframes@latest
for the repo-local CLI in the gate step, and fixes a lint issue in Jake's
seam-gate.mjs (ternary-for-side-effect -> if/else).

Validated end-to-end by Home on Jun 23-29 (MP4 posted in C0ACCNHLG3U
thread 1784181166.041319). Independently reviewed R1/R2/R3 by Magi.

Co-authored-by: Jake Moran <jake@heygen.com>
2026-07-16 17:29:19 -04:00
James 68beec0e76 fix(studio): guard storyboard history navigation 2026-07-16 15:49:40 -04:00
James ded443647d fix(studio): address storyboard review feedback 2026-07-16 14:56:11 -04:00
James 73dd38f93e feat(studio): clarify storyboard review handoff 2026-07-16 14:56:11 -04:00
James d4cfa08cb6 fix(engine): escalate hung browser lease closes 2026-07-16 12:58:10 -04:00
James 9b23c00237 refactor(engine): add fingerprinted browser leases 2026-07-16 12:58:10 -04:00
James Russo 1b4cf12cd3 refactor(cli): add stderr diagnostics logger + oxlint guard (#2551) 2026-07-16 09:35:27 -04:00
James Russo d92d1d4f51 fix(cli): keep diagnostics on stderr for --json commands (#2522) 2026-07-16 09:14:39 -04:00
James Russo 6a1585df27 refactor(render): centralize execution ownership (#2158) 2026-07-16 05:00:40 -04:00
James Russo 21cb722ebd refactor(core): unify composition contract (#2157)
* refactor(core): unify composition contract

* fix(parsers): parse start expressions linearly
2026-07-16 02:44:22 -04:00
James Russo b179c95362 fix(engine): emit SystemMemory cgroup notice to stderr, not stdout (#2520) 2026-07-16 00:36:25 -04:00
James Russo 2417293dab fix(studio): enforce optimistic file concurrency (#2156)
* fix(studio): enforce optimistic file concurrency

* fix(studio): harden conditional file writes

* fix(studio): honor explicit file preconditions

* test(producer): allow zero-ms encode timing
2026-07-15 18:07:04 -04:00
James Russo dbdf03cd6b fix(ci): keep contact sheet test lightweight (#2492) 2026-07-15 14:38:57 -04:00
James Russo 42055296ee fix(studio): make sdk cutover transactional (#2155) 2026-07-15 10:29:22 -04:00
James Russo fb0d823500 fix(render): publish artifacts atomically (#2154) 2026-07-14 12:54:13 -04:00
James Russo cb69d3fe00 fix(render): surface structured outcomes (#2153) 2026-07-13 19:07:39 -04:00
James 16942c0c12 fix(player): own connection and media resources 2026-07-13 14:34:54 -04:00
James cc3ca2f9f7 fix(core): preserve runtime transport contract 2026-07-13 14:08:52 -04:00
James dcefdd98ca fix(player): version runtime protocol 2026-07-13 13:28:11 -04:00
James 979e9d0902 test(repo): verify packed consumers 2026-07-13 11:35:37 -04:00
James 83db364f81 test(repo): enforce workspace contracts 2026-07-11 21:57:45 -07:00
James RussoandClaude Opus 4.8 f1f317a20c fix(cli): grant s3 encryption actions in lambda policies user output (#2289)
`hyperframes lambda deploy` runs `sam deploy --resolve-s3`, and SAM's
managed artifacts bucket (aws-sam-cli-managed-default) is created with
default SSE encryption. Setting that requires s3:PutEncryptionConfiguration,
which the generated deploy policy did not grant, so a first deploy by a
user provisioned exactly per `lambda policies user` 403s on the bucket and
the managed stack rolls back.

Add s3:GetEncryptionConfiguration and s3:PutEncryptionConfiguration to the
s3Bucket action set (Get pairs with Put for CloudFormation update/drift
reads, matching the existing Get/Put pairs in the list). Also add a hint to
the sam-deploy failure path pointing at the ROLLBACK_COMPLETE recovery step,
since first-time users hit the stuck-rollback error on their retry.

Fixes #2137

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 21:36:17 -07:00
James 0af07a07c5 fix(core): enforce strict runtime safety 2026-07-11 21:31:30 -07:00
James 9e7b11998c test(producer): gate source tests by execution lane 2026-07-11 10:38:02 -07:00
James 179c942cd2 fix(core): make timing masks safe for source loaders 2026-07-11 10:04:27 -07:00
James 585aa9f6b2 chore(repo): forbid tracked generated artifacts 2026-07-11 01:20:22 -07:00
James Russo d4b3bcaba3 Merge pull request #2119 from heygen-com/remove-unused-hyperframes-animation-assets
chore(skills): remove unused animation example assets
2026-07-09 20:48:37 -07:00
James ac8ac34bd8 chore(skills): remove unused animation example assets 2026-07-09 19:15:53 -07:00
James a9901cb661 feat(core): sub-composition variable render path 2026-07-09 13:31:04 -07:00
James 8de80bf369 feat(sdk,studio): editable template sub-compositions + promote sub-comp element properties 2026-07-09 13:31:04 -07:00
James 7c144ecc30 feat(studio): promote to variable from the design panel 2026-07-09 13:31:03 -07:00
JamesandClaude Fable 5 267b289bb8 feat(studio): bind selected element properties to variables
Ninth PR of the template-variables stack: the promote-a-property gesture.
Select an element on the canvas/timeline, open the Variables tab, and the
panel offers per-property bind actions.

- "Bind selected" card in the Variables panel, built from the selection:
  image/media source (img/video/audio), text, text color, background, and
  font. Each action declares a variable whose default is the element's
  CURRENT value (promoting never changes the render — computed rgb colors
  convert to hex, the first computed font family becomes the font default)
  and writes the declarative binding the runtime resolves: data-var-src /
  data-var-text attributes or `<prop>: var(--id)` styles. Declare + bind
  run as one batched schema edit (one undo step); binding to an
  already-declared id skips the declare and just binds.
- guarded to selections from the composition the session models — a
  selection in another source file never writes bindings into this one.
- core: extract readVariablesForElement into runtime/variableScope.ts,
  shared by color grading and the declarative bindings (was duplicated).
- fix(studio-server): buildSubCompositionHtml's extractElementAttrs
  rebuilt html/body attributes without HTML-escaping values, shredding
  quote-bearing attributes — data-composition-variables (a JSON array)
  came out as mangled bogus attributes, so getVariables() silently
  returned {} on every /preview/comp/* page (no declared defaults, no
  runtime bindings). Pre-existing bug surfaced by live-testing this
  feature; regression test added.

Verified end-to-end in a live session: select headline → Bind text color
→ declaration + var(--headline-color) written to disk → override in the
panel → runtime applies the custom prop and the element renders the
override.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 13:31:03 -07:00
James f7ee0768ae feat(core): declarative variable bindings — data-var-src, data-var-text, css custom props 2026-07-09 13:31:03 -07:00
James bc0e0b314b fix(studio): code-review and live-test fixes for the variables stack 2026-07-09 13:31:03 -07:00
JamesandClaude Fable 5 010d49327e feat(studio): render with preview variables + template handoff + docs
Sixth PR of the template-variables Studio stack — closing the loop from
preview to render to developer handoff.

- renders started from the Renders tab now carry the active preview
  variable overrides (StartRenderOptions.variables → POST /render →
  RenderConfig.variables), so "render" produces exactly what the user is
  previewing.
- Variables panel "Use this template" footer: copy the effective values
  (defaults merged with overrides) as JSON, or as a ready-to-run
  `npx hyperframes render <comp> --variables '<json>'` command.
- gitignore: negate the renders/ output rule for the tracked
  src/components/renders/ source dir — without it, pre-commit's format
  re-stage (`git add {staged_files}`) hard-fails on any change to those
  files.
- docs: the Studio panel docs/concepts/variables.mdx described was
  aspirational — replace with the real Variables-in-Studio section
  (declare/edit, render-truthful preview, render-with-values, handoff,
  usage badges); document the new SDK variable APIs in
  docs/sdk/reference/composition.mdx (declaration ops, read APIs,
  setPreviewVariables).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 13:31:03 -07:00
James b34ad85165 feat(studio): variables inspector panel with live preview values 2026-07-09 13:31:03 -07:00
JamesandClaude Fable 5 2e0b884521 feat(studio-server): preview variable injection + render variables forwarding
Fourth PR of the template-variables Studio stack — the HTTP plumbing.

- preview routes (/preview and /preview/comp/*) accept
  ?variables=<url-encoded json> and inject
  `window.__hfVariables = {...}` into <head>, before the runtime and any
  composition script — the exact global the engine sets via
  evaluateOnNewDocument at render time, so preview-with-values cannot
  diverge from render output. Values are escaped against </script>
  breakout, malformed payloads 400 instead of silently previewing
  defaults, and the ETag is salted with a hash of the payload so cached
  previews revalidate when values change.
- POST /projects/:id/render accepts variables ({variableId: value}) and
  forwards them through StudioApiAdapter.startRender into the producer's
  RenderConfig.variables — the same channel `hyperframes render
  --variables` uses. Wired in both adapters (CLI embedded server + vite
  dev adapter).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 13:31:03 -07:00
James 91831f3f8e feat(sdk): variable usage scan + preview-values adapter seam 2026-07-09 13:31:02 -07:00
James Russo 839881a98e feat(sdk): variable declaration edit ops (declare/update/remove) (#2047) 2026-07-09 13:29:24 -07:00
James Russo fcbd4cb0f6 feat(sdk): variable declaration read apis + browser-safe variables entry (#2046) 2026-07-09 13:15:21 -07:00
James RussoandClaude Opus 4.8 ccab6207c4 fix(cli): bump @puppeteer/browsers to ^3.0.6 to fix render hang on Node >=24.16 (#2103) (#2104)
* fix(cli): bump @puppeteer/browsers to ^3.0.6 to fix render hang on node >=24.16

`hyperframes render` (and `browser ensure --force`) hangs forever during
Chrome provisioning on Node >= 24.16 (repro'd on macOS arm64 / Node 26.5.0;
fine on Node 22). Root cause is a transitive extractor bug, not our logic:

  @puppeteer/browsers@2.13.x install()
    -> extract-zip@2.0.1 -> yauzl@2.10.0

A classic-stream backpressure regression (nodejs/node#63487, works 24.15,
breaks 24.16+) surfaces a latent fd-slicer destroy() bug in yauzl 2.x
(yauzl#169). The inflate read stream stalls partway through the first entry
large enough to cross the write highWaterMark (chrome-headless-shell's
1.86MB LICENSE.headless_shell, stalls at ~1.31MB), never emits `end`, so
stream.pipeline never settles and extraction busy-spins. The half-extracted
cache has no executable, so every later render re-enters
"Cached binary missing -> re-download" and hangs again (puppeteer#14957).

Fix: @puppeteer/browsers 3.0.2 dropped extract-zip/yauzl entirely (now uses
modern-tar). Verified 3.0.6 extracts chrome-headless-shell cleanly under
Node 26.5.0 and keeps the full API manager.ts uses (install,
getInstalledBrowsers, Cache, computeExecutablePath, detectBrowserPlatform,
Browser) with an identical on-disk cache layout. Cross-platform (the same
.zip/yauzl path affected Linux + Windows too).

Adds a regression guard asserting the pin stays on the extractor-free
major (>= 3) and never reintroduces extract-zip/yauzl.

Fixes #2103

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

* chore(cli): clarify extractor-guard wording — yauzl is an optional peer, not dropped entirely

Review note on #2104: @puppeteer/browsers 3.0.6 keeps yauzl as an optional
peer fallback (default extractor is modern-tar), so the regression-guard
comment + it-text shouldn't say it was 'dropped entirely'. Test assertions
(extract-zip + yauzl absent from `dependencies`) unchanged and correct.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 10:08:57 -07:00
James RussoandClaude Opus 4.8 5ebc5bb10f fix(producer): scope per-instance variables for repeated sub-composition mounts (#2070)
#2066 fixed sub-composition data-variable-values on the render path for a single
mount, but the reusable-template pattern from #2064 (the same sub-comp mounted
multiple times with different values) still diverged from preview/snapshot:
every mount shared one __hfVariablesByComp key and one CSS scope selector, so
the last mount's values clobbered the earlier ones and all-but-one instance
rendered blank.

The producer now assigns per-instance runtime composition ids
(assignBundledRuntimeCompositionIds) and threads hostIdentityMap into the shared
inliner, mirroring the preview bundler. The shared inliner's default
buildScopeSelector already scopes by the runtime id, and timelines remap to it
via the scoping proxy, so each instance's variables, CSS, and timeline land
under its own id.

Pixel-verified end to end: two mounts of one sub-comp with different
data-variable-values now render their own content (green CARD_A / blue CARD_B),
matching snapshot; single-instance behavior is unchanged.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 13:15:53 -07:00
James RussoandClaude Opus 4.8 bb423dd217 docs: add modal deployment template to deploy guide (#2069)
Add Modal as a third official deployment template alongside Vercel and
Cloudflare: comparison-table row, a Modal tab (deploy commands, what-you-get,
performance, pricing, async spawned-function note), architecture/pre-baking
updates, swap-the-composition steps, and a source card. Update the cloud.mdx
cross-reference to include Modal.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 12:01:38 -07:00
James RussoandClaude Opus 4.8 232d591479 feat(cli/telemetry): surface unrecognized agents in the agent_runtime=null bucket (#1978)
* feat(cli/telemetry): surface unrecognized agents in the agent_runtime=null bucket

agent_runtime is a closed allowlist: an agent we have no rule for collapses
to null with no trace of what it was, so ~18% of CLI users are unattributable
and new agents stay invisible until reverse-engineered by hand.

Add detectAgentHints(), a self-populating residual signal computed only for
the null bucket (gated off classified events):
- agent_hint: value of AGENT / AI_AGENT (the emerging self-identification
  convention; Crush and Goose set AGENT=<name>) — names agents the allowlist
  misses.
- term_program: raw TERM_PROGRAM (editor name) — catches the IDE-terminal
  class the same way the cursor/windsurf rules do.
- agent_env_hints: sorted, comma-joined "agent-ish" env-var KEY names present
  but matched by no vendor rule — a fingerprint that clusters by agent.

Privacy stays consistent with the existing "never read secret-shaped values"
stance: agent_env_hints emits key names only; the three value-reads are vars
whose sole purpose is non-secret identification, each passed through a strict
short-slug allowlist so anything long/spaced/secret-shaped is dropped.

Breaking down agent_hint / agent_env_hints filtered to agent_runtime IS NULL
AND is_tty=false gives a ranked leaderboard of new agents to promote into
VENDOR_RULES.

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

* fix(cli/telemetry): guard agent_hint/term_program against short credential-shaped values

Review feedback (Magi, #1978): the short-slug allowlist in sanitizeHint() still
accepted short credential-shaped values (AGENT=sk-ant-api03,
AGENT=AKIAIOSFODNN7EXAMPLE, AGENT=github_pat_abc), so the "never emit a secret"
claim wasn't actually enforced — only overlong values were dropped.

Add a credential-shape guard on top of the slug allowlist:
- known token/credential prefixes (sk-, ghp_, github_pat_, akia, ya29, ...)
- any unbroken alphanumeric run >= 16 chars (key bodies, hex, base64-ish),
  while agent names segment on _/-/. and keep each run short.

Replace the single overlong-value test with the short credential shapes from the
review (parametrized) plus a positive case (gemini_managed_agent) proving real
multi-segment names still pass.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 22:31:13 -07:00
James 9de41ce316 chore: release v0.7.37 2026-07-06 05:59:36 +01:00
James RussoandClaude Opus 4.8 2b1109db4c test(lambda): behavioral guard for handler CJS banner shims (#1977)
Miguel's fix (f999b40d) added the require/__filename/__dirname shims to the
Lambda handler bundle after #1932 crashed every render at import with
"__dirname is not defined in ES module scope" (wawoff2's emscripten build
reads __dirname at module scope; it's inlined via producer -> fontCompression).

The accompanying test only grepped build-zip.ts for the banner literals, so it
passes even if the shim is renamed, reordered into a broken form, or if a new
inlined CJS dep needs a global the banner doesn't provide.

Replace it with a behavioral test: extract the banner to _handlerBanner.ts
(build-zip.ts self-executes on import, so it can't be imported directly),
bundle a fixture that touches __dirname/__filename/require with the real
banner, and import the output under real Node -- not the bun test runtime,
which defines __dirname in ESM and would mask a missing shim. The import
faithfully reproduces Lambda's Node ESM environment and fails with the exact
#1932 error when any shim is dropped.

Handler bundle output is unchanged (identical banner string).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 21:51:08 -07:00
James RussoandClaude Opus 4.7 a59ff0d91b feat(cli): migrate cloud-render upload to /v3/assets/direct-uploads (200MB) (#1844)
* chore(cli): regenerate cloud client for createAssetUpload + completeAssetUpload

Regenerated from experiment-framework `master` at commit `e74815f7af` (the
merge of EF#41085, which added `/v3/assets/direct-uploads` +
`/v3/assets/{asset_id}/complete` to the `TARGET_ENDPOINTS` allowlist in
`scripts/generate_hyperframes_cli_client.py`).

The `sync-hyperframes-codegen.yml` workflow that normally auto-opens this
PR failed with a `gh: Not Found (HTTP 404)` on the PR-creation step (run
28556975483); regenerated manually with:

  cd experiment-framework
  PYTHONPATH=. python3 scripts/generate_hyperframes_cli_client.py \\
    --out /path/to/hyperframes-oss

This commit is codegen-only — no hand edits. The direct-upload wire-up
that consumes the new `createAssetUpload` + `completeAssetUpload` methods
lands in the follow-up commit.

— Jerrai

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(cli): migrate cloud-render upload to /v3/assets/direct-uploads (200MB)

Replaces the legacy `client.uploadAsset(...)` multipart POST to
`/v3/assets` (32 MB in-memory proxy path) with the three-step direct-to-
S3 flow that lifts the practical per-project ceiling to 200 MB:

  1. `POST /v3/assets/direct-uploads` — declares filename, content-type,
     size, and SHA256 checksum; returns `asset_id`, presigned
     `upload_url`, and required `upload_headers`.
  2. Raw `PUT` to `upload_url` with the zip bytes + `upload_headers`
     verbatim. No CLI auth attached — the presigned URL signature carries
     authorization, and any extra headers would break the signature.
  3. `POST /v3/assets/{asset_id}/complete` — finalizes into a reusable
     asset. Retried up to 5x on 409 ("Uploaded object not found yet"), a
     documented race between S3 write consistency and the finalize check.

The returned `asset_id` is the same namespace the legacy path produced
(both write into `movio_asset`), so the downstream render submission at
`createRender({project: {type: "asset_id", asset_id}})` is unchanged.

Server-side context (EF#41085): the direct-upload endpoint now accepts
`application/zip` via a scoped `_ZIP_MIME_TO_EXT` map — the shared media/
PDF allowlist stays zip-free. The exact-MIME cross-check at the sniff
step guards against zip<->PDF confusion under the shared 'document'
category. Canonical S3 key layout matches the legacy proxy path
(`document/{asset_id}/original.zip`), so the render-side head_object
gate is transparent to which upload path produced the asset.

The prior codegen commit added the generated createAssetUpload +
completeAssetUpload methods this commit consumes.

— Jerrai

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-02 16:01:08 -07:00
James Russo bf8b3d9796 Keep Codex plugin category as Creativity (#1860) 2026-07-01 23:08:07 -07:00
James RussoandClaude Opus 4.8 9b41891f3a feat(cli): emit render_preflight_rejected telemetry for P1-3 pre-flight saves (#1856)
The P1-3 aspect/alpha/HDR pre-flight (#1843) aborts an incompatible render before any browser/ffmpeg work, but that "save" was invisible on dashboard 1783183 — indistinguishable from a deep failure or a user giving up.

checkRenderResolutionPreflight now returns { message, kind } (kind = the existing low-cardinality OutputResolutionIssueKind), and the render command emits render_preflight_rejected { kind } before exiting. No parsers change — the helper already carried kind. trackRenderPreflightRejected is typed to the union so the metric can't carry free text.

Tests: preflight tests assert kind for all five kinds; an events test locks the emit.

Further follow-up (still log-only): encoder-frame-0-exit counter and a P1-4 doctor cli_env_check event.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 22:10:37 -07:00
James RussoandClaude Opus 4.8 438474c968 test(cli): de-flake cold-import tests under CI contention via vitest timeouts (#1855)
The CLI Test job (bun run --filter '!@hyperframes/producer' test) intermittently failed unrelated PRs (#1843, #1850) with `Test timed out in 5000ms` / `Hook timed out in 10000ms`. Root cause: multiple CLI tests cold-import a heavy command module graph via dynamic import() (render.js, auth/status.js, telemetry/system.js), which under the full parallel monorepo run contends for CPU and blows vitest's 5s/10s defaults on constrained runners. Not a product bug.

Fix at the right altitude: set testTimeout 20s + hookTimeout 30s once in packages/cli/vitest.config.ts instead of per-test/per-hook bandaids, and remove the now-redundant explicit 30s beforeAll timeouts added in #1843.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 22:08:44 -07:00
James RussoandClaude Opus 4.8 733f88cb1f feat(producer,cli): render-reliability telemetry counters for capture hardening (#1850)
Follow-up to the render-reliability batch (#1841/#1842/#1843). Threads two capture-reliability counters through the existing observability → CLI-telemetry pipeline (no new PostHog wiring) so #1842's hardening is measurable on dashboard 1783183:

- transient-retry burn (CaptureAttemptSummary.reason gains "transient-retry"; counted into RenderCaptureObservability.transientRetries on BOTH the recovered and the still-failed paths via a shared helper).
- OOM classification (memoryExhaustionDetected set when describeMemoryExhaustion classifies the failure).

Surfaced as capture_transient_retries + capture_memory_exhaustion_detected render-event props. Tests cover the attempt tagging and the payload mapping.

Further follow-up (different subsystems): encoder-frame-0-exit signal, and P1-3 pre-flight-rejection / P1-4 cli_env_check counters.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 20:57:39 -07:00
James RussoandClaude Opus 4.8 6be46813a2 fix(render): pre-flight aspect-ratio / alpha preset mismatch with actionable guidance (#1843)
Users pick an --resolution preset whose orientation/aspect ratio (or alpha/HDR mode) conflicts with the composition; the render fails deep in the compiler with a cryptic message. ~8K err / ~1K users.

- New shared pure helper checkOutputResolutionCompatibility in @hyperframes/parsers — single source of truth for aspect/alpha/HDR/downsample/non-integer-scale constraints; suggests the matching-orientation, tier-preserving preset.
- CLI render pre-flight aborts early (before browser/ffmpeg) with an actionable, fix-suggesting message; resolveDeviceScaleFactor delegates to the same helper for identical defense-in-depth messages.
- Suggest (not auto-select); defers when dims can't be determined rather than guessing.
- suggestMatchingPreset keys tier off the -4k suffix so square-family swaps (square + landscape-4k -> square-4k) aren't downgraded to HD.
- render.js DOM polyfill made a lazy import; render.test cold-import beforeAll hooks given a 30s timeout to absorb CI contention.

Render-reliability workstream P1-3. Success measured on PostHog dashboard 1783183.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 19:27:08 -07:00
James RussoandClaude Opus 4.8 c0c3abf0f1 fix(producer): harden capture against timeouts, transient tab deaths, and OOM (#1842)
Four independent capture-infra hardening changes for the P2-5 failure bucket (~15K err / ~7K users):

- protocolTimeout auto-scales by device-scaled output area (applied before probe launch, since it's immutable post ppt.launch()).
- Single bounded transient retry (MAX_TRANSIENT_CAPTURE_RETRIES=1) on Target closed / Page crashed in the parallel disk-capture path; abort short-circuits before retry.
- Narrow OOM classification (Set maximum size exceeded etc., disjoint from transient) → actionable guidance naming output dims.
- StreamingEncoder.getExitError() threads FFmpeg's real exit reason into frame-0 encoder-death errors.

Render-reliability workstream P2-5. Success measured on PostHog dashboard 1783183.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 18:50:16 -07:00
James RussoandClaude Opus 4.8 180f368af1 fix(cli): detect missing Chrome libs & ffmpeg on Linux/WSL in doctor (#1841)
WSL first-render success (34.7%) is dominated by a downloaded chrome-headless-shell that launches into `libnss3.so: cannot open shared object file` — doctor/preflight only checked the binary exists, never that it can load its libraries.

- New linuxDeps.ts: /etc/os-release distro detection (Debian/Fedora/Arch/Alpine) + WSL detection, per-distro Chrome dep set, ldd-based shared-lib probe.
- preflight.checkChrome downgrades a found-but-unlaunchable Chrome to a render-blocking error with the exact per-distro install command.
- Distro-aware ffmpeg hints; launch failures converted to actionable guidance pointing at `hyperframes doctor` (skipped on ARM64).
- Detect + print remediation (no auto-install).

Render-reliability workstream P1-4. Success measured on PostHog dashboard 1783183.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 18:49:48 -07:00
James RussoandClaude Sonnet 5 24edb15095 fix(runtime): auto-infer composition duration for CSS/WAAPI/Lottie so data-duration is optional (#1830)
* fix(runtime): auto-infer composition duration for CSS/WAAPI/Lottie so data-duration is optional

The #2 render failure bucket ("Composition has zero duration") accounts for
~27K errors / ~7K affected users over 30 days (PostHog project 356858). Root
cause: only GSAP timelines got their duration auto-detected — CSS, WAAPI, and
Lottie compositions had no source of truth for total duration unless the
author remembered to set data-duration on the root element, and the render
engine hard-failed capture when neither was present.

Adds getInferredDurationSeconds() to the CSS, WAAPI, and Lottie runtime
adapters (packages/core/src/runtime/adapters/*.ts) — each reports the longest
finite end time it can discover from its own animations (CSS: computed
timing offset by data-start; WAAPI: effect.getComputedTiming().endTime;
Lottie: totalFrames/frameRate or the player's own duration). Infinite/
unbounded animations correctly return null and still require data-duration.
Wires this into the runtime's existing duration-floor resolution
(resolveAdapterDurationFloorSeconds in runtime/init.ts), alongside the
existing media-duration and authored-composition floors, so
window.__hf.duration becomes positive without any author action for
finite-duration non-GSAP compositions. Three.js is unchanged — no
AnimationClip/AnimationMixer inspection exists in that adapter, so
data-duration remains required there.

Tightens frameCapture.ts's zero-duration fast-fail gate to also check
hf.duration directly (not just the two authored signals), so a composition
mid-inference isn't fast-failed before its adapter-derived duration lands.

Adds a new lint rule (root_composition_missing_duration_source) that errors
only on genuinely non-inferable cases: no animation signal at all, Three.js
without data-duration, or an infinite/unbounded CSS or WAAPI animation
without data-duration. Deliberately silent on finite CSS/WAAPI/Lottie
animations, since the runtime now infers those — an autofix that "inserts
the inferred value" was considered and rejected: every case the rule flags
has no derivable value (an infinite spinner has no finite end time; a
duration-less Three.js scene has nothing to measure), so any autofix would
have to fabricate a placeholder, trading a loud correct failure for a silent
wrong-length render.

Updates the CSS/WAAPI/Lottie/Three adapter skill docs and the
hyperframes-core determinism-rules/data-attributes references to document
the new optionality and the runtime mechanism backing it.

Verified end-to-end against the real render pipeline (not just unit tests):
a CSS-only composition with a finite 3s animation, no GSAP timeline, and no
data-duration now renders a correct 3.000s MP4 via `hyperframes render`
(previously: "Composition has zero duration" failure). The infinite-CSS
negative control still fails fast with a clear diagnostic, matching the new
lint rule.

Adds a file-level fallow health exemption for lottie.ts's pre-existing
`seek` handler — unrelated to this change, but its line numbers shifted when
new functions were added earlier in the file, tripping fallow's
inherited-finding fingerprint (documented pattern already used elsewhere in
.fallowrc.jsonc for the same reason).

Known limitation: the static WAAPI usage detector in the lint rule
(/\.animate\(\s*[\[$A-Za-z_]/) can miss unusual call shapes; it only affects
whether the "no signal at all" branch fires, and errs toward NOT flagging
(reducing false positives) rather than over-flagging.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(lint): close 3 correctness gaps in root_composition_missing_duration_source

- Strip JS/CSS comments before scanning for GSAP/WAAPI/Three/Lottie/CSS
  animation signals, so a commented-out `.animate()` call or a commented
  `animation: ... infinite` rule can no longer satisfy the "has a duration
  source" check and mask a real zero-duration render failure.
- Broaden the WAAPI detection regex to also match the object-literal
  (PropertyIndexedKeyframes) form of `.animate()`, e.g.
  `el.animate({ opacity: [0,1] }, { duration: 2000 })`, which the previous
  character class silently missed. Corrected the adjacent comment that
  incorrectly claimed this shape "can't be a false negative".
- Fix hasInfiniteCssAnimation to stop false-positiving on animation NAMEs
  that merely contain the substring "infinite" (e.g. `infinite-spin`) by
  anchoring the `infinite` keyword with hyphen-aware boundaries instead of
  a bare `\b`. Also makes the longhand `animation-name` + separately
  declared `animation-iteration-count: infinite` pattern detected
  consistently.

Adds targeted unit tests for each fixed false-positive/false-negative.

* fix(runtime): keep finite duration signal when an unbounded animation coexists

getInferredDurationSeconds in the CSS and WAAPI adapters returned null
outright whenever any animation on the composition was unbounded
(infinite iteration count), even when other finite animations on the
same composition could still supply a valid duration. This disagreed
with the new root_composition_missing_duration_source lint rule, which
treats any animation-name as sufficient — so a composition mixing a
finite fadeIn with a decorative infinite spin passed lint but still
failed at render with "zero duration".

Unbounded animations are now skipped when computing the max end time
instead of short-circuiting the whole calculation. null is only
returned when every animation on the composition is unbounded, i.e.
there is no finite signal to fall back on at all.

Co-Authored-By: Claude <noreply@anthropic.com>

* docs(skills): fix table separator width in data-attributes.md

oxfmt flagged the merged Composition Root table from the post-rebase
merge of the auto-infer-duration docs onto main's reformatted table —
the separator row was one dash short of the header width.

* fix(lint): keep infinite-CSS duration rule strict but make its message honest

Post-review (Vance): after the finite+infinite adapter fix, the runtime infers
a length for a mixed finite+infinite CSS composition, but this lint rule still
(intentionally) errors on it — an unbounded animation makes the intended total
length ambiguous, so we require explicit data-duration. Keep that strictness
(lint is advisory by default; it only blocks under --strict, and data-duration
is the one duration signal guaranteed correct across every adapter, known and
future). But the message wrongly claimed the render "will fail" — false for the
mixed case, where the runtime falls back to the finite animation. Rewrite it to
describe the ambiguity honestly, correct the rule's block comment, and add a
mixed finite+infinite test asserting it still errors with an honest message.

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-01 14:28:16 -07:00