mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-17 08:36:25 +03:00
* Add data-impeccable-ignore scoped waivers; fix occlusion and image-backed contrast FPs Three changes that let a page hosting deliberate anti-pattern exhibits scan clean without losing coverage, prepared with AI assistance (Claude Code) on maintainer instruction: - data-impeccable-ignore="rule-a rule-b" (or "*" / bare) on any element suppresses matching findings for its whole subtree, in the browser overlay, the extension, and the static engine. The DOM twin of the line-based impeccable-disable comments (which a live DOM cannot apply) and the generalization of data-impeccable-allow-kickers. Applied at the addBrowserFindings choke point, at the static element walk, and for regex findings that carry a live selector. - text-occlusion: an occluder whose effective opacity multiplies out to ~0 paints nothing. An opacity-0 range scrubber stretched over a before/after comparison produced 16 "100% covered by an opaque element" findings on one page because elementFromPoint returns it and its UA background-color read as opaque paint. Invisible-at-rest elements are also no longer probed as victims. - Analytic contrast now skips what it cannot measure: a url() image layer anywhere in the background stack ends the gradient-stops walk (dark ink on a bright gold-leaf image measured 2.6:1 against the wash composited over the wrong base), and elements that are invisible at rest (visibility hidden, effective opacity ~0 — hidden scene decks) are skipped by the color checks in both engines. The static cascade now tracks opacity to support this. Covered by a new scoped-ignore fixture (exact rule, star, comma list, nested depth, wrong-rule control) tested in both engines, a scrubber pass case in the occlusion fixture, and image-backed / photo-panel / hidden-scene pass cases in the gradient-ground fixture. Full suite passes; browser and extension bundles regenerated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * CSS-scan findings carry their enclosing selector; browser pass resolves them Page-level CSS-text findings (marquee, dark-glow, radial-halo, repeating-stripes, codex-grid, ai-color-palette, image-hover-transform, pseudo/inset side-tab stripes) now attach the selector of the rule that matched, via a best-effort enclosingCssSelector() helper or the selector already in scope. The browser pass resolves that selector against the live DOM: pseudo segments are stripped, a selector that renders nowhere on the page drops the finding (the CSS ships there but the pattern never paints — the live DOM is ground truth in a browser scan), and matches under a data-impeccable-ignore ancestor are waived. Static scans are unchanged: partial documents keep the text-level findings. Applied with AI assistance (Claude Code). Covered in the scoped-ignore fixture: a live marquee under a marquee waiver is suppressed, and dead two-axis grid CSS matching no element is dropped. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Attribute selectors on gradient-text and bounce-easing page emitters too Same mechanism as the previous commit, extended to the three page-level motion/text emitters that were still selector-less. Applied with AI assistance (Claude Code). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * text-overflow: skip SVG content; scrollWidth lies there Chrome reports arbitrary non-zero scrollWidth/clientWidth on SVG elements (a <text> gave 78/48 while its rendered length sat inside its box), so the box-metric delta is noise. SVG clips to its own viewport anyway. Pass case added to the quality fixture. Applied with AI assistance (Claude Code). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Overlay samples image-backed text at the pixel level by default Visual contrast gains a third mode. Explicit true still runs the full sampled pass, explicit false still disables everything (the mode the test suites use), and unset — the default overlay run — now samples ONLY image-backed text: the one class the analytic walk deliberately skips, because a url() layer's pixels are unknowable without looking. The cost is bounded and the method is precise: at most a 3x3 grid of sample points per candidate (degrading to 3 or 1 for small rects), the source image drawn once to a canvas with only those pixels read, and glyph ink never pollutes the samples because the image is drawn alone. A cross-origin image without CORS headers reports unresolved rather than guessing. Applied with AI assistance (Claude Code). Covered by a new fixture: white text on a near-white same-origin data-URI image background flags via sampled pixels under default options; dark ink on the same image passes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Review fixes: root opacity, keyframe steps, static parity, attributed fixtures Applied with AI assistance (Claude Code), addressing all seven findings from the automated reviews: - effectiveOpacityDOM walks through body and html: a page-fade wrapper with body/html opacity 0 hides every descendant (Greptile executed a Chromium repro of the false positive). - enclosingCssSelector refuses `from`/`to` keyframe steps, which read as never-matching type selectors and got valid findings wrongly dropped by the zero-match rule (Bugbot, high). Regression case: an overshoot bezier inside a `to` step must survive as page-level. - The static cascade now inherits visibility, so descendants of a hidden container compute as hidden like the browser path; a declared visibility:visible still overrides. - The static engine applies scoped waivers to selector-backed html-pattern findings, mirroring the browser — but keeps findings whose selector matches nothing, since static scans see partial documents. - The scoped-ignore fixture grows to the mandated matrix: 4 flag cases (control, other-rule waiver, sibling waiver, misspelled rule id) and 5 waived shapes (exact rule, nested depth, star, comma list, self), each with a unique border width so every finding attributes to exactly one case in both engines' tests. - The image-backed contrast test pins its cases via the sampled finding's candidate text: the white-on-light specimen must flag and the dark-ink control must stay clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Review fixes: image-only starvation, selector rejection class, inset stripes Second review round, applied with AI assistance (Claude Code): - The image-only filter moves inside the candidate collector, before the cap: gradient/opacity/filter candidates earlier in DOM order no longer consume the 12-candidate budget and starve the url()-backed texts the mode exists to sample (Bugbot, high). The regression fixture packs 14 gradient decoys ahead of the photo panels, and the test now drives the overlay entry (impeccableDetectAsync, default options) rather than detectUrl's Node-side full fallback, which is where the image-only mode actually lives. - enclosingCssSelector no longer rejects the child combinator or quoted attribute selectors; only braces and angle brackets disqualify. - The inset box-shadow side-tab scanner attaches its selector like the pseudo-element scanner does, so those findings waive and dead-drop the same way. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
101 lines
4.7 KiB
HTML
101 lines
4.7 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>Fixture: data-impeccable-ignore scoped waivers</title>
|
|
<style>
|
|
body { margin: 0; padding: 40px; background: #faf8f4; font-family: Arial, sans-serif; color: #262421; }
|
|
/* Every case carries a UNIQUE border-left width so each finding's snippet
|
|
(`border-left: Npx`) attributes to exactly one case. Flag widths: 5, 6,
|
|
8, 12. Pass widths: 4, 7, 9, 10, 11. */
|
|
.tabbed {
|
|
width: 420px;
|
|
padding: 16px 20px;
|
|
margin: 0 0 24px;
|
|
background: #ffffff;
|
|
border: 1px solid #ddd8d0;
|
|
border-left: 6px solid #b8422e;
|
|
}
|
|
.w4 { border-left-width: 4px; }
|
|
.w5 { border-left-width: 5px; }
|
|
.w7 { border-left-width: 7px; }
|
|
.w8 { border-left-width: 8px; }
|
|
.w9 { border-left-width: 9px; }
|
|
.w10 { border-left-width: 10px; }
|
|
.w11 { border-left-width: 11px; }
|
|
.w12 { border-left-width: 12px; }
|
|
/* Marquee specimen: keyframes + an infinite loop track. The section that
|
|
hosts it carries a marquee waiver; the CSS-scan finding must resolve the
|
|
selector to the live element and honor the ancestor attribute. */
|
|
@keyframes ticker-loop { from { transform: translateX(0); } to { transform: translateX(-50%); } }
|
|
.quote-track { display: flex; width: max-content; animation: ticker-loop 80s linear infinite; }
|
|
.quote-track span { padding: 8px 16px; }
|
|
/* Dead grid CSS: this two-axis hairline grid selector matches nothing on
|
|
this page, so the browser pass must drop the codex-grid finding — the
|
|
pattern never renders here. */
|
|
.never-rendered-grid {
|
|
background:
|
|
linear-gradient(90deg, rgba(200,180,120,0.08) 1px, transparent 1px),
|
|
linear-gradient(180deg, rgba(200,180,120,0.08) 1px, transparent 1px);
|
|
background-size: 24px 24px;
|
|
}
|
|
/* Overshoot bezier inside a keyframe STEP: enclosingCssSelector must refuse
|
|
`to` as a selector (it would match nothing and wrongly drop the finding),
|
|
so this bounce-easing finding stays page-level and is retained. */
|
|
@keyframes springy {
|
|
from { transform: none; }
|
|
to { transform: translateY(-4px); animation-timing-function: cubic-bezier(0.3, 1.8, 0.6, 1); }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<!-- FLAG 6px — control: no waiver anywhere above. -->
|
|
<article class="tabbed control-flags">The control case has a 6px side-tab accent border and no waiver, so the side-tab rule must flag it.</article>
|
|
|
|
<!-- FLAG 8px — waiver names a different rule, so side-tab still applies. -->
|
|
<section data-impeccable-ignore="pulsing-dot">
|
|
<article class="tabbed w8 flags-other-rule-waived">The waiver names pulsing-dot, not side-tab, so this 8px case must still flag.</article>
|
|
</section>
|
|
|
|
<!-- FLAG 12px — waiver sits on a SIBLING, not an ancestor: no effect. -->
|
|
<div data-impeccable-ignore="side-tab"></div>
|
|
<article class="tabbed w12 flags-sibling-waiver">A waiver on a preceding sibling is not an ancestor; this 12px case must still flag.</article>
|
|
|
|
<!-- FLAG 5px — waiver value misspells the rule id: exact matching only. -->
|
|
<section data-impeccable-ignore="side-tabs">
|
|
<article class="tabbed w5 flags-misspelled-rule">"side-tabs" is not a rule id; the 5px case must still flag.</article>
|
|
</section>
|
|
|
|
<!-- PASS 7px — exact-rule waiver on the container: the subtree is exempt. -->
|
|
<section data-impeccable-ignore="side-tab">
|
|
<article class="tabbed w7 pass-exact-rule">Inside a container waived for exactly side-tab; must not flag.</article>
|
|
</section>
|
|
|
|
<!-- PASS 9px — two levels below the waiving container. -->
|
|
<section data-impeccable-ignore="side-tab">
|
|
<div>
|
|
<article class="tabbed w9 pass-nested-depth">Two levels below the waiving container; the ancestor walk must still find it.</article>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- PASS 10px — star waiver: every rule is exempt in the subtree. -->
|
|
<section data-impeccable-ignore="*">
|
|
<article class="tabbed w10 pass-star">Inside a star-waived container; must not flag.</article>
|
|
</section>
|
|
|
|
<!-- PASS 11px — comma list including side-tab. -->
|
|
<section data-impeccable-ignore="pulsing-dot, side-tab">
|
|
<article class="tabbed w11 pass-rule-list">side-tab appears in the comma list; must not flag.</article>
|
|
</section>
|
|
|
|
<!-- PASS 4px — waiver on the flagged element itself (self counts). -->
|
|
<article class="tabbed w4 pass-self-waiver" data-impeccable-ignore="side-tab">The waiver sits on the element itself; must not flag.</article>
|
|
|
|
<!-- Live marquee inside a rule-scoped waiver: the CSS-scan marquee finding
|
|
resolves .quote-track to this element and is waived by the ancestor. -->
|
|
<section data-impeccable-ignore="marquee">
|
|
<div class="quote-track"><span>One voice.</span><span>Another voice.</span><span>One voice.</span><span>Another voice.</span></div>
|
|
</section>
|
|
</body>
|
|
</html>
|