You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Both npx hyperframes validate and the bundled scripts/contrast-report.mjs emit null:1 (validate) / NaN:1 (contrast-report) contrast warnings for text elements whose bounding box falls outside the captured frame buffer.
Root cause is in sampleRingMedian (in dist/skills/hyperframes/scripts/contrast-report.mjs, the same logic backs the validate command). When bbox.y > frameHeight (or bbox.x > frameWidth), the ring-sampling loop calls pushPixel(x, y) with coordinates beyond the buffer's bounds. pushPixel does not bounds-check before reading raw[i], so the channel arrays fill with undefined. median([undefined, undefined, ...]) returns undefined, which serializes to null in JSON and renders as NaN in the human report. The element then "fails" WCAG with a meaningless ratio.
This couples with the snapshot/capture-viewport bug (#587 — sibling): for portrait compositions (data-width=1080, data-height=1920), the captured frame is hardcoded 1920×1080, so every element with bbox.y > 1080 (i.e. roughly the bottom half of the composition) produces a false-positive warning.
In a typical multi-clip 1080×1920 narrative composition, this can mean dozens of bogus warnings — the production composition that surfaced this had 33 NaN entries out of 45 total samples drowning out the real findings.
Expected: 10/20 entries are NaN:1 for #t-bottom (positioned at top: 1500px in the 1080×1920 portrait composition; falls outside the 1920×1080 captured frame).
Expected behavior
One of the following:
Bounds-check before sampling. In sampleRingMedian / pushPixel, skip pixels whose x or y is outside [0, width) / [0, height). If the resulting channel arrays are empty, mark the element as "off-frame, skipped" instead of returning null ratio. Treat off-frame elements as informational, not as WCAG failures.
Filter elements at probe time. In probeTextElements, skip elements whose getBoundingClientRect() is entirely outside the viewport.
Then in the caller, if r.length === 0 after sampling, return a sentinel (e.g. null-with-reason, or skip the element from the warnings list entirely with a skippedReason: "off-frame" marker).
Describe the bug
Both
npx hyperframes validateand the bundledscripts/contrast-report.mjsemitnull:1(validate) /NaN:1(contrast-report) contrast warnings for text elements whose bounding box falls outside the captured frame buffer.Root cause is in
sampleRingMedian(indist/skills/hyperframes/scripts/contrast-report.mjs, the same logic backs the validate command). Whenbbox.y > frameHeight(orbbox.x > frameWidth), the ring-sampling loop callspushPixel(x, y)with coordinates beyond the buffer's bounds.pushPixeldoes not bounds-check before readingraw[i], so the channel arrays fill withundefined.median([undefined, undefined, ...])returnsundefined, which serializes tonullin JSON and renders asNaNin the human report. The element then "fails" WCAG with a meaningless ratio.This couples with the snapshot/capture-viewport bug (#587 — sibling): for portrait compositions (
data-width=1080,data-height=1920), the captured frame is hardcoded 1920×1080, so every element withbbox.y > 1080(i.e. roughly the bottom half of the composition) produces a false-positive warning.In a typical multi-clip 1080×1920 narrative composition, this can mean dozens of bogus warnings — the production composition that surfaced this had 33 NaN entries out of 45 total samples drowning out the real findings.
Link to reproduction
https://github.com/sidorovanthon/hyperframes-repro-contrast-out-of-clip
Steps to reproduce
npm install.npx hyperframes validatenode node_modules/hyperframes/dist/skills/hyperframes/scripts/contrast-report.mjs .NaN:1for#t-bottom(positioned attop: 1500pxin the 1080×1920 portrait composition; falls outside the 1920×1080 captured frame).Expected behavior
One of the following:
sampleRingMedian/pushPixel, skip pixels whosexoryis outside[0, width)/[0, height). If the resulting channel arrays are empty, mark the element as "off-frame, skipped" instead of returningnullratio. Treat off-frame elements as informational, not as WCAG failures.probeTextElements, skip elements whosegetBoundingClientRect()is entirely outside the viewport.hyperframes snapshotignores rootdata-width/data-height, defaults to 1920×1080 viewport #587) so portrait compositions are captured at their declared dimensions, and these elements never end up off-frame in the first place.Actual behavior
contrast-report.jsonentries for off-frame elements have"bg": [null, null, null, 1]and"ratio": null.Suggested fix
In
dist/skills/hyperframes/scripts/contrast-report.mjs, modifypushPixel(around line 188) to bounds-check:Then in the caller, if
r.length === 0after sampling, return a sentinel (e.g.null-with-reason, or skip the element from the warnings list entirely with askippedReason: "off-frame"marker).Environment
hyperframes0.4.41Related
hyperframes snapshotignores rootdata-width/data-height, defaults to 1920×1080 viewport #587 —hyperframes snapshotignores rootdata-width/data-height(the upstream cause that puts portrait elements off-frame in capture).