Skip to content

v2.0: Detection engine, Chrome extension, data-driven skill#56

Merged
pbakaus merged 173 commits intomainfrom
v2.0
Apr 8, 2026
Merged

v2.0: Detection engine, Chrome extension, data-driven skill#56
pbakaus merged 173 commits intomainfrom
v2.0

Conversation

@pbakaus
Copy link
Copy Markdown
Owner

@pbakaus pbakaus commented Mar 20, 2026

Summary

v2.0 of Impeccable. Three big themes stack on top of each other:

  1. A real anti-pattern detection layer — 25 deterministic rules with a CLI (npx impeccable detect), a browser overlay, and a Chrome DevTools extension. AI agents can now ground their design review in something other than vibes.
  2. A data-driven rewrite of the core skill (renamed from frontend-design to impeccable). Built against an internal eval framework that runs the same brief through frontier models with and without the skill loaded. Result: dramatically more font and color diversity, sharper overall design quality, and much stronger Codex support.
  3. A full docs site with top-level Skills, Anti-Patterns, Visual Mode, and Tutorials sections, 21 per-skill pages, 38 anti-pattern rule cards, and two end-to-end walk-throughs.

What's in this PR

Anti-pattern detection engine

  • 25 deterministic rules across typography, color, layout, motion, visual details, and quality. Handles oklch / oklab / lch / lab color formats, CSS variables inside border shorthands, gradient-backed text, and emoji-only nodes.
  • Dual-mode scanner: jsdom-based deep analysis (default) with regex-only fast mode (--fast) for huge codebases.
  • Universal core: a single source file (src/detect-antipatterns.mjs) auto-detects environment and adapts to Node and the browser. The browser bundle is generated from the same source at build time.

CLI: npx impeccable detect

Scans HTML, CSS, JSX/TSX, Vue, Svelte, and CSS-in-JS. Framework detection, multi-file import tracking, Puppeteer-backed live URL scanning, CI-ready JSON output, and a --fast regex mode.

Chrome DevTools extension

One-click detection on any page: yours, staging, production, or someone else's. Reads live computed styles, surfaces findings in an interactive panel, and highlights elements on the page. Currently in Chrome Web Store review.

/critique got teeth

  • Persona sub-agents review in parallel and score against Nielsen's heuristics.
  • Detector runs automatically as a separate pass to avoid biasing the LLM review.
  • Live browser overlay opens during the review so you can walk each finding in place.
  • Sub-agents now use isolated browser tabs to prevent interference.

Visual Mode (live browser overlay)

  • Standalone CLI (npx impeccable live) and inline use from /critique.
  • Overlays detected anti-patterns directly on the page, with positioning that survives ancestor CSS transitions and IntersectionObserver-aware hiding for non-rendered elements.
  • New /visual-mode top-level page demos it on synthetic slop pages.

Data-driven impeccable skill rewrite

  • Core skill renamed from frontend-design to impeccable.
  • Rebuilt against an internal eval framework (gpt-5.4 + Qwen 3.6 Plus across 15 niches) that measures how much the output collapses into monoculture.
  • Key learning baked into the skill: an anti-attractor procedure that forces the model to enumerate and reject its reflex defaults before picking. Plus tightened CSS bans on side-stripe accent borders and gradient text, theme-from-context guidance, principles inlined into SKILL.md so they always load.

New docs site

  • Top-level Docs, Anti-Patterns, and Visual Mode sections.
  • 21 per-skill pages with before/after demos and the canonical SKILL.md inline.
  • Two tutorials (getting started + critique-with-overlay walk-through).
  • 38 anti-pattern rule cards with inline visual examples.
  • Editorial wrappers, responsive sidebar, copy buttons on code blocks, polished header, sitemap covering all 29 canonical URLs.

New command: /shape

Interviews you about purpose, audience, and goals before any code is written, so the model has a brief in hand before the first line.

New harness: Rovo Dev

11 supported AI tools total.

Build / infrastructure

  • Shared transformer (shared.js) replaces per-provider duplication.
  • {{scripts_path}} build placeholder resolves per-provider.
  • Browser detector script auto-generated at build time from the universal source.
  • Cloudflare Pages config: _headers, _redirects, _routes.json generated by the build, with cache headers for hashed assets and SWR for HTML.
  • 600+ test assertions covering every detection rule, Puppeteer-backed fixture suite for browser-only rules, full skills CLI test coverage.

Test plan

  • bun run test — full unit + fixture suite passes (Puppeteer fixtures use --no-sandbox in CI).
  • bun run build — static site, all 11 provider bundles, both ZIPs, API JSON, and CF config produced cleanly.
  • bun bin/impeccable.mjs detect public/index.html — CLI runs end-to-end.
  • /critique — sub-agent flow, console-based overlay reading, persona scoring.
  • /visual-mode page renders live overlay on synthetic slop demos.
  • CI green: GitHub Actions test job + Cloudflare Pages preview deploy.

🤖 Generated with Claude Code

pbakaus and others added 30 commits March 17, 2026 10:54
… build DRY refactor

- Anti-pattern detector script (source/skills/critique/scripts/detect-antipatterns.mjs):
  CLI tool that scans files/dirs for UI anti-patterns via regex. Detects side-tab
  accent borders and border-accent-on-rounded patterns across Tailwind, CSS, JSX.
  Context-aware: skips safe elements (blockquotes, nav, inputs, code), neutral
  colors, and adjusts thresholds based on border-radius co-occurrence.

