Repository navigation
feat(cli): opt-in check flag that leaves out contrast measured mid-animation - #6396
Conversation
…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.
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Automations to automatically generate PRs for you. |
There was a problem hiding this comment.
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:
activeInRootreadsparent.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:
__contrastAuditPreparepushesout[i]andrestores[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
buildContrastResultsorcontrastFailureAnnotations, 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)
Edit accuracy: accurate 271 (base branch 271), smooth 104 of thoseThe gate passes. Quarantined, measured but not gated (0) Unstable (2)
|
What
An opt-in switch for
hyperframes check:--skip-contrast-in-motion, orHYPERFRAMES_CHECK_SKIP_CONTRAST_IN_MOTION(read with the existing
envFlagEnabled). When on, a contrast failure measured while its text is mid-animation isnot reported. With neither set, check's output is unchanged.
"Mid-animation" means, at the contrast sample time:
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.buildContrastResults, so counts,ok, the exit code and snapshot annotations leavethem 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
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-pagecontrast script with a GSAP-shaped timeline, switch off/on);
check.test.ts(flag, env1, env0, neither).Author validation:
vitest runon the three files: 190/190 after rebasing onto main.Followthrough validation: 205/205 on those files; 3/3 in
checkBrowser.chromium.test.tswith 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.
4 s camera drift, and unanimated all still reported as errors). On 9 real projects,
check --jsonwith the switchoff 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).
docs/packages/cli.mdx, one line inskills/hyperframes-cli/references/lint-validate-inspect.md, regeneratedskills-manifest.json.Not covered: two
checkSweep.chromium.test.tsWebGL tests failed locally because headless Chrome had no WebGLcontext; they don't touch this change and were not re-run on main.