The U-final CSS-cascade correction picked the earliest IMPLICIT tween per
(target, property), so an implicit fade tween stacked after an explicit
burst tween (e.g. apple-money-count's confetti: translateY [0, y] then an
implicit drift-and-fade) was rewritten to the page-load identity value,
discarding anime.js's correct chain from the burst's end value and
collapsing the burst mid-flight (22.0dB at t=4.42s vs the GSAP baseline).
Correct an implicit tween only when it is the first tween of ANY kind on
its (target, property) pair. Verified by full re-render: apple-money-count
t=4.42s recovers to 50.0dB and vfx-liquid-glass's t=0.2-0.6s low band
recovers to 45.1dB; the product-promo stacked-implicit case that motivated
the earliest-implicit narrowing keeps its behavior (unit-tested).
primeAnimeInstance() seeks every registered timeline to its full
duration and back to 0 at registration time, purely to force
lazily-rendered CSS onto elements before the composition's first real
seek. anime.js fires tl.call()/onComplete callbacks as a side effect
of any seek that crosses their position, including this internal
priming seek -- so a composition using tl.call() (or a raw onComplete
tween) renders as if it had already reached that callback's end state
from frame 0 onward, regardless of the time actually requested later.
anime.js's own Timer.prototype.seek(time, muteCallbacks) already
suppresses onBegin/onComplete/onUpdate/onLoop/onRender for a given
seek; pass true on both priming seeks instead of reimplementing that
suppression. Widened RuntimeAnimeInstance.seek to accept the optional
second argument.
Confirmed against liquid-glass-widgets (a tl.call() status-text swap
scheduled at t=5.2s was rendering already-fired at t=1.434s) and
flowchart/flowchart-vertical (their addTimelineCall()/onComplete
helper had the whole diagram, cursor, and text-correction sequence
visible from frame 0). Added a regression test exercising the same
tl.call()-during-priming scenario directly against the adapter.
The engine's browserManager launches render Chrome with
--enable-features=CanvasDrawElement globally, but the snapshot command's own
puppeteer launch omitted it, so any composition using ctx.drawElementImage()
(vfx-shatter, vfx-portal, ios26-liquid-glass, ...) showed its 'API required'
placeholder in snapshots while rendering fine in the real pipeline.
<hyperframes-player> couldn't play a raw anime.js composition from src: the
inline hyperframesAnime.register() call throws before the runtime exists,
and the existing late-injection fallback only fires for GSAP's __timelines
and appends the runtime after parse, which is too late for a call that
already ran.
Add a runtime-src attribute to override the runtime URL, and a pre-parse
path in the probe: once it observes a same-origin composition that
references hyperframesAnime but never got a runtime/timeline/registry,
fetch the original src, inject the runtime script + a <base> tag ahead of
the composition's own scripts, and reload via srcdoc (once per src).
shouldInjectRuntime now also refuses to late-inject once an anime registry
is present, since that path can never help these compositions.
The prior implicit-transform-from fix corrected EVERY implicit-from tween
matching a decomposed transform property, not just the first one on a given
target. Confirmed via a real render + PSNR A/B test against the commit
before that fix: registry/examples/product-promo's nested
scene1-logo-intro.html animates each Figma logo piece with two tweens on
the same target+property (an instant duration:0 jump off-screen, then an
implicit-from "converge to center" tween) -- the second tween correctly
inherits its "from" from the first tween's just-set value inside anime.js,
but the prior fix overwrote it with the element's true CSS/page-load
identity value instead, making the piece appear already-assembled at the
center from frame 0. This regressed product-promo's PSNR from ~50.5dB to
~47.0dB.
Only apply the CSS-cascade correction to the tween with the smallest
`_absoluteStartTime` for each distinct (target, property) pair; every later
implicit-from tween on that same pair is left untouched, since anime.js
already chains it correctly from the preceding tween. Verified: the
product-promo regression is resolved (51.2dB, back above baseline) while
flowchart/kinetic-type retain their original fix's improvement.
anime.js v4.5.0 cannot correctly resolve a literal "none" clip-path target
when an earlier tween on the same element used a structured circle()
shape. Confirmed via internal tween inspection: it silently re-templates
"none" as a zeroed copy of the preceding circle()'s numeric parameters
(`_toNumbers` become `[0, 0, 0]`, reusing the prior string template),
rendering as `circle(0% at 0% 0%)` -- a near-fully-clipped, effectively
black/empty box -- instead of the intended fully-revealed state. This is
baked in at `.add()`-call time, before any seek ever runs, so it cannot be
fixed by mutating tween fields afterward.
Patch every timeline's `.add()` to rewrite a literal `clipPath: "none"`
into an equivalent fully-open `circle(150% at X% Y%)` (same shape function
anime already interpolates correctly, radius large enough to cover any
aspect ratio) whenever a tracked preceding value for that element was a
circle() shape. Leaves "none" untouched when there is no preceding
structured shape to conflict with (anime resolves that case correctly on
its own), and leaves every other property/shape untouched.
Verified against registry/blocks/transitions-light and
registry/blocks/transitions-radial (the confirmed "Family B" black/hidden
content defect from the U-final adjudication) with real render+compare-item
PSNR improvement; no regression on the other Family B members
(vfx-portal/vfx-shatter/play-mode), which do not use this clip-path pattern
and remain open with a different, not-yet-identified root cause.
anime.js v4.5.0 does not read an element's CSS-cascaded `transform` value
when resolving an implicit "from" for decomposed transform sub-properties
(translateX/Y, scale, scaleX/Y, rotate) authored without an explicit
`[from, to]` array. It silently defaults to identity instead, so any
element relying on a stylesheet rule like
`.node { transform: translate(-50%, -50%) scale(0); }` to start hidden
renders at (or near) its final position/scale from the very first frame.
Confirmed via isolated puppeteer reproduction: this is baked into anime's
internal tween node (`_fromNumber`) at `.add()` time, independent of seek
order or priming.
Correct each affected tween's `_fromNumber`/`_number` with the true
CSS-cascaded value (decomposed via DOMMatrixReadOnly) before the instance's
first-ever seek, in the existing per-instance priming pass. Verified
against registry/blocks/flowchart and registry/examples/kinetic-type
(same "content resolves ahead of schedule" defect family from the U-final
adjudication) with real render+compare-item PSNR improvement.
The anime.js adapter registers every composition and sub-composition as a
flat entry in window.__hfAnime and seeked ALL of them with the same raw
global time. Unlike GSAP (where sub-composition timelines are nested inside
a parent timeline and inherit correct local time from that nesting), each
anime.js registration needs its own composition's cumulative start time
subtracted before seeking, or a sub-composition mounted at a nonzero
data-start renders far ahead of where it should be (confirmed: at global
t=1.6s with data-start=1.5s, the child received seek(1600ms) instead of the
correct seek(100ms)).
Resolve each registration's host via data-composition-id, subtract its
cumulative start (reusing the existing runtime start-time resolver), and
clamp to the authored/host duration before seeking.
Verified: registry/examples/product-promo average PSNR vs GSAP baseline
20.5dB -> wait, 33.3dB -> 50.5dB (damaged checkpoints 77 -> 32) with no
regressions in the other items spot-checked. Full regression suite passes
(82 files, 1168 tests) plus a new red/green test for the nested-seek case.
The studio browser bundle imported the parsers and core package roots
(via classifyAnimationRuntime and resolveEase), dragging in node-only
assetPaths through the barrel under vite's import condition. Adds
animation-runtime-detection and ease-map subpath exports and points the
browser imports at them; repo-wide oxfmt pass for files agents missed.
Round 2: forEach over named const arrays, direct window.__timelines
member registration, no-op .call labels. Fixes four silent-data-loss
bugs: mutated arrays trusted at initial literal, helper declarations
dropped without expansion, bare reassignment and loop counters treated
as constants (which also overcounted round-1 conversions; 9 files
honestly demoted to manual). Final: 28 converted / 2 warnings / 117
manual. Shared finitePositiveNumber extracted in eval scripts.
Gate re-run passes 8/8. Duck-typed anime wrappers lacking a readable
duration now prime via a large clamped seek. The animejs-adapter fixture
had two registered timelines animating scale on the same element, which
is order-dependent under independent seeks; corrected per the new
one-property-owner contract rule recorded in U3-GATE-RESULT.md. Eval
scripts get the scripts-convention fallow suppressions.
Anime.js 4.5.0 timeline children added at position greater than zero do
not render their from-values until the timeline has been sought past
that position once; a cold seek to frame 0 left them untouched. Prime
each instance once at registration (seek to duration, back to 0),
mirroring the GSAP adapter's totalTime nudge. Covers keyed and legacy
array registrations. Found by the U3 determinism gate.
Allow timeline registry lint to accept hyperframesAnime.register() and __hfAnime.
Treat registered finite anime instances as duration sources.
Add anime.js registration, autoplay, and infinite-loop diagnostics.
Make the validate CDN-timeout hint engine-neutral.
Adds focused anime.js 4.5 determinism fixtures plus a Puppeteer gate that compiles and serves each fixture through the producer runtime path, then checks repeat seeks, out-of-order seeks, backward seeks, bounds behavior, and anime engine activity from DOM snapshots.
* fix(cli,skills): install workflow skills on demand instead of re-pulling the full set
Users report every init re-pulls all 21 skills into ~/.agents/skills
whenever anything is stale or missing - heavy, noisy, and it re-expands
deliberate partial installs.
Split the set into two tiers:
- core: the /hyperframes router + hyperframes-* domain skills +
media-use, which every workflow references structurally. init and
bare 'skills update' keep these (plus anything already installed)
fresh, and never expand the install.
- on demand: the end-user workflow skills (and figma). They install at
trigger time via 'skills update <name...>' - positional names are
the only way update expands an install: one targeted
'skills add --skill <name>' covering only stale/missing targets, a
fast no-op when current, presence-verified after install, exit 1 on
unknown names, and a presence-only degrade when GitHub is
unreachable.
The /hyperframes router now runs 'skills update <workflow>' after
routing and before reading the workflow skill, so a routed workflow is
guaranteed present even on a machine that only has the core set. Each
on-demand skill also opens with the same self-maintenance step (run
'npx hyperframes skills update <name>' silently), so a workflow
triggered directly - without the router - still refreshes itself and
restores any missing core skill before relying on it.
When the manifest is unreachable (offline / rate-limited) the engine
degrades honestly instead of claiming success: named runs presence-check
the request plus a pinned fallback core list (unit-pinned to skills/)
and blind-install whatever is absent; a bare strict update fails loudly
so the 'check || update' chain can't pass while everything stays stale;
init reports the skipped freshness check. --json emits structured
errors on failure paths.
skills check still lists every skill, but exits non-zero only for
stale installed skills, an incomplete core set, or removed leftovers -
workflow skills not yet installed are reported as available on demand.
Bare 'hyperframes skills' (and 'skills add --all') remain the explicit
full-set installs.
Verified end-to-end with a sandboxed $HOME: fresh init installs the 9
core skills only; 'skills update slideshow' adds exactly that skill
(no-op on re-run, exit 1 on unknown names); bare update refreshes
without expanding; a live Claude Code run routed PR-to-video, executed
the router's update step, and the workflow skill appeared before use;
and a second live run triggered an installed workflow directly, whose
opening maintenance step restored a deliberately removed core skill.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(skills): clarify update-engine contracts + document lazy-install model
- skills.ts: note the UpdateSkillsResult.unknown strict-mode contract,
verifyInstalled's non-strict (warn-not-throw) intent, and that a
partial install stays "refreshed but never expanded" (review nits).
- docs/guides/skills.mdx: add a "Keeping skills current" section covering
the core-eager / workflow-on-demand model and the skills check|update
commands, per the repo's catalog-maintenance rule.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: kiritowoo <295860553+kiritowoo@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Miao Yang <miao.yang@heygen.com>
- slugify: replace the anchored alternated trim regex (/^-+|-+$/g) with a
character-scan trim — CodeQL js/polynomial-redos blocker.
- readRenderOverrides: fold the readOverrides wrapper into the exported
function (one name, no pass-through).
- getVariables: deduplicate declarers with a Set, matching
injectCompositionCssVariables.
- Move the tokenSlug import to the top of the file.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Live testing of the compile-time variable emission surfaced four gaps:
- The producer render path never emitted the compile-time stylesheet (only
the preview bundler did), so eval-time reads — GSAP .from immediateRender,
top-level getComputedStyle — saw undefined vars in rendered output. The
producer's inlineSubCompositions now calls the shared
emitRootCompositionVariableStyles and passes the variable hooks.
- --variables overrides weren't visible at eval time. They now thread from
the orchestrator / distributed plan through compileStage into the emitted
rules (window.__hfVariables still covers script reads).
- Per-declarer rules anchored on data-composition-id, which two inlined
instances of one sub-composition share — instance A's rule restyled
instance B, and a rule directly on the declarer defeated the host's
inherited data-variable-values. Rules now anchor on per-instance
data-hf-var-scope markers and layer nearest-host values over declared
defaults, mirroring the runtime loader.
- Emission ignored authored CSS; a declared default now yields to a var
already defined in an authored <style> block (define-if-absent, matching
the runtime injection).
Also: the figma importer emits background-color (longhand) for solid fills.
GSAP backgroundColor tweens cannot read a var() through the background
shorthand — its pending-substitution longhands serialize empty, so .from
captured nothing and settled on transparent (pre-existing GSAP interaction,
reproduced with no composition variables involved).
Validated live: eval-time default + override, .from + override, two-instance
host branding, authored :root precedence, SDS brand-loop pixel parity.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>