Skip to content

feat(cli): opt-in check flag that leaves out contrast measured mid-animation - #6396

Merged
xuanruli merged 4 commits into
mainfrom
feat/check-skip-contrast-in-motion
Oct 26, 2026
Merged

xuanruli merged 4 commits into
mainfrom
feat/check-skip-contrast-in-motion

Conversation

@xuanruli

@xuanruli xuanruli commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

What

An opt-in switch for hyperframes check: --skip-contrast-in-motion, or HYPERFRAMES_CHECK_SKIP_CONTRAST_IN_MOTION
(read with the existing envFlagEnabled). When on, a contrast failure measured while its text is mid-animation is
not reported. With neither set, check's output is unchanged.

"Mid-animation" means, at the contrast sample time:

  • a non-ambient GSAP tween targeting the text or an ancestor is active, or
  • the text's cumulative opacity (product up the ancestor chain) is below 1.

A tween is ambient, and never counts as active motion, if it repeats, belongs to an infinitely repeating timeline,
or one iteration lasts 4 s or more (e.g. a slow camera push). The independent opacity rule still applies, including
deliberately translucent targeted text at rest.
Text no GSAP tween targets, and pages without GSAP, are never dropped. Only the contrast section is affected.

Why

Contrast is measured at one instant, so a label fading or sliding in fails at partial opacity and then reads fine
once settled. In a review of real Desktop check reports these findings were all judged not worth fixing, and they
carry no persistence measure. Harnesses that show check output to an agent want to leave them out; CLI users keep
today's behaviour by default, so this is opt-in.

Related work

None.

How

  • checkBrowser.ts: when the switch is on, right after the existing contrast prepare step and at the same seek,
    one extra in-page read collects the GSAP tweens on each candidate's element and ancestors, their activity on
    the current parent clocks (including nested scaling and repeat/yoyo), and cumulative opacity. No extra seek, render or screenshot. When off, the read never
    runs and the driver gets exactly the same arguments as before.
  • contrastMotion.ts: the pure rule (active non-ambient tween || cumulativeOpacity < 1) and the in-page reader.
  • Dropped entries never reach buildContrastResults, so counts, ok, the exit code and snapshot annotations leave
    them out. Severity logic is untouched. One consequence: a dropped mid-motion sample no longer counts toward "seen at
    two samples". A single retained failure becomes a warning when entries at multiple timestamps remain; if only
    one measured timestamp remains overall, the existing single-sample rule keeps full severity.

Test plan

  • Unit tests added/updated: contrastMotion.test.ts (rule and reader: mid-tween vs at rest, partial opacity,
    ambient and repeating tweens, nested timelines, no GSAP, untargeted text); checkBrowser.test.ts (the real in-page
    contrast script with a GSAP-shaped timeline, switch off/on); check.test.ts (flag, env 1, env 0, neither).
    Author validation: vitest run on the three files: 190/190 after rebasing onto main.
    Followthrough validation: 205/205 on those files; 3/3 in checkBrowser.chromium.test.ts with headless Chrome.
    Real-GSAP root-repeat and root-yoyo regressions failed on the original reader and pass with parent-clock sampling.
    Also covers nested timeScale/repeat/yoyo/repeat-delay, paused descendants, ambient thresholds, cumulative opacity,
    env/flag precedence, filtered checked counts and snapshot annotations. Three infinite-timeline pulse regressions
    also fail before the ambient fix and pass after it; retained severity/ok/exit behavior is explicitly covered. Initial workspace build, CLI typecheck,
    lint/format and comment checks pass.
  • Manual testing performed: a real-GSAP 3.14.2 fixture (fading card label dropped; fully opaque low contrast at rest, under a
    4 s camera drift, and unanimated all still reported as errors). On 9 real projects, check --json with the switch
    off is byte-identical to main for contrast; with the flag or the env var, the same 7 of 49 contrast findings are
    dropped and no finding changes severity. Other sections match apart from run-to-run variance that main itself shows
    (craft hold times, one project's layout frame offsets).
  • Documentation updated: the check flag table and a paragraph in docs/packages/cli.mdx, one line in
    skills/hyperframes-cli/references/lint-validate-inspect.md, regenerated skills-manifest.json.
  • Comments follow CONTRIBUTING.md "Comments".

Not covered: two checkSweep.chromium.test.ts WebGL tests failed locally because headless Chrome had no WebGL
context; they don't touch this change and were not re-run on main.

…imation

`hyperframes check --skip-contrast-in-motion`, or
HYPERFRAMES_CHECK_SKIP_CONTRAST_IN_MOTION=1, leaves out a contrast failure
measured while a GSAP tween on the text or an ancestor is running, or while
the text's cumulative opacity is below 1. Repeating tweens and tweens of 4 s
or longer (camera drifts) do not count as motion. Text no GSAP tween targets
is reported as before. With neither set, check is unchanged.

The motion state is read at the same contrast sample, with no extra seek or
screenshot.
@mintlify

mintlify Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated
hyperframes 🟢 Ready View Preview Oct 26, 2026, 5:59 AM

💡 Tip: Enable Automations to automatically generate PRs for you.

@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: REQUEST CHANGES · 1 blocker · 2 important · 0 nits

With the switch on, two policy gaps hide failures that persist at rest, and a retained at-rest error can drop to a warning. Details ↓ inline. Also: skills-manifest.json conflicts with main, and both CI runs for this head were cancelled, so there is no CI verdict yet.

Scope

Read all source changes end to end and the tests in full. Ran the 3 PR test files locally after a full build: 201/201 pass. Reproduced every finding with real GSAP 3.x in happy-dom, using the PR's own readContrastMotion and measuredInMotion.

Checks that came back clean:

  • Parent clocks: activeInRoot reads parent.time() at each level and recurses up to the root. Scaled, repeat, yoyo and repeat-delay parents all resolve correctly, and a child of a nested timeline that is outside its own window is not counted.
  • Candidate identity: __contrastAuditPrepare pushes out[i] and restores[i] together, and the reader runs before finish clears the restore list. Index alignment holds, and the selector+text join is unchanged.
  • Annotations and counts: dropped entries never reach buildContrastResults or contrastFailureAnnotations, so the counts and the overlay agree.
  • Switch off: the driver gets exactly the same arguments as before.

WebGL sweep tests: not run.

— Genesis (reviewing for @xuanruli)

Comment thread packages/cli/src/utils/checkBrowser.ts Resolved
@github-actions

Copy link
Copy Markdown
Contributor

Edit accuracy: accurate 271 (base branch 271), smooth 104 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 (0)

Unstable (2)

  • seqheldtwice-tlhold-px-r0-root-z100: tracking 120.29, pressJump 312.3, drop 0, reload 0, render 0.02, renderKey -, undo true, teleport false / tracking 0.41, pressJump 0.11, drop 0.35, reload 0.35, render 0.02, renderKey -, undo true, teleport true / tracking 0.41, pressJump 0.11, drop 0.35, reload 0.35, render 0.02, renderKey -, undo true, teleport true
  • seqheldtwice-tlhold-px-r0-nested-z100: tracking 120.29, pressJump 312.3, drop 0.07, reload 0.07, render 0.03, renderKey -, undo true, teleport false / tracking 0.41, pressJump 0.11, drop 0.39, reload 0.39, render 0.02, renderKey -, undo true, teleport true / tracking 0.41, pressJump 0.11, drop 0.39, reload 0.39, 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 — 1 resolved, 0 open, 0 new · 078df18 → 319aef7 · The infinite-timeline fix checks out, the two policy findings are accepted as documented opt-in behaviour, and CI is green on this head.

@xuanruli
xuanruli added this pull request to the merge queue Oct 26, 2026
Merged via the queue into main with commit afdad4e Oct 26, 2026
76 of 77 checks passed
@xuanruli
xuanruli deleted the feat/check-skip-contrast-in-motion branch October 26, 2026 06:59

This branch was successfully deployed

1 active deployment
staging - docs — 319aef7c Deployed Oct 26, 2026 by mintlify[bot]
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