Skip to content

docs(website-to-hyperframes): add load-bearing GSAP authoring rules#364

Merged
ukimsanov merged 3 commits intoheygen-com:mainfrom
ibrews:skill-load-bearing-gsap-rules
Apr 24, 2026
Merged

docs(website-to-hyperframes): add load-bearing GSAP authoring rules#364
ukimsanov merged 3 commits intoheygen-com:mainfrom
ibrews:skill-load-bearing-gsap-rules

Conversation

@ibrews
Copy link
Copy Markdown
Contributor

@ibrews ibrews commented Apr 21, 2026

Summary

Adds a new Load-bearing rules for animation authoring subsection to skills/website-to-hyperframes/references/step-6-build.md, capturing five GSAP authoring rules the linter cannot catch but that silently ship broken output — compositions that pass lint and render elements invisible, unscrubbing, or frozen.

Evidence

Surfaced from two independent website-to-hyperframes builds on 2026-04-20:

  • spatial-deck/promo/hyperframes-auto/ — single-page SPA source
  • ~/harvardxr-auto/ — marketing-site source

Both lint-clean on the first try. Both rendered cleanly only because we injected these rules into the sub-agent prompt before dispatch. Without them, sub-agents produced lint-passing compositions with:

  • Hero images that entered then vanished (stacked y-entrance + scale Ken Burns tweens on one element)
  • Auras that looked right in the studio preview but were missing from the rendered MP4 (standalone gsap.to() that didn't scrub)
  • Scene transitions where elements flashed visible before their own entrance (gsap.from() immediateRender interacting with .clip boundaries)

Rules added

  1. No iframes for captured content — don't seek deterministically, cannot scrub.
  2. Never stack two transform tweens on one element — the second tween's immediateRender resets the first, element ends up invisible. With before/after code showing two fixes (combine into one fromTo, or split across parent/child wrappers).
  3. Prefer tl.fromTo() over tl.from() inside .clip scenesfrom()'s default immediateRender: true writes state before the scene's data-start is active and misbehaves under non-linear seeking.
  4. Ambient pulses must attach to the seekable tl, never bare gsap.to() — standalone tweens run on wallclock, don't scrub, absent from rendered output.
  5. Generalize the existing caption hard-kill rule to every scene-boundary exit — any element whose visibility changes at a beat boundary needs the deterministic tl.set() kill after its fade, not just captions.

Rules already present in the file (repeat: -1, data-start/data-duration on template roots, caption hard-kill) are untouched — these five are net-new or are generalizations.

Related observations (not in this PR, flagging for maintainers)

Two adjacent findings from the same builds, worth filing separately if you want them tracked:

  • Linter heuristic inconsistency. Identical hard-kill patterns at beat boundaries trigger overlapping_gsap_tweens on some builds but not others. Stabilizing this heuristic would remove a source of sub-agent confusion.
  • step-1-capture.md thin output on canvas-driven / deck-style sources. Single-page apps that navigate via keyboard or internal state produce sections: 0, CTAs: 0, scroll-screenshots: 1. A --deck-mode flag that navigates via keyboard and captures per-state — or a documented pre-seed-screenshots workaround — would help.

Happy to open either as a separate issue if useful.

Test plan

  • Render the diff locally and confirm markdown formatting is consistent with the rest of step-6-build.md (code fences, hyphen bullets, em-dashes).
  • Optional: re-run the website-to-hyperframes skill against a test site with these rules present vs. absent — the "ambient pulse doesn't scrub" failure is the easiest to reproduce.

Add five animation-authoring rules to step-6-build.md that the linter
cannot catch but that silently ship broken output. Surfaced from two
independent builds (2026-04-20) where compositions passed lint and
still rendered elements invisible / unscrubbing.

Rules added:
- No iframes for captured content (don't scrub)
- Never stack transform tweens on one element (entrance + Ken Burns
  on same img silently kills it)
- Prefer tl.fromTo() over tl.from() inside .clip scenes
  (immediateRender interacts badly with scene boundaries)
- Ambient pulses must attach to seekable tl, not standalone gsap.to()
- Generalize the caption hard-kill rule to every scene-boundary exit
@jrusso1020 jrusso1020 requested a review from ukimsanov April 21, 2026 17:53
Copy link
Copy Markdown
Collaborator

@ukimsanov ukimsanov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice contribution — these rules address real failure modes that pass lint but break renders. Tested thoroughly, approving.

What we tested

A/B test — ran the full website-to-hyperframes pipeline on 2 sites (HeyGen, Railway), once without these rules and once with them. Compared the compositions:

Metric HeyGen WITHOUT rules HeyGen WITH rules
tl.from() 24 0
tl.fromTo() 4 54

Rule 3 had clear measurable impact — the agent switched entirely to fromTo() when the rules were present. That alone justifies the PR.

We also checked our existing Stripe compositions (built without these rules) — found 86 exit animations with only 1 tl.set() hard-kill, and black frame gaps at every beat boundary in the rendered snapshots. Rule 5 addresses exactly this.

Test plan

  • Markdown formatting consistent with rest of step-6-build.md (code fences, bullets, em-dashes)
  • A/B test: full skill runs with/without rules on 2 sites
  • Scanned 40+ existing compositions from prior regression tests for rule violations

One question on Rule 2

We tried to reproduce the "hero enters then vanishes" scenario with tl.from(".hero", {y: 50, opacity: 0}, 0) + tl.to(".hero", {scale: 1.04, duration: 3}, 0) on a 4s composition. The hero did vanish — but at 3s, exactly when the to() tween ended. When we matched the tween duration to data-duration (changed to 4s), it stayed visible the whole time.

Did your compositions have timeline duration matching data-duration? It might be that from() resets to its initial state when seeked past the timeline end, rather than the transform stacking itself. The workarounds you suggest are still great practice — just curious if the root cause is more about duration mismatch + from() reset.

Note on Rule 5

In our A/B test, the agent didn't add tl.set() hard-kills even with the rules present (0 in both runs). The rule is correct — we see the bug in existing renders — but agents might need a stronger signal. A linter rule for this could be more reliable than documentation alone. Worth tracking as a follow-up.

Related observations

Both are worth separate issues — the linter heuristic inconsistency and the thin capture on deck-style SPAs are real gaps we've seen too.

LGTM 👍

@ukimsanov
Copy link
Copy Markdown
Collaborator

@ibrews Format check failed — looks like the markdown needs a formatting pass. Can you run bunx oxfmt skills/website-to-hyperframes/references/step-6-build.md and push? That should fix it. And again, thanks for contribution!

@ibrews
Copy link
Copy Markdown
Contributor Author

ibrews commented Apr 24, 2026

Thanks for the thorough A/B test — the tl.from()tl.fromTo() flip being total (24 → 0) is more decisive than I expected. Good signal that Rule 3 is load-bearing and not just defensive.

On Rule 2 / the duration-mismatch hypothesis:

That's a plausible root cause. In our case the timeline durations were matching data-duration, but I can't rule out that the sub-agent introduced a subtle mismatch upstream — these were auto-generated compositions and we didn't audit every tween duration before observing the vanish. Your hypothesis (from() resets to initial state when seeked past timeline end) is the more precise framing, and worth noting in the rule itself. I'll update the wording to mention that as the likely mechanism so the rule has sharper rationale, not just a "don't do this."

On Rule 5 / agents ignoring the hard-kill even with the rule present:

Agreed — documentation alone isn't reliable enough for this one. A linter rule is the right floor. I'll open a separate issue for it so it doesn't get lost.

…as root cause

Per review feedback on PR heygen-com#364: the 'hero vanishes' failure has two
mechanisms. Primary: second tween's immediateRender overwrites the first
at construction time. Secondary: tl.from() resets to its declared from-
state when seeked past timeline end, which the capture engine triggers.
Both are now named so the rule has precise rationale, not just a pattern
to avoid.

Ref: heygen-com#364 (review)
@ukimsanov
Copy link
Copy Markdown
Collaborator

Almost there — just the format check left. Can you run this and push?

bunx oxfmt skills/website-to-hyperframes/references/step-6-build.md
git add -A && git commit -m 'style: format' && git push

I'll merge once CI goes green. Thanks for the quick Rule 2 update btw — the dual-mechanism framing is much better.

@ibrews
Copy link
Copy Markdown
Contributor Author

ibrews commented Apr 24, 2026

Done — formatted and pushed. CI should be green.

@ukimsanov ukimsanov merged commit 309c74e into heygen-com:main Apr 24, 2026
32 checks passed
jrusso1020 pushed a commit to openai/plugins that referenced this pull request Apr 27, 2026
Pulls all skill content updates from heygen-com/hyperframes that have
landed since the last openai/plugins sync. Net effect: the plugin's
skills/ directory matches the source-of-truth heygen-com/hyperframes
skills/ directory at the head of #504.

Source PRs:
- heygen-com/hyperframes#480 (Visual Inspect command)
  * skills/hyperframes/SKILL.md: adds Visual Inspect section + checklist
  * skills/hyperframes-cli/SKILL.md: adds Visual Inspect section, updates
    workflow ordering and description
- heygen-com/hyperframes#364 + #490 (GSAP authoring rules + data-duration)
  * skills/website-to-hyperframes/references/step-6-build.md: adds
    Load-bearing rules for animation authoring section, refines
    data-duration phrasing
- heygen-com/hyperframes#504 (preview handoff URL)
  * skills/hyperframes-cli/SKILL.md: adds Studio handoff URL guidance
  * skills/website-to-hyperframes/SKILL.md: Step 7 deliverable + gate
  * skills/website-to-hyperframes/references/step-7-validate.md: adds
    Handoff URL section

The agents/openai.yaml manifests added by #189 are
preserved as-is; they are openai-plugins-specific and have no upstream
counterpart in heygen-com/hyperframes.
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.

2 participants