- Browser visualizer (public/js/detect-antipatterns-browser.js):
  Drop-in script that highlights anti-patterns directly in the browser with
  labeled overlays. Two modes: "static" (regex, matches CLI) and "computed"
  (getComputedStyle, catches CSS cascade). Scans both inline styles and
  <style> blocks.

- Gallery of Shame (public/gallery.html):
  Standalone page showcasing 11 AI anti-pattern examples with thumbnails
  and links. Anti-pattern example pages updated from 1080x1080 Twitter
  format to responsive layouts, labels removed, screenshots retaken at 16:10.

- Critique skill updated to run detector before manual review.

- Build system: skills now support scripts/ directories alongside reference/.
  All 8 provider transformers refactored to use shared.js (DRY).

- 58 new tests covering detection logic, fixtures, CLI integration.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…lat hierarchy

Three new detections:
- overused-font: flags Inter, Roboto, Open Sans, Lato, Montserrat, Arial
  as primary font-family or via Google Fonts imports
- single-font: file-level analyzer flags pages using only one non-generic
  font family (needs pairing for typographic hierarchy)
- flat-type-hierarchy: file-level analyzer collects all font-size values
  (px, rem, Tailwind text-* classes, clamp min/max) and flags when the
  max/min ratio is below 2.0

Detection engine extended to support file-level analyzers alongside
line-level matchers. Typography fixtures added for both should-flag
and should-pass cases.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Three detection tiers:
- file/dir (default): fast regex scan, zero dependencies
- file + --deep: jsdom computed styles, resolves linked local stylesheets
  by inlining <link rel="stylesheet"> content before parsing
- URL (https://...): auto-launches Puppeteer for full browser rendering,
  handles CDN stylesheets, JS-rendered content, everything

New exports: detectAntiPatternsDeep(), detectAntiPatternsUrl()
jsdom added as devDependency; puppeteer remains optional (npx cache).

TDD: linked-stylesheet fixture demonstrates the gap — regex finds 0
border issues, --deep correctly catches side-tab and top-accent from
the external CSS file.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Architecture simplified to two paths:
- HTML files: jsdom with getComputedStyle (resolves linked CSS, cascade)
- Non-HTML files: regex fallback (CSS, JSX, TSX, etc.)
- URLs: Puppeteer (unchanged)
- --fast flag forces regex-only for all files

Removed --deep flag (jsdom is now the default). Removed static mode
from browser script (always uses getComputedStyle — it's in a real
browser). Anti-pattern definitions split into:
- checkElementBorders() — shared element-level computed style checker
- checkPageTypography() — shared page-level checker
- REGEX_MATCHERS/REGEX_ANALYZERS — regex fallback for non-HTML

Browser script simplified from 470 lines to 250. CLI script reduced
from 810 lines to 440. Detection logic is now single-source for
jsdom/puppeteer/browser.

Fixtures now served via /fixtures/* route in dev server for proper
CORS handling of linked stylesheets.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Page-level typography checks (flat hierarchy, single font, overused font)
now only run on files that look like full pages (have <!DOCTYPE, <html>,
or <head> tags). Partials and components still get element-level border
checks.

isFullPage() strips HTML comments before checking to avoid false matches
on prose that mentions tag names.

Added partial-component.html fixture that has Inter, flat sizes, and a
side-tab border — verifies only the border is flagged.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Five new detections:
- pure-black-white: flags #000/#fff in styles via regex (jsdom bg
  resolution unreliable for this)
- gray-on-color: gray text (low chroma, mid luminance) on colored
  backgrounds via getComputedStyle + ancestor bg walk
- low-contrast: WCAG AA violation (4.5:1 body, 3:1 large text) via
  computed contrast ratio with resolved effective background
- gradient-text: background-clip:text + gradient combo via regex
  (jsdom doesn't compute background-clip)
- ai-color-palette: conservative purple/violet accent detection via
  regex on known hex values in prominent contexts

Background resolution handles jsdom limitation where background
shorthand isn't decomposed — falls back to parsing raw style attribute
for hex colors.

Color fixtures added for both should-flag (all 5 types) and
should-pass (tinted neutrals, good contrast, non-purple accents).

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Element-level (jsdom) and regex (--fast) detection for:
- bg-black, bg-white, text-black: pure black/white
- text-white without dark bg class: pure white on light
- text-gray-*/slate-*/zinc-* on bg-{color}-*: gray on colored bg
- text-purple-*/violet-*/indigo-* on headings/large text: AI palette
- from-purple-* to-indigo-*: purple gradient
- bg-clip-text + bg-gradient-to-*: gradient text (already existed)

text-white is NOT flagged when paired with a dark bg class (bg-black,
bg-gray-700+, bg-blue-500+, etc.) since that's intentional contrast.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Mirrors all 5 color anti-pattern checks from the CLI:
- pure-black-white (computed bg + Tailwind classes)
- gray-on-color (computed style + ancestor bg resolution + Tailwind)
- low-contrast (WCAG AA ratio via computed styles)
- gradient-text (computed background-clip + Tailwind bg-clip-text)
- ai-color-palette (computed hue analysis + Tailwind purple classes)

Browser version uses real getComputedStyle so bg resolution works
properly (unlike jsdom). Tailwind class checks are shared logic.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…cing, centering

Four new layout detections:
- nested-cards: jsdom DOM walk finds card-like elements (shadow + rounded + bg)
  nested inside other card-like elements. Excludes dropdowns (absolute/fixed),
  form inputs, code blocks, badges (<20 chars), and known component classes.
- identical-card-grid: detects grid/flex parents with 3+ children sharing the
  same structural fingerprint (icon + heading + paragraph template pattern).
- monotonous-spacing: regex on raw HTML collects padding/margin/gap values
  (px, rem, Tailwind classes), rounds to nearest 4px, flags when >60% use
  the same value with <=3 distinct values.
- everything-centered: regex counts text-align:center and Tailwind text-center
  on text elements, flags when >70% of 5+ text elements are centered.

Also narrowed pure-black-white to only flag #000 as background color —
text-black, text-white, bg-white, and #fff are no longer flagged (too
common, per user feedback).

Extensive should-pass fixture covers: shadcn card sub-components, cards
with form inputs/dropdowns/code blocks/badges/accordions/tabs/images,
pricing cards, varied spacing, mixed centered/left-aligned layouts.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Matches CLI behavior: only bg-black and computed #000 backgrounds are
flagged. bg-white, text-black, text-white, and #fff are no longer
flagged in the browser visualizer.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Adds checkLayout() with nested-cards and identical-card-grid detection
using computed styles and DOM tree walking. Same logic as CLI:
- isCardLike() checks shadow + rounded + bg/border (2 of 3)
- Excludes dropdowns, modals, tiny elements, safe tags
- Identical grid fingerprints icon + heading + paragraph structure

Layout findings are highlighted on the actual elements (not just
in the page banner).

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
- Nested cards: fixed dedup to use WeakSet on actual elements instead
  of tag-name key, so all nested card instances are found (not just
  the first div-in-div pair). Now catches all 4+ nesting examples.
- Dropped identical-card-grid: too many legitimate uses (data displays,
  pricing cards, navigation tiles) make false positives unavoidable.
- Removed from CLI, browser script, tests, and ANTIPATTERNS registry.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Same bug as the CLI had: Set with tag-name key ('DIV:DIV') deduped
all nested divs to one finding. Now uses WeakSet on actual elements
so each nested card instance gets its own outline.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
… fallback

Three fixes:
- Only flag innermost nested cards: if L1>L2>L3, only L3 gets flagged
  (not L2). Uses ancestor-filtering after collection pass.
- Lower text threshold from 20 to 10 chars to catch short card content
  like "Inner card via CSS."
- isCardLike now also checks raw inline style attribute for box-shadow
  and border-radius (jsdom doesn't resolve CSS shorthands). Tightened
  heuristic: shadow or border is mandatory (not optional).

Fixes false positive on layout-should-pass where a tinted subsection
(rounded + bg, no shadow) inside a card was incorrectly flagged.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
WeakSet.prototype[Symbol.iterator] doesn't exist — can't use for..of.
Changed to Set (same fix as CLI). Also updated isCardLike heuristic
to require shadow or border as mandatory, matching the CLI.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…dd browser tests

Major cleanup:
- detectUrl() now injects the browser script via page.evaluate() and
  calls window.impeccableScan() instead of reimplementing all detection
  logic inline. Removes ~80 lines of triple-duplicated code.
- Removed dead isPureBlackOrWhite function.
- CLI reduced from 1286 to 1212 lines.

New: Puppeteer-powered browser parity tests (detect-antipatterns-browser.test.js):
- Starts a local HTTP server for fixtures
- Loads fixture pages in headless Chrome
- Runs the browser detection script via impeccableScan()
- Verifies findings match expectations for all fixture categories:
  borders, colors, layout, typography, partials

8 new browser tests catch desync between CLI and browser script
(like the WeakSet iteration bugs we hit earlier).

puppeteer added as devDependency.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
DRY refactor:
- detect-antipatterns-core.mjs (297 lines): shared constants (SAFE_TAGS,
  OVERUSED_FONTS, GENERIC_FONTS, ANTIPATTERNS), color utilities (parseRgb,
  relativeLuminance, contrastRatio, hasChroma, getHue, colorToHex,
  isNeutralColor), and pure detection functions (checkBorders, checkColors,
  isCardLikeFromProps).
- CLI (889 lines, was 1212): imports from core, keeps jsdom-specific
  resolveBackground, page-level analyzers, regex fallback, and CLI logic.
- Browser wrapper (335 lines): template with browser-specific DOM adapters,
  highlighting, scan loop. Core is injected at build time.
- build-browser-detector.js: reads core, strips exports, injects into
  wrapper, writes to public/js/detect-antipatterns-browser.js (generated).
- Build step added to scripts/build.js (runs before Bun bundling).

Source of truth for detection logic is now the core module. Browser script
is generated — do not edit public/js/detect-antipatterns-browser.js directly.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
The generated browser detector now lives alongside the CLI script in
.claude/skills/critique/scripts/ — clearly a build artifact, not a
hand-maintained source file in public/js/.

- build-browser-detector.js outputs to .claude/ instead of public/js/
- Dev server serves .claude/skills/* for local testing
- All fixture and antipattern-example HTML files updated to new path
- Puppeteer detectUrl reads browser script from same directory
- Browser parity test server updated to serve from .claude/
- Deleted public/js/detect-antipatterns-browser.js

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
The skill sync wipes .claude/skills/ and re-copies from dist, deleting
the generated browser script. Moved build-browser-detector.js to run
AFTER the sync. Dev server's /js/* route now falls through to
.claude/skills/critique/scripts/ for built artifacts. All fixture HTML
references use /js/detect-antipatterns-browser.js (clean URL).

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Combine detect-antipatterns-core.mjs, detect-antipatterns.mjs, and
detect-antipatterns-browser-wrapper.js into a single universal file
that auto-detects browser vs Node via IS_BROWSER. Shared constants,
color utilities, and pure detection logic exist once instead of
being duplicated across files.

Build script simplified to strip @browser-strip-start/end markers,
set IS_BROWSER=true, and wrap in IIFE.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
New detections:
- bounce-easing: flags bounce/elastic animation names, animate-bounce
  (Tailwind), and cubic-bezier curves with overshoot (y values outside
  [0, 1])
- layout-transition: flags explicit transition of width, height, padding,
  margin, and max-height/min-width variants; skips transition: all
- dark-glow: flags colored box-shadow with blur > 4px on dark backgrounds
  (luminance < 0.1); skips gray shadows, focus rings (no blur), and
  non-dark backgrounds

Includes 48 new tests across unit, regex, and jsdom fixture tests with
dedicated should-flag and should-pass HTML fixtures for both categories.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Replace the buried "Gallery of Shame" inline link with a compact
detector summary card showing all 16 auto-detected anti-patterns
as chips grouped by category (Borders, Typography, Color, Layout,
Motion). Prominent gallery link in the footer. Revised lead copy
to mention the automated detector.

Keeps the tabbed Do/Don't pattern reference below unchanged.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Replace the detector summary card and badge system with a simple
inline note below the tabbed Do/Don't patterns: "/critique catches
all of these. 16 deterministically, the rest through LLM analysis."
Gallery of Shame and Suggest a pattern links sit inline alongside.

Subtle divider separates the note from the patterns above.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Extract all regex-on-HTML checks into shared checkHtmlPatterns()
function called by both browser and Node paths. Eliminates drift
between checkTypography/checkPageTypography and removes separate
checkPageMotion/checkPageGlow functions.

Add parseGradientColors() utility and checkElementGradientDOM()
to detect purple/violet gradient backgrounds on any element
including buttons (bypasses SAFE_TAGS). Fix false low-contrast
findings on gradient backgrounds by returning null from
resolveBackground when a gradient is encountered.

Fix "Only font:" double-colon in browser labels.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Detection improvements:
- Remove SAFE_TAGS from glow check (buttons/links with glows are valid)
- Add gradient color parsing (parseGradientColors) for AI palette
  detection on gradient backgrounds including buttons
- Detect cyan neon text on dark backgrounds as AI palette
- Resolve gradient backgrounds as dark for glow detection
- Fix pure-black false positive on semi-transparent overlays (a >= 0.9)
- Skip low-contrast/gray-on-color when background is a gradient
- Fix "Only font:" double-colon in browser labels

Test performance:
- Split jsdom fixture tests to Node's test runner (bun + jsdom hangs
  after ~13 instances due to resource leak)
- bun test for unit/regex/CLI tests (94 tests, 4s)
- node --test for jsdom fixtures (15 tests, 1.3s)
- Total: 109 tests in ~5s (was 280s+)

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
New detections (browser-only, DOM-based):
- line-length: text wider than ~85 chars per line
- cramped-padding: <8px padding in bordered/bg containers (2+ borders)
- tight-leading: line-height < 1.3x on body text
- small-target: interactive elements < 44x44px
- skipped-heading: heading levels that skip (h1 then h3)
- justified-text: text-align: justify without hyphens: auto
- tiny-text: font-size < 12px on body text (>20 chars)
- all-caps-body: text-transform: uppercase on >30 chars of body text
- wide-tracking: letter-spacing > 0.05em on non-uppercase body text

Browser overlay improvements:
- Hover swaps label for detail tooltip (CSS-based, not JS events)
- Border goes transparent on hover to reveal element underneath
- Fixtures: quality-should-flag.html and quality-should-pass.html

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
- Switch from border to CSS outline for overlays (cleaner, animatable)
- On hover: outline expands outward 4px, label shifts up to match,
  tooltip slides in from below with fade, z-index elevates above others
- Exclude page banner from hover transitions via .impeccable-banner class
- Reposition overlays on window resize via requestAnimationFrame
- CSS-only hover states (no JS event listeners)

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Usage: npx impeccable detect [file-or-dir-or-url...]

Subcommand structure designed for future expansion. The detect
subcommand delegates to the existing detection engine with all
its modes (jsdom, regex, Puppeteer).

- bin/impeccable.mjs: CLI entry point with bun shebang
- package.json: bin field added
- Export detectCli (main) from detection script
- Updated help text to show impeccable detect usage
- Updated CLAUDE.md and README.md with CLI docs

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
The critique skill now has three layers of detection:

1. CLI-first pass (always): Runs the deterministic detector via
   `node {{skills_dir}}/critique/scripts/detect-antipatterns.mjs --json`
   with scope checks (file count estimation, --fast for 200-500 files,
   user prompt for >500 files)

2. Browser visualization (when available): If the AI harness has
   browser automation (Chrome MCP, Cursor browser), injects the
   detection script into the page for live visual overlays. Reads
   the browser script via cat, injects via javascript_tool.

3. LLM analysis (always): The existing deep design critique across
   10 dimensions, now informed by deterministic scan results.

Add {{skills_dir}} placeholder to build system for cross-provider
script paths (.claude/skills, .cursor/skills, .gemini/skills, etc).

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
- Add trigger context to description ("Use when...")
- Compress scan workflow from 4 verbose steps to concise block
- Remove false "zero false negatives" claim, add false-positive guidance
- Avoid loading 55KB browser script into context (inject directly)
- Remove time-sensitive "2024-2025" reference
- Compress 10 evaluation dimensions to AI Slop (expanded) + single
  holistic review paragraph (Claude knows how to evaluate these)
- Revert {{skills_dir}} placeholder (relative paths per skill docs)

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
pbakaus and others added 26 commits April 8, 2026 11:38
Recognize "DO NOT" / "DO" lines (with optional colon) inside <rules>
and <absolute_bans> blocks, and make skillGuideline substring matching
case-insensitive so the validator handles the new XML-structured
SKILL.md without rejecting the refactored prose.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
The eyebrow ('Drag or hover to compare') was a sibling of
.split-comparison, sitting at the left edge of the outer .skill-demo
section. Because .split-comparison has 32px padding, the visible card
inside sat 32px to the right of the eyebrow, creating a visible
indentation mismatch. Move the eyebrow inside .split-comparison so it
inherits the same 32px offset and aligns with the card's left edge
(same as how .split-labels and .skill-demo-caption already align).

Note: the HTML order inside .split-comparison is now eyebrow -> container
-> labels -> caption, which matches the homepage's before/after demo
flow (card -> BEFORE/AFTER -> descriptive caption).
…ptions

The skill's "don't use Inter / don't use dark / don't center" negatives
were creating new attractors (the model picks Fraunces / light / grid
instead, every time). Inline always-applicable principles into SKILL.md,
add a font selection anti-attractor procedure that forces the model to
enumerate AND reject its reflex defaults, switch high-stakes blocks to
XML structure, tighten side-tab and gradient-text bans to specific CSS
patterns, ban Syne explicitly, and strip named font/color prescriptions
from the references. Validated against the internal eval framework on
Qwen 3.6 Plus across 7 niches: Fraunces dropped from 92% to 0% on kids
reading, side-tabs from 76% to 20% on vintage moto, no theme regressions.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Adds an Evals Framework section pointing future Claude sessions at
evals/AGENT.md (the comprehensive private guide) and inlines the
highest-leverage facts: primary baseline model is gpt-5.4 medium
reasoning, n=20 standard sample size, do not use Haiku as primary
target, always smoke test before sweep.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
The demo's Before/After labels and the descriptive caption were on two
separate rows below the card. Merge them into one row: Before pinned
left, caption centered in the middle, After pinned right.

- Move the caption <p> inside .split-labels between the two label spans.
  If a skill has no caption, emit an empty <span> placeholder so the
  grid still has three cells and Before/After sit at the edges.
- Switch .split-labels from flex space-between to a 3-column grid
  (auto minmax(0,1fr) auto) with baseline alignment. Before is
  justify-self: start, After is justify-self: end, caption is
  justify-self: center.
- Reset the caption's typography inside the grid (default body font,
  not mono; text-transform: none; letter-spacing: 0) since it inherits
  the label row's monospace caps by default.
Three docs sidebar improvements.

1. Collapsible mobile menu. The sidebar on narrow viewports used to
   dump 21 skill links and 2 tutorial links inline above the content,
   forcing a long scroll past the nav. Add a toggle button at the top
   of the sidebar that shows the current page label (e.g. "/overdrive"
   or "Getting started") plus a chevron, and collapses the menu behind
   it on mobile. Click the button to open/close. On desktop (>=920px)
   the toggle is hidden and the menu shows unconditionally as before.
   Pure aria-expanded state driven by a small delegated click handler
   in render-page.js.

2. Active-state breathing room. The left-border accent on the current
   sidebar item used to sit 2px from the text, which felt cramped. Pull
   the border 14px to the left via margin-left and push the text 12px
   to the right via padding-left. The net result: the accent bar sits
   in the layout gutter, the text keeps its alignment with the brand
   logo in the header, and there's now 12px of comfortable space
   between the border and the text.

3. Active state visibility. The same change makes the accent bar more
   visible on desktop, since it no longer hugs the text. 'aria-current'
   was already being set correctly on /skills/* and /tutorials/* pages;
   the bar just looked too subtle at 2px of clearance.
jsdom's CSSOM silently drops any border shorthand containing var(),
leaving the computed style empty — which hid the canonical real-world
side-tab pattern (border-left: Npx solid var(--brand)) from the Node
detector path. Real browsers resolve var() natively, so this only
affected the jsdom path.

Add a pre-pass that walks the stylesheets, reads border shorthands off
rule.style (jsdom preserves them there even when it drops them from
cssText), resolves var() against :root custom properties via the
documentElement's computed style, and attaches the result to a per-
element override map. checkElementBorders consults the map whenever
jsdom returned an empty width, or substitutes a resolved color when
jsdom kept a literal var() string. Hex and named colors are normalized
to rgb() so isNeutralColor can classify them correctly — without that,
--line:#e5e7eb slipped through as non-neutral.

Adds four flag cases and three pass cases to modern-color-borders.html
covering shorthand, mixed neutral+colored, border-right, card-shaped
label, neutral-resolving var, thin var, and uniform all-sides var.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
The previous attempt put the active-state border at margin-left: -14px
so it would sit in the layout gutter while keeping the link text
aligned with the header logo. Problem: .skills-sidebar uses
overflow-y: auto, and per CSS spec that coerces overflow-x from
visible to auto too, which clips any content outside the column. The
border was being painted and then clipped, so the user saw nothing.

Rework:
- Border now sits inside the normal flow. padding: 4px 0 4px 12px
  with a 2px border-left means link text is 14px inset from the column
  edge. Group titles pick up the same 14px padding-left so the two
  align vertically.
- Add a subtle accent-dim background on the active item (not just the
  border) so the cell reads as highlighted, not just marked.
- Add a hover background tint so items feel interactive.
- Remove the duplicate .skills-sidebar-list a[aria-current] block that
  was left over from the previous rewrite.

Trade-off: links are now 14px to the right of where the header logo
sits (before, they aligned). Worth it: the active state is now clearly
visible on both desktop and mobile.
The accent-dim background fill on active items was too loud. Keep
only the border-left accent, ink color, and bold weight. Hover tint
on other items still works as a subtle interactivity hint.
Expands the v2.0 entry from 5 flat bullets to 9 grouped highlights
and surfaces the additions the existing entry missed: the data-driven
skill rewrite (validated against the internal eval framework with
concrete per-niche metrics), the Chrome DevTools extension, the
rebuilt site and docs, /critique's persona sub-agents, Rovo Dev
support, and Apache 2.0 unification. Each item leads with a bold
label so the list stays scannable despite the length.

Hero version link tightened to signal the three most visible pieces
of the release (detection engine, Chrome extension, data-driven
skill) instead of just the detector.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Three additions to the anti-patterns catalog page, all sourced from a
new content/site/anti-patterns-catalog.js file so the user's parallel
edits to src/detect-antipatterns.mjs don't conflict with display metadata.

1. Detection layer badge per rule. Three layers:
     cli     - static analysis or jsdom. Runs from `npx impeccable detect`
               on files, no browser required. 23 of 25 current rules.
     browser - needs real browser layout (getBoundingClientRect).
               Runs via the browser extension or Puppeteer, not the
               plain CLI. Only 2 rules: cramped-padding and line-length,
               as documented in tests/detect-antipatterns-browser.test.mjs.
     llm     - no deterministic detector. Flagged by /critique's LLM
               review pass. 13 rules live only in the skill's DON'T list.
   Each card renders a mono pill with the layer label, color-coded per
   layer (neutral mist for CLI, blue tint for browser, amber tint for LLM).
   The How-to-read legend grows a dl explaining what each layer means.

2. Inline visual example per detected rule. All 25 detection rules get
   a ~140px tall preview area at the top of the card showing the bad
   pattern as live HTML (cream background, self-contained inline styles).
   Visuals for side-tab, gradient-text, dark-glow, nested-cards, and the
   rest let you see what the detector is actually flagging. LLM-only
   rules ship without visuals for now; their card bodies take the full
   card height.

3. LLM-only rules merged into the sections. Parsed out from
   source/skills/impeccable/SKILL.md DON'T lines that the detector
   doesn't cover: Syne, monospace-as-technical, dark-mode-default,
   everything-in-cards, identical-card-grids, hero-metric-layout,
   glassmorphism, sparkline-decoration, generic-drop-shadows,
   modal-reflex, every-button-primary, redundant-headers,
   mobile-amputation. Each renders like a detection rule card but
   shows the 'LLM only' layer badge and has no rule id chip. They
   slot into the same section groups as detected rules (Interaction
   and Responsive sections added to the section order so these get
   real headings).

- scripts/lib/sub-pages-data.js: imports the catalog, enriches
  detected rules with { layer, visual }, appends LLM_ONLY_RULES with
  layer: 'llm'. Re-exports LAYER_LABELS and LAYER_DESCRIPTIONS for
  the generator.
- scripts/build-sub-pages.js: renderRuleCard adds the visual block
  and the layer badge; LLM rules drop the rule id chip since their id
  is just an internal slug. groupRulesBySection now extends the
  primary order with whatever extra sections rules reference.
- public/css/sub-pages.css: .rule-card now has a .rule-card-visual
  preview area on top with border-bottom, body section below. New
  .rule-card-layer pill styling per layer. Layer legend dl using a
  2-column grid for badge -> description.

Dev server serves 38 total cards (25 detected + 13 LLM) across 8
sections: Visual Details, Typography, Color & Contrast, Layout & Space,
Motion, Interaction, Responsive, General quality.
… legend, sidebar divider fix

Six fixes from the first-pass review.

1. Visuals for all 13 LLM-only rules. The catalog now ships a preview
   snippet for every card: Syne-style display, monospace-as-technical,
   dark-mode-default, everything-in-cards (nested), identical card
   grids (literal 3x2), hero metric layout (big number + gradient +
   supporting stats), glassmorphism (backdrop-filter on a gradient),
   sparkline decoration, generic drop shadows (three rounded squares),
   modal reflex (backdrop + centered dialog), every-button-primary,
   redundant-headers, mobile-amputation. Every rule card now has the
   same ~160px preview treatment.

2. Lede font normalized to match skill detail pages. .sub-page-lede
   dropped from clamp(1.0625, 1.6vw, 1.25rem) to clamp(1, 1.4vw, 1.125rem)
   so the paragraph under the anti-patterns title is the same size as
   the tagline on every other /skills page.

3. "How to read this" legend collapsed into a <details> disclosure.
   Summary is a single compact row with the title + chevron, padding
   14px vertical. Body appears when opened, same content as before.
   Chevron rotates on open.

4. Visual example height bumped 140px -> 160px for more breathing
   room with the complex snippets.

5. Wider grid on the anti-patterns page. .anti-patterns-content no
   longer has a 820px max-width; only the header (720px max) and
   legend (720px max) are capped. The rule card grid fills the full
   main column width on wide viewports, so 38 cards stop wasting
   horizontal space.

6. Sidebar divider extends to the bottom of the viewport. Add
   min-height: calc(100vh - var(--site-header-height)) to .skills-sidebar
   so the sticky column fills the full viewport vertically regardless
   of content height, and the border-right reaches the footer.
Two fixes from the review.

1. Rule id chip hidden. The internal slugs (e.g. 'border-accent-on-rounded')
   are not useful to readers, only to detector code. Drop the
   .rule-card-id element from the card head entirely. The DOM id on
   the article stays so rules can still be anchor-linked.

2. Merge /gallery into /anti-patterns and drop 'Gallery' from the nav.
   'Gallery' in the top nav reads as 'things built with impeccable'
   when it is actually a curated collection of AI-generated UI in the
   wild — the complement to the rule catalog above.

   - Add GALLERY_ITEMS to content/site/anti-patterns-catalog.js
     (11 entries, same ids and copy as the old gallery.html)
   - Render a new 'In the wild' section at the bottom of
     /anti-patterns with a card grid of the 11 specimens, each linking
     to its standalone live example under /antipattern-examples/{id}.html
   - New .gallery-card CSS: square thumbnail, italic display title,
     charcoal body, hover lifts the card and tints the title accent
   - Add an 'In the wild' entry to the anti-patterns TOC sidebar so
     readers can jump to it
   - Drop the 'Gallery' link from the top-level nav in the shared
     header partial and the 4 hand-authored HTML pages. The old
     /gallery route still serves its page directly (for bookmarked
     links), but the nav no longer advertises it and the gallery page
     itself now marks Anti-Patterns as the active nav item.
The 'In the wild' section at the bottom of /anti-patterns was
mischaracterizing synthetic fixtures as real examples and was buried
deep in a taxonomy of detection rules. The specimens belong somewhere
that frames them as what they actually are: live pages you can click
into to experience Visual Mode. Split them off into a new top-level
page that also finally gives Visual Mode first-class treatment.

- New /visual-mode page, top-level nav item, single-column layout (no
  sidebar). Structure:
    1. Editorial header with an "Live detection overlay" eyebrow.
    2. Live iframe embed of visual-mode-demo.html inside mac-window
       chrome, same preview component the homepage uses.
    3. "Three ways to run it" section with three method cards:
         - /critique runs the overlay inside its browser pass
         - `npx impeccable live` starts a standalone overlay server
         - Chrome extension, marked coming soon, with a cream bg
    4. "Try it live" gallery of the 11 synthetic specimens as
       clickable cards. Each links to /antipattern-examples/{id}.html
       where the detector script is already injected so the reader
       lands on a live overlay.
- scripts/build-sub-pages.js: new renderVisualModeMain(); visualMode
  added to outDirs; generator loop writes /visual-mode/index.html.
- server/index.js: new /visual-mode route serving the generated file.
- Top nav on every page gains 'Visual Mode' between Anti-Patterns
  and the GitHub pill. Updated the partial + all 4 hand-authored
  HTML pages.
- .gitignore adds public/visual-mode/.

- /anti-patterns: 'In the wild' section and its TOC entry removed.
  Replaced with a one-line pointer at the end of the lede: "Want to
  see them live on real pages? Try Visual Mode." GALLERY_ITEMS stays
  in the catalog file (now used by /visual-mode only).

- public/css/sub-pages.css: new .visual-mode-page-body + .visual-mode-*
  classes. Ports the mac-window chrome (dots + mono title) from
  main.css, adds three-card method grid, and reuses the existing
  .gallery-card styles for the specimen list.

Clean up a few em-dashes in the catalog (block comments + one visible
visual example) so the build-time validator stays clean.

Server restart required to pick up the new /visual-mode route.
Replaces the brand-only card with a split layout: wordmark left,
floating Chrome extension detection panel right. Generator now counts
user-invocable, non-deprecated skills from source/skills/ (v2.0 unified
structure) instead of the removed source/commands/ directory.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
The min-height: calc(100vh - var(--site-header-height)) added earlier
so the sticky sidebar's border-right divider reaches the bottom of
the viewport on desktop was applying on mobile too. On mobile the
sidebar is static (not sticky) and collapses behind a toggle, so the
min-height reserved a full viewport of empty space above the main
content whenever the menu was collapsed. The result: opening
/anti-patterns on mobile showed just the 'Sections' dropdown in the
first screen, then a blank viewport, then the rules below the fold.

Wrap the min-height rule in a min-width: 921px media query so it only
applies on desktop, matching the breakpoint that switches the layout
to the two-column grid.
Tightened the v2.0 changelog on the homepage. Same information density,
fewer words, no em dashes, and dropped what does not concern users.

- Skill rewrite bullet: same numbers, shorter framing.
- Detection engine bullet: dropped the 'hard-to-hit cases that slip
  past regex-only scanners' flourish at the end.
- CLI bullet: collapsed parenthetical clauses into short phrases.
- Chrome extension bullet: replaced the em dash with a colon.
- /critique bullet: tightened.
- /shape bullet: replaced the em dash with a period break.
- "Rebuilt site and docs" renamed to "New docs site" and trimmed to
  just what users experience (top-level sections, skill pages,
  tutorials, rule cards). Dropped the 'mobile experience overhauled'
  line — implementation detail, not a user-facing feature.
- Licensing bullet: renamed to 'Apache 2.0 throughout'.
Impeccable has always been Apache 2.0; the back-and-forth on licensing
was internal to the v2.0 PR and is not a user-facing change.
Drop the metric-heavy framing and lead with the user-facing wins
(font/color diversity, design quality, Codex support) plus a brief
nod to the eval framework and anti-attractor technique.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Call out the frontend-design to impeccable rename on its own (and the
/teach-impeccable to /impeccable teach move), and reframe the /shape
bullet to cover both /shape and /impeccable craft as the new ways to
create with Impeccable.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Two unrelated breakages were stacking on the v2.0 PR:

1. The static site build crashed because the generated /visual-mode page
   referenced images via root-absolute paths (/antipattern-images/*.png).
   Bun's HTML loader resolves <img src> at build time relative to the
   source HTML file and treats a leading slash as filesystem-absolute, so
   it could not find the images. Use a relative path so Bun bundles and
   hashes them the same way the homepage already does.

2. The Puppeteer-backed fixture tests crashed in GitHub Actions because
   the Ubuntu runners block unprivileged user namespaces, so Chrome's
   sandbox cannot initialize. Pass --no-sandbox / --disable-setuid-sandbox
   only when process.env.CI is set, so local users keep the hardened
   default launch.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…page

The /visual-mode sub-page rules added in 27d1b13 duplicated
.visual-mode-preview (and its header/dot/title children) in
sub-pages.css with a max-width + margin: 0 auto. Because sub-pages.css
loads after main.css on index.html, those styles won on the homepage
too. Auto margins on a grid item disable justify-self: stretch, so the
preview collapsed to the iframe's 300px intrinsic width instead of
filling its 3fr cell in .visual-mode-demo.

Scope the rules to .visual-mode-page so they only apply on the sub-page
and the homepage falls back to main.css's .visual-mode-preview rule.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
When Bun.build aggregates resolution failures, the thrown error keeps the
real causes on error.errors (an array). The previous handler only printed
error.message and error.stack, both of which are generic / undefined for
this kind of failure, so CI logs read as 'Bundle failed / undefined' with
no clue what was unresolved. Walk error.errors first so the actual file +
import that failed shows up in CI output.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Cloudflare Pages ships an older Bun than the one used locally. That
version emits shared CSS chunks via the default 'chunk-[hash]' naming
template, but the [hash] token isn't always populated when the chunk
is shared across multiple HTML entrypoints — every sub-page that
imports sub-pages.css ends up wanting the same './chunk-' filename
and the build aborts with 'Multiple files share the same output path'.

Pin the chunk and asset naming explicitly so [hash] is always present
regardless of Bun version.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Bundles recent CLI/detector work that landed on v2.0 since 2.0.6:
side-tab border detection on oklch/oklab/lch/lab and CSS variables,
emoji-only handling in contrast/icon-tile rules, asymmetric
font-size-aware cramped-padding rule, full anti-pattern names in
overlay labels, and the CI sandbox flags for Puppeteer fixture tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
The Bun shipped with Cloudflare Pages doesn't dedupe shared CSS chunks
across HTML entrypoints — each entry tries to emit its own copy. With
chunk: '[name]-[hash].[ext]', three sub-pages all named index.html
(skills/, tutorials/, anti-patterns/) plus shared CSS content end up
producing chunks with identical name+hash and the build aborts on
'Multiple files share the same output path'.

Including [dir] in the chunk and asset templates scopes each chunk to
its entry's source directory, so the per-entry copies land in unique
paths even when dedupe is off. Local Bun (1.3.x) still emits a single
shared chunk because [dir] is only used when there are multiple chunk
candidates.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@pbakaus pbakaus changed the title v2.0: Anti-pattern detection system v2.0: Detection engine, Chrome extension, data-driven skill Apr 8, 2026
@pbakaus pbakaus merged commit e7afda2 into main Apr 8, 2026
2 checks passed
@pbakaus pbakaus deleted the v2.0 branch April 8, 2026 21:57
@PeterHollens
Copy link
Copy Markdown

I CAN'T WAIT!!!!!!!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants