Commit Graph

3187 Commits

Author SHA1 Message Date
James Russo fe2cc92050 fix(studio-server): bound preview variable insertion scans (#3715)
* fix(studio-server): bound preview variable insertion scans

* docs(studio-server): update preview variables helper reference
2026-09-05 15:32:34 -04:00
James Russo 7d7003aa64 fix(studio): match style attributes with explicit quote boundaries (#3712)
* fix(studio): match style attributes with explicit quote boundaries

* fix(studio): apply quote boundaries to active source writers
2026-09-05 14:50:27 -04:00
James Russo e9250fcc45 fix(studio-server): simplify normalized group ID trimming (#3711) 2026-09-05 14:12:24 -04:00
James Russo 51a3c7e245 fix(engine): isolate WAV staging in a private directory (#3709)
* fix(engine): create WAV staging files exclusively

* fix(engine): isolate WAV staging in a private directory
2026-09-05 13:44:44 -04:00
James Russo c564daf210 fix(studio): avoid inline style regex backtracking (#3708) 2026-09-05 12:58:26 -04:00
James Russo eec04340b3 fix(core): avoid grade stats regex backtracking (#3706) 2026-09-05 12:28:59 -04:00
James Russo e5d1d9bf0e fix(engine): avoid transform regex backtracking (#3704) 2026-09-05 11:53:43 -04:00
James Russo f13037ecd4 fix(engine): isolate chunked encode temporary files (#3680) 2026-09-05 11:15:46 -04:00
miga-heygen ae3d80c30f chore: release v0.8.29 (#3690) 2026-09-04 21:50:46 -04:00
miga-heygen 00c575d23b fix(studio): prevent preview hang on burst external file rewrites (#3648)
* fix(studio): prevent preview hang on burst external file rewrites

Two interacting bugs caused Studio to freeze when multiple processes
(generator, check, snapshot) burst-wrote index.html within seconds:

1. SSE listener leak: the /api/events handler added a watcher listener
   per client connection but never removed it on disconnect. Reconnects
   accumulated dead listeners, each triggering readFileSync on every
   file change and writing to closed streams.

2. Generation starvation: processChange incremented generationRef and
   awaited drainPendingChanges. A second event arriving mid-drain bumped
   the generation, causing the first drain to bail at the generation
   check. With rapid writes, no drain ever completed and Studio stayed
   frozen on stale content.

Fix 1: use stream.onAbort() to remove the watcher listener when the
SSE connection closes.

Fix 2: gate processChange with a draining ref. While a drain is in
progress, stash the latest event. On completion, process the stashed
event — the last write in a burst always completes its reload.

Closes #3646

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

* fix(studio): align coordinator tests with drain serialization

Update existing test to expect the new behavior: when two events
fire in quick succession, the first drain completes and triggers a
reload (previously it was silently discarded). The stashed event
then starts a second drain.

Also fix the burst-write test to use the drains array pattern and
explicit act() flushes for stashed event processing.

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

* fix(studio): stash events with allowDuplicate and harden listener cleanup

Address Rames's review findings:

- Stash with allowDuplicate: true so re-dispatched events are not
  swallowed by the duplicate guard (the identity was already written
  on the way in, so the stashed event matched itself on re-entry).
- Wrap SSE keepalive loop in try/finally so the listener is removed on
  both abort and throw, not just abort.
- Restore stale-completion guard test coverage lost in the rename.
- Use await act(async () => {...}) for burst dispatches so assertions
  depend on the stash guard rather than scheduling.

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

* fix(studio): simplify drain serialization and restore SSE cleanup

Restructure processChange into intake + drain loop:

- processChange is now synchronous — validates, dedupes, checks own
  echoes, enqueues the accepted payload, and starts the drain loop
- startDrainLoop runs while the pending slot is non-null, draining
  one event per iteration via drainOnePending
- No recursive void processChange(...) from finally, so no
  allowDuplicate escape hatch needed — stashed events never re-enter
  intake guards

SSE listener: restore stream.onAbort alongside try/finally. Hono's
sleep() never throws, so finally alone doesn't fire on disconnect.
Both paths call removeListener (Set.delete is idempotent).

Tests: remove stale-drain test that contaminated subsequent tests by
emptying the shared roots array mid-test. Use sync act() for burst
dispatches — the stash decision is synchronous.

All 10 coordinator tests pass locally (NODE_ENV=test).

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-04 21:44:25 -04:00
Miguel Ángel 64ce9fdf1f chore: release v0.8.28 (#3689) 2026-09-04 21:01:08 -04:00
James Russo 9ed50028c4 fix(capture): sample computed shadows on ordinary boxes (#3686)
Co-authored-by: Xuanru Li <157947275+xuanruli@users.noreply.github.com>
2026-09-04 20:28:17 -04:00
James Russo a7cdd583e7 fix(capture): retain opaque and glass button styles (#3685)
Refresh the capture fixes proposed in #1880 with browser-backed regression coverage.

Co-authored-by: Xuanru Li <157947275+xuanruli@users.noreply.github.com>
2026-09-04 20:28:07 -04:00
James Russo 65cf87de38 fix(core): scope composition root pattern selectors per instance (#3684)
Port the authored-root selector fix from Thomaswebstich in #2262 to current main.

Co-authored-by: Thomaswebstich <thomas.schnerb@gmail.com>
2026-09-04 20:27:56 -04:00
James Russo 19dee4cede fix(render): serve media assets with registered content types (#3677)
Port the MIME mappings reported by fix2015 in #1836 to both render file servers.

Co-authored-by: vitalii.semianchuk <fix20152@gmail.com>
2026-09-04 19:54:29 -04:00
James Russo 1f9f86c857 docs(studio): align stack note with package dependency contract (#3675)
Co-authored-by: yoma <yingwaizhiying@gmail.com>
2026-09-04 19:53:07 -04:00
James Russo 44208ceada docs(cli): document current publish workflow (#3674)
Refresh the README guidance from #1950 for current auth, visibility and updates.

Co-authored-by: yoma <yingwaizhiying@gmail.com>
2026-09-04 19:52:54 -04: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
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 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 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 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
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 faaafbb251 fix(cli): split Kokoro text on token overflow (#3041) 2026-09-04 18:06:15 +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) 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