mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-11 21:57:14 +03:00
5a7e2837d2036b2ea8386031c2cd9a539b0dab13
44
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
fa44839f72 |
Fix detector URL scans and advisory handling (#709)
* Fix detector URL and advisory handling Recover joined URL arguments without splitting local paths, derive advisory behavior from registry severity across consumers, inspect readable linked CSS in URL scans, and report only the dominant primary font. AI assistance disclosure: Implemented and verified with Codex under maintainer direction. * Filter linked CSS to rendered selectors Flatten linked stylesheet grouping rules and collect only selector rules that target the live DOM, preventing unused grouped and selector-less patterns from leaking into URL findings. AI assistance disclosure: Implemented and verified with Codex under maintainer direction. * Fix detector review edge cases AI assistance disclosure: Codex implemented and verified these fixes under maintainer direction. * Preserve unresolved linked CSS selectors AI assistance disclosure: Codex implemented and verified this fix under maintainer direction. * Fix linked CSS selector filtering Resolve pseudo-element selectors to live hosts, reject unresolvable linked CSS findings, and make the regression assertions independent. Also ignore comment delimiters when recovering CSS rule selectors. AI assistance disclosure: This commit was prepared with Codex under maintainer direction. * Skip unresolved container query CSS Exclude linked container-query groups when their current applicability cannot be resolved, with a browser regression proving inactive styles do not leak. AI assistance disclosure: This commit was prepared with Codex under maintainer direction. * Detect active container query CSS Use a temporary custom-property probe so the browser decides whether a nested style rule actually applies in the current container layout. AI assistance disclosure: Codex helped implement and test this fix under maintainer direction. * Filter inactive linked CSS states Keep valid empty pseudo-class matches authoritative and omit selector-less linked at-rules that cannot be tied to rendered nodes. AI assistance disclosure: Codex helped implement and test this fix under maintainer direction. * Parse pseudo-elements without rewriting literals Preserve quoted attribute values and escaped identifiers while resolving real pseudo-elements to live hosts. AI assistance disclosure: Codex helped implement and test this fix under maintainer direction. * Restore live linked keyframes AI assistance disclosure: Codex helped implement and verify this fix under maintainer direction. * Handle grouped linked keyframes AI assistance disclosure: Codex helped implement and verify this fix under maintainer direction. * Respect keyframe definition order AI assistance disclosure: Codex helped implement and verify this fix under maintainer direction. * Resolve effective linked keyframes AI assistance disclosure: Codex helped implement and verify this fix under maintainer direction. * Fix keyframe easing detection Serialize effective per-keyframe easing back into the linked stylesheet corpus so overshoot motion is detected. Add a browser regression with a neutral animation name.\n\nAI assistance disclosure: Codex helped implement and test this fix under maintainer direction. |
||
|
|
84728e9ce4 |
Fix flat type hierarchy false positives (#702)
* Fix flat type hierarchy false positives Use rendered semantic roles and dominant size frequency, align the adjacent-step guidance, and abstain in source-only scans.\n\nAI assistance: prepared with Codex under maintainer direction. * Fix static hidden typography filtering Honor the hidden attribute in the static wrapper and use raw browser findings in regression coverage. AI assistance: prepared with Codex under maintainer direction. * Align typography sampling with painted content Count visibly painted aria-hidden text and exclude content-visibility hidden subtrees in both static and browser scans. AI assistance: prepared with Codex under maintainer direction. |
||
|
|
2cfd60765a |
Fix: do not flag Roboto in system font stacks (#678)
Treat the leading system face as primary so later Roboto fallbacks do not trigger overused-font, while named web-font primaries still flag. AI-assisted merge: reviewed and executed by Codex under maintainer direction. |
||
|
|
3818a5655b |
Address the Bugbot and Copilot findings on #599
- keyChroma re-encodes with the PNG's tEXt chunks intact (the embedded prompt survived generation but not keying)
- organic-clip-path counts relative curve commands too (path data letters are only commands, so the match is case-insensitive)
- buried-raster normalizes percentage alphas (parseFloat('80%') read as 80) and reads 4- and 8-digit hex alpha instead of treating #rrggbbaa as opaque
- the extension-injected-node skip in checkQuality runs before any finding is pushed (a low-opacity injected raster was recorded, then returned by the skip)
- fake-mode plates carry impeccable:fake tEXt and the plates gate's crop-identity refusal skips them (fake mode IS the crop by design; the refusal is for models shipping the comp's pixels as artwork)
Findings by cursor[bot] and Copilot on PR #599; detector engines rebuilt (build:browser, build:extension).
AI-assisted (Claude Code).
|
||
|
|
5856161014 |
Plate pipeline, asset producer rewrite, and two detector rules for CSS standing in for material
generate-image.mjs --plate produces one raster region of the measured spec from the comp crop, scores it against the crop, and refuses under --min. The asset producer's job becomes producing the spec's plates. Detector gains organic-clip-path (many-vertex polygon / curved path() clips) and buried-raster (raster under a near-opaque wash or at near-zero opacity), wired into both engines with fixtures. AI-assisted (Claude). Co-Authored-By: Claude <noreply@anthropic.com> |
||
|
|
a735bc55cd |
Merge pull request #630 from pbakaus/fix/592-oklch-parseanycolor
Fix: parse oklch in visual-contrast and neon-text (#592) |
||
|
|
a236137bc6 |
Fix: stop flagging 1D dashed rules as grid backgrounds (#615)
codex-grid-background treated any 2D px background-size as a grid, so a single hairline tiled as a dash or rail false-positived. A finding now requires two hairline gradients plus a px tile. Prepared with AI assistance (Cursor agent), directed by @abdulwahabone. Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
1b7da15b56 |
Fix: parse oklch in visual-contrast and neon-text (#592)
Bare parseRgb() dropped Tailwind v4 computed colors, so contrast sampling skipped and neon-text never fired. AI-assisted (Cursor agent). Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
ac0416b655 |
Stop assuming white when a background cannot be read (#541)
* Stop assuming white when a background cannot be read Dark themes came back from a scan buried in low-contrast findings that all claimed the light text sat on #ffffff. Two live runs against impeccable.style produced 102 and 95 of them. Two causes, both fixed here. Parsing. Browsers keep the authored color space in getComputedStyle output: oklch() stayed oklch, but color-mix results come back as color(srgb 1.04 0.72 -0.21), wide-gamut authors get color(display-p3 ...), and lch()/lab() survive verbatim. The parser read none of those, so those surfaces registered as unset. parseGradientColors was worse: it matched only rgba() and #hex, so a ground painted as linear-gradient(oklch(...), oklch(...)) counted as a gradient with no stops at all. Guessing. When the ancestor walk ran out of readable color it returned white, and on a body-level gradient it returned white without even looking. Light copy on a lacquer-black page then measured 1.3:1 against a canvas the visitor never sees. resolveBackgroundInfo now separates three outcomes: a resolved surface, a gradient the caller should fall back to stops for, and an unreadable layer. The last one makes both color adapters skip their contrast checks entirely. White survives in exactly one case, the one that earns it: every layer up to the document root was genuinely transparent. Color conversions moved to cli/engine/shared/color.mjs and gained lab, lch, and color() for srgb, srgb-linear, and display-p3. Spaces outside that set return null, which now routes to abstention rather than to a color nobody painted. Every conversion is pinned against what Chrome itself paints for the same string. Rescanning impeccable.style: 102 low-contrast findings down to 30, none of them on an invented white ground. Assisted-by: Claude Code * fix: address PR review bot findings on background resolution - Treat a url() image layer stacked above a gradient as an occluding, unreadable surface: resolveBackgroundInfo now returns unresolved so the gradient-stop fallback never measures stops the image hides (greptile-apps finding, reproduced in Chrome). - Route the glow and AI-palette DOM adapters through resolveBackgroundInfo so an unresolved surface makes them abstain instead of hunting gradient ancestors past an unreadable layer (Cursor Bugbot finding). - Resolve background-color keywords jsdom hands through verbatim: inherit now reads as no-paint (the ancestor walk IS its resolution) and currentcolor substitutes the element's own computed text color instead of forcing an abstention (Copilot finding). - Regression coverage in the dark-theme fixture for all three, asserted in both the jsdom and real-Chrome suites; browser detector regenerated. AI-assisted: prepared with Claude Code at the maintainer's direction. Co-Authored-By: Claude <noreply@anthropic.com> * fix: keep zero-offset glow findings when the surface is unreadable The browser glow adapter abstained from the whole element when resolveBackgroundInfo reported an unreadable surface, which also dropped zero-offset chromatic halo findings that do not depend on the background at all. It now skips only the gradient hunt past the unreadable layer and scores the halo tell against a null surface, matching what the static loop already did. Fixture cases pin both sides: the halo over a url() image ancestor flags in both engines, and an offset chromatic shadow on the same unknown surface stays abstained. Also hardens the currentcolor background substitution with the parseColorResolved fallback used by the text-color path, and adds fixture coverage proving tokenized currentcolor surfaces already resolve through the static cascade (flag when knowable, abstain when the token is undefined). Addresses Cursor Bugbot review findings on PR #541. AI-assisted-by: Claude Code Co-Authored-By: Claude <noreply@anthropic.com> * fix: abstain on translucent gradients over images, drop phantom color-mix stops Two follow-up review findings on the merge with main. A gradient leading a url() layer was treated as a resolvable surface even when its stops are translucent, so the glow and AI-palette hunts averaged wash stops (a 20% black wash reads as pure black) while the real surface blends with image pixels the engine cannot read. resolveBackgroundInfo now marks gradient-over-image unresolved unless every readable stop of the leading gradient is opaque, in which case the gradient provably covers the image and remains the scorable surface. parseGradientColorsModern predated this branch's parseGradientColors rewrite: its second regex pass re-extracted color tokens nested inside color-mix() stops that the shared parser already captures whole via balanced-paren tokens, appending ingredient colors that are never painted. The worst-case stop ratio then invented low-contrast findings against a color nobody sees. The helper is removed; all callers use the shared parser, which covers the modern syntaxes it existed for. Fixture coverage pins both: the translucent-wash-over-image glow abstains in both engines, an opaque gradient over an image still flags in the browser, and the color-mix wash case stays clean in the static engine. Each new assertion was verified to fail against the previous engine. Addresses Greptile and Cursor Bugbot review findings on PR #541. AI-assisted-by: Claude Code Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
aee5ddd10c |
data-impeccable-ignore scoped waivers + occlusion and image-backed contrast FP fixes (#559)
* 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> |
||
|
|
5f7b001cbe |
Fix: measure gradient body grounds instead of assuming white (low-contrast false positives) (#557)
* Fix: measure gradient body grounds instead of assuming white (browser mode) A page whose ground is set via background: linear-gradient(...) on body leaves backgroundColor transparent, and resolveBackground assumed white for any body/html-level gradient. In a real browser that assumption is wrong: the shorthand is always decomposed there, so reaching that branch means the ground truly is the gradient. On a dark oklch gradient ground (impeccable.style's lacquer) this turned every light-on-dark text into a ~1.3:1 "on #ffffff" low-contrast finding, ~120 false positives on one site. Browser mode now returns null so the caller measures against the actual gradient stops; the white assumption stays for jsdom, where the undecomposed-shorthand rationale still holds. Gradient stops also now parse modern color syntax: computed backgroundImage keeps oklch()/oklab()/hsl()/hwb() stops as authored, and parseGradientColors only read rgb()/hex, so a token-driven gradient ground was invisible even once the walk deferred to it. New parseGradientColorsModern routes those stops through parseAnyColor. Covered by a Puppeteer fixture (dark oklch body gradient): light text on the ground must not flag, muted dark-gray ink must, proving the stops are measured rather than the checks silently skipping. Prepared with AI assistance (Claude Code), on maintainer instruction. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Composite translucent layers over gradient stops; parse modern glow stops Review fixes from PR #557's automated reviews, applied with AI assistance (Claude Code): - Cursor Bugbot found the new browser-mode early return discarded the translucent ancestors resolveBackground had collected: text on a frosted wash over a body gradient was measured against raw stops. resolveGradientStops now collects translucent layers during its own walk (through readCascadeBackgroundColor, extracted so both walks read surfaces identically) and composites every stop under them. - Copilot flagged the other legacy parseGradientColors call sites. The glow-context fallback now uses parseGradientColorsModern, since body gradients reach it more often after this change. The AI-palette rule and the injected analytic sampler stay on the legacy parser deliberately: the former is a rule-behavior expansion deserving its own fixtures, the latter degrades to pixel sampling or a skip. - Greptile asked for standard fixture structure: the fixture now has labeled flag/pass cases (3 flag, 5 pass) including the frosted-wash pair that locks the overlay compositing in both directions and a legacy hex-stop gradient guarding the original parser path. The test scopes itself to the DOM path via visualContrast: false, the suite's established pattern; the screenshot sampler is a separate subsystem with its own coverage. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Pin gradient-ground flag cases to their snippet signatures Bugbot follow-up: a count-only assertion let an offsetting miss and false positive cancel, especially the frosted pair. Each flag case now asserts its full text-on-background signature, so the frosted case must measure against the composited wash and the count guard excludes any pass case flagging in its place. Applied with AI assistance (Claude Code). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Comments: the static path is the custom engine now, not jsdom jsdom left the dependency tree when the static-html engine (StaticElement + css-cascade.mjs) replaced it, and that engine does decompose the background shorthand, so the comments this PR added were dated in both name and rationale. Only comments touched by this PR are renamed; the ~40 legacy jsdom mentions elsewhere in checks.mjs are a separate sweep. Applied with AI assistance (Claude Code). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Static engine: measure body gradients too, dropping the white assumption Follow-up to the browser-mode fix: the white assumption for body/html gradients was a jsdom guard, and jsdom is gone. The static cascade decomposes the background shorthand (expandStaticDeclaration) and preserves var() colors for later resolution, so a missing solid under a body gradient is now as real in static mode as in a browser — and the static engine had the identical false-positive class (light text on a dark gradient ground flagged "on #ffffff") while missing the muted-ink true positives on the same page. The old catastrophic case cannot recur: opaque stops fully cover any hidden solid (they are the ground), alpha stops composite over the resolved base or the white canvas default, and unresolvable stops drop rather than guess. Static twin of the browser test added over the same fixture; the full suite, the url()-ancestor guard, and a source scan of impeccable.style (0 low-contrast findings) all stay clean. Applied with AI assistance (Claude Code). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
19e400e392 |
Retire the single-font rule
One family with weight and size contrast carrying the hierarchy is a legitimate type system, and in practice the rule mostly punished minimal pages: it was the loudest cross-rule noise on the fixture corpus's should-pass columns. Removed from the registry, both engine paths, the regex page analyzers, and the devtools category map; the negative assertions stay as resurrection guards, and the text-content analyzer index base shifts down one with the removal. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
cfb6274a18 |
See through versioned stylesheets and catch standard-tracked kickers
Paul's codex build carried an element literally named class="kicker" and the detector returned one finding. Two independent blind spots: - The linked stylesheet was styles.css?v=3, and the href resolved as a literal path with the query string in it, so the whole sheet was invisible to every element-level check: 1 finding with the link, 18 with the CSS inlined. Hrefs now strip query and hash before resolving. - The kicker gate demanded letter-spacing >= max(1px, 0.08 * size). The wild's most common recipe, 0.08em at 12px, computes to 0.973px and lost to the absolute floor by a fraction. The floor is now purely proportional (0.06 * size), with a fixture case pinning the exact shape that slipped through. With both fixed, the failed codex build scans at 18 findings including its numbered section kickers (numbered-section-labels), side-tab stripe, and grid background. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
8ebcfccc64 |
Scope the page-level pattern checks to style carriers, not raw source
The static engine's checkHtmlPatterns ran its CSS-property regexes over the entire source string, so documentation ABOUT css flagged as css: impeccable.style's changelog line naming background-clip: text inside a <code> tag tripped gradient-text, the purple hexes in a <pre> sample read as the AI palette, and a commented-out stripe rule counted as a live one. The browser path shared the exposure through outerHTML. The fix is engine-level, not a per-rule patch. The pattern pass now scans scoped corpora: styleText carries <style> block contents, style="" attribute values, and the linked stylesheets the static engine already reads for the cascade; classText carries class attribute values for the utility-class scans. The static engine builds both from its parsed document, so escaped code samples never contribute; other callers fall back to a tag-scoped extraction in buildHtmlPatternCorpora, and bare CSS input stays its own style text so direct callers keep working. The pulsing-dot and marquee scanners take a second markup argument for the parts that really are markup: landmark ranges, Tailwind class positions, the <marquee> tag itself. Rendered-text checks (theater phrases) and markup-shaped checks (svg scenes, img hover classes) keep the full source on purpose. No registry ids change; this is scoping, not a new rule. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
33a1c5fcae |
Ban kickers outright: one eyebrow above a heading is one too many
The detector's repeated-section-kickers rule waited for three tracked labels before calling the pattern; generated pages earn the finding on the first one. Retire that id and replace it with kicker-above-heading, which flags any tracked-caps or small-caps label block sitting directly above an h1-h4 or heading-role element, at full warning severity. The candidate gate absorbs the false-positive shapes the repetition count used to paper over: editorial category-and-date meta lines, breadcrumbs with separators, legal and chapter numbering, application panel context labels, nav landmarks before page titles, and stat callouts with the label below the number. Hero-scale h1 eyebrows stay with hero-eyebrow-chip so one element gets one finding, and the static cascade now carries font-variant so small-caps kickers register. The craft floor entry moves from caution to ban in the same breath. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
63ecc37e54 |
Merge pull request #415 from pbakaus/css-pseudo-stripe-coverage
Detect pseudo-element stripes in standalone stylesheets and style blocks |
||
|
|
a4a076005b |
Carry source lines on pseudo-stripe findings and skip commented-out rules
Review bots caught two real gaps in the pseudo-stripe wiring: findings had no source line (so line-scoped impeccable-disable directives could not match them), and the scanner read commented-out CSS as live rules. scanCssTextForPseudoStripe now blanks comment bodies byte-for-byte (preserving offsets) and returns each rule's selector offset; the three regex-engine call sites convert that to a real line, including the whole-file line for component style blocks and CSS-in-JS templates. The HTML path ignores the new field. Tests now assert every finding's line against the selector's actual position and cover a commented-out stripe. Prepared with AI assistance (Claude Code), directed by @pbakaus. Co-Authored-By: Claude Code <noreply@anthropic.com> |
||
|
|
7622cc8440 |
Derive static-cascade color extraction from the shared named-color table
The static-html engine never emitted side-tab for `border-left: 4px solid purple` (or any named color outside a hardcoded 9-name list) in .html files: extractStaticColor's regex dropped the color token from border shorthands, the side defaulted to neutral black, and checkBorders skipped it. The same declaration in a .css file was flagged by the regex engine, so the two engines disagreed while both exited cleanly (issue #359). Build the extraction alternation from the same CSS_NAMED_COLORS table parseAnyColor resolves against (longest-first, whole-token), so the set of names the extractor recognizes and the set the parser can resolve cannot drift apart again. STATIC_NAMED_COLORS shrinks to the one keyword parseAnyColor deliberately refuses (`transparent` as zero-alpha), since parseAnyColor already covers every real named color in the table. New two-column fixture (named-color-borders.html) covers the issue reproducers: purple shorthand + radius, rebeccapurple (substring-safe matching), crimson top stripe, bare 3px teal, var() resolving to a named color, and an inline style attribute — with neutral named colors (dimgray, gainsboro, black), thin, and uniform borders as pass cases. Prepared with AI assistance (Claude Code), directed by @pbakaus. Co-Authored-By: Claude Code <noreply@anthropic.com> |
||
|
|
6ff9f957ac |
Add radial-spotlight-glow detector rule
Flags the decorative low-opacity chromatic radial-gradient "spotlight" washed behind a hero or section and fading to transparent, an AI-slop reflex the saturated radial-halo gate lets slip (e.g. rgba(80,111,255, 0.26) -> transparent on a mobile hero). Gates: a non-repeating radial-gradient whose last stop is transparent, whose visible stops are all low-opacity (alpha < 0.45) with at most two of them, at least one chromatic (channel spread >= 24 exempts neutral vignettes), on a decorative-scale surface (width >= 240, height >= 160, exempting badges/avatars/small lights). The alpha band is disjoint from radial-halo (>= 0.7), so the two never double-report. Wired into both element loops (static-html + injected browser) with the pure checkRadialSpotlight shared by both adapters. TDD fixture with 5 flag / 9 pass shapes. Browser-path sweep over the eval corpus: 29 hits on 11 pages, 0 false positives. Count 59 -> 60. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
507725c935 |
Harden detector against form.id shadowing and gradient/non-rendered false positives
Fixes three detector bugs that surfaced on real-world (Shopify) URL scans: #407 — DOM named-property shadowing crash. On a <form> with a named control like <input name="id"> (every Shopify product form), HTMLFormElement's [LegacyOverrideBuiltIns] behavior makes `form.id` return the input element, not the id string, so `elId.startsWith(...)` throws and aborts the whole scan. Read the id via getAttribute whenever `el.id` is not a string, at all three sites: checkQuality (checks.mjs) and collectBrowserFindings + generateSelector (browser/injected/index.mjs). Regenerated the browser bundle. #408 — tiny-text / undersized-ui-text flagged non-rendered elements. On sites that set html{font-size:62.5%} the root computes to 10px, so <script>/<style>/ <title>/<noscript> and display:none / visibility:hidden blocks — whose JS/CSS/ JSON-LD text clears the hasDirectText gate — produced dozens of phantom "10px body text" findings. Added isNonRenderedText() (tag list + head descendants + display/visibility) and gated both text-size floors on it. #409 — contrast rules misjudged gradients. Case A: background-clip:text paints its glyphs with the element's own gradient, not a backdrop, so measuring the never-painted `color` against those stops is a guaranteed false positive; skip the backdrop-contrast checks when bgClip is 'text' (the gradient-text pattern flag still fires). Case B: a translucent gradient stop (e.g. a 9%-alpha accent glow) was treated as an opaque accent; composite alpha stops over the resolved surface beneath the gradient in resolveGradientStops(), dropping the stop rather than guessing when that surface is unresolvable. Fixtures + tests: shadowed-form-id.html (browser, #407), nonrendered-text.html (#408), and gradient-clipped + alpha-glow cases added to color.html (#409). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
daec380cdb |
Add undersized-ui-text rule for functional text below an 11px floor
The existing `tiny-text` rule owns long body copy and deliberately exempts the UI furniture layer (nav, footer, links, buttons, labels, uppercase micro-labels). That left a real gap: a build shipped its entire furniture layer (nav links, category names, timecodes, meta rows) at 8px because the chosen pixel font only steps in 8px increments, and the design hook waved it through as merely "not on the DESIGN.md ramp" -- which the model resolved by adding 8px to the ramp. Being on the ramp launders the token, not the legibility problem. New `undersized-ui-text` quality rule closes that laundering path: - Flags interactive and short content-bearing text (links, buttons, nav items, labels, table cells, meta rows, timecodes) below an 11px floor. The floor holds inside a footer; only non-interactive legal smallprint gets the softer 10px floor. - Ignores the design system entirely, so a value ON the ramp is still flagged. - Uppercase letterspaced micro-labels stay in scope (still functional). - Exempts sup/sub, visually-hidden (sr-only) text, and code/terminal contexts. em/rem/%-sized text that computes at or above the floor never fires. - Complements tiny-text without double-flagging: long non-furniture body copy stays with tiny-text. Implemented as a single check in checkQuality (rules/checks.mjs), so both the static-html (jsdom) and browser adapters pick it up through the unified per-element path -- no dual wiring. Registered in registry/antipatterns.mjs. TDD: fixture tests/fixtures/antipatterns/undersized-ui-text.html (7 flag / 7 pass shapes), failing test first, then implement. Full fixtures suite 64/64. Deferred (blocked by an active release-gate eval reading build/_data/dist): regenerate the browser bundle (bun run build:browser -> cli/engine/detect-antipatterns-browser.js) and the extension detector (bun run build:extension -> extension/detector/detect.js + antipatterns.json) so the standalone browser/extension artifacts carry the new rule. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
270f4d20aa |
Make em-dash-overuse an advisory rule with browser parity
Em-dashes are used legitimately by humans, so em-dash-overuse fired far too often. Reclassify it as the first advisory-tier rule: detected, but never a failure. Engine - Add `advisory: true` to the rule metadata schema (em-dash-overuse is the first). findings.mjs stamps `advisory: true` on advisory findings so every consumer can partition without a registry lookup. Rule count stays 58. - Raise the firing threshold from a flat 5 dashes to two gates: an absolute floor of 8 and a density of about one dash per 500 characters of body text. A long article that uses a few em-dashes no longer trips; a short, dash-per-clause page still does. Entity decoding (mdash, numeric, hex) is unchanged. Thresholds live in shared/constants.mjs so every engine agrees. Browser parity - The browser bundle carried a registry entry but no logic, so the overlay and extension could never flag it. Add checkEmDashOveruse / checkEmDashOveruseDOM in rules/checks.mjs (reads rendered text, no entity decoding needed), wire it into the injected page-level pass, and carry the advisory flag through serializeFindings so the overlay/extension can render it with the mildest affordance. CLI - Advisory findings print under a separate dimmed "Advisory" section, are excluded from the failure count, and never change the exit code (an advisory-only scan exits 0). JSON keeps them with `"advisory": true`. `--no-advisory` suppresses them entirely. Hook - Advisory rules are skipped by default in both the per-edit and Stop deep-pass hooks, so the hook never nags about them. Opt in with `.impeccable/config.json` -> `detector.advisoryRules: "include"`. Tests - Fixture + threshold + browser-adapter coverage; advisory-skip default and opt-in for the hook; formatFindings partitioning. The em-dash-overuse stand for a deferred copy rule in the tier tests is swapped to marketing-buzzword. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
7557935fdb |
Expand concept system: modes, ratings, re-roll, breadth strategy
Catalog: mode-aligned staging surfaces (persuade/operate/read/experience), star ratings on approvals feeding challenger draw weights, family retirements, authoring strategy and territory guide, rework and breadth authoring rounds, composition mining from rejected worlds. Seed: six challengers (two per tier), --reroll chains, --mode staging filter, rating-weighted draws. New-work: Present/visualize/re-roll flow, image-gen requirement, register-neutral vocabulary. Pipeline: per-mode staging prompts with split frames, hero-from-board reference generation, render-safety guards. Labs: ratings UI, unrated filter, mode chips, composition approve-guard. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
144cee5c36 |
Fix detector coverage for generated UI tells
Remove provider gating, share grid-background detection across source and rendered scan paths, and update the detector catalog and tests.\n\nAI-assisted: prepared by Codex at Paul's request. |
||
|
|
77c7d8e0fc | Refine product and visual work lifecycle | ||
|
|
bbed6eef08 |
Refresh the Impeccable product experience
Rework the landing page proof, steering demo, feature grid, slop catalog, detector coverage, theming, Live workflow, and responsive behavior.\n\nAI-assisted implementation by OpenAI Codex. |
||
|
|
8682c85c57 |
Fix Live side-tab validation gaps
Scan Astro style blocks for inset-shadow stripes, recognize semantically chromatic external tokens without flagging neutral unknowns, and make the polling generator run advisory detector checks before publication. Sync the affected detector bundles and add a paired regression fixture.\n\nAI-assisted: Codex analyzed the failed Live task, implemented the detector and generator changes, and ran the validation suites under maintainer direction. |
||
|
|
ed7a6fbe4e |
detector: text-occlusion + first-viewport-column-overflow (57 -> 59)
Two browser-engine quality rules, both warning severity. text-occlusion / element-overlap fires on three shapes: an opaque decorated box painted over a text element (elementFromPoint confirms real coverage, box >= 30%), one text run buried under another when at least one side is a positioned layer (text >= 45%, so line-box leading bleed between stacked flow blocks does not count), and an inline element whose opaque fill leaks past its line onto a neighbour (the class-name collision bug). A large headline whose edge overhangs a bounded content card is caught as an element collision even when the text stays on top. Gradient scrims, decorative SVG emblems, fixed/sticky overlays, floats, and raw image backdrops (contrast territory, deduped against the pixel low-contrast rule) are exempt. first-viewport-column-overflow fires when a multi-column opening section runs one column past 140% of the viewport while a sibling fits inside one screen, the stretched-hero signature. Single-column pages and full-page heroes with no fitting sibling are exempt. Validated: fires on the diagnosed repros, clean across a 60-sample sweep. Fixtures + browser tests added. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
dc0b25d393 |
detector: hero pulsing-dot promotion, nav-CTA contrast gap closure, shape-assembled-illustration (56 -> 57)
Item 1 (hero liveness theater):
- pulsing-dot now merges declarations per selector across rule blocks
(cascade-approximate), descends into media queries, and strips
prefers-reduced-motion: reduce overrides before the predicate runs.
Catches the shipped split-block constructions (size in the base rule,
animation added later or inside a no-preference media block).
- Dots whose element sits inside a header/nav landmark are promoted to
error severity (string-level landmark ranges in both engines); the
browser engine additionally promotes dots resting in the first ~900px.
- blinking-cursor findings in the first ~900px or inside header/nav are
promoted from advisory to warning.
- Per-finding severity overrides now flow through static-html,
browser-injected serialization, and detect-url.
Item 2 (nav-CTA contrast constructions):
- The a24-opus 01/002 header CTA already fires (specificity cascade +
oklch + var() all resolved); systematic sweep found two remaining
escapes and closes both:
- own gradient background on a SAFE_TAGS element (checkColors styled-
control exception now treats an own gradient as an own surface,
contrast measured against the worst stop)
- ::before/::after full-cover surface (static cascade marks pseudo
surfaces; browser adapter reads the pseudo computed style) so text is
measured against the surface the browser actually paints
- nav-cta-constructions fixture locks all eight computable construction
families; background-image: url() remains unflaggable by design.
Item 3 (shape-assembled-illustration, slop/advisory):
- New rule for large inline SVGs composing a pictorial scene from >= 8
primitive shapes at >= 200x200 intrinsic size with >= 3 distinct fills.
Charts (axis labels), stroke-only technical drawings, icons/logos
(small explicit size), and pattern-tiled backgrounds are exempt.
1.8 percent fire rate over the 3069-sample eval corpus, all verified
pictorial scenes; zero fires across val-a22/val-a24.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
c98f5d42ed |
detector: script-error, content-hidden-at-rest, edge-flush-cards + chip contrast and inline-overflow widenings (53 -> 56)
Three new rules and three widenings, all from confirmed eval-corpus
escapes found by eye:
script-error (quality, error severity, URL engine): pageerror listener
attached before goto catches uncaught exceptions AND parse errors (a
syntax error fires during the initial parse, long before load). Deduped
by message, capped at 3. A JS typo was silently deleting whole pages.
content-hidden-at-rest (quality, error, URL engine): after the main
at-rest scan, an instant-scroll reveal sweep (bypasses scroll-behavior:
smooth, which silently defeated the first sweep design) gives every
IntersectionObserver reveal its chance to fire, returns to top, then
measures the share of text characters still at opacity 0 / visibility
hidden. display:none / [hidden] / aria-hidden subtrees stay out of the
denominator. Fires above 30% with a 200/150-char floor. Calibration on
30 corpus samples: broken repro holds 83% after the sweep, all clean
samples (including 0.75-0.93 at-rest reveal pages) drop to <= 7%.
edge-flush-cards (quality, warning, browser): cards with their own
opaque background or 2+ borders inside a horizontal scroller, flush
against one edge of the clip box at rest (< 8px, > -24px so deliberate
mid-card peeks stay exempt) while keeping a gutter on the other side.
Grouped per scroller. Repro: transit-mobile pager whose first snap
panel is 407px wide inside a 390px clip. New --viewport WxH CLI flag
makes mobile-width URL scans reachable (--viewport 390x844).
Chip/badge contrast widening: the SAFE_TAGS styled-button exception in
checkColors now covers any text-bearing element painting its own opaque
background at >= 9px font, not just a/button. The shipped miss: a span
SEV-2 chip whose white text lost a specificity fight and rendered
muted-on-red at 1.2:1. Static adapter also resolves var() own-bg via
the custom-property map so the gate engages on token backgrounds.
background:none cascade fix: the background shorthand now resets
background-color/-image when it names neither (and no var()). Exposed
by the chip widening: pre code { background: none } left an earlier
surface color standing and manufactured 1.1:1 phantom findings.
text-overflow inline-owner widening: inline elements have no client
geometry (clientWidth 0) so the scrollWidth path never saw them, and
their block parent owns no direct text. New branch measures the inline
rect against the nearest block container's padding box (16px floor,
transform-path exempt). Repro: nowrap span.v spilling 45px past its
grid cell.
The round-3 nav-CTA contrast escape (val-a22-opus obs 003 header CTA)
was verified already covered at HEAD by the earlier parseAnyColor
oklch fallback; both engines fire 3.6:1 on the repro, no change needed.
FP sweep across 36 val-a21/a22/a23 samples: new rules fire only on
their repros (script-error also catches a second genuinely broken
sample); static-engine delta is limited to the chip repro plus two
borderline-but-real chip findings on one sample.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
cc8906ecaa |
detector: add heading-rhythm and blinking-cursor rules (51 -> 53)
heading-rhythm (quality): a heading binds to the content it introduces, so its rendered space above must exceed its space below. Browser-only: measures real getBoundingClientRect gaps (margin collapsing, flex rows, and section padding make authored margins untrustworthy), merges eyebrow labels into the heading cluster, requires same-column edges, and exempts first-in-container headings, bounded bands, and small cards. Fires only when 2+ headings on a page invert the rhythm. blinking-cursor (slop, advisory): a decorative blinking caret (solid block, underscore bar, or block glyph) bound to an infinite blink animation in the landing region of a page. Real editable surfaces (contenteditable, role=textbox, inputs) are exempt; round pulsing dots stay with the pulsing-dot rule. Verified against eval corpus repros: heading-rhythm fires on the val-a18 observability sample Paul flagged (6 headings, 0px above vs 40px below) and blinking-cursor on the val-a19 hero terminal cursor; 10 other samples across both runs stay clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
7a99e1725d |
detector: four human-review rules — nav-CTA oklch contrast, numbered section labels, floating side-tab stripes, repeated card text
Four gaps found shipping in Opus 4.8 eval samples during human review: 1. low-contrast (extended): the browser adapters parsed text/own-bg colors with parseRgb only, so Chrome's oklch()-serialized computed colors silently skipped every contrast check — a flat dark-on-dark nav CTA (broader nav selector beating the button class) shipped at 1.5:1 undetected. checkElementColorsDOM and readOwnBackgroundColor now fall back to parseAnyColor. Near-threshold ratios print two decimals so a 4.497 finding no longer reads "4.5 needs 4.5". 2. NEW numbered-section-labels (slop, advisory): tiny (<=13px) styled numeric index labels riding beside section headings, repeated across 2+ sections with distinct indices. Sibling of repeated-section-kickers (which deliberately excludes bare numeric labels); handles both the direct prev-sibling shape and label-before-heading-wrapper shape. List/nav/table/card-item numbering is exempt. 3. side-tab (extended): the vertical pseudo-element stripe scan required the stripe to touch both corners (top/bottom 0 or height 100%), so a left accent bar inset a few px from each end evaded it; small end insets (<=20px each) now count. Added a browser-side pseudo-element check (getComputedStyle(el, '::before'/'::after')) since runtime- assigned custom-property colors are invisible to the text scanner. Selection-state exemptions stay as narrowed: only aria-selected=true / aria-current / active-class markers exempt, plus button/link affordances on the horizontal variant. 4. NEW repeated-container-text (quality): the same literal string (>=4 chars, contains letters) rendered 3+ times at 3+ structurally distinct positions inside one bordered/elevated container. Parallel/templated repetition (table cells, calendar grids, nav lists, identical sibling rows) never counts — structural signatures, not word lists. Verified: each rule fires on its repro sample via the file:// browser scan; clean eval samples add no new findings (the new low-contrast hits on other samples are genuine sub-AA oklch button pairs). Full test suite green; browser bundle regenerated; README/homepage rule counts bumped 49 -> 51 (docs-integrity test enforces them). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
b6304913ef |
detector: narrow the tab-strip stripe exemption to actual selection state
Tab-strip MEMBERSHIP no longer exempts chromatic top/bottom stripes — only a real selection marker does: aria-selected="true", aria-current (any non-false value), or an active/current/selected class hint. A stripe repeated on every tab in the group ([role=tab], .tabs items, aria-selected="false" tabs) is decoration and flags as side-tab; the selected tab's own underline — including the reserved-space transparent-border pattern — stays legal. Applied consistently across the element border path (isTabContextElement), the pseudo-element stripe scan, and the inset box-shadow stripe scan. Also replaces a stray NUL byte in the marquee scanner's dedupe key that made tools treat checks.mjs as binary. Browser bundle regenerated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
ea44f514f9 |
detector: grid-background variants, dash-prefix eyebrow, marquee rule, inset-shadow stripes
Four gaps from human review of gpt-5.6 eval artifacts: 1. codex-grid-background variants: the block scan now also matches the inverted end-of-tile hairline form (transparent calc(100% - Npx)) and reads the tile cell from the background shorthand's `/ Npx Npx` slot, not just background-size declarations. A single hairline layer qualifies when tiled by a px pair cell (page-scale line field); percent-tiled single hairlines (background-size: 25% 100% rules on data-viz tracks/graphs) stay legal. 2. hero-eyebrow-chip branch C (dash-prefix): sentence-case, regular- weight microlabels above the h1 announced by a short chromatic ::before/::after bar (8-80px x 1-6px, accent fill). Static cascade marks dash-pseudo targets during rule collection; the browser path reads getComputedStyle(el, '::before'/'::after'). 3. New `marquee` slop rule: <marquee> elements, and infinite animations bound to keyframes with >= 20 percentage points of X travel. Percent travel only — px-travel loops are bespoke product animations (waveform playheads, progress sweeps). Centered elements animating other properties (constant -50% X), non-infinite slide-ins, rotations, and pulses never qualify. 4. side-tab inset box-shadow variant: single-edge inset shadows (3-12px offset on one axis, no blur/spread, chromatic) drawn as stripes on cards/badges/menu items. Selection-state indicators ([aria-current], [aria-selected], [role=tab], active/current/selected hints, interaction states) stay exempt; the same stripe repeated unconditionally on every item flags. Narrow fixed-width glyphs (logo marks) are exempt. isTabContextElement narrowed to match: bare nav ancestry no longer blanket-exempts top/bottom border stripes — only explicit tab semantics or state markers do. Browser bundle regenerated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
1a4b5c2fa2 |
detector: hover-state contrast + color-mix/compositing, radial-halo rule, top/bottom stripe variant, file:// browser scans
Four changes driven by human design review of eval artifacts:
1. Static engine contrast fidelity (nav-CTA cascade miss):
- parseAnyColor evaluates color-mix() (premultiplied sRGB mix; exact
for the dominant `color-mix(in oklab, C n%, transparent)` chip form)
- extractStaticColor captures color-mix() balanced instead of plucking
"transparent" out of the expression
- resolveBackground composites translucent layers over the opaque base
in both engines instead of skipping (static) or returning them
as-if-opaque (browser)
- NEW hover pass in the static cascade: :hover rules are matched via
state-stripped selectors, merged per-property against the resting
cascade with real specificity, and checked for WCAG contrast on
styled controls (checkHoverContrast). Catches the recurring miss
where a broader selector (.nav-links a:hover) beats the CTA's own
hover color and drops the pair below AA.
2. New `radial-halo` slop rule: chromatic radial-gradient wash (visible
saturated center -> transparent) as a decorative background on a dark
page. Exempts achromatic vignettes, opaque-end sheens, px-stop dot
textures, url() photo layers, and translucent (<0.7 alpha) staged-
light washes. Separate id from dark-glow so dashboards track the
gradient-drawn variant independently.
3. side-tab horizontal variant: 3-12px chromatic border-top/bottom (and
top/bottom-anchored full-width pseudo stripes) on cards/badges flag as
side-tab. Exempt: tablist/nav/aria-selected underlines, link/button
affordances, table cells, hr, state-conditional pseudo stripes, and
>12px bands. Badge-shaped spans (own visible background) participate.
4. CLI: file:// URLs route to the Puppeteer browser engine (~2s on a
50KB page), and detect --json findings now carry the registry
`category` field so downstream QA loops can separate mechanical slop
tells from judgment calls.
Fixture policy update: flat 3px top-accent cards moved from should-pass
to flag columns; tablist-underline and 16px-band pass cases added.
Browser bundle regenerated.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
cfdb7d4c81 |
detector: catch pseudo-element side stripes; add pulsing-dot rule
Two gaps surfaced by human eval review of real artifacts: 1. side-tab missed the pseudo-element variant. The accent stripe drawn as an absolutely-positioned ::before/::after (left/right: 0, top+bottom: 0 or height: 100%, narrow width, colored background) uses no border property at all, so neither the element-level border checks (pseudo elements never enter the static cascade or DOM walk) nor the border-left/right regexes could see it. New scanCssTextForPseudoStripe scans stylesheet text for that shape, mirroring the border rule's gates: >= 3px thick (<= 12px), chromatic fill (var()-resolved, neutral dividers skipped), full height against a side edge, with the blockquote/prose exemptions preserved. 2. New pulsing-dot rule (slop): small circular "live" indicator dots (<= 16px, border-radius >= 40% or pill values) bound to an infinite animation whose keyframes vary opacity, scale, or box-shadow — or pulse/blink/ping names when the keyframes aren't in the scanned text — plus the Tailwind animate-ping/pulse + rounded-full + tiny-size utility combo. Rotation-only keyframes (spinners) never flag, including when they hide behind a pulse-like name. Both scanners live in checkHtmlPatterns, so the static-html engine and the browser bundle share the same detection path. Browser/extension bundles regenerated; docs rule count bumped to 47. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
0d72991bc8 |
detector: catch glow shadows in any color format, zero-offset halos on any background, and text-shadow glows
- parseAnyColor now covers oklab(), hsl()/hsla(), hwb(), and ~35 common named colors on top of rgb/rgba/hex/oklch, so checkGlow sees the color regardless of authoring format (Chrome preserves oklch() in computed styles, which the old rgba-only match silently passed). - checkGlow gains a second tell: a zero-offset chromatic box/text-shadow with blur > 4px is flagged on ANY background (the halo pattern); achromatic zero-offset shadows and focus rings stay legal. The existing chromatic-blur-on-dark-background rule is unchanged in semantics but now parses every color format. - text-shadow is checked wherever box-shadow was (browser DOM path with inherited-value dedupe, static engine via new textShadow cascade support, text engines). - The page-level text scan (regex engine + checkHtmlPatterns) is now a shared scanCssTextForGlow that resolves single-level var() refs against custom properties collected from the same text; unresolvable var() in a shadow color position is skipped, never guessed. Its dark-page heuristic accepts var()/oklch backgrounds but only when declared at root scope (body/html/:root or body inline style). - dark-glow keeps its id; registry name/description updated to cover both cases. Validated: three eval repro samples with oklch / var(--x) glows that previously produced zero findings now flag on the static CLI path; ten known-good largerun samples stay clean except one with genuine amber status-dot halos (0 0 12px oklch(.73 .17 65/.4)). Note: cli/engine/detect-antipatterns-browser.js and the extension detector are generated and still need 'node scripts/build-browser-detector.js' + 'node scripts/build-extension.js' once builds are unblocked. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
9f49cb85cc | Fix Google Fonts css2 family parsing (#349) | ||
|
|
f5c1bd65ae |
Add codex-grid-background detector rule (#328)
* Add codex-grid-background detector rule Detects the Codex two-axis grid-line background tell: a single background value carrying two or more hairline `linear-gradient(... 1px, transparent 1px)` layers (one per axis), usually paired with a repeating `background-size` cell. Gated behind --gpt like the sibling codex tells, off by default. Counts hairline stops within a single background declaration (not across the page) so unrelated single-axis ruled lines don't add up to a false flag, and matches the stop directly rather than parsing whole gradient layers, since colors like oklch(...) carry nested parens. Extends the gpt-tells fixture with one flag case and two pass cases (single-axis rule, two-color blend), regenerates the browser detector bundle, and bumps the rule count 44 -> 45. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Require tiling background-size for codex-grid-background Address review: two hairline gradients alone draw a fixed crosshair, not a grid. Scope detection to a single style block (CSS rule body or inline style attr) and require both >=2 hairline stops AND a tiling `background-size` px cell in the same block, matching the skill rule's "plus background-size" wording. Add a crosshair-without-tiling pass case. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Scope codex-grid-background hairline count to background values Address review: count hairline stops only inside background/background-image declaration values, not the whole style block, so a hairline in an unrelated property (mask-image, border-image) can't stand in for the grid's second axis. Add a bg+mask-image hairline pass case. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
858b9bbea6 | Improve hook false-positive handling | ||
|
|
e3e22007a9 |
[codex] Improve detector false positive handling (#232)
* Improve detector false positive handling * Register docs integrity test * Fix clipped overflow decorative skip |
||
|
|
4e251061b8 | Fix sr-only text overflow false positive (#197) | ||
|
|
9ffd3211d5 |
Neo Kinpaku design system + Live Mode v3 (#169)
* Add neo kinpaku design system page * skill: rip out baked-in category recipes and saturated-default motion tropes Programmatic bias mining (impeccable-evals) traced four major defects back to specific lines in this skill that contradicted SKILL.md's own first-order-reflex warning: - brand.md "Pairing and voice" prescribed four category→aesthetic recipes (editorial → serif+sans, tech/dev/fintech → tight tracking, consumer/food/travel → script/display serif, creative → rule-break). These directly drove OpenAI's 76% extreme-negative letter-spacing on tech briefs and Anthropic/Google's 28-34% italic-serif-display slop on editorial/food briefs. Replaced with one sentence: the shape depends on the brand, not on the brand's category. - brand.md "Brand permissions" had "Typographic risk. Enormous display type, unexpected italic cuts, mixed cases, hand-drawn headlines, a single oversize word as a hero." — a four-for-one slop driver behind 97% OpenAI comically-large H1, 42% bad-SVG illustration, and the editorial-italic slop. Deleted outright. - typeset.md and teach.md repeated the same category recipes; trimmed to the principle without the recipe. - SKILL.md Typography: added a hard hero-H1 ceiling (clamp() max ≤ 6rem ≈ 96px), with a <codex> block to make it explicit since OpenAI over-indexes here (97% ≥128px vs 24% for Anthropic). - animate.md, bolder.md, brand.md: removed "staggered reveals" and "scroll-triggered transitions" as the prescribed default ambitious motion. By 2026 that's the saturated AI tell, not a choreography. Reserved stagger for legitimate list-sibling rhythm. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * skill: anti-cream + codex-specific defect bans + universal slop bans Second pass after measuring more biases against the eval corpus. - SKILL.md Color: explicit "cream/sand/beige body bg is the saturated AI default of 2026" rule. Tone down the "tint every neutral" line so it doesn't read as "default to warm-tinted near-white" (which OpenAI hits at 74% and Anthropic at 31%-47%). - SKILL.md Absolute bans: add universal bans for two slop patterns detected at 55-95% across providers — tiny uppercase tracked eyebrow above every section (the 2023-era kicker that's now AI grammar) and numbered section markers (01/02/03). Also explicit "text that overflows its container is the universal defect on tablet/mobile." - SKILL.md Absolute bans → <codex> block: ban the GPT-specific defects Paul annotated repeatedly — `border:1px solid` + soft-wide-shadow (≥16px blur) "ghost cards", `border-radius:32px+` over-rounding, hand-drawn/sketchy SVG illustrations (loose-sketch / *-sketch classes, feTurbulence paper-grain filters), repeating-linear-gradient stripes, "X theater" AI-slop copy phrases. - SKILL.md Motion → <gemini> block: the image :hover transform tell (38% Google skill-on rate). Hover effects on images add no info; the image isn't an action target. Animate card chrome, not the image. - SKILL.md Typography: hard display letter-spacing floor ≥-0.04em (OpenAI defaults to -0.075em → cramped). Existing hero ceiling <codex> block extended with the letter-spacing rule. - codex.md Step A example: stop seeding "warm-grounded (deep oxblood + cream)" as the warm-palette template, which primes the cream default. - colorize.md Tinted backgrounds: stop printing the literal cream recipe `oklch(97% 0.01 60)`; replace with brand-anchored guidance. - document.md examples: warm-ash-cream → cool-paper so the example doesn't seed cream as the canonical neutral example. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * skill: universal anti-slop bans + contrast/font-count/all-caps-body rules Third pass after measuring the rest of the cross-provider matrix: - Color: explicit "Verify contrast" rule. Low-contrast text fires at 68% across all providers skill-on (90+% off). The most common failure is muted gray body on a tinted near-white; light-gray-for- elegance is named as the single biggest cause of unreadable AI pages. - Typography: max-3-font-families rule. Overused-fonts (>4 families) fires at 28% Anthropic / 36% Google / 0% OpenAI skill-on; >50% off. Also: universal "no all-caps body copy" (moved from brand-only ban to Shared design laws since product-register also overuses caps). - Copy: anti-aphoristic-cadence ban targets Anthropic's signature "X. No Y." / "X. Just Y." voice (63% skill-on copy-slop rate, 77% off — the worst rate in the matrix). Once-is-voice / three-or-more- is-tell framing per the runner's copy-slop detector. - Copy: anti-SaaS-buzzword-string ban with the literal phrase list the detector watches for (streamline/empower/supercharge, trusted- by-leading, best-in-class/enterprise-grade/cutting-edge, etc). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * skill: strengthen anti-cream rule across full warm-neutral band Smoke validation showed the cream fix worked for Google + OpenAI but Anthropic Sonnet italian-restaurant still shipped `--paper: oklch(90% .018 88)` — cream just outside the L≥95% band the rule cited. Broaden the rule: - Band: OKLCH L 0.84-0.97, C < 0.06, hue 40-100 (was 95-97% / 60-95). - Name the token-name tells explicitly (paper / cream / sand / bone / flour / linen / parchment / wheat / biscuit / ivory) — the model defaults to one of these regardless of what hex it lands on. - Call out the specific brief patterns ("warm, traditional, family- coastal-Italian" / "editorial-restraint") that the model translates into cream by reflex. Then provide three explicit non-cream options: saturated brand color, true off-white at C=0, or darker mid-tone. Warmth in the brand is carried by accent + typography + imagery, not by body bg. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * v3.2.0: skill bias-fix release Bumps version from 3.1.1 to mark the four-commit skill cleanup that rips out baked-in category recipes (brand.md), saturated-default motion tropes (staggered reveals everywhere), the cream/sand body-bg AI tell, codex-specific defects (1px+wide-shadow, over-rounding, hand-drawn SVGs, stripes, X-theater copy), the extreme-letter-spacing default, and universal slop bans (all-caps eyebrow on every section, numbered-section markers, all-caps body, font-family-count > 3, aphoristic copy cadence, SaaS buzzword strings). Plus a hard hero-H1 ceiling (clamp() ≤6rem) and a Gemini-specific image:hover transform block. Validated against ~190 post-fix samples — see impeccable-evals biases tab for per-provider deltas. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * drop "no pure black/white" rule entirely The rule was contested in the design world and causing more damage than good — pushing every page into the tinted-near-white default which is the cream/sand AI tell we already explicitly ban elsewhere. Vercel, SVKMS, Brutalist sites, et al. use pure black/white successfully; the skill shouldn't second-guess that. Skill markdown deletions: - SKILL.md Color: drop the "Never use #000 or #fff" bullet. - color-and-contrast.md: drop the "Never Use Pure Gray or Pure Black" subsection, the "Never pure black" table-row prescription, and the "Avoid: Using pure black for large areas" bullet. - colorize.md: drop the "NEVER use pure black or pure white for large areas" bullet. - polish.md: drop the "Tinted neutrals: No pure gray or pure black" half of the bullet (the gray-on-color bullet survives). Detector code (cli/engine): - registry/antipatterns.mjs: remove the `pure-black-white` entry. - rules/checks.mjs: remove the three `findings.push({ id: 'pure-black-white', ... })` emit points (inline #000 bg, Tailwind bg-black class, plain-HTML scan path). - engines/regex/detect-text.mjs: remove the two pure-black-white regex rules (CSS `background: #000…` + Tailwind `bg-black`). - detect-antipatterns-browser.js: regenerated via scripts/build-browser-detector.js. Tests: - detect-antipatterns-fixtures.test.mjs: invert the assertion that pure-black-white fires; expect it to NOT fire post-v3.2. Drop the Tailwind bg-black-opacity edge-case test (no longer relevant). - detect-antipatterns.test.js: drop the standalone "detects pure- black-white in styled-components" test and remove pure-black-white from the multi-detector assertions in PricingCard, globals.css, and GlobalStyle.tsx tests. 166 bun tests pass; 24 node fixture tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * skill: strip example patterns from copy rules, strengthen gemini block v3.2 rerun validation surfaced two issues: 1. Copy-slop detector fires more on Gemini under v3.2 (48% → 84%) than under no-skill baseline. Root cause: the anti-aphoristic-cadence rule printed the literal "X. No Y." / "X. Just Y." patterns as examples, and Gemini imitated them as the recommended voice. Same recipe-becomes- bias trap we hit with brand.md:116's "Enormous display type, unexpected italic cuts, mixed cases, hand-drawn headlines" enumeration. Fix: describe the cadence as a rhythm ("serious statement, then punchy short negation") without printing literal patterns. Buzzword list trimmed to a single inline phrase family rather than quoted strings. 2. Gemini image:hover transform Gemini-tell hadn't dropped (31% off → 32% v3.2). Strengthen the <gemini> block: explicit "Never animate <img> elements on hover", call out the Tailwind group-hover:scale / group-hover:rotate / group-hover:translate parent-hover patterns by name (Gemini was reaching for these via Tailwind even though the prior text talked about :hover on the image directly). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * skill: simplify context loading and inline register directive Replaces load-context.mjs's JSON output with a tight markdown block from the renamed context.mjs. The script now extracts PRODUCT.md's `## Register` field and appends a `NEXT STEP:` directive naming the matching reference (brand.md / product.md), which moved Gemini from skipping the register load entirely to honoring it. Drops the `.impeccable.md` auto-migration; makes IMPECCABLE_CONTEXT_DIR a lazy escape hatch consulted only when the default paths come up empty. Setup is now four bullets in one list. The DESIGN.md nudge is gone; in its place, a "familiarize with the existing design system" step that calls out CSS / tokens / running app as authoritative sources alongside DESIGN.md. The standalone `### Register` H3 stays for the cascade rules (task cue → surface → register field). New LLM-backed test suite at tests/skill-behavior/ runs five scenarios against claude-haiku-4-5, gpt-5.4-mini, and gemini-3.1-flash-lite via Vercel AI SDK. Captures real tool traces, asserts on context.mjs calls, brand.md loads, and teach.md fallback. Skips cleanly when API keys are unset. 13-14/15 pass; only stable failure is the v3.2.0-era gpt-mini S4 "don't re-run" regression. Adds @ai-sdk/google as devDep and the test:skill-behavior npm script. Touches em-dashes in skill/SKILL.md and four reference files so `bun run build:skills` passes its skill-prose validator. teach.md and document.md drop their "re-run the loader to refresh session cache" steps since the agent's own write is now the freshest source. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * skill: merge orphan reference files into command sub-skills + inline S-tier invariants Two related restructurings: 1. SKILL.md now carries the cross-domain invariants that catch defects in any project (contrast/placeholder/gray-on-color, similar-font pairing, text-wrap, tabular-nums, centered-stack default, Flex/Grid choice, auto-fit grids, semantic z-index, reduced motion, stagger vs section-fade, premium motion materials, focus-visible, placeholders-aren't-labels, dropdown overflow trap, button/link copy). Greenfield-only rules (theme picking, color strategy, tinted neutrals) live under "New projects only". 2. Reference files merged into their command counterparts: - spatial-design.md -> layout.md - motion-design.md -> animate.md - color-and-contrast.md -> colorize.md - responsive-design.md -> adapt.md - ux-writing.md -> clarify.md - typography.md -> typeset.md (bolder.md redirected) - cognitive-load.md + heuristics-scoring.md + personas.md -> critique.md craft.md and shape.md "load references" lists updated to new file homes. interaction-design.md stays standalone (no 1:1 command verb). Net: 36 -> 27 reference files. Same content, fewer files, no orphaned reference loaded only from craft.md. Also extends the routing rules: if the user's first word doesn't match a command but the intent clearly maps to one, load that command's reference and proceed as if invoked. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * skill: add sub-command + existing-project scenarios; move sub-command load to step 2 Adds three new LLM-backed scenarios to tests/skill-behavior: - S6: `/impeccable polish` → loads polish.md - S7: `/impeccable audit` → loads audit.md - S8: existing SvelteKit project (PRODUCT.md + DESIGN.md + src/app.css + src/lib/components/*.svelte + src/routes/+page.svelte) → agent reads at least one project code file to understand the existing design system S6/S7 surface a real model-floor: gpt-5.4-mini reads brand.md, reads the target index.html, and just does the polish/audit without ever loading the sub-command reference. Stronger SKILL.md wording didn't move it. Captured in the README baseline as a known weakness. Claude and Gemini honor the load reliably. To fix Gemini on S6/S7, sub-command reference loading is now Setup step 2 (right after context.mjs), not step 4 — placing it before the model gets focused on "doing the work". Step 3 (design-system familiarization) is tightened to require at least one project code read even when a sub-command reference loads in step 2, so Claude doesn't laser-focus on the sub-command flow and skip the broader exploration. Two new fixtures: MINIMAL_LANDING_HTML (a tiny static landing page for S6/S7) and SVELTE_PROJECT_FILES (a minimal SvelteKit scaffold with tokens, components, and a routes/+page.svelte for S8). Both designed to look real enough that agents treat them as production code. Suite is now 24 tests across three providers; baseline is 21-22/24, with the stable failures being gpt-5.4-mini scenarios 6 and 7. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * skill: add reveal-animation safety rule (must enhance, not gate visibility) Class-triggered visibility transitions pause on hidden tabs and headless renderers. The italian-restaurant smoke produced a build where 2 sections shipped opacity:0 because the CSS transition never advanced past currentTime=0 (timeline paused). Added one-liner under Motion to prevent the antipattern: reveals must enhance an already-visible default, never gate content visibility on a class-triggered transition. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * skill: restore prescriptive cream/sand/beige paragraph Bisection across 5 historical skill commits on Gemini 3.5 flash fast lane n=3 found that |
||
|
|
e1d3ea0b6f |
Detector architecture v2: static engine, benchmarks, lab, and visual contrast (#156)
* Add detector benchmark lab and visual contrast fallback * Expand visual contrast fixture coverage * Add browser visual contrast fallback * Show visual contrast overlays in detector lab * Fix detector lab short viewport layout * Fix detector lab visual overlays * Add visual contrast to browser scan overlays * Avoid browser scroll jumps during visual contrast scans * Resolve visual contrast lazily on scroll * Refresh detector lab visual counts lazily * Update pnpm lockfile for static parser deps * Address Bugbot detector API comments * Report extension visual contrast errors * Refactor detector into engine modules * Address Bugbot detector comments * Fix latest Bugbot detector notes * Fix visual contrast fixture labels * Refine detector lab fixtures * Fix stale detector overlay references * Fix detector lab fixture URLs * Fix typography lab fixture highlights * Fix typography lab page-level signal * Fix visual overlay lifecycle cleanup * Remove dead spotlight timer cleanup * Make browser async APIs reject consistently |