Skip to content

fix(cli): check skips a contrast sample measured over a layer that is mid-motion - #7205

Merged
miguel-heygen merged 3 commits into
mainfrom
fix/contrast-skips-sample-over-moving-backdrop
Nov 5, 2026
Merged

miguel-heygen merged 3 commits into
mainfrom
fix/contrast-skips-sample-over-moving-backdrop

Conversation

@miguel-heygen

Copy link
Copy Markdown
Collaborator

What

When a host asks check to skip contrast samples taken mid-motion (the check-review capability's skipContrastInMotion, which Desktop's agent sets), the motion read now also counts two cases it missed:

  1. A layer that overlaps the text and is itself entering or leaving (a light backdrop fading in under still text).
  2. A sub-composition's own timeline. It is registered paused and seeked together with the root, and the reader treated any paused nested timeline as stopped, so a fade inside a sub-composition was never seen as motion.

Why

check --json could flag text at a sample where the light card behind it was still fading in (1.25:1 on dark text over a half-faded card), although every later sample passes. The skip already covered motion on the text and its ancestors, but not the layer the text is measured against, and it never saw motion inside a sub-composition.

Related work

Follows the skip added in the contrast motion policy (contrastMotion.ts); no open PR touches it.

How

  • readContrastMotion: a nested timeline counts as running when it is registered (window.__timelines) and its clock is where its parent's puts it. An unregistered timeline that was paused stays stopped, as the existing test requires.
  • sampleOf: tweens on an element whose box overlaps the text's box are added to the text's tweens, so measuredInMotion sees a moving backdrop. An element that is an ancestor or a descendant of the text is skipped, as before, and a layer beside the text does not count.

Test plan

  • Unit tests added/updated
  • Manual testing performed
  • Documentation updated (if applicable)
  • Comments follow CONTRIBUTING.md "Comments"

fails without the fix: packages/cli/src/utils/contrastMotion.test.ts › a layer under the text › counts as motion while it fades in under still text, and not once it has settled; and › a sub-composition's timeline › is moving when it is registered paused and seeked with the root, and stopped when its clock is elsewhere. With the source change reverted both fail (2 failed, 20 pass); with it all 22 pass, including the existing paused-nested-timeline and ambient-motion cases.

Ran for real (Linux, check --json with the check-review capability on, a dark page with a light sub-composition card whose background fades in from 1 s over 1.5 s, text still): hyperframes 0.9.13 flags contrast_aa_failure 1.25:1 at 1.111 s; the CLI built from this branch reports no contrast finding, and the later samples still pass. Not run: a Desktop app session; without the capability set, check still checks every sample as before.

Size

A lone small fix in one module with its tests; nothing open to fold it into.

@github-actions

github-actions Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Edit accuracy: accurate 267 (base branch 267), smooth 110 of those

The gate passes.
Smoothness is reported in the artifact, not gated. A case fails only if it fails 2 of 3 runs.

Quarantined, measured but not gated (8)

Unstable (1)

  • seqrepeat-none-center-r0-nested-z100: tracking 0.05, pressJump 0, drop 0, reload 0, render 0.02, renderKey -, undo true, teleport true / tracking 0.05, pressJump 0, drop 0, reload 0, render 0.02, renderKey -, undo true, teleport true / tracking 0.05, pressJump 0, drop 0, reload 0, render 0.02, renderKey -, undo true, teleport true

@heygengenesis heygengenesis Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Verdict: APPROVE · 0 blocker · 0 important · 1 nit · head aa007e39

I confirmed the fails without the fix: claim. With contrastMotion.ts reverted to its source on main, the two named tests fail (2 failed, 20 passed). At this head, all 22 pass.

  • nit (contrastMotion.ts:99): tweensUnder counts any overlapping tweened element, so a layer above the text counts too, not only one under it. That is arguably right, since an overlay moving across the text also changes the measured pixels. The name and the comment on measuredInMotion say "under", though.
Scope
  • Read contrastMotion.ts end to end and the new tests.
  • seekedWithParent compares time() with parent.time() - startTime(). A nested timeline with timeScale ≠ 1 or repeats will not match, so it falls back to the old "paused means stopped" result. That is conservative: the sample still gets checked.
  • tweensUnder uses only closure locals, so readContrastMotion still works when serialized into the page.
  • The skip still applies only when the host opts in with skipContrastInMotion.
  • CI: every Actions run at this head finished with success.

@miguel-heygen
miguel-heygen added this pull request to the merge queue Nov 5, 2026
Merged via the queue into main with commit 7862e27 Nov 5, 2026
84 checks passed
@miguel-heygen
miguel-heygen deleted the fix/contrast-skips-sample-over-moving-backdrop branch November 5, 2026 04:33
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.

1 participant