mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-10-30 09:16:55 +00:00
feat(cli): skipContrastInMotion moves to HF_TOOL_CONTEXT check-review (#6400)
* feat(cli): skipContrastInMotion moves to HF_TOOL_CONTEXT check-review The --skip-contrast-in-motion flag and HYPERFRAMES_CHECK_SKIP_CONTRAST_IN_MOTION env give way to a check-review capability in HF_TOOL_CONTEXT, so only a harness turns it on. The skill text and manifest go back to main. * fix(cli): satisfy comment ratchet for check capability
This commit is contained in:
@@ -77,6 +77,20 @@ id, and says to delete it to continue. The lock is the file `.hyperframes/projec
|
||||
Choose `waitMs` below your tool call's own timeout, so a wait ends in the CLI's message rather than
|
||||
the harness killing the call.
|
||||
|
||||
## `check-review`: review switches for `check`
|
||||
|
||||
```json
|
||||
{ "skipContrastInMotion": true }
|
||||
```
|
||||
|
||||
Each field is a boolean and defaults to `false`; a value that is not `true` is
|
||||
off, and unknown fields are ignored. Without the capability, `check` output is
|
||||
the same as in a terminal.
|
||||
|
||||
| Field | What it does |
|
||||
| --- | --- |
|
||||
| `skipContrastInMotion` | Leaves out contrast failures measured while a GSAP tween moves the text or an ancestor, or while the text is partly transparent (see [check](/packages/cli#check)). Counts, `ok` and persistence leave them out too. |
|
||||
|
||||
## Hooks that run code
|
||||
|
||||
Running your own command before or after a CLI command is not a `HF_TOOL_CONTEXT` capability and
|
||||
|
||||
@@ -1058,7 +1058,6 @@ cover every finding from the full check.
|
||||
| `--tolerance` | Allowed overflow in px before reporting (default 2) |
|
||||
| `--timeout` | Render-ready budget in ms; also raises page navigation above its 10s floor (default 3000) |
|
||||
| `--no-contrast` | Skip the WCAG pass while iterating |
|
||||
| `--skip-contrast-in-motion` | Leave out contrast failures measured mid-animation (see below); also `HYPERFRAMES_CHECK_SKIP_CONTRAST_IN_MOTION=1` |
|
||||
| `--strict` | Exit non-zero on warnings too (default: errors only) |
|
||||
| `--caption-zone "<x0=..;y0=..;x1=..;y1=..>"` | Opt-in band gate. Flags a text element's DOM box that overlaps the fractional band. Optional `severity` and `seek`. |
|
||||
| `--frame-check` | Opt-in out-of-frame detection for `img`, `svg`, `video`, and `canvas` |
|
||||
@@ -1098,9 +1097,10 @@ Contrast failures are errors, and each one reports the sampled foreground and
|
||||
background colours, the measured ratio against the required one, and a
|
||||
compliant colour you could use instead.
|
||||
|
||||
`--skip-contrast-in-motion`, or `HYPERFRAMES_CHECK_SKIP_CONTRAST_IN_MOTION=1` in the
|
||||
environment, leaves out a contrast failure measured while a GSAP tween on the text
|
||||
or an ancestor is running, or while the text's opacity multiplied up its ancestors
|
||||
An agent harness can turn on `skipContrastInMotion` in the `check-review`
|
||||
capability of [`HF_TOOL_CONTEXT`](/developers/harness-tool-context). It leaves
|
||||
out a contrast failure measured while a GSAP tween on the text or an ancestor
|
||||
is running, or while the text's opacity multiplied up its ancestors
|
||||
is below 1. The failure is not reported, and the counts and `ok` leave it out.
|
||||
Repeating tweens, tweens inside infinitely repeating timelines, and tweens of
|
||||
4s or longer, such as a slow camera drift, do not count as motion. The opacity
|
||||
|
||||
@@ -453,18 +453,19 @@ describe("skip contrast in motion", () => {
|
||||
vi.unstubAllEnvs();
|
||||
});
|
||||
|
||||
it("is off unless the flag or the environment turns it on", async () => {
|
||||
vi.stubEnv("HYPERFRAMES_CHECK_SKIP_CONTRAST_IN_MOTION", "");
|
||||
it("is off unless HF_TOOL_CONTEXT's check-review capability turns it on", async () => {
|
||||
const review = (value: unknown) =>
|
||||
vi.stubEnv(
|
||||
"HF_TOOL_CONTEXT",
|
||||
JSON.stringify({ version: 1, capabilities: { "check-review": value } }),
|
||||
);
|
||||
vi.stubEnv("HF_TOOL_CONTEXT", "");
|
||||
expect(await parsedOptions([])).not.toHaveProperty("skipContrastInMotion");
|
||||
expect(await parsedOptions(["--skip-contrast-in-motion"])).toMatchObject({
|
||||
skipContrastInMotion: true,
|
||||
});
|
||||
vi.stubEnv("HYPERFRAMES_CHECK_SKIP_CONTRAST_IN_MOTION", "1");
|
||||
review({ skipContrastInMotion: true });
|
||||
expect(await parsedOptions([])).toMatchObject({ skipContrastInMotion: true });
|
||||
expect(await parsedOptions(["--no-skip-contrast-in-motion"])).toMatchObject({
|
||||
skipContrastInMotion: true,
|
||||
});
|
||||
vi.stubEnv("HYPERFRAMES_CHECK_SKIP_CONTRAST_IN_MOTION", "0");
|
||||
review({ skipContrastInMotion: "1" });
|
||||
expect(await parsedOptions([])).not.toHaveProperty("skipContrastInMotion");
|
||||
review({});
|
||||
expect(await parsedOptions([])).not.toHaveProperty("skipContrastInMotion");
|
||||
});
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
} from "@hyperframes/studio-server/history";
|
||||
import { formatFindingTimes } from "../utils/checkFindings.js";
|
||||
import { withProjectLock } from "../utils/projectLock.js";
|
||||
import { reportArtifacts } from "../utils/toolContext.js";
|
||||
import { readToolContext, reportArtifacts, type ToolContext } from "../utils/toolContext.js";
|
||||
import { defineCommand, parseArgs } from "citty";
|
||||
import type { ArgsDef } from "citty";
|
||||
import type { Example } from "./_examples.js";
|
||||
@@ -37,7 +37,6 @@ import {
|
||||
} from "../utils/checkPipeline.js";
|
||||
import type { CaptionZoneOptions, FrameCheckOptions, LayoutOptions } from "../utils/checkTypes.js";
|
||||
import { resolveLocalBrowserGpuMode } from "../browser/gpuPolicy.js";
|
||||
import { envFlagEnabled } from "../utils/attendedTerminal.js";
|
||||
import { preResolveHostileMediaProxies, type PreparedMediaFile } from "../utils/checkBrowser.js";
|
||||
|
||||
export const examples: Example[] = [
|
||||
@@ -216,12 +215,6 @@ const CHECK_COMMAND_ARGS = {
|
||||
description: "Run the WCAG AA contrast pass (enabled by default)",
|
||||
default: true,
|
||||
},
|
||||
"skip-contrast-in-motion": {
|
||||
type: "boolean",
|
||||
description:
|
||||
"Leave out contrast failures measured while a GSAP tween moves the text or an ancestor, or at partial opacity (also HYPERFRAMES_CHECK_SKIP_CONTRAST_IN_MOTION=1)",
|
||||
default: false,
|
||||
},
|
||||
strict: {
|
||||
type: "boolean",
|
||||
description: "Exit non-zero on warnings too",
|
||||
@@ -382,6 +375,10 @@ async function runPrepareMedia(
|
||||
setCommandExitCode(failed.length === 0 ? 0 : 1);
|
||||
}
|
||||
|
||||
function checkReview(): ToolContext["checkReview"] {
|
||||
return readToolContext().checkReview;
|
||||
}
|
||||
|
||||
function parseCheckOptions(args: Record<string, unknown>): CheckOptions {
|
||||
const maxTransitionSamples = positiveInteger(args["max-transition-samples"], 0);
|
||||
return {
|
||||
@@ -396,10 +393,7 @@ function parseCheckOptions(args: Record<string, unknown>): CheckOptions {
|
||||
tolerance: nonNegativeNumber(args.tolerance, DEFAULT_CHECK_OPTIONS.tolerance),
|
||||
timeout: Math.max(500, positiveInteger(args.timeout, DEFAULT_CHECK_OPTIONS.timeout)),
|
||||
contrast: args.contrast !== false,
|
||||
...(args["skip-contrast-in-motion"] === true ||
|
||||
envFlagEnabled("HYPERFRAMES_CHECK_SKIP_CONTRAST_IN_MOTION")
|
||||
? { skipContrastInMotion: true }
|
||||
: {}),
|
||||
...(checkReview()?.skipContrastInMotion ? { skipContrastInMotion: true } : {}),
|
||||
strict: args.strict === true,
|
||||
snapshots: args.snapshots === true,
|
||||
captionZone: parseCaptionZone(args["caption-zone"]),
|
||||
|
||||
@@ -47,6 +47,16 @@ describe("readToolContext", () => {
|
||||
readToolContext(context({ events: { transport: { type: "http", path: "/x" } } })),
|
||||
).toEqual({});
|
||||
});
|
||||
|
||||
it("reads check-review switches as booleans that default to false", () => {
|
||||
expect(
|
||||
readToolContext(context({ "check-review": { skipContrastInMotion: true, other: 1 } })),
|
||||
).toEqual({ checkReview: { skipContrastInMotion: true } });
|
||||
expect(readToolContext(context({ "check-review": { skipContrastInMotion: 1 } }))).toEqual({
|
||||
checkReview: { skipContrastInMotion: false },
|
||||
});
|
||||
expect(readToolContext(context({ "check-review": true }))).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe("reportArtifacts", () => {
|
||||
|
||||
@@ -6,6 +6,7 @@ const TOOL_CONTEXT_VERSION = 1;
|
||||
export interface ToolContext {
|
||||
events?: { path: string };
|
||||
projectLock?: { waitMs: number };
|
||||
checkReview?: { skipContrastInMotion: boolean };
|
||||
}
|
||||
|
||||
export type ArtifactRole = "contact-sheet" | "frame" | "finding" | "check-report";
|
||||
@@ -34,6 +35,11 @@ function parseProjectLock(value: unknown): ToolContext["projectLock"] {
|
||||
return { waitMs };
|
||||
}
|
||||
|
||||
function parseCheckReview(value: unknown): ToolContext["checkReview"] {
|
||||
if (!isRecord(value)) return undefined;
|
||||
return { skipContrastInMotion: value.skipContrastInMotion === true };
|
||||
}
|
||||
|
||||
export function readToolContext(raw = process.env.HF_TOOL_CONTEXT): ToolContext {
|
||||
if (!raw) return {};
|
||||
let value: unknown;
|
||||
@@ -46,7 +52,12 @@ export function readToolContext(raw = process.env.HF_TOOL_CONTEXT): ToolContext
|
||||
return {};
|
||||
const events = parseEvents(value.capabilities.events);
|
||||
const projectLock = parseProjectLock(value.capabilities["project-lock"]);
|
||||
return { ...(events && { events }), ...(projectLock && { projectLock }) };
|
||||
const checkReview = parseCheckReview(value.capabilities["check-review"]);
|
||||
return {
|
||||
...(events && { events }),
|
||||
...(projectLock && { projectLock }),
|
||||
...(checkReview && { checkReview }),
|
||||
};
|
||||
}
|
||||
|
||||
const MEDIA_TYPES: Record<string, string> = {
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"files": 77
|
||||
},
|
||||
"hyperframes-cli": {
|
||||
"hash": "70516c62caf45a8a",
|
||||
"hash": "08a4a3a815d71988",
|
||||
"files": 13
|
||||
},
|
||||
"hyperframes-registry": {
|
||||
|
||||
@@ -38,7 +38,6 @@ npx hyperframes check --at-transitions # also sample every tween start/end bou
|
||||
npx hyperframes check --tolerance 4 # allowed overflow px before reporting (default 2)
|
||||
npx hyperframes check --timeout 30000 # initial render-ready + navigation minimum in ms (defaults: 3000 / 10000)
|
||||
npx hyperframes check --no-contrast # skip the WCAG audit while iterating
|
||||
npx hyperframes check --skip-contrast-in-motion # leave out contrast failures measured mid-animation (or HYPERFRAMES_CHECK_SKIP_CONTRAST_IN_MOTION=1)
|
||||
npx hyperframes check --strict # exit non-zero on warnings too (default: only errors)
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user