Skip to content

Two animations on the same property compose multiplicatively, but the docs promise last-wins #322

Description

@LeadcodeDev

ExtractedEffects::keyframe_animations documents that the last effect in the array wins on a shared property. That holds inside a single resolve_animations call. Across buckets it does not: resolve_props_for_effects folds each bucket through AnimatedProperties::merge, which multiplies opacity and scale and adds translate and rotation. merge's own doc says "override".

There is a second edge inside it. A bucket that resolves a property to exactly its neutral value — opacity 1.0, scale 1.0 — is a no-op, because merge guards on a difference from neutral. So an animation can never take a property back from an earlier effect that moved it away from neutral.

This is not obviously a bug. Two presets on the same property often should compose: a pulse layered on a fade_in wants the product, not the second one winning. The defect is that the code does one thing and says the other, so an author reading the documented rule gets a result it does not predict, and neither behaviour is stated anywhere an author would look.

What needs deciding is which contract the engine offers, and then saying it in one place:

  • compose, and document that effects on a shared property multiply or add by kind; or
  • last-wins across buckets, and give authors an explicit way to ask for composition.

The first keeps every existing scenario rendering as it does. The second matches what the code already claims and what an author coming from CSS animations expects.

Found during the audit's timing pass on chantier/audit-2026-09-25, which deliberately did not change the semantics: that is a product call, not a remediation one.

Refs #315

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

questionFurther information is requested

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions