You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs(skills): correct composition-contract claims the code contradicts
The runtime has absorbed a series of authoring mistakes over time (array
`__timelines`, a missing root `data-start`, a mistyped timeline key, unnested
child timelines), and `init.ts` says so in its own comments. The skills kept
teaching the old rules, so agents were told to satisfy constraints that no
longer exist and, in four cases, were told to do things the gates now reject.
Four instructions actively cost an agent a failing run:
- `variables-and-media.md` said to add `crossorigin="anonymous"` for canvas
capture. `media_crossorigin_breaks_preview` rejects it unconditionally, with
no suppression, even for the readback case its own fixHint mentions.
- `determinism-rules.md` banned building a timeline inside an async callback.
`gsap_timeline_registered_before_async_build` calls that build the documented
contract; what it rejects is registering the key before the build finishes.
- `determinism-rules.md` banned `gsap.set()` on later-scene clips as "not in the
DOM at page load". They are in the DOM, and two lint fixHints instruct exactly
that set, so following the rule left two findings unfixable.
- `creator-editing-recipes.md` shipped 12 copyable `<audio>`/`<video>` snippets
with no `id`. `media_missing_id` is an error, and an id-less `<audio>` is never
picked up by the mixer, so a copied recipe renders silent.
Corrected elsewhere, in every place each claim appeared:
- `data-track-index` is a Studio display lane, not a timing constraint. The
render never reads it, no rule forbids same-track overlap, and the allocation
ritual around it is removed. 11 sites.
- `class="clip"` is a layout and tooling convention. Visibility keys on
`data-start`. The class stays recommended, because the shared `.clip` rule is
what gives a scene its box.
- Timed elements may nest. The "must be DIRECT children of the root" rule was
false; what is true is that only root children with `data-start` get automatic
layout, which is why the `full-screen-motion.md` background snippet rendered
0px tall. That snippet now carries the CSS it always needed.
- The visibility window is half-open, `[start, start + duration)`, pinned by
`init.test.ts`. It was documented as inclusive, so an animation landing exactly
on `data-duration` never rendered its last frame.
- The relative-start grammar has four silent failure modes, all resolving to 0,
none checked by lint. `intro-0.5` without spaces parses as an id, references
resolve document-wide, cycles resolve to 0, and an unresolvable target duration
lands on the target's start rather than its end.
- Sub-composition host `data-width`/`data-height` are backfilled by the compiler,
and a host id that differs from the file's is supported. Both were "Required".
- The root background is stripped only on the layered-composite path (HDR content
or shader transitions), not on every render.
- Sub-composition Pitfall 3 kept a symptom fixed by #1886 and pinned by
`packages/producer/tests/sub-comp-class-selector/`. The lint rule is still the
reason to use `#root`; the render is not.
- Cross-file duplicate media ids no longer render blank for `src`-bearing media,
which the compiler stamps with `data-hf-render-id`. Media using `<source>`
children is not stamped, so that case is called out instead.
The three workflow `assemble-index.mjs` and `transitions.mjs` copies attributed
their same-track lane rule to `timeline_track_too_dense`, which counts elements
per lane and never compares windows. The checks are kept, since each workflow's
frame injector assumes one clip per lane, but they now say it is their own
convention rather than a framework rule. The scaffolded project instructions
written into every `hyperframes init` project carried the same two false lines
and are corrected in lockstep.
`coreSkillContent.test.ts` pinned the literal sentence that listed root
`data-start` as required, so every correction here would have reddened CI to
protect a false claim. It now pins structure plus the regression it genuinely
catches.
Copy file name to clipboardExpand all lines: packages/cli/src/templates/_shared/AGENTS.md
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -88,8 +88,8 @@ Fix all errors before presenting the result. Warnings should be reviewed before
88
88
89
89
## Key Rules
90
90
91
-
1. Every timed element needs `data-start`, `data-duration`, and `data-track-index`
92
-
2.Elements with timing **MUST** have `class="clip"` — the framework uses this for visibility control
91
+
1. Every timed element needs `data-start` and a duration. `data-start` is what marks it as timed; `data-track-index` is an optional Studio display lane the render never reads
92
+
2.Give timed visual elements `class="clip"`. The framework keys visibility off `data-start`, not the class, but the shared `.clip` CSS is what gives a scene its full-frame box, and `lint` warns without it
93
93
3. Timelines must be paused and registered on `window.__timelines`:
Copy file name to clipboardExpand all lines: packages/cli/src/templates/_shared/CLAUDE.md
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -88,8 +88,8 @@ Fix all errors before presenting the result. Warnings should be reviewed before
88
88
89
89
## Key Rules
90
90
91
-
1. Every timed element needs `data-start`, `data-duration`, and `data-track-index`
92
-
2.Elements with timing **MUST** have `class="clip"` — the framework uses this for visibility control
91
+
1. Every timed element needs `data-start` and a duration. `data-start` is what marks it as timed; `data-track-index` is an optional Studio display lane the render never reads
92
+
2.Give timed visual elements `class="clip"`. The framework keys visibility off `data-start`, not the class, but the shared `.clip` CSS is what gives a scene its full-frame box, and `lint` warns without it
93
93
3. Timelines must be paused and registered on `window.__timelines`:
Copy file name to clipboardExpand all lines: skills/faceless-explainer/scripts/assemble-index.mjs
+5-2Lines changed: 5 additions & 2 deletions
Original file line number
Diff line number
Diff line change
@@ -12,7 +12,10 @@
12
12
// document order. Transitions are NOT written here — the transitions injector
13
13
// mutates this file afterward (data-start/duration/track-index + GSAP).
14
14
//
15
-
// Track lanes (same-track time-overlap is illegal — lint timeline_track_too_dense):
15
+
// Track lanes. Same-track time-overlap is this workflow's own assembly convention,
16
+
// not a framework rule: the render never reads data-track-index, and no lint rule
17
+
// checks overlap (timeline_track_too_dense counts elements per lane for readability).
18
+
// The convention exists because the frame injector below ping-pongs 0/1 for overlaps:
16
19
// 1 frame sub-comp clips (sequential; the injector 0/1-ping-pongs for overlaps)
17
20
// 2 captions sub-comp clip (full-duration overlay, on top of frames)
18
21
// 10 per-frame voice <audio>
@@ -215,7 +218,7 @@ function guardFrame(html, label) {
215
218
for(leti=1;i<list.length;i++){
216
219
if(list[i].start<list[i-1].end-EPS){
217
220
errors.push(
218
-
`${label}: clips on track ${track} overlap (one ends at ${r3(list[i-1].end)}s, the next starts at ${r3(list[i].start)}s) — same-track time-overlap causes a render conflict. Put them on distinct data-track-index lanes or fix their windows.`,
221
+
`${label}: clips on track ${track} overlap (one ends at ${r3(list[i-1].end)}s, the next starts at ${r3(list[i].start)}s). This workflow's injector assumes one clip per lane at a time. The render itself tolerates the overlap; put them on distinct data-track-index lanes or fix their windows.`,
Copy file name to clipboardExpand all lines: skills/hyperframes-animation/adapters/gsap.md
+3-3Lines changed: 3 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -27,7 +27,7 @@ HyperFrames controls GSAP through its `gsap` runtime adapter. Create a paused ti
27
27
- The registry key must match the composition root's `data-composition-id`.
28
28
- Bracket and dot syntax both register: `window.__timelines["main"] = tl` and `window.__timelines.main = tl` are equivalent (the linter recognizes both). Bracket form is required when the id isn't a valid identifier (e.g. contains `-`).
29
29
- Do not call `tl.play()` for render-critical motion.
30
-
-Do not build timelines inside async code, timers, or event handlers.
30
+
-Building inside an async callback such as `document.fonts.ready` is supported and common. What breaks is **registering the key before the build finishes**: an empty timeline registered early is treated as ready and nested empty, so it renders blank (`lint`: `gsap_timeline_registered_before_async_build`, error). Assign `window.__timelines[id] = tl` at the end of the callback. Do not drive render-critical motion from timers or event handlers.
31
31
- Keep loops finite. HyperFrames renders finite video durations.
32
32
-**Render duration comes from `data-duration` on the composition root, not from GSAP timeline length.** Do not pad the timeline with empty tweens like `tl.set({}, {}, 283)` to "extend" it. (Some external docs show this trick; in HyperFrames it conflicts with the seek-driven duration model — set `data-duration` instead.)
33
33
@@ -70,10 +70,10 @@ HyperFrames is stricter than vanilla GSAP. Animate only:
70
70
71
71
**Forbidden** (breaks the renderer or the clip lifecycle):
72
72
73
-
-`display`, raw `visibility`— never duration-tween these directly. Use `autoAlpha` (opacity plus endpoint visibility) or a zero-duration timeline set at an explicit boundary, and only on a non-clip element or wrapper inside a clip. Never target `.clip` itself.
73
+
-`display`, raw `visibility`**on a clip element**: never duration-tween these. HyperFrames owns a clip's visibility and `lint` rejects it. Use `autoAlpha` (opacity plus endpoint visibility) or a zero-duration timeline set at an explicit boundary. Animating a clip element's other visual properties is fine and the shipped catalog does it throughout; what is forbidden is taking over its visibility.
74
74
- Anything driven by `Math.random()`, `Date.now()`, `performance.now()`, or event handlers — animation state must be deterministic from time alone.
75
75
76
-
> **Note**: `docs/guides/gsap-animation.mdx` lists `width`/`height`/`visibility` in its "Supported Properties" — that list is too permissive for HyperFrames composition rules. This allowlist is the canonical one. See `hyperframes-core/references/determinism-rules.md` for the full deterministic-render contract.
76
+
> **Note**: the list above is a **denylist**, not an allowlist. Properties outside it, including `width`, `height`, `filter`, `clipPath` and `strokeDashoffset`, are legitimate targets; prefer transforms and opacity where you have the choice, for performance rather than correctness. See `hyperframes-core/references/determinism-rules.md` for the full deterministic-render contract.
0 commit comments