mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-11 21:57:14 +03:00
2cfd60765a0e6c95cfecc3c5a2fd1bee78ea9bab
16
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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. |
||
|
|
886cd669ef |
Fix: compile closest() selectors once per document in the static scanner (#575) (#595)
StaticElement.closest() handed the raw selector string to css-select's is() on every ancestor step, recompiling the same selector N times for an element N levels deep. StaticDocument now caches one compiled matcher per selector (failed compiles cached as rethrowers so bad selectors still return null). Findings are byte-identical across the fixture corpus; scan time drops to ~62% on the fixtures and ~7x faster on deep-DOM pages. Prepared with AI assistance (Cursor agent), directed by @abdulwahabone. Co-authored-by: Cursor <cursoragent@cursor.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> |
||
|
|
ab9a29728b | Warn when static HTML parser dependencies are unavailable (#465) | ||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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. |
||
|
|
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>
|
||
|
|
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> |
||
|
|
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>
|
||
|
|
776c019041 |
Add inline, in-file ignore comments for the detector (#283) (#285)
* Add inline, in-file ignore comments for the detector (issue #283) Complement config ignores with eslint-disable-style waivers that live where they apply and travel with the file when it leaves the repo. The motivating case is a generated/exported standalone document that legitimately uses a first-party brand typeface (on the overused-font list) and is later scanned without .impeccable/config.json present. Marker is comment-syntax-agnostic (works in //, /* */, <!-- -->, #, {/* */}): impeccable-disable <rule>[, <rule>...] [-- reason | : reason] whole file impeccable-disable-line <rule>... same line impeccable-disable-next-line <rule>... next line Bare directive or * means every rule; reason is optional and discarded at scan time. Behavior is suppression, for parity with config ignores. Implementation: - New pure module cli/engine/shared/inline-ignores.mjs (parser + filter, no Node deps). Static-HTML findings have no line number, so only whole-file directives apply there -- exactly the standalone-document case; the regex/text engine additionally honors the line-scoped forms. - Wired into detectText and detectHtml, gated by options.inlineIgnores. - detect CLI applies inline ignores by default; --no-inline-ignores skips just them, --no-config skips config and inline ignores together. Docs: config.md (new section), detector.md, README. skill/reference/hooks.md reversed its prior "inline comments are not supported" guidance and now points the agent to inline waivers for the travels-with-the-file case. Changelog 3.x. Tests: tests/inline-ignores.test.mjs (parser units, detectText/detectHtml integration, CLI end-to-end), registered in the detector suite. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Reconcile design hook wording with inline ignores Two hook-side fixes prompted by review of the new inline-ignore feature: 1. Clean-ack steer line. The old line ("Keep typography hierarchy, spacing rhythm, and color contrast intentional on the next change.") read as an odd non-sequitur after "No anti-patterns." Reworded the whole clean ack to say what it means: a clean scan only clears the deterministic rule set, not overall design quality, so keep following the design system and skill guidance. Now: "Design hook scanned X. No deterministic design-quality issues found. That does not mean the design is good: keep following the project design system and the impeccable skill guidance." 2. Directive footer. It still told the agent "Do not add source comments such as `impeccable: ignore`; those pollute the code and do not suppress hook findings." That is now misleading: the hook runs the same detector engine as the CLI, which honors inline `impeccable-disable` waivers, so they DO suppress hook findings (consistent with config ignores, which filterFindings already honors). Reworded to: don't silence a real finding to skip fixing it; suppress only after the user confirms intent; prefer a config ignore, and reach for an inline `impeccable-disable <rule>` comment only when the waiver must travel with a file that leaves the repo. Added a hook test asserting an inline `impeccable-disable-line` comment makes the hook scan the file clean (locks in the cross-cutting behavior), and updated the clean-ack / footer assertions to the new wording. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Address review on inline-ignores parser - Case-insensitive fast-path bail-out (Cursor): the cheap substring guard was lowercase-only while DIRECTIVE_RE has the `i` flag, so a mixed-case marker like `Impeccable-Disable` skipped parsing entirely and never suppressed. Switched the guard to `/impeccable-disable/i.test(...)`. Added a regression test. - Removed the unreachable `-->` branch from TRAILING_CLOSER_RE (Greptile): `--+>` already matches `-->` and any longer dash run. - Replaced the always-truthy lazy-match + `if (sep)` reason strip with an explicit first-separator slice (Greptile): clearer and drops the dead branch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Align inline-ignore line numbering with the detector (CRLF/CR endings) parseInlineIgnores split lines with /\r\n|\r|\n/, but detectText numbers lines with split('\n'). On classic `\r`-only endings the two diverged, so a disable-line / disable-next-line directive could key a different line than the finding it should waive (Cursor review). Split on '\n' only, matching the detector exactly; the directive regex already excludes '\r', so a trailing '\r' on CRLF files is never captured into the rule list. Added a CRLF regression test through the real detectText. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
51d01e3a5f |
[codex] Add design-aware detector rules (#252)
* Add design-aware detector rules * Fix design-aware detector noise * Unify CLI and hook detector ignores * Fix remaining design-system review findings * Add detector ignore CLI * Fix design detector review findings * Fix design color source false positives * Fix core test suite registration * Add design-aware detector docs * Fix font priority design-system parsing * Fix color ignore value matching |
||
|
|
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 |