Commit Graph

4166 Commits

Author SHA1 Message Date
heygen-rui-bot fb07c52076 codegen: resync cloud client from EF e53ca0c1 2026-09-05 02:40:12 +03:00
James Russo 893141413e docs(core): fix published frame adapter reference links (#3667)
Co-authored-by: yoma <yingwaizhiying@gmail.com>
2026-09-04 19:30:59 -04:00
James Russo 36ec028d5f fix(cli): forward GIF and frame-format options in batch renders (#3666)
Port Dustin Persek’s fix and command regression from #1833 onto current main.

Co-authored-by: Dustin Persek <dustin.persek@protonmail.com>
2026-09-04 19:30:50 -04:00
James Russo 66052255cf fix(producer): isolate font cache temporary writes (#3669) 2026-09-04 19:30:47 -04:00
James Russo a93106aebf docs(skills): fix creative house style reference links (#3668)
Co-authored-by: yoma <yingwaizhiying@gmail.com>
2026-09-04 19:19:25 -04:00
Miguel Ángel cb44fe1c0b fix(producer): constrain delivered AAC true peak (#3644) 2026-09-04 23:16:07 +00:00
Miguel Ángel 32f9ed9880 fix(producer): preserve nested entry asset base (#3645) 2026-09-04 23:16:03 +00:00
Miguel Ángel 3314168e30 fix(producer): guard disk capture capacity (#3643) 2026-09-04 23:15:53 +00:00
Miguel Ángel c0303fc523 fix(engine): fail closed on blocked SRI scripts (#3664) 2026-09-04 23:15:41 +00:00
Miguel Ángel 50af764b7d fix(producer): scale high-quality encode timeout (#3649) 2026-09-04 23:15:37 +00:00
Miguel Ángel 2ca493094e fix(producer): follow variable audio duration (#3647) 2026-09-04 23:15:33 +00:00
Miguel Ángel 78a28b8942 fix(producer): preserve render warning attribution (#3642) 2026-09-04 23:15:30 +00:00
Miguel Ángel f334b0e735 fix(engine): retry capture screenshot timeouts (#3641) 2026-09-04 23:15:14 +00:00
Miguel Ángel c8ddbe0a4b fix(runtime): align nested activation to export frames (#3640) 2026-09-04 23:14:50 +00:00
James Russo 723cd0d785 fix(studio-server): block dangling symlink upload escapes (#3661)
* fix(studio-server): block dangling symlink upload escapes

* fix(studio-server): contain rename reference updates
2026-09-04 19:03:08 -04:00
James Russo 7b772465e3 docs(adopters): add PandaStudio (#3663)
Co-authored-by: Kamal Kannan <kamalkannan.sankarraj@gmail.com>
2026-09-04 18:51:06 -04:00
James Russo f7cac7287a docs(adopters): add Mini Course Generator (#3662)
* Add Mini Course Generator to adopters list

Added a new card for the Mini Course Generator showcasing its AI-powered course generation capabilities.

* docs(adopters): add Mini Course Generator

---------

Co-authored-by: eren-commits <eren@minicoursegenerator.com>
2026-09-04 18:40:10 -04:00
James Russo dd06b321cd fix(shader-transitions): preserve gravitational lens start frame (#3660)
Builds on #1579, verified with hardware WebGL endpoint and intermediate pixel comparisons.

Co-authored-by: Carlos Alcaraz <193642530+calcarazgre646@users.noreply.github.com>
2026-09-04 18:40:00 -04:00
James Russo 0ad9c700ac fix(lint): give randomness guidance for crypto values (#3659)
Builds on #1729 in the extracted lint package.

Co-authored-by: AGRO-CODEX <brstopo@gmail.com>
2026-09-04 18:39:51 -04:00
James Russo 29efd5dfa8 docs(registry): document installer metadata (#3658)
Co-authored-by: Kiyeon Jeon <kiyeon.jeon.21@gmail.com>
2026-09-04 18:39:41 -04:00
James Russo d2741b3a28 fix(studio-server): preserve binary file writes and versions (#3653)
* fix(studio-server): preserve binary file writes and versions

* fix(studio-server): rely on exclusive file creation for POST

* test(studio-server): create race fixture atomically
2026-09-04 17:44:19 -04:00
James Russo 924b98a0dc fix(captions): preserve zero plate grain in generated postfx (#3656) 2026-09-04 17:38:43 -04:00
James Russo 3bc46a8ade fix(studio-server): cascade GSAP cleanup when deleting subtrees (#3655) 2026-09-04 17:38:33 -04:00
James Russo ef63e02936 fix(fonts): retain non-Latin subsets for bundled weights (#3652)
Builds on #3086 with canonical alias supplementation and bundled Latin precedence.

Co-authored-by: Akshay Kumar Sharma <25038017+akzarma@users.noreply.github.com>
2026-09-04 17:38:24 -04:00
James Russo bec11b1293 fix(runtime): wait for a finite dotlottie duration (#3651) 2026-09-04 17:37:53 -04:00
heygengenesis[bot] 62d3304caf fix(embedded-captions): support heroless themes (#3632)
Co-authored-by: heygengenesis[bot] <262951085+heygengenesis[bot]@users.noreply.github.com>
Co-authored-by: miguel.sierra <229591595+miguel-heygen@users.noreply.github.com>
2026-09-04 18:27:47 +00:00
Miguel Ángel 19a512c05a fix(cli): count font axes as motion in the sweep fingerprint (#3107)
The layout audit fingerprints each element by rect and opacity, plus an
8x8 pixel hash for canvas and video. A composition whose only change over
the timeline is font-variation-settings touches none of those, so all six
samples hash identically and the audit reports sweep_static on a timeline
that is visibly animating.

Duplexed variable fonts make it unavoidable rather than unlikely. Recursive
holds one advance width across its whole weight axis by design, so even the
element rect stays fixed while the letterforms change.

Fold the computed font-variation-settings into the per-element fingerprint.
It reads normal on every element that does not use the property, so no
existing composition's fingerprint changes.

This is the third instance of this failure class in the same function,
after the canvas-repaint and opacity-reveal cases whose regression tests
already sit alongside these. Two more join them: a scene where only the
axes move must change the fingerprint, and a scene where nothing moves
must hash identically twice. The second is the load-bearing one, since a
fingerprint that varies on its own would make sweep_static unfireable and
every green layout verdict meaningless.
2026-09-04 18:07:32 +00:00
Miguel Ángel e147613542 fix(cli): upgrade project wrapper pins (#3042) 2026-09-04 18:06:51 +00:00
Miguel Ángel 709d70c675 fix(cli): render SVG selector proof strips (#3537) 2026-09-04 18:06:49 +00:00
Miguel Ángel 2dc4bbfa75 feat(cli): forward a reported catalog gap to the feedback channel (#3204)
A search miss already reached PostHog, where nobody was watching it. It now
also goes to the same channel a CLI rating goes to, by the same best-effort
POST, so a move the catalog is missing is read rather than queried.

The forward is bounded and swallowed, and the ack prints before it, so a gap
report can never fail or delay the command that sent it.
2026-09-04 18:06:36 +00:00
Miguel Ángel 353b9eb3da fix(producer): scale frame coverage by playback rate (#3542) 2026-09-04 18:06:34 +00:00
Miguel Ángel ece09d830d fix(studio): drop the resurrected WebMCP polyfill assertion (#3556)
#3516 merged from a branch cut before da6514d45 and brought back
`expect(document).not.toHaveProperty("modelContext")`, along with the old
test title. This re-applies that fix.

The assertion is racy, not merely wrong. The hook awaits a dynamic import
of the `@mcp-b/global` polyfill inside its mount effect, so whether
`document.modelContext` exists when the assertion runs depends only on
whether that import has resolved yet. `await act()` usually returns first,
which is why main went green while another PR's run went red. Inserting a
100ms wait before the assertion on the current tip makes it fail every
time, and the failure dump shows all five tools registered through the
polyfill: the real behaviour is the opposite of what the line asserted.

Installing `document.modelContext` where the browser has none is the
polyfill's job. What the case is really for is that mounting does not
throw, so a browser without native WebMCP still boots Studio.
2026-09-04 18:06:24 +00:00
Miguel Ángel faaafbb251 fix(cli): split Kokoro text on token overflow (#3041) 2026-09-04 18:06:15 +00:00
heygengenesis[bot] 0154b40b65 fix(captions): derive preview GSAP integrity (#3631)
Co-authored-by: heygengenesis[bot] <262951085+heygengenesis[bot]@users.noreply.github.com>
Co-authored-by: miguel.sierra <229591595+miguel-heygen@users.noreply.github.com>
2026-09-04 18:04:26 +00:00
Miguel Ángel a3954aa1cb fix(cli): persist what the server answered for a captured page (#3553)
* fix(cli): persist what the server answered for a captured page

A page that renders is not a page that succeeded. An error page has a title,
a palette, typefaces and a DOM, so every extractor downstream reads it happily
and produces a design system belonging to whoever wrote the error page rather
than to the site's owner.

The status was already read, once, to feed `detectBlockedPage`, and then
dropped. That helper cannot stand in for it: it decides whether the rendered
document LOOKS like a protection wall, over a minimal-DOM heuristic that a
rich error page passes. "Was this blocked?" and "what did the server answer?"
are two questions, and widening the first to carry the second would leave a
heuristic owning a fact.

So the response status is persisted plainly, as its own record, and every
consumer decides for itself what a non-success response means for its product.
Written before the blocked-page check runs, so the record's absence means
"navigation never produced a response" — a third state distinct from a status
of 404 and from a status of null.

* fix(cli): report the response status in the capture command's JSON output

The status reached `CaptureResult` and was then dropped at the CLI boundary,
which is the same read-once-and-discard that made the error page harvestable
in the first place. `--json` is the documented programmatic surface, and an
agent reading `ok: true` off a capture of a 404 has no way to see it there.
2026-09-04 18:03:35 +00:00
WaterrrForever c8300c69a1 fix(cli): install the published skill set, not every SKILL.md in the repo (#3636)
Bare `hyperframes skills` passed `--skill '*'` to the upstream installer, which discovers every SKILL.md in the clone, so a full install put 26 skills on disk against the 20 the manifest publishes (the six repo-internal ones under .claude/skills / .agents/skills). It now resolves the canonical manifest and installs its names, the same path `skills update` uses; offline it warns and degrades to the pinned core set instead of the wildcard. Docs point the full set at `npx hyperframes skills` and say what `skills add --all` really pulls.
2026-09-04 23:25:43 +08:00
WaterrrForever c5224ac7e3 feat(cli): snapshot --against pairs each frame with a reference video (#3637)
Rebuilding footage means checking the render against the source at the same timestamps. `--against <video>` saves the reference frame at every captured time (frame-exact seek: -ss after -i) plus a labelled render|reference pair sheet built with the existing contact-sheet helper. Reference frames and pair sheets are derived artifacts, kept out of the snapshot list like contact-sheet.jpg. No new dependencies.
2026-09-04 23:25:36 +08:00
Miguel Ángel e0007b86e0 fix(render): allow resolution scaling for alpha output (#3634) 2026-09-04 10:28:56 -04:00
miga-heygen 05b57f9204 fix(fonts): make Google Fonts subsetting CSS text-transform aware (#3577)
* fix(fonts): make Google Fonts subsetting CSS text-transform aware

Extends the subset character closure to cover locale/context-sensitive
case transforms and non-case CSS text-transform values:

- Parse lang attributes from authored HTML and apply toLocaleUpperCase/
  toLocaleLowerCase for each detected locale (covers Turkish İ/ı,
  Azeri, German ẞ, and other locale-dependent casing)
- Map ASCII U+0021–U+007E to fullwidth equivalents U+FF01–U+FF5E when
  full-width appears in the source
- Map small hiragana/katakana to full-size equivalents when
  full-size-kana appears in the source
- Preserve the existing 1700-char encoded URL budget and full-font
  fallback

Closes #3496

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

* refactor: extract helpers to reduce complexity and duplication

Split extractGoogleFontsText into addCaseClosure, addFullwidthVariants,
and addFullSizeKanaVariants. Extract subsetTextFor test helper to
eliminate repeated URL→text boilerplate.

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

* fix: validate lang tags, scope transform gates, deduplicate closure input

- Validate lang attributes with Intl.getCanonicalLocales before passing
  to toLocaleUpperCase — malformed tags (en_US, x, 123) no longer throw
  RangeError.
- Gate fullwidth/kana expansion on text-transform declarations instead
  of raw html.includes — a CSS class named .full-width no longer eats
  half the URL budget.
- Deduplicate characters before the closure loop (new Set) to avoid
  redundant locale conversions on base64-heavy compositions.
- Benchmark now asserts the transform cost delta, not just that one
  small fixture fits under the cap.
- Restore over-approximation comment.

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

* fix: prevent regex bridging across rules and add case-insensitive matching

Exclude {} from the text-transform regex character class so the match
cannot cross rule boundaries when the trailing semicolon is omitted.
Add /i flag so uppercase declarations (text-transform: FULL-WIDTH)
are not missed. Test now uses a fixture with both a text-transform
declaration and a .full-width class to exercise the bridging case.

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

* fix: remove redundant \s* to eliminate quadratic backtracking

Drop the \s* between : and [^;{}]* — the character class already
matches whitespace, and the adjacent quantifiers caused quadratic
backtracking on inputs like "text-transform:" + " ".repeat(N).

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

* fix: replace regex with linear indexOf/slice scan (js/polynomial-redos)

The text-transform regex backtracks O(n²) on input with many
text-transform: runs and no ;{} between them. Replace with a linear
indexOf/slice scan following the repo's existing pattern for this
CodeQL rule (beatFile.ts, utils.ts, parseStoryboard.ts).

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-09-03 05:39:21 +00:00
Xuanru Li 8fade97da9 feat(lint): error when a video or img src is the other media kind (#3609)
Agents were shipping <video src="….jpg"> / <img src="….mov"> because the existing URL-resolvable check stayed green. Report an error so the agent rewrites the tag; skip audio, <source>, and extensionless URLs.
2026-09-02 22:25:44 -07:00
Miguel Ángel 19ab83f929 chore: release v0.8.27 (#3608) v0.8.27 2026-09-03 00:27:31 -04:00
miga-heygen 1a788d62e7 fix(core): catch PostCSS parse errors instead of dropping compositions (#3589)
* fix(core): catch PostCSS parse errors instead of dropping compositions

Invalid CSS in a sub-composition style block made postcss.parse throw
inside scopeCssToComposition. The throw propagated to the composition
loader's catch block, which emptied the host — silently dropping the
entire scene. Lint swallowed the same error via catch { continue },
reporting 0 warnings.

Two fixes:
- Runtime: wrap postcss.parse in try/catch and return the original
  (unscoped) CSS on failure, so the composition still mounts
- Lint: emit a css_parse_error finding instead of silently continuing

Fixes #3585.

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

* fix: drop unparseable CSS instead of leaking it unscoped

Return "" on PostCSS parse failure so sub-composition stylesheets
that cannot be scoped are dropped rather than injected unscoped into
the parent document. Updates test fixture to use valid+malformed CSS
that demonstrates the leak risk.

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-09-03 04:22:21 +00:00
Miguel Ángel 7dc31bd2cc fix(cli): score apple-touch-icon-precomposed at the 180px default (#3607)
declaredSize() matched the rel token "apple-touch-icon" exactly, so the
legacy "apple-touch-icon-precomposed" spelling (one token, not two) fell
through to 0 instead of the 180px Apple default, even though the selector
already collects it. Same page, one spelling apart, opposite winner inside
tier 1 — it never drops a candidate and never beats the .ico tier.

Switch to a startsWith check on the token. Exact-token matching stays for
mask-icon, where a longer rel really would be a different asset.
2026-09-03 00:21:05 -04:00
miga-heygen da09428af1 fix(producer): degrade gracefully when font cache directory is unwritable (#3572)
* fix(producer): degrade gracefully when font cache directory is unwritable

When the font cache root (~/.cache/hyperframes/fonts/) cannot be created
(EPERM on read-only filesystems, restricted home directories, etc.), the
render aborts with a raw mkdir error. The cache is an optimization, not
a requirement — a missing cache should mean slower first renders, not
broken renders.

Fall back to a temporary directory under os.tmpdir() when the configured
cache root fails, so Google Fonts downloads still proceed. The fallback
cache is per-process and not persistent across renders, but the render
completes.

Fixes #3412.

* fix: use mkdtempSync for font cache fallback, restore warning

Rames Jusso's review caught a regression in the force-push: the
predictable tmpdir path is unsafe (symlink attacks in world-writable
dirs), and the warning log was dropped. Restore the mkdtempSync
pattern matching lambdaFontCacheRoot, add a CLI hint per Miguel's
request, and reuse the ephemeral root across calls.

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

* fix: emit font cache fallback warning once per run, not per font

Gate the warning on whether this is the first fallback activation.
The ??= already suppresses repeat mkdtempSync, but the warn fired
for every font family in the composition.

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-09-03 04:00:37 +00:00
Miguel Ángel 5a5e841c5d fix(cli): pick the highest-quality favicon and keep its transparent background (#3606)
* fix(cli): capture the best declared favicon, not the first one

Pages routinely declare a legacy 16px .ico first and the good asset (an SVG,
or a 180x180 apple-touch PNG) after it. The capture's page evaluate kept only
{rel, href} and the download loop took whichever candidate fetched first, so
the .ico won on every such page. The dropped sizes/type attributes are the only
evidence of quality: page.html on disk does not keep the <link> tags and only
the winner's bytes are fetched, so the choice was unrecoverable downstream.

Keep sizes and type, and rank candidates before downloading: SVG first, then
the largest declared size (an unsized apple-touch-icon counts as 180), then
.ico. The loop still falls through to the next candidate when one fails to
download, so the ranking changes which icon wins, never whether one lands.

Ranking is a pure function over the declared attributes, unit-tested against
the link shapes three sites actually publish.

* fix(cli): rank a pinned-tab mask-icon below real favicons

`link[rel*="icon"]` also matches `rel="mask-icon"`, Safari's pinned-tab
asset: a single-colour silhouette drawn in a browser-chosen tint, not the
site mark. It is served as an SVG, so ranking by format alone promoted the
outline above the page's actual colour favicon.

Give mask-icon its own lowest tier rather than dropping it, so a page that
declares nothing else still lands an icon instead of none.
2026-09-02 23:53:45 -04:00
Miguel Ángel ae7e530f2d feat(cli): capture reports why a referenced asset is not in the folder (#3598)
Capture drops assets for four reasons and reported none of them, so a folder
with thirty images and a folder truncated to thirty images were the same
object. Every drop site was a bare `continue`, `break`, `return null` or an
empty `catch`, and the only signals downstream were two hand-written warning
strings that fired when the budget was already gone before a download pass
started, which is the one case where the pass could not say how much it lost.

`downloadAssets` and `downloadAndRewriteFonts` now return an `AssetDropCounts`
tally beside their result, incremented at the single line that performs each
drop. `capture --json` carries it as `dropped`; the human summary prints a
`Dropped:` line when it is non-zero.

Four reasons, three decisions and one failure:

  size-floor        fetched, then judged too small to be a real asset
  budget-exhausted  the post-navigation clock ran out before this one
  cap-reached       30 inline SVGs, 30 fonts, or 6 faces per family
  unavailable       the request or the write failed

A break now counts everything it did not reach rather than the one it stopped
on, because "how many did we lose" is the question and one is never the answer.

The two budget warnings are gone. Both existed only to cover the case where
the budget ran out before a pass was called, so both passes are now called
unconditionally: a zero budget makes each loop break on its first item and
record `budget-exhausted` for the rest, which costs no network and produces a
real number instead of the word "some". The single remaining warning is derived
from the tally, so the prose and the count cannot disagree.

Measured on a live capture of a large marketing site:

  default budget    232 kept, 91 dropped (39 size-floor, 20 cap-reached,
                    32 unavailable)
  15s budget         30 kept, 299 dropped (279 budget-exhausted, 20 cap-reached)

Same page, same command, and until now both runs described themselves the same
way.
2026-09-02 23:24:59 -04:00
Miguel Ángel 84ed587f33 chore: release v0.8.26 (#3597) v0.8.26 2026-09-02 10:36:01 -04:00
Miguel Ángel 81f1a903e1 fix(studio): rebind paused preview after live edits (#3596) 2026-09-02 10:23:09 -04:00
Miguel Ángel 6b360f56f7 chore: release v0.8.25 (#3595) v0.8.25 2026-09-02 01:29:06 -04:00
Miguel Ángel 6b5b4cb988 feat(studio): make agent edits live and explicit (#3581) 2026-09-02 01:11:13 -04:00