docs(website-to-hyperframes): add load-bearing GSAP authoring rules#364
Conversation
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
ukimsanov
left a comment
There was a problem hiding this comment.
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 👍
|
@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! |
|
Thanks for the thorough A/B test — the On Rule 2 / the duration-mismatch hypothesis: That's a plausible root cause. In our case the timeline durations were matching 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)
|
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 pushI'll merge once CI goes green. Thanks for the quick Rule 2 update btw — the dual-mechanism framing is much better. |
|
Done — formatted and pushed. CI should be green. |
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.
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 sourceBoth 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:
y-entrance +scaleKen Burns tweens on one element)gsap.to()that didn't scrub)gsap.from()immediateRenderinteracting with.clipboundaries)Rules added
immediateRenderresets the first, element ends up invisible. With before/after code showing two fixes (combine into onefromTo, or split across parent/child wrappers).tl.fromTo()overtl.from()inside.clipscenes —from()'s defaultimmediateRender: truewrites state before the scene'sdata-startis active and misbehaves under non-linear seeking.tl, never baregsap.to()— standalone tweens run on wallclock, don't scrub, absent from rendered output.tl.set()kill after its fade, not just captions.Rules already present in the file (
repeat: -1,data-start/data-durationon 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:
overlapping_gsap_tweenson some builds but not others. Stabilizing this heuristic would remove a source of sub-agent confusion.step-1-capture.mdthin output on canvas-driven / deck-style sources. Single-page apps that navigate via keyboard or internal state producesections: 0, CTAs: 0, scroll-screenshots: 1. A--deck-modeflag 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
step-6-build.md(code fences, hyphen bullets, em-dashes).