Skip to content

chore: sync upstream + lint GSAP animating <video> dimensions#17

Merged
cuio merged 9 commits intomainfrom
chore/upstream-sync-and-video-dim-lint
Apr 25, 2026
Merged

chore: sync upstream + lint GSAP animating <video> dimensions#17
cuio merged 9 commits intomainfrom
chore/upstream-sync-and-video-dim-lint

Conversation

@cuio
Copy link
Copy Markdown
Owner

@cuio cuio commented Apr 25, 2026

Summary

Two things bundled in one PR because they're both small and the merge needs to land first so the lint rule sits on a current base:

  1. Sync heygen-com/hyperframes upstream — pull 7 commits we were behind on (3 fixes + v0.4.25/26/27 releases + a Claude Design refactor).
  2. GAP-1 lint rule (gsap_animates_video_dimensions) — flag GSAP tweens that animate box dimensions on a <video> directly. This is the upstream Common Mistakes guide's feat(elevenlabs): add ElevenLabs TTS provider (CLI + Studio API) #1 footgun and previously had no enforcement.

Verified: all 5 packages green — 630 core (+8 vs pre-PR: 6 from upstream lint additions, 6 from new rule, minus a removed rule), 178 cli (+4 from upstream's publish/lint tests), 221 studio, 491 engine + 3 LFS-skipped, 84 player. oxlint clean. oxfmt clean. tsc --noEmit clean.

Phase 5 — upstream sync (7 commits)

Upstream Why it matters
hyperframes#491cli publish staged uploads Our fork heavily uses publish. Single-shot upload was failing for large projects on big payloads.
hyperframes#489 — sub-comp <audio> src path Audio in sub-compositions resolved against the parent, not the sub. Silent breakage when nesting compositions with audio.
hyperframes#490 — drop root_composition_missing_data_duration Removes a noisy false-positive lint rule that was firing on perfectly-valid setups.
#495 — Move Claude Design instructions to docs/ Renames skills/claude-design-hyperframes/SKILL.mddocs/guides/claude-design-hyperframes.md. The old SKILL.md becomes a 5-line stub.
v0.4.25 / v0.4.26 / v0.4.27 release commits Version bumps in package manifests across packages.

Merge was clean — zero conflicts. Upstream's new tests (composition.test.ts +59L, lintProject.test.ts +43L, publishProject.test.ts +88L) all pass against our combined tree.

GAP-1 — gsap_animates_video_dimensions

The upstream Common Mistakes guide opens with:

Symptom: Video frames stop updating, or browser performance drops severely.
Cause: GSAP animating width, height, top, left directly on a <video> element can cause the browser to stop rendering frames.

…but our linter never caught it. It does now.

Rule shape: for each script with GSAP tweens, intersect the tween's target id selector with our <video> tag inventory. If the selector matches a video AND the tween animates width, height, top, left, x, or y, raise an error-severity finding with a wrapper-pattern fix-hint.

False-positive guard: only direct id selectors fire. Class and attribute selectors are ignored — those might legitimately wrap a video, and we don't want to penalize the right pattern.

File: packages/core/src/lint/rules/gsap.ts (new rule appended to gsapRules)

Tests — 6 cases in gsap.test.ts:

Case Expected
tl.to("#video", { width, height }) on a <video id="video"> error fires
tl.fromTo("#video", {}, { x, y, top }) on a <video> error fires
tl.to("#video", { opacity, scale, filter }) does NOT fire
tl.to("#pip-wrapper", { width, height, top, left }) (wrapper) does NOT fire
tl.to("#card", { width, height }) on a non-video does NOT fire
tl.to(".bg", { width }) against a class selector matching video does NOT fire

Test plan

  • bun run --cwd packages/core test — 630 / 630
  • bun run --cwd packages/cli test — 178 / 178
  • bun run --cwd packages/studio test — 221 / 221
  • bun run --cwd packages/engine test — 491 / 491 + 3 LFS-skipped
  • bun run --cwd packages/player test — 84 / 84
  • bunx oxlint . — clean
  • bunx oxfmt --check — clean
  • tsc --noEmit — clean every package

🤖 Generated with Claude Code

MuTsunTsai and others added 9 commits April 25, 2026 16:55
heygen-com#489)

`lintAudioSrcNotFound` resolves every `<audio src>` against the project
root, which is correct for index.html but wrong for sub-compositions —
their srcs are written relative to the sub-composition file (e.g.
`../assets/foo.mp3`), and the bundler rewrites them at compile time.

Carry the sub-composition path alongside each html source and run
`<audio src>` strings starting with `../` through the existing
`rewriteAssetPath` helper before checking existence on disk. Mirrors the
runtime/bundler behaviour so the lint check sees the same path the
renderer will fetch.

Original (un-rewritten) src is still surfaced in the finding message so
authors can grep for it in their HTML.
* fix(cli): publish projects through staged uploads

* fix(cli): honor signed upload length header
)

* fix(lint): remove root_composition_missing_data_duration

Lint cannot statically observe the runtime's true Infinity-emission
condition: it requires a finite GSAP timeline duration AND a finite
media/sub-comp window AND timeline > floor + 1, none of which are
visible to the static linter. The looping shapes that drive the
condition are already covered by `gsap_infinite_repeat` and
`gsap_repeat_ceil_overshoot` (both from heygen-com#243), which point at the
real authoring mistake — flagging the missing duration separately
was a noisy proxy for the same signal.

Per heygen-com#490 review discussion, deprecate the static rule and let those
two GSAP rules carry the pre-render coverage. If perfect precision
on `durationInFrames = Infinity` is needed, that belongs on the
runtime/render path where `shouldEmitNonDeterministicInf` is
actually known.

Add regression tests pinning the removal: a docs-compliant root
without `data-duration` no longer warns, and the canonical
loop-inflated shape now surfaces only via `gsap_infinite_repeat`
instead of two duplicate findings.

* docs(skills): update step-6 build checklist after rule removal

Drops the `root_composition_missing_data_duration` reference now that
the rule is gone. Keeps the authoring recommendation (and explains the
runtime Infinity case) but points authors at the GSAP rules that
actually carry the lint signal: `gsap_infinite_repeat` and
`gsap_repeat_ceil_overshoot`.
…t/x/y

Animating box dimensions on a <video> directly causes Chromium to stop
pushing frames or to drop the compositor — documented as the most common
cause of broken compositions in the upstream Common Mistakes guide. The
fix is always the same: wrap the <video> in a non-timed <div> and animate
the wrapper instead.

The rule fires when:
- a GSAP method (to/from/fromTo/set on a timeline or gsap.*) targets a
  direct id selector (#id), AND
- the matching tag is a <video>, AND
- the tween animates one of width/height/top/left/x/y.

False-positive guard: only direct id selectors are flagged. Class or
attribute selectors might wrap a video legitimately and we don't want to
penalize wrapper-pattern code.

Tests cover: width/height fires, x/y/top/left fires, opacity/scale/filter
do not fire, wrapper-div pattern does not fire, non-video element does
not fire, class selector touching a video does not fire.

Refs: hyperframes Common Mistakes guide — "Animating video element
dimensions" — closes the GAP-1 audit finding.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
@cuio cuio merged commit a65e432 into main Apr 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants