mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-14 18:01:20 +08:00
v0.7.6
1505 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
eee376cb5e | chore: release v0.7.6 (#1707) | ||
|
|
9bf1e1c298 |
fix(runtime): keep stamped flow children in document flow in preview (#1702)
Studio/preview stamps `data-start` onto ID'd and GSAP-targeted flow children (eg. a <header>/<footer> in a flex column) so the design panel can discover them. applyClipLayout then force-absolutized those stamped elements as if they were authored overlay clips, collapsing the layout: the footer shrink-wrapped and its `justify-content: space-between` clustered into the top-left, while the rendered video — which never stamps (production renders run as the top-level page, not in an iframe) — stayed correct. Mark runtime-stamped clips with `data-hf-autostamped` and skip them in applyClipLayout so they remain in document flow. The preview now matches the rendered video (true WYSIWYG). Authored overlay clips are unchanged, so the golden regression suite is unaffected. |
||
|
|
1c389983de |
fix(cli): ship player + slideshow bundles so present/play work from npm (#1706)
`present` and `play` render compositions in the standalone browser player, resolving the player/slideshow IIFE bundles via resolvePlayerPath / resolveSlideshowPath. Those resolvers look for the bundles alongside the built CLI (dist/hyperframes-player.global.js, dist/hyperframes-slideshow.global.js), but build-copy.mjs never staged them into dist/. The remaining candidate paths are monorepo-dev only, so an npm install has nothing to resolve. Result: `npx hyperframes present` always failed with "@hyperframes/player not found", forcing users to run the presenter from a monorepo checkout. Copy both player globals from packages/player/dist into the CLI dist during build:copy (existsSync-guarded + warn, matching the surrounding pattern). The runtime bundle is already handled by build:runtime. Verified: the globals now appear in `npm pack`, and `node dist/cli.js present` starts without the player-not-found error. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
89ff299a11 |
fix(engine): defend macOS regular Chrome screenshots
Fixes #1699. |
||
|
|
821bf2921a |
feat(cli): export .srt/.vtt caption sidecars from a transcript (#1704)
Add formatSrt/formatVtt/wordsToCues to normalize.ts (the inverse of the existing parseSrt/parseVtt) and a 'hyperframes transcribe <transcript> --to srt|vtt' export mode. Word-level whisper transcripts group into cues on sentence boundaries with maxChars/maxGap guards; imported phrase-level cues pass through unchanged. Default transcribe behavior is unchanged and no new dependencies are added. Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> |
||
|
|
97db811a2f |
feat(studio): per-keyframe ease presets, velocity fitting, gesture smoothing (#1694)
Per-keyframe speed-curve editing, velocity-based ease fitting, and Gaussian gesture smoothing. Easy Ease presets, per-segment KeyframeEaseList with a bezier editor, AE-convention ease fitting, position-only set-tween rows, and AnimationCard extraction. |
||
|
|
8ae010bf51 |
feat(studio): marquee multi-selection + off-canvas indicators (#1693)
* chore(studio): remove all console.* calls from studio package * chore(studio): address review — remove dead stubs, restore consent notice - Delete empty if-blocks left after console removal (snapTargetCollection, Player asset-poll, useTimelineSyncCallbacks 5s probe, useGestureRecording dev guard + now-unused isDevBuild) and the stale "surface in dev" comment. - Drop the dangling no-console pragma + dead duplicate-id branch in sourcePatcher. - Restore the one-time telemetry consent disclosure in showNoticeOnce (kept behind a pragma — it is a user-facing notice, not debug noise). - Remove the missed timelineIcons console.warn while preserving the `tag || "div"` null-safety fallback. - Route caption auto-save failures (a data-loss path) through telemetry instead of swallowing silently. - Restore the accidentally-clobbered css-var-fonts output.mp4 fixture. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(runtime): immediateRender for set tweens + array timeline normalization - Set tweens now emit immediateRender:true so they render on page load without requiring the runtime to seek past position 0 - Runtime IIFE normalizes array timelines (window.__timelines = [tl]) to keyed objects, and auto-adds data-start on root elements - Drag teardown clears translate:none to prevent #1673 fly-off - Position-only set tweens hidden from timeline diamonds (3 cache paths) - Parser: ease-only keyframe update preserves existing properties * fix(runtime): address review — restore perf gate, debug surface, scrub restore - Restore the #1651 skipForInjectedVideo gate in media.ts that was dropped on restack — avoids ~2400 wasted per-tick seeks on video-heavy renders. - Restore the console.debug body + docstring bullet of swallow() in diagnostics.ts: the __hfDebug opt-in debug surface had been gutted to an empty if-block. - Rebind: after the progress-cycle set() kick, seek to state.currentTime via totalTime() instead of snapping to 0, so a rebind after scrub / soft-reload restore keeps the playhead. - Array __timelines normalization + data-start default now resolve the root via a shared findRootCompositionEl() that honors data-root="true" first (matches resolveRootCompositionElement, which now delegates to it). - Ease-only keyframe update leaves a primitive (non-object) keyframe value untouched instead of wiping it to {}; add a preservation unit test. - Document the boundDuration<=0 progress(1) kick + restore the STATIC-case comment in gsapRuntimeBridge. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(studio): marquee multi-selection + off-canvas indicators - Click+drag on empty canvas draws dashed selection rectangle - SAT/OBB intersection handles rotated/scaled/skewed elements - Shift+marquee adds to existing selection - Click on empty canvas deselects - Off-canvas elements show dashed outline indicators (clickable) - Dashed border only shows outside canvas, solid inside (clip-path) - 12 geometry unit tests * feat(studio): address review — group-aware off-canvas indicators + fixes - Off-canvas indicator suppression now skips every selected element (primary AND marquee group members), not just the primary, so group members no longer render a doubled overlay (group rect + dashed indicator). - Drop selection from the off-canvas layout effect deps; the selected-element filter runs at render time. Avoids re-walking geometry on each selection change. - applyMarqueeSelection now honors STUDIO_INSPECTOR_PANELS_ENABLED. - Restore the stale-selection clear in useDomEditPreviewSync when the selected element no longer resolves after a re-sync. Drag-release stays handled by suppressNextBoxClickRef. - Off-canvas indicator is keyboard-accessible; canvas cursor driven by marquee rect state, not a render-time ref read. - Rename partiallyOutside -> extendsOutsideComp + comment the clip-path hit-test. - Extract OffCanvasIndicators into its own component (DomEditOverlay was already over the 600-LOC cap on this branch; extraction brings it under). - Declare onUpdateKeyframeEase on PropertyPanelProps so this branch typechecks standalone (handler + wiring already here; only the type had leaked upstack). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
6987447a75 |
fix(runtime): immediateRender for set tweens + array timeline normalization (#1692)
* chore(studio): remove all console.* calls from studio package * chore(studio): address review — remove dead stubs, restore consent notice - Delete empty if-blocks left after console removal (snapTargetCollection, Player asset-poll, useTimelineSyncCallbacks 5s probe, useGestureRecording dev guard + now-unused isDevBuild) and the stale "surface in dev" comment. - Drop the dangling no-console pragma + dead duplicate-id branch in sourcePatcher. - Restore the one-time telemetry consent disclosure in showNoticeOnce (kept behind a pragma — it is a user-facing notice, not debug noise). - Remove the missed timelineIcons console.warn while preserving the `tag || "div"` null-safety fallback. - Route caption auto-save failures (a data-loss path) through telemetry instead of swallowing silently. - Restore the accidentally-clobbered css-var-fonts output.mp4 fixture. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(runtime): immediateRender for set tweens + array timeline normalization - Set tweens now emit immediateRender:true so they render on page load without requiring the runtime to seek past position 0 - Runtime IIFE normalizes array timelines (window.__timelines = [tl]) to keyed objects, and auto-adds data-start on root elements - Drag teardown clears translate:none to prevent #1673 fly-off - Position-only set tweens hidden from timeline diamonds (3 cache paths) - Parser: ease-only keyframe update preserves existing properties * fix(runtime): address review — restore perf gate, debug surface, scrub restore - Restore the #1651 skipForInjectedVideo gate in media.ts that was dropped on restack — avoids ~2400 wasted per-tick seeks on video-heavy renders. - Restore the console.debug body + docstring bullet of swallow() in diagnostics.ts: the __hfDebug opt-in debug surface had been gutted to an empty if-block. - Rebind: after the progress-cycle set() kick, seek to state.currentTime via totalTime() instead of snapping to 0, so a rebind after scrub / soft-reload restore keeps the playhead. - Array __timelines normalization + data-start default now resolve the root via a shared findRootCompositionEl() that honors data-root="true" first (matches resolveRootCompositionElement, which now delegates to it). - Ease-only keyframe update leaves a primitive (non-object) keyframe value untouched instead of wiping it to {}; add a preservation unit test. - Document the boundDuration<=0 progress(1) kick + restore the STATIC-case comment in gsapRuntimeBridge. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
adb40321d6 |
chore(studio): remove all console.* calls from studio package (#1691)
* chore(studio): remove all console.* calls from studio package * chore(studio): address review — remove dead stubs, restore consent notice - Delete empty if-blocks left after console removal (snapTargetCollection, Player asset-poll, useTimelineSyncCallbacks 5s probe, useGestureRecording dev guard + now-unused isDevBuild) and the stale "surface in dev" comment. - Drop the dangling no-console pragma + dead duplicate-id branch in sourcePatcher. - Restore the one-time telemetry consent disclosure in showNoticeOnce (kept behind a pragma — it is a user-facing notice, not debug noise). - Remove the missed timelineIcons console.warn while preserving the `tag || "div"` null-safety fallback. - Route caption auto-save failures (a data-loss path) through telemetry instead of swallowing silently. - Restore the accidentally-clobbered css-var-fonts output.mp4 fixture. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
54cab331d0 |
feat(cli): shared TTS/BGM auth preflight + caption and skill-workflow fixes (#1697)
* fix: handle caption skin workflow * docs(skills): simplify the finalize step across video workflows - Drop --strict-layout; all skills use plain `hyperframes inspect` - Add the caption text_box_overflow false-positive note to faceless-explainer - On a failed check, the orchestrator makes the cheapest safe edit itself (no worker re-dispatch / Step 3 backtrack language) - Snapshot: glance at the stitched contact-sheet.jpg and move on Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(auth): onboarding-first `auth status` + shared TTS/BGM preflight When no HeyGen credential is configured, `hyperframes auth status` now prints registration-first guidance instead of a terse error: - Interactive / agent-driven sessions get sign-in guidance led by `hyperframes auth login` (the OAuth step that also creates an account and is shared with heygen-cli), and never steer users to a per-repo `.env`. CI / non-interactive runs get a terse note. Exit 1 is kept so the "am I logged in?" `$?` contract still holds. - It probes which local engine voice/music will fall back to (Kokoro / MusicGen, mirroring the skill resolution order) and whether their Python deps are installed, with a pip hint when missing. `--json` exposes `recommended_action` + `offline_engines` for skills to branch. - `doctor` gains matching "TTS (Kokoro)" / "BGM (MusicGen)" checks via the same shared probe (findPython/hasPythonModules extracted to tts/python.ts; provider resolution in audio/providers.ts). Every TTS/BGM workflow now relays this at Step 0 (setup) instead of improvising its own "missing key" prompt: pr-to-video, product-launch- video, faceless-explainer, website-to-video, music-to-video. The canonical behavior + key-priority table live once in hyperframes-media. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(pr-to-video): scale recommended video length to PR change size Step 0 led with a fixed ~60-90s length default. Now the recommended length is derived from the PR's diff stat (lines added+deleted, nudged by file count) on a tier scale (trivial ~20-40s → large ~110-180s, hard cap ~3 min), reusing the same PR peek already done to infer the angle. The agent states the basis when proposing it, and a huge PR with one headline change still stays tight. User can always override. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(captions): embed brand fonts whose files use separators brandFontFaces() matched font files by stripping only whitespace, so an underscore/hyphen-named file (TT_Norms_Pro_Bold.woff2) never matched the family key "ttnormspro" — captions shipped with no @font-face, the font_family_without_font_face bug. Now both family and filename normalize away all non-alphanumerics; families match longest-key-first so a parent family can't swallow a more specific one's files (TT Norms Pro vs Mono); each file is claimed once; "demibold" ranks before "bold"; and when nothing matches it warns loudly at build time instead of returning "". Also: parseFonts() falls back to h1/h2/title/hero display roles, and the frame-worker + caption authoring docs spell out that only shipped font files render — no system CJK/Devanagari families on the headless renderer. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(hyperframes-media): enforce sign-in preflight on standalone BGM/TTS A one-off "generate me a BGM" request went straight to local MusicGen without recommending sign-in: bgm.md/tts.md framed the no-credential path as an automatic fallback, so the generation path bypassed the Preflight stop, and the preflight used a bare `hyperframes auth status` that isn't on PATH in a fresh `npx skills` project. - Preflight now applies to one-off generation as well as workflows, uses `npx hyperframes auth status`, and says: if the CLI can't run, still recommend signing in and STOP — never treat "no credential" as a silent green light for local generation. - bgm.md and tts.md point at the Preflight before generating, reframing local generation as the fallback the user opts into, not a default. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(auth): add Authentication & API keys guide Document signing in, the keys each capability (voice, music, capture) uses, their resolution priority, and the fully local fallback. Add the guide to the nav and cross-link it from the cloud deploy note and the CLI env-var reference. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(lint): strip HTML comments in a fixpoint loop (CodeQL) Single-pass <!-- --> removal can re-form a complete comment from adjacent markers (e.g. `<<!-- -->!-- ... -->`), letting a decoy <template> survive and hijack the template-boundary match. Loop to a fixpoint, mirroring the captions.mjs precedent; add a regression test that fails on single-pass (2 root findings) and passes on the loop. Also wrap the build-frame.mjs node:fs imports to satisfy oxfmt — the new copyFileSync import pushed the line past the width limit, which was the sole cause of the Format / Preflight CI failures. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(lint): strip HTML comments with a linear scan (CodeQL ReDoS) The fixpoint loop still ran a /<!--[\s\S]*?-->/ regex per pass, which backtracks O(n^2) on inputs with many unterminated "<!--" — CodeQL js/polynomial-redos (high). Looping the same regex (the prescribed fix) never addressed this; only the regex itself does. Replace it with an indexOf-based linear strip in utils.ts (stripHtmlComments), kept in a fixpoint loop so markers that re-form when a comment is removed are still stripped. 200k unterminated "<!--" now strips in ~3ms instead of quadratic time; behavior is otherwise unchanged — unterminated comments are kept verbatim, as the old regex left them. The re-forming regression test still guards it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(auth): make TTS/BGM sign-in guidance accurate and runnable From team review of the not-signed-in onboarding: - OAuth is a `hyperframes auth login` feature only. The separate `heygen` CLI is API-key-only — `heygen auth login` stores a pasted key, it is not OAuth and does not create an account. Stop presenting the two CLIs as the same OAuth/sign-up step. - Use `npx hyperframes` in every imperative and runtime hint. Bare `hyperframes` is not on PATH on a fresh machine (command not found); only `npx hyperframes` is guaranteed. Also updates the JSON recommended_action. - Drop `heygen auth login` from the terminal/skill onboarding: it needs its own install and there is no `npx heygen`, so it was a command-not-found trap. The shared-credential fact stays in the reference docs. Covers the `auth status` guidance + tests, the Authentication docs, the shared hyperframes-media preflight (SKILL, requirements, tts, error hints), and the `npx hyperframes auth status` preflight in every TTS/BGM workflow (pr-to-video, product-launch-video, faceless-explainer, website-to-video, music-to-video). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
c7b9bf3386 |
fix(studio): resolve project-root-relative asset URLs in preview iframe (#1698)
## What Studio preview now resolves `<video src="../../assets/x.mp4">` (and the same shape for `<img>`, `<audio>`, inline `style` `url()`, and `<style>` CSS `url()`) against the sub-composition's URL — matching what the server-side bundler already does for the render path. ## Why Authored compositions live at `compositions/frames/*.html` and reference project-root assets either as plain `assets/x.mp4` (already correct because the main document's `<base href>` points at the project preview root) or as `../../assets/x.mp4` (the explicit project-root-relative form). The server-side `inlineSubCompositions` flattens sub-comps into `index.html` and rewrites the `../`-form against the sub-comp's source path so it resolves against the project root in the baked render. The browser-side runtime that mounts external sub-compositions via `fetch` did no such rewriting. So `<video src="../../assets/x.mp4">` authored inside a `compositions/frames/scene.html` resolved against the main document's base href, climbed above the project root, and 404'd in Studio preview — even though the same path rendered correctly in the final video. An OSS user (Miao Yang) hit this in a real project. ## How Added `rewriteSubCompositionAssetPaths` to the runtime `compositionLoader`. After parsing the fetched sub-composition HTML and before extracting any nodes, walk the parsed document and rewrite the same surface the server-side path touches: - `[src]` and `[href]` attributes on every element - `[style]` attribute `url(...)` references - `<style>` element CSS `url(...)` references The rewrite mirrors the producer's semantics exactly: only values that start with `../` (or are literal `..`) are rewritten — against the sub-composition's URL via `new URL(value, compositionUrl)`. Absolute URLs, root-relative paths, `data:`, hash refs, and plain `assets/x.mp4` are left untouched. **Plain relative paths must not be rewritten** because the main document's `<base href>` already covers them; rewriting would double-prefix the URL. The walk recurses into `<template>` content because authored compositions typically wrap their rendered body in a `<template>` and `querySelectorAll` does not enter template content (it lives in a detached `DocumentFragment`). ## Test plan - [x] Unit tests added (6 new tests in `compositionLoader.test.ts`): rewrites `../`-traversing src on template-wrapped sub-comps; leaves plain relative paths untouched (no double-prefix); leaves absolute / data / hash / root-relative URLs untouched; rewrites CSS `url()` in `<style>` blocks and inline `style` attributes; rewrites for non-template (full-HTML-doc) sub-comps. - [x] Full core test suite green (2065 tests). - [x] Full studio test suite green (1148 tests). - [x] Manual verification with the reporter's actual project: before the fix one `<video>` with a `../../assets/...` src returned `MEDIA_ELEMENT_ERROR: Format error`; after the fix all 7 `<video>` elements load (`readyState=4`, correct `currentSrc`). The 6 plain `assets/...` paths are *unchanged* (no double-prefix) and continue to resolve via `<base href>` as before. - [x] `bun run lint`, `bun run format:check`, `bun run typecheck`, `fallow audit` all green. Reported by Miao Yang. — Jerrai (https://claude.com/claude-code) |
||
|
|
5242dde2dc |
feat(telemetry): attribute renders to the authoring workflow skill (#1695)
* feat(telemetry): attribute renders to the authoring workflow skill Add an optional `--skill` flag to `hyperframes render` and tag the `render_complete` / `render_error` events with `authoring_skill`, so render usage can be broken down per authoring workflow. The value is slug-gated (a malformed value is ignored) and the existing anonymous / opt-out telemetry pipeline is otherwise unchanged. Each end-user workflow that renders now passes `--skill=<name>` on its render command: embedded-captions, faceless-explainer, graphic-overlays, motion-graphics, music-to-video, pr-to-video, product-launch-video, remotion-to-hyperframes, website-to-video. Not instrumented, by design: general-video renders freeform with no canonical render command to attach to, and slideshow produces an interactive deck rather than a rendered video. Both can follow up if per-skill numbers are wanted. * fix(telemetry): address review — shared slug util, equals-form flag, invalid-value warning - Extract the SKILL_SLUG regex + a normalizeSkillSlug() helper into telemetry/skill.ts, shared by the `events` and `render` commands (the regex was duplicated). `render` adopts normalizeSkillSlug (so it now trims the value, matching `events`); `events` references the shared SKILL_SLUG. + unit test. - `render` warns on a non-empty but invalid --skill value (e.g. a camelCase typo) so attribution isn't silently lost — stderr only, never fails the render. - embedded-captions render script: `--skill embedded-captions` -> `--skill=embedded-captions`. On an older CLI that does not declare --skill, the space form leaks the value as a positional and clobbers the project dir (resolveProject fails); the equals form is parsed as a self-delimiting flag and safely ignored. Verified via Node parseArgs(strict:false). Addresses review feedback on the PR (shared util + .trim drift, version-skew safety, invalid-value visibility). --------- Co-authored-by: kiritowoo <295860553+kiritowoo@users.noreply.github.com> |
||
|
|
649c216394 | chore: release v0.7.5 | ||
|
|
ea23e6309f | fix(player): treat runtime timeline as cross-origin ready (#1690) | ||
|
|
546b2d770b |
fix(producer): retry probe stage on transient browser errors (#1688)
* fix(producer): retry probe stage on transient browser errors (#1687) The distributed render plan stage crashes when headless Chrome encounters a transient frame detachment ("Navigating frame was detached") during browser probe, with no retry logic. The plan tarball is never uploaded, and all downstream chunk workers fail with S3 404. Add a retry-with-fresh-session mechanism to the probe stage: - `isTransientBrowserError()` classifier in the engine identifies 9 known transient Puppeteer/Chrome errors (frame detached, target closed, session closed, protocol error, page crashed, execution context destroyed, etc.). - `runProbeStage()` wraps browser session creation + initialization in a retry loop (max 2 attempts). On transient error: logs structured diagnostics (attempt, isTransient, error message, elapsed time), closes the crashed session cleanly, creates a fresh browser, and retries. Non- transient errors throw immediately without consuming retry budget. - 17 unit tests for the error classifier, 3 integration tests for retry behavior (successful retry, immediate throw on non-transient, exhaust retry budget on persistent transient). Closes #1687 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address review feedback — widen retry scope, deduplicate patterns - Move createCaptureSession inside the retry try/catch so browser launch failures (Failed to launch the browser process, ECONNREFUSED) are also retried — not just initializeSession errors. - Deduplicate transient error patterns: remove "Protocol error.*Target closed" (subsumed by "Target closed") and "Navigation failed because browser has disconnected" (subsumed by "browser has disconnected"). - Add browser launch failure patterns: "Failed to launch the browser process" and "ECONNREFUSED". - Add test for createCaptureSession transient throw (browser launch retry). - Update test mock comment to document sync requirement with engine pattern list. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
899b8faa12 |
fix: flag visible GSAP transition overlays (#1686)
* fix: flag visible GSAP transition overlays * fix: cover GSAP overlay lint review cases |
||
|
|
ba77a0bd72 | chore: release v0.7.4 | ||
|
|
45a9440c61 |
fix(producer): derive duration from sub-composition timing when root has no data-duration (#1680)
When the root element lacks an explicit data-duration attribute and there is no GSAP timeline, getDeclaredDuration now computes max(data-start + data-duration) across all sub-compositions instead of returning zero. |
||
|
|
656c1200ee | fix(core): skip empty sub-composition files instead of aborting render (#1678) | ||
|
|
d4aa5f93e1 |
fix(studio): guard against null tag in timeline track style (#1679)
getTrackStyle() can receive a falsy tag at runtime (e.g. empty string from timeline element defaults), causing toLowerCase() and startsWith() to throw. Default to "div" when tag is falsy. |
||
|
|
344618e22a |
fix(cli): improve whisper-cpp install guidance and add doctor check (#1681)
- Platform-specific install instructions for Linux (apt) and Windows (releases page / cmake) instead of a generic "see GitHub" fallback - Export getInstallInstructions so doctor can reuse it - Add whisper-cpp check to `hyperframes doctor` after the Environment check — reports path when found, shows install hint when missing |
||
|
|
f622e5a7ba | fix(engine): restore fast screenshot path for viewport captures (#1670) | ||
|
|
bf4f34b359 |
fix(cli): install skills into project dir on non-interactive init (#1671)
`hyperframes init` only installed AI coding skills on the interactive path (behind a clack confirm). When an agent drives it non-interactively (no TTY), it just printed `npx skills add ...` and returned — so skills were never installed and the agent later hit `Unknown skill: <workflow>`. - init: both interactive and non-interactive branches now run `npx skills add` with `cwd` set to the new project dir so skills land there, not in the caller's working directory. Non-interactive additionally passes `--yes`; when Claude Code is driving (CLAUDECODE env var), adds `--agent claude-code` so skills target `.claude/skills/`. - skills: `runSkillsAdd` accepts `cwd` and `extraArgs` so callers can control where and how skills are installed. - templates: CLAUDE.md / AGENTS.md now tell agents to run `npx skills add heygen-com/hyperframes` to install or update skills. Co-authored-by: Wenbo Zhu <295860553+kiritowoo@users.noreply.github.com> |
||
|
|
7133c396eb |
fix(cli): expose render debug mode
Adds the render --debug CLI flag, forwards it through Docker/local render paths, and captures full producer debug artifacts from pipeline start. |
||
|
|
4961e4e477 |
feat(cli): per-skill usage telemetry
Adds the hyperframes events CLI command for skill invocation/completion telemetry. |
||
|
|
4a8e2f1cd0 |
chore(lint): keep the fallow audit gate green
Extract rootClassStyledSelectors so the subcomposition_root_styled_by_class rule drops below the complexity threshold, and ignore the music-to-video reference HTML (template + motion-primitive materials forked by path, not import-graph reachable) — same treatment as motion-graphics/grounding. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
119284d377 |
fix(producer): store css-var-fonts baseline as raw binary, not LFS pointer
The previous restore was re-filtered into a 130-byte LFS pointer by the .gitattributes lfs rule; main stores this fixture as a raw binary blob committed directly. Commit the exact blob so the regression harness reads real frames and the file matches main. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
f012b8b846 |
fix(producer): restore css-var-fonts regression baseline
Accidentally deleted by a prior `git add -A`; it is the golden output.mp4 the distributed regression harness diffs against. Restored byte-identical to main. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
a34d3dba4d |
feat(skills): unify bgm-to-video flows into music-to-video
Replace bgm-to-video, bgm-to-video-new, bgm-to-video-refactor, and the standalone beat-sync/montage skills with a single music-to-video skill. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
9ccae86372 |
fix(lint): catch CSS↔GSAP transform conflicts in scoped selectors and frame sub-compositions
gsap_css_transform_conflict existed but missed the most common real-world
shape (a label centered with CSS translateX(-50%) plus a GSAP xPercent that
stacks to -100% in the capture path), for three independent reasons:
- selector matching was exact-string, so a scoped/grouped GSAP selector
("#root .label, #root .sub") never matched a CSS class rule (.label)
- the acorn parser only captures timeline-rooted calls (tl.to/tl.set), so a
standalone gsap.set("#root .label", { xPercent: -50 }) was invisible to it
- lintProject read compositions/ non-recursively, so per-frame compositions
in compositions/frames/*.html were never linted at all
Fix: token-decompose grouped/descendant/compound selectors and match by
id/class against CSS transform rules; additionally scan standalone gsap.*
transform calls; and recurse into compositions/ subdirectories so frame
sub-compositions are linted.
Adds unit tests (grouped gsap.set repro, descendant tl.to, negative case) and
an end-to-end lintProject test that writes compositions/frames/04-*.html and
asserts the conflict is reported there.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
997823b6b5 | chore: release v0.7.3 | ||
|
|
11d6227d06 | fix: hide descendants of inactive render clips (#1662) | ||
|
|
b651a9d218 |
perf(producer): skip wasted Chrome media work + injector page.evaluates during render (#1651)
## What Two small render-side improvements for video-heavy compositions: 1. **`packages/core/src/runtime/media.ts`** — gate the per-tick `el.currentTime = relTime` set + the `el.load()` drift-recovery retry on the *absence* of a `<img id="__render_frame_<id>__">` sibling (i.e., we're in render mode + this video's visual is bypassed by frame injection + its audio is mixed by ffmpeg from source files). 2. **`packages/engine/src/services/videoFrameInjector.ts`** — probe `window.__hfReseekGpu` and `window.__hf.colorGrading.redraw` once at the first injector call; cache the booleans; skip the per-frame `page.evaluate` round-trips when neither capability is registered. ## Why #### media.ts During render the runtime calls `el.currentTime = relTime` on every active video per sync tick. For frame-injected videos that's pure waste: - The visual comes from the `<img id="__render_frame_<id>__">` sibling injected by the producer's `videoFrameInjector` — the `<video>` element is `visibility: hidden`. - Audio is mixed by ffmpeg from the source files in `runAudioStage` (separate stage) — it never goes through the in-browser audio pipeline during render. So every per-tick seek just kicks Chrome's media pipeline (buffering checks, range fetches, decoder state changes) for no visible or audible benefit. On a 30 × 32 MB synth comp, that's ~2,400 wasted seeks per render — and the cost wasn't on the JS critical path, so it didn't show up in `avgBeforeCapture` directly. It bled into the BeginFrame compositor's per-frame screenshot time. Preview is unaffected: the injection sibling only exists during render. In preview `hasInjectionSibling` is always false → existing seek path runs unchanged. #### videoFrameInjector.ts The injector hook ran `__hfReseekGpu` and `redrawRuntimeColorGrading` via `page.evaluate` on every render frame. For comps that don't register either capability (the common case — anything without WebGL/WebGPU video sub-comps or a color-grading layer), each was a no-op page-side function preceded by a ~CDP-round-trip-worth of overhead. Probing once and caching `false` eliminates that for the rest of the render. ## How was this validated Stress shape: `synth-30-heavy` — 30 × 32 MB MP4 / 3 s each, sequenced end-to-end over a 90 s timeline (`data-composition-id` root + per-video `<video id="vid-NN" data-start data-duration data-track-index>`). Host: 8-core / 30 GB Linux. N=3 baseline against stock `origin/main` (post-#1630), N=3 with-fix on the same machine, same corpus, fresh worker pool each run. Phase timings via `[Render:trace]` JSON; per-frame sub-breakdown via a one-line `[CapturePerf]` stderr emit (kept locally, not in this PR — `dedupPerfs` already carries the data, this branch surfaces it). | | Baseline N=3 | With-fix N=3 | Δ | |---|---|---|---| | wall mean | 119.5 s ± 1.4 s | **117.3 s ± 0.9 s** | **-2.2 s (-1.8%)** | | avg screenshot / frame | 50.0 ms | **49.0 ms** | -2.0% | | avg beforeCapture / frame | 13.0 ms | **12.1 ms** | -7.0% | | avg total / frame | 66.0 ms | 63.9 ms | -3.2% | | output md5 | `5a22be64...` | identical ×3 | ✓ | The 1 ms screenshot drop is the load-bearing signal: it confirms the kicked Chrome media-pipeline work *was* bleeding into BeginFrame compositor time, even though it wasn't on the JS critical path. Per-frame budget improved 2.1 ms × 2700 / 3 workers ≈ 1.9 s of `capture_disk` savings, which matches the observed wall delta. This stacks cleanly with #1630 (which removed the injector's fileServer contention). #1630 moved the injector's PNG fetches off the fileServer's hot path; this PR keeps Chrome's media pipeline quiet during render so the BeginFrame compositor runs unhindered. ## Test plan - [x] Local-CLI render on `synth-30-heavy` × N=3 baseline + N=3 with-fix; wall, per-frame, md5 captured (above). - [x] Lint / format / typecheck via lefthook pre-commit (`oxlint`, `oxfmt`, `fallow audit`, `tsc --noEmit` across `@hyperframes/core` + `@hyperframes/engine` + `@hyperframes/producer`). - [ ] *Real-world video-heavy comp validation* — would love a Magi / Miga eye on a HF-heygen-stripe-shape or a Rahino-shape comp to confirm there's no audible artifact on unmuted videos. The change shouldn't affect them — in render mode the audio path is ffmpeg, not the in-browser pipeline — but a sanity-check render is cheap. ## Scope notes - *Not addressed in this PR*: the user-facing request for an upfront-extract concurrency cap (`Promise.all` in `extractAllVideoFrames` is currently unbounded across all videos). Filing as a follow-up PR — different layer of the pipeline, different user surface (CLI flag), worth keeping separate for review. - *Edge case*: in the calibration test-frame phase, the injection sibling may not yet exist when drift recovery first checks a video at the very start of its active window. The gate correctly defaults to "no sibling → run the seek" in that case, which is the existing behavior. _Authored by Jerrai (Rames team)._ |
||
|
|
b8d6c4aa21 | chore: release v0.7.2 | ||
|
|
468f7ec35b |
fix(compiler): skip CSS var() in font resolver (#1655)
* fix(compiler): skip CSS var() in font resolver — fixes FONT_FETCH_FAILED on distributed renders The font scanner treated `var(--ui-font)` as a literal font family name, causing fail-closed distributed renders to throw FONT_FETCH_FAILED for any composition using CSS custom properties in font-family declarations. CSS var() expressions resolve at browser paint time, not at compile time. The regex-based font scanner cannot resolve them statically — skip them and let headless Chrome handle variable substitution during render. Closes #1654 — Miga * test(regression): add distributed css-var-fonts fixture Regression test for compositions that use CSS custom properties in font-family declarations. Exercises the var() skip guard in extractRequestedFontFamilies() under the distributed renderer's fail-closed font resolution path. Baseline needs to be generated on first CI run with --update. — Miga * fix(compiler): address review feedback — mixed declaration test + validator TODO Add unit test verifying concrete fonts alongside var() in mixed declarations still get resolved (non-aggression pin). Add TODO(#1654) in validateNoSystemFonts for the var()-as-primary gap flagged by both reviewers. — Miga * fix(test): correct stale 4xx fail-closed test expectations The 4xx tests expected no throw, but that was the contract before #1255 added the system font capture path (Path 3). Post-#1255, a font that gets 4xx from Google Fonts AND isn't a bundled alias AND has no system font IS genuinely unresolvable — fail-closed mode should throw. The 4xx distinction still matters at the fetch level (no retry, treated as deterministic "not served"), but at the final unresolved check, a completely unresolvable font must throw regardless of the HTTP status that caused the Google Fonts path to return empty. Updated tests to match the actual contract: 4xx + unresolvable = throw. Also set allowSystemFontCapture: false to match how distributed renders (plan.ts:799) actually call the function. — Miga --------- Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com> |
||
|
|
8c13a72696 | fix(producer): capture page-side shader composites via screenshot (#1656) | ||
|
|
60d3eeb1f7 |
test(producer): add stream duration parity check to regression harness (#1652)
Probes the rendered output for video and audio stream durations after render and fails the test if they differ by more than 0.5s. Catches mux-level truncation regressions like the ffmpeg -shortest bug (#1648) where one stream gets silently cut short. Runs on all non-png-sequence fixtures with audio — no new meta.json field needed since this is a universal invariant, not a per-fixture threshold. |
||
|
|
4976b5e036 | fix(producer): split capture average timing (#1657) | ||
|
|
e9957ecaf4 |
fix(engine): remove -shortest from muxVideoWithAudio (#1648) (#1650)
FFmpeg 6.0 (bundled by ffmpeg-static) has a regression where -shortest combined with -c:v copy over-truncates the video stream while leaving audio untouched. The flag is also redundant — the audio mixer already pads/caps all tracks to totalDuration via apad=whole_dur and -t. Closes #1648 |
||
|
|
3a6742bd13 | chore: release v0.7.1 | ||
|
|
82a5298058 |
fix(studio): selectedGsapAnimations empty after gesture recording (#1646)
- include target in useGsapAnimationsForElement fetch key so a selection change triggers a re-fetch even at the same cache version - add gsapCacheVersion to useDomEditPreviewSync deps so the selection re-syncs after every soft reload - trim manualEditsDom.ts to 600 LOC (filesize compliance) Fixes #1645 |
||
|
|
4be81b4fb4 |
fix(producer): inline base64 frames in injector to unblock video-heavy renders (#1630)
* fix(producer): inline base64 frames in injector to unblock video-heavy renders The URL-served frame path (PR #596) hands each injected `<img>` a fileServer URL instead of a base64 data URI, on the theory that shipping a short URL through `page.evaluate` beats shipping a multi-MB base64 string per frame. That holds when the fileServer is otherwise idle. But on video-heavy compositions, the same fileServer also serves every `<video>.src`. The runtime's drift-recovery branch (`runtime/media.ts:294-302`) issues `el.load()` on the underlying `<video>` during seeks, kicking off full-file downloads that occupy the fileServer's single Node event loop (it uses `readFileSync` and offers no `Accept-Ranges`). The injector's `<img>.decode()` then queues behind those video fetches and is never serviced before puppeteer's protocol timeout fires, surfacing as `Runtime.callFunctionOn timed out` in `capture_streaming`. Reproducer (30 × 32 MB videos / 90 s comp / 8-core / 30 GB host): baseline (broken corpus) 537 s render fails baseline (corpus-fixed) 428 s render fails this fix (drop frameSrcResolver) 121 s render succeeds, 69 MB MP4 Control corpus (30 × 1.6 MB / 60 s) shows no regression: 137 s with this change vs ~135 s on \`main\`. The \`createCompiledFrameSrcResolver\` builder and the \`frameSrcResolver\` option stay in the codebase, just unused for now — re-enabling them behind a proper gate ("only use URL-served frames when the page has zero fileServer-bound \`<video>.src\` traffic") is a follow-up. The cache memory ceiling (\`frameDataUriCacheBytesLimitMb\`, default 1500 MB above 8 GB hosts) already bounds the cost of base64 inlining. — Jerrai * refactor(producer): drop unused frameSrcResolver builder import in render orchestrator Followup to the previous commit. The void-call and the `createCompiledFrameSrcResolver` import in `renderOrchestrator.ts` were left behind as a no-op breadcrumb for the future gating PR. Code review (PR #1630) correctly flagged this as dead code — the builder is a pure factory with no side effects, so calling it and discarding the result is just wasted CPU. Remove both and explain in the in-source comment where the builder still lives, so the gating PR knows where to re-import from. — Jerrai |
||
|
|
a97f80433f | chore: release v0.7.0 | ||
|
|
1f5dbdbc4c |
fix(studio,producer): post-merge regressions and open issues (#1643)
- fix gsapDragCommit.test.ts import for commitGsapPositionFromDrag - add generateId() helper with insecure-context fallback (fixes #1637) - enable STUDIO_KEYFRAMES_ENABLED by default - prevent clock duration from shrinking during playback (fixes #1636) - add maxRetries to rmSync cleanup calls to handle ENOTEMPTY race (fixes #1644) |
||
|
|
4db708eb4a |
fix(core): register sub-composition timelines after async build + lint rule (#1638)
* fix(core): register sub-composition timelines after async build + lint rule
When a composition builds its GSAP timeline inside document.fonts.ready (or any
async callback), registering window.__timelines[id] BEFORE the build leaves an
EMPTY timeline registered. The runtime's sub-composition readiness gate treats
"key present" as "ready" and nests the child once — an empty timeline gets
nested empty and is never re-nested, so the frame renders blank when used as a
sub-composition.
- registry/blocks/code-{diff,highlight,morph,scroll,typing}: register the
timeline AFTER the fonts.ready build completes, then call
window.__hfForceTimelineRebind() to re-nest now that it is populated.
- core lint: add rule gsap_timeline_registered_before_async_build to flag the
early-registration anti-pattern, with tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(studio): import commitGsapPositionFromDrag from its actual module
The function was split out into gsapDragPositionCommit.ts in #1605, but the
test kept importing it from ./gsapDragCommit, which no longer exports it —
yielding 'is not a function' at runtime. Import from the correct module.
Inherited main breakage (same fix as #1631/#1635); fixes the Test CI check on
this branch independently of merge order.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
0e75eb2510 |
fix(core): render standalone sub-composition previews + surface captured video assets (cli) (#1631)
* fix(core): escape digit-leading id selectors in standalone sub-composition preview
A CSS identifier cannot start with a digit, so an authored rule like
`#01-wall-pushes-back { ... }` is an invalid selector and the browser drops
the whole rule — taking the root's size/background with it. A full
composition masks this (the host stretches/paints the frame), but a
standalone preview has no host, so the root collapses to height:0 +
transparent and renders blank.
extractFullDocumentParts now rewrites `#<digit-leading-id>` selectors to
their escaped valid form (`#\30 1-...`, still matching the element id),
scoped to ids actually present and matched only as `#id` not followed by an
ident char so hex colors are never touched. Also harden the <template>
inner-HTML extraction to use the DOM instead of a greedy regex. Tests added.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit
|
||
|
|
1967901b57 |
refactor(skills): move product-launch / pr-to-video / faceless-explainer onto the script-driven architecture (#1635)
* refactor(product-launch-video): restructure onto script-driven architecture Move product-launch-video onto the shared script-driven authoring flow: build-frame remixes a hyperframes-creative preset onto brand tokens, audio routes through the shared hyperframes-media engine, per-preset caption skins, and every frame is authored as a directed shot. Removes the old bespoke scripts (captions/validate/prep/hoist/…) in favour of the shared lib. assemble-index.mjs keeps upstream #1629's blank/partial scene-file guard (reject an empty or markup-less scene file at assembly, before emitting data-composition-src, and re-dispatch) carried onto the restructured reader. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(pr-to-video): restructure onto script-driven architecture Move pr-to-video onto the shared script-driven authoring flow: ingest.mjs folds the gh PR artifacts into the synthetic capture package the shared backend (build-frame / captions / assemble-index) reads, add the mechanism beat, route audio through hyperframes-media, and remix a hyperframes-creative preset onto brand tokens via the shared lib. - Fix skill name: pr-to-video-refactor -> pr-to-video (match directory). - Drop a stale faceless-explainer-refactor reference in an ingest.mjs comment. - assemble-index.mjs keeps upstream #1629's blank/partial scene-file guard. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(faceless-explainer): restructure onto script-driven architecture Move faceless-explainer onto the shared script-driven authoring flow: every visual is invented (typography / abstract graphics / diagram / data-viz) and authored through the shared backend (build-frame remixes a hyperframes-creative preset onto tokens, audio via hyperframes-media, assemble-index builds the standalone index.html) using the shared lib. - Fix skill name: faceless-explainer-refactor -> faceless-explainer (match directory). - assemble-index.mjs keeps upstream #1629's blank/partial scene-file guard. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(skills): refresh test-skills-fresh.sh workflow roster Update the install-and-verify harness to the current surface: 10 workflows (adds website-to-video, embedded-captions, graphic-overlays, slideshow; drops the removed footage-recut) and refreshed example prompts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * style(product-launch-video): oxfmt storyboard.mjs Run oxfmt over lib/storyboard.mjs — formatting only, no logic change. Fixes the Format / Preflight CI check. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(studio): import commitGsapPositionFromDrag from its actual module The function was split out into gsapDragPositionCommit.ts in #1605, but the test kept importing it from ./gsapDragCommit, which no longer exports it — yielding 'is not a function' at runtime. Import from the correct module. Inherited main breakage (same fix as #1631); fixes the Test CI check on this branch independently of merge order. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(hyperframes): refine router skill metadata tags Update the entry router's metadata tags (video / animation / router focus); oxfmt collapses the now-shorter metadata to a single line. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(skills): tighten caption comment-strip + document audio --only merge Review follow-ups (#1635): - captions.mjs (x3): the HTML-comment strip used a single global replace, which CodeQL flags as incomplete multi-character sanitization (a nested/partial pair can re-form a marker the single pass misses). Strip in a fixpoint loop instead. Input is preset-library content, not user-controlled, so this is lint- cleanliness, not XSS defense. - audio.mjs (x3): document that fetch-sfx (--only sfx) MERGES into the neutral audio_engine_meta.json sidecar — the engine reads prev and recomputes only the sfx section, so voices/bgm from the generate pass are preserved (review Q). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(skills): remove existsSync->write TOCTOU in workflow scripts Clears the 9 js/file-system-race CodeQL alerts (captions/audio/transitions x3). Each was an existsSync precheck followed by a later write of the same path: - captions.mjs: caption-overrides shim -> atomic writeFileSync({ flag: 'wx' }). - audio.mjs (sync-durations) + transitions.mjs (inject): drop the existsSync precheck and read directly, surfacing the same friendly error from a try/catch on readFileSync — no check->write gap. Behavior is unchanged (same error messages); these are local single-process deterministic scripts so the race was never a real risk, but this clears the gate. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(skills): paint root composition ground color in assemble-index Per-frame roots carry data-start/data-duration and get clip-gated against the global timeline at render, so only the first frame's window overlaps global 0 — a frame's own full-bleed background can't serve as the video ground, and every frame after the first renders on the bare body color (black). Paint the ground on the always-present root composition using the project's frame.md canvas color (the same role the caption skin maps to --cap-canvas); fall back to the body letterbox color when frame.md is absent or has no resolvable ground. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(hyperframes): drop router-tag edit (moved to the foundation PR) The entry SKILL.md is rewritten wholesale by the frame-presets/media foundation PR (#1632); editing it here too guaranteed a merge conflict. Restore this file to main and let the router-tag tweak live with the rewrite in #1632, so the two PRs no longer both touch it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
091137e3c3 |
feat(studio): instant, flicker-free manual editing (#1605)
* chore(producer): shim __filename/__dirname in the CJS banner Bundled CJS deps like wawoff2 call __dirname; without the shim they throw "__dirname is not defined in ES module" at render time. Also ignore .zed/. * chore(producer): use a template literal for the CJS banner (review nit) * feat(core): add GSAP keyframe + motion-path source mutations Array-form keyframe removal in both the recast and acorn writers, plus update/add/remove-motion-path-point and add-motion-path. Exclude _auto and data from tween property-group classification. * fix(core): address #1554 review — data-exclusion test, split-fix doc, motion-path sentinel, parity blocks - Regression test for the `data` GSAP-key exclusion (parallel to _auto). - splitAnimationsInScript: documented that .fromTo()/.to() correctly stay out of the from-branch (only .from() reverts) and the <= boundary; added mid-flight straddle tests. - addMotionPathToScript failure path returns id: null (was empty-string sentinel); caller updated. - Parity blocks for addKeyframeToScript array-form + updateKeyframeInScript (mirroring removeKeyframeFromScript). Surfaced a latent acorn array-form partial-props merge bug — documented as it.skip with a ready assertion (acorn cutover follow-up). * feat(core): route motion-path mutations through studio-api + fix clip stamping Wire the new mutations into the file save route. Only authored clips suppress descendant stamping, so auto-stamped animated scenes can inline-expand. Hide in-flow timed clips with `display:none` only when they are LEAF clips (no nested timed clips). `display:none` on a container removes its whole subtree, hiding descendants that are still inside their own visibility window — e.g. an in-flow composition root whose effective window clamps to the timeline end would black out a child video that should still show (the hdr-hlg regression). Containers keep `visibility:hidden`, which a visible descendant can override; only leaves leave the flow, which is all the split-overlap case needs. * feat(core): strip legacy path-offset/rotation + drop obsolete studio lint rule A position or rotation add/set mutation makes the GSAP timeline the single source of truth for that channel, so any lingering --hf-studio-offset / --hf-studio-rotation CSS var must be cleared to avoid double-applying. stripStudioEditsFromTarget now clears both channels, and the add-strip fires for the position AND rotation property groups. Also removes the obsolete `gsap_studio_edit_blocked` lint rule: it warned that Studio cannot save drag/resize edits to elements in a registered timeline — the exact premise the single-source work inverts (the timeline is now the edit target). Removed the rule, its now-unused TIMELINE_REGISTRY_ASSIGN_PATTERN import, and its 5 tests. * fix(core): address #1555 review — complete hold-sync, invalidate clip cache, strip rotation channel - HOLD_SYNC_MUTATION_TYPES: add add-motion-path (load-bearing — addMotionPathToScript authors past t=0 → first-frame snap-to-(0,0) without the hold), update-meta, shift-positions, scale-positions, split-animations. (add stays out: flat tweens only, syncPositionHoldsBeforeKeyframes is a no-op for non-keyframed tweens.) - init.ts: timedClip in-flow/leaf WeakMaps now invalidate on clipTreeSignature change; visible/hidden branches both go through isTimedClipInFlow (was .get() by accident). - keyframesWriteRotation mirrors keyframesWritePosition so a rotation-only keyframe set strips the stale --hf-studio-rotation channel. * feat(studio): GSAP runtime read layer + shared helpers * fix(studio): address #1607 review — cold-parse vs fetch-error budgets, isZeroDurationSet, array-ease tests - useGsapAnimationFetchFallback: discriminate resolved/fetch-error/cold; only the cold (warm-but-zero) race gets the full ~600ms retry budget — a hard fetch error retries once. - Extract isZeroDurationSet (was !(duration>0) duplicated); rejects NaN, documents intent. - parsePercentageKeyframes: cite GSAP even-index spread; tests that a per-entry/interior ease is stripped without shifting the other keyframes' percentages. * feat(studio): GSAP drag/commit/bridge editing infra * fix(studio): address #1608 review — facade awaits commit, strict stale-parse guard, clearProps restore BLOCKER: useSafeGsapCommitMutation now RETURNS the (.catch-chained) commit promise and the commitMutation facade awaits it — so await session.commitMutation(...) resolves AFTER the server save, fixing both consumers (useEnableKeyframes + useGestureCommit's showToast/requestSeek/idle, which were firing before the save landed). SafeGsapCommitMutation return type widened void→Promise<void> (fire-and-forget consumers ignore it). - stale-parse guard uses hasNonHoldTweenForElement (a leftover hold set no longer counts as live). - commitFlatViaKeyframes snapshots dragged gsap values before clearProps + restores after seek, so a failed commit leaves the dropped pose, not a cleared element. * feat(studio): motion-path geometry + commit helpers * docs(studio): address #1609 review — document occlusion fade-in invariant, donut limit, nearestPointOnPath t-semantics * feat(studio): on-canvas motion-path overlay * fix(studio): address #1610 review — scope dblclick to pan-surface, kind-aware geometry guard, gate createMode, screen-space drag threshold * feat(studio): keyframes flag, gesture recording + timeline/selection refinements * fix(studio): address #1611 review — fetch-first keyframe path, gated hydration, dev-gated debug + gesture warn, per-group gesture tweens - useEnableKeyframes: parse current source first (null-vs-[] distinction) so a delete-all's empty parse isn't overridden by a stale selectedGsapAnimations cache. - useStudioUrlState: freeze the hydration effect's time dep once hydrated (was re-running every tick). - useGestureRecording: dev-gated console.warn when the live-preview runtime throws (was silent). - playerStore: gate window.__playerStore behind dev (guarded import.meta.env.DEV). - useGestureCommit: partition recorded keyframes by property group → one add-with-keyframes per group, so a mixed gesture no longer yields an untagged legacy tween. * feat(studio): single-source manual offset + rotation via the GSAP timeline Dragging or rotating an element writes into the GSAP timeline (the single source of truth) instead of a parallel --hf-studio-offset / --hf-studio-rotation CSS var: static elements commit a tl.set (idempotent on re-edit), tweened elements edit keyframes, and the live preview moves via gsap.set so what you see equals what is written and renders. Removes the dual-channel CSS-var/transform reconciliation behind the fling / disappear / runaway / double-stack / wrong-start bug class — for BOTH position and rotation (gesture base read from the gsap transform, gsap.set live preview, tl.set/ keyframe commit, dropped the handleDom*Commit CSS fallbacks). Subcompositions edit the same single-source way, which surfaced and fixes: - resolve a subcomp element's source file via the composition-id map (the runtime drops the source linkage when inlining the subcomposition); - a selected element's selection box AND motion path use basic visibility, not the occlusion heuristic (a backgroundless opacity-1 scene above it is not an opaque cover); - soft reload rebuilds ONLY the committed composition's timeline, leaving other compositions' timelines intact (no cross-composition revert); - read keyframes from the element's OWN composition timeline (scan all timelines, not the first unstable key); - delete-all uses a soft reload too, so editing no longer hard-reloads the iframe. * fix(studio): address #1567 review — drop drag-intercept flag, harden softReload onerror, tighten runtime ladder, per-group gestures - DROP STUDIO_GSAP_DRAG_INTERCEPT_ENABLED: single-source GSAP intercept is the only position/rotation channel; the false branch silently killed drag+rotate (and let GSAP elements into the keyframe-corrupting CSS path). Removed flag + dead branch + env def + tests. - gsapSoftReload: plugin onerror no longer fakes success — signals onAsyncFailure so the caller full-reloads; honors __hfMotionPathPluginLoading so a concurrent reload can't queue a dup script. - gsapDragCommit: resolveDragRuntime narrows the as-any ladder; a mid-seek throw logs + drops partial reads (no phantom identity) and re-applies the drag override in finally. - MotionPathOverlay: park-timer cleanup keyed on animId change. - useGestureCommit: partitionKeyframesByGroup wraps the add-with-keyframes sites (per #1611 review). * feat(studio): patchRuntimeTweenInPlace — update a tween's values in place Defensive runtime helper: locate the element's tween in window.__timelines via the shared resolveRuntimeTween scan, update its set/keyframe vars, invalidate, and re-seek the playhead — without re-running the whole composition. Returns false (caller falls back to soft reload) for any shape it can't safely patch (no tween, dynamic/computed keyframes, motionPath arc, channel mismatch, or any error). Foundation for instant, flicker-free manual edits. * fix(studio): address #1612 review — channel-aware set resolution + decline dynamic-expression patches - resolveRuntimeTween gains an optional channels[] hint; for kind:set it prefers the set whose vars carry one of the patched channels and never returns a disjoint-only set (e.g. won't write {x,y} into a co-located {rotation} set). patchRuntimeTweenInPlace derives channels from the props. - patchSet declines (returns false → soft reload) when overwriting a string/dynamic vars[ch], instead of silently dropping the computed expression. * feat(studio): instantPatch fast path in runCommit A commit carrying an instantPatch option tries patchRuntimeTweenInPlace first; on success the preview updates in place with NO reload (instant), on false it falls back to the existing soft reload. Extracts the preview-sync tail into a testable applyPreviewSync helper. No behavior change when instantPatch is absent. * feat(studio): route static position/rotation set drags through instantPatch Static-element position and rotation set commits now attach instantPatch{selector, change:{kind:set}} so the drag updates in place with no reload. Structural ops (new tween add, delete-all, convert/split/materialize) and keyframe edits deliberately omit it and keep the soft reload — keyframe instant-patch needs object-form keyframe support in patchRuntimeTweenInPlace (deferred). * fix(studio): address #1613 review — derive instantPatch from the mutation, patch both coalesced commits, wire onAsyncFailure - commitStaticGsapPosition/Rotation derive instantPatch.change.props from the actual update-property mutation(s) sent (one source of truth → findUnsafeMutationValues-validated values flow into the patch; can't drift). - Coalesced x/y: the intermediate x commit also carries instantPatch{x}, the y commit {x,y}, so a second-POST failure still leaves the preview patched for what persisted. - applyPreviewSync passes reloadPreview as onAsyncFailure (plugin-CDN load error → full reload); per U4 the synchronous false still does NOT escalate. - (channel disambiguation from #1612 verified end-to-end: {x,y}→position set, {rotation}→rotation set.) * feat(studio): no full iframe remount for soft-reloadable edits A softReload edit (and the SDK single-script refresh) no longer escalates to a full reloadPreview() iframe remount when applySoftReload returns false — the live gsap.set already shows the value, and a remount is the worst flash + re-inlines subcomps (reverting their keyframes). verifyTimelinesPopulated now checks the expected target keys the re-run registers, so a correct scoped re-run doesn't spuriously report empty. Full reload stays only for the structural (no-softReload) and ambiguous-script paths. * feat(studio): pre-load MotionPathPlugin so motion-path edits don't async-flash ensureMotionPathPluginLoaded() runs once at the preview iframe-load seam (NLELayout onIframeLoad), eagerly loading + registering MotionPathPlugin without killing the timeline. So when a user adds a motion path to a composition that didn't originally use one, the soft reload runs synchronously instead of taking the kill-then-await-CDN async path (the flash). Idempotent + defensive; the existing async fallback stays for genuine cold-start/CDN-failure. * fix(studio): don't re-save + reload when source editor syncs externally The SourceEditor's CodeMirror update listener fired onChange on ANY docChanged — including the programmatic dispatch that syncs external content (e.g. a manual-edit commit writing the source back into the open editor). That made the editor re-save the file and bump refreshKey, fully reloading the preview iframe on every drag/keyframe edit — defeating the in-place instant patch and causing the flash. Annotate the programmatic sync (ExternalSync) and skip onChange for it, so only real keystrokes save. * fix(core): inject MotionPathPlugin into preview when a composition uses motionPath A studio-created motion path writes a gsap motionPath tween into the single-source timeline, but the preview HTML only loaded gsap core — so the first render threw "Invalid property motionPath ... Missing plugin?". Detect motionPath usage and inject MotionPathPlugin right after the composition's gsap script, version-matched to it. * fix(studio): dedup __hfMotionPathPluginLoading type decl (restack artifact) * fix(studio): address #1605 review — distinguish soft-reload failure modes + observability, SourceEditor focus guard BLOCKER: applySoftReload now returns SoftReloadResult ('applied' | 'verify-failed' | 'cannot-soft-reload') instead of a bare bool. applyPreviewSync + sdkRefresh escalate to a full reloadPreview() on the PERMANENT 'cannot-soft-reload' (no gsap/rebind hook/scopable key/script, or sync re-run threw) — fixing the silent-stale-preview U4 dropped — but still suppress the TRANSIENT 'verify-failed' (live gsap.set is correct). Telemetry: gsap_soft_reload_outcome (origin/result/escalated) + gsap_instant_patch_fallback, so the U4 invariant is enforced, not asserted. - SourceEditor: skip the programmatic external-sync replace while the editor is focused, so an in-flight commit doesn't clobber the user's uncommitted keystrokes (ExternalSync kept for unfocused). - Verified ensureMotionPathPluginLoaded already guards __hfMotionPathPluginLoading (no double-append). * fix(core): align __clipTree and __clipManifest ids via stableClipId Timeline inline expansion was dead for nested children inside index.html: the tree keyed id-less elements by a synthetic __clip-N while the manifest keyed them null, so parent<->child never joined. Both now resolve identity through stableClipId (id || data-hf-id), which every generated element has. * fix(core): strip baked runtime + tag comp root in preview assembly Comps that ship a baked inline runtime were double-loaded (preview injects its own) and the baked copy failed to parse inline (Unexpected token '<'). Strip it in buildSubCompositionHtml + the disk-fallback preview path. Also tag the comp root with data-composition-file so the studio resolves a comp's top-level elements to the right source file instead of defaulting to index.html (which made the GSAP panel parse the wrong, multi-timeline file). * feat(studio): set motion-path destination from a toolbar toggle Replaces the double-click-on-canvas UX (which painted text over the preview) with a 'Set motion destination' toggle next to Snap/Grid, shown only when the selected element can take a path. While armed, one canvas press places the destination. Also removes the dead TimelinePropertyRows component. * fix(studio): center timeline keyframe diamonds on their percentage Dropped clampDiamondLeft, which forced boundary keyframes fully inside the clip so a 0% diamond sat half a diamond right of the 0% point. Each diamond's midpoint now sits exactly on its % (the clip is overflow-visible). * fix(studio): resize static elements via tl.set, not a single-stop keyframes tween Resizing an element with no size animation wrote keyframes:{ <playhead%>: {width,height} } — one mid-point stop GSAP can't interpolate, so it rendered NaN/0 dimensions at every other frame and the element vanished (worst off 0%). Added commitStaticGsapSize (mirrors commitStaticGsapPosition): a static resize now writes tl.set({width,height}), held at all frames; re-resizing updates it in place. * fix(studio): negative-cache failed media probes Only successful probes were cached, so CORS/404 cross-origin media was re-probed every rAF-driven timeline re-derive, flooding the console. Remember failed URLs and skip them. * fix(studio): type window.setTimeout handle as number ReturnType<typeof window.setTimeout> infers NodeJS.Timeout when @types/node is present and clashes with the DOM number the call returns. Type it number. * fix(studio): drag/resize disappearance, stale-ID duplicates, soft-reload clearProps - Fix soft-reload clearProps destroying element inline styles — save cssText, clear, restore, strip only transform - Fix resize no-op on re-resize: delete+add instead of two update-property - Route set tweens through static resize path (convertToKeyframes skips sets) - Re-fetch animation ID before drag commit to prevent stale-ID duplicates - Guard editDebugLog for Node test environments - Fix NLELayout setState-during-render (move reset to useEffect) - Stop SnapToolbar pointer events propagating to canvas deselect handler - Enable click-to-add waypoints on cubic motion paths - Add whole-path drag offset (Alt+drag shifts all keyframes together) - Add Canvas shortcuts section to ShortcutsPanel - Extract useMotionPathData + commitGsapPositionFromDrag (filesize compliance) - Delete dead code (getElementDepth, isElementVisibleInPreview, unused exports) |
||
|
|
e0822c6e85 |
chore(producer): shim __filename/__dirname in the CJS banner (#1553)
* chore(producer): shim __filename/__dirname in the CJS banner Bundled CJS deps like wawoff2 call __dirname; without the shim they throw "__dirname is not defined in ES module" at render time. Also ignore .zed/. * chore(producer): use a template literal for the CJS banner (review nit) |
||
|
|
f12754f5ad |
fix(cli): classify missing whisper-cpp as a setup gap, not a command error (#1628)
transcribe hard-failed with cli_error whenever whisper-cpp was absent. On Linux/Docker/CI (no Homebrew, no compiler toolchain) that is unavoidable, so it drove ~30k cli_error/day that are really "install the prerequisite" rather than bugs — and buried genuine transcription failures in the command-error budget. ensureWhisper now throws a typed WhisperUnavailableError when no binary exists and none can be built. The transcribe command reports that on a dedicated transcribe_unavailable metric instead of cli_error, and a new --optional flag lets pipelines skip captions and exit 0. Real transcription crashes still fail as cli_error. init and the skill pipelines already continue without captions. Also removes a stale doc reference to a `transcribe --provider groq` flag that does not exist. |