diff --git a/.agents/skills/impeccable/SKILL.md b/.agents/skills/impeccable/SKILL.md index 586e026b8..1e464af87 100644 --- a/.agents/skills/impeccable/SKILL.md +++ b/.agents/skills/impeccable/SKILL.md @@ -56,6 +56,9 @@ Choose fonts that are beautiful, unique, and interesting. Pair a distinctive dis **DON'T**: Use overused fonts like Inter, Roboto, Arial, Open Sans, or system defaults **DON'T**: Use monospace typography as lazy shorthand for "technical/developer" vibes **DON'T**: Put large icons with rounded corners above every heading. They rarely add value and make sites look templated +**DON'T**: Use only one font family for the entire page. Pair a distinctive display font with a refined body font +**DON'T**: Use a flat type hierarchy where sizes are too close together. Aim for at least a 1.25 ratio between steps +**DON'T**: Set long body passages in uppercase. Reserve all-caps for short labels and headings ### Color & Theme → *Consult [color reference](reference/color-and-contrast.md) for OKLCH, palettes, and dark mode.* @@ -84,6 +87,7 @@ Create visual rhythm through varied spacing, not the same padding everywhere. Em **DON'T**: Use the hero metric layout template (big number, small label, supporting stats, gradient accent) **DON'T**: Center everything. Left-aligned text with asymmetric layouts feels more designed **DON'T**: Use the same spacing everywhere. Without rhythm, layouts feel monotonous +**DON'T**: Let body text wrap beyond ~80 characters per line. Add a max-width like 65–75ch so the eye can track easily ### Visual Details **DO**: Use intentional, purposeful decorative elements that reinforce brand diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index a3ae97b4a..30c7d32a8 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -2,7 +2,7 @@ "$schema": "https://anthropic.com/claude-code/marketplace.schema.json", "name": "impeccable", "metadata": { - "description": "Design fluency for AI harnesses. 1 skill, 22 commands, and curated anti-patterns for impeccable frontend design." + "description": "Design fluency for AI harnesses. 1 skill, 21 commands, and curated anti-patterns for impeccable frontend design." }, "owner": { "name": "Paul Bakaus", @@ -11,7 +11,7 @@ "plugins": [ { "name": "impeccable", - "description": "Design vocabulary and skills for frontend development. Includes 22 commands (/polish, /distill, /audit, /typeset, /overdrive, etc.) and an enhanced impeccable skill with curated anti-patterns.", + "description": "Design vocabulary and skills for frontend development. Includes 21 commands (/polish, /distill, /audit, /typeset, /overdrive, etc.) and an enhanced impeccable skill with curated anti-patterns.", "version": "2.0.6", "author": { "name": "Paul Bakaus", diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index f38859d73..7049f0ee0 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "impeccable", - "description": "Design vocabulary and skills for frontend development. Includes 22 commands (/polish, /distill, /audit, /typeset, /overdrive, etc.) and an enhanced impeccable skill with curated anti-patterns.", + "description": "Design vocabulary and skills for frontend development. Includes 21 commands (/polish, /distill, /audit, /typeset, /overdrive, etc.) and an enhanced impeccable skill with curated anti-patterns.", "version": "2.0.6", "author": { "name": "Paul Bakaus", diff --git a/.claude/agents/anti-patterns.md b/.claude/agents/anti-patterns.md new file mode 100644 index 000000000..1ad860923 --- /dev/null +++ b/.claude/agents/anti-patterns.md @@ -0,0 +1,278 @@ +--- +name: anti-patterns +description: Use when adding, modifying, or debugging an anti-pattern detection rule in this repo. Walks through the TDD recipe, the rule schema, all five plug-in points, jsdom constraints, the cross-validation step against the impeccable skill, and the post-implementation checklist. Trigger this for any work touching src/detect-antipatterns.mjs, tests/fixtures/antipatterns/, or extension/detector/. +tools: Read, Edit, Write, Glob, Grep, Bash, mcp__claude-in-chrome__navigate, mcp__claude-in-chrome__javascript_tool, mcp__claude-in-chrome__tabs_context_mcp, mcp__claude-in-chrome__tabs_create_mcp +--- + +# Anti-Pattern Engine Maintenance + +This agent handles every step of adding or modifying an anti-pattern detection rule in the impeccable repo. The rule engine is wired into many places and a single source-of-truth design ties them together. Skip any step at your peril — the build's cross-validator will fail loudly if drift slips in. + +## The five things that need to stay in sync + +When you add a rule, all of these update or get regenerated: + +| Where | What | How it stays in sync | +|---|---|---| +| `src/detect-antipatterns.mjs` `ANTIPATTERNS` | Rule metadata (id, category, name, description, skillSection, skillGuideline) and the detection logic (`checkXxx`) | **Hand-edited.** Source of truth. | +| `src/detect-antipatterns-browser.js` | Browser-bundled engine for the public site overlay | Generated by `bun run build:browser` | +| `extension/detector/detect.js` | Browser-bundled engine for the Chrome extension | Generated by `bun run build:extension` | +| `extension/detector/antipatterns.json` | Rule list (id, name, category, description) for the extension's devtools panel — drives rule toggles UI | Generated by `bun run build:extension` | +| `public/js/generated/counts.js` | `DETECTION_COUNT` integer for homepage display | Generated by `bun run build` | +| `source/skills/impeccable/SKILL.md` | Per-rule **DON'T** line in the right `### Section` — taught to users via the impeccable skill | **Hand-edited.** Validator catches drift. | + +The CLI (`bin/cli.js`) imports `ANTIPATTERNS` directly from `src/detect-antipatterns.mjs` — no separate sync needed. + +## Rule schema + +Each entry in the `ANTIPATTERNS` array (around src/detect-antipatterns.mjs:77) looks like this: + +```js +{ + id: 'icon-tile-stack', // kebab-case, unique, stable + category: 'slop', // 'slop' or 'quality' (see below) + name: 'Icon tile stacked above heading', // human-readable, used in extension UI + description: // 1–2 sentences. Used in CLI output, extension tooltips, web overlay labels + 'A small rounded-square icon container above a heading is the universal AI feature-card template — every generator outputs this exact shape. Try a side-by-side icon and heading, or let the icon sit in flow without its own container.', + skillSection: 'Typography', // OPTIONAL but strongly recommended. Must be one of the parser's allowed sections (see below) + skillGuideline: 'large icons with rounded corners above every heading', // OPTIONAL but strongly recommended. Substring that must appear in some **DON'T**: line of the named section in source/skills/impeccable/SKILL.md +} +``` + +### Categories + +- **`slop`** = "AI tells". Patterns that scream *AI generated this*. Things like purple gradients, gradient text, dark glow accents, thick side borders, icon-tile-stacks. Flagging these is about taste and freshness, not correctness. +- **`quality`** = real design or accessibility issues regardless of who wrote the code. WCAG contrast, line length, padding, line height, justified text, skipped headings, etc. + +If you're not sure, ask: *"would a human designer who's careful and tasteful still ship this?"* If no, it's `quality`. If they would (because it works fine, it just looks templated), it's `slop`. + +### `skillSection` allowed values + +These are the section names the `readPatterns()` parser at scripts/lib/utils.js:221 understands. Use **exactly** these strings (note the parser normalizes `Color & Theme` → `Color & Contrast`): + +``` +Typography +Color & Contrast +Layout & Space +Visual Details +Motion +Interaction +Responsive +UX Writing +``` + +### `skillGuideline` substring + +A 3–6 word substring that appears verbatim in some `**DON'T**:` line of the named section in `source/skills/impeccable/SKILL.md`. The build validator checks this with `String.includes()`. Pick a substring that's: + +- Short enough that benign rewordings of the DON'T won't break it +- Specific enough that it can't accidentally collide with an unrelated DON'T + +Examples: `'AI color palette'`, `'large icons with rounded corners above every heading'`, `'WCAG AA contrast'`. + +If a rule genuinely doesn't deserve a skill DON'T (rare — only the most niche a11y-only rules), omit both `skillSection` and `skillGuideline`. The validator skips rules without `skillGuideline`. + +## The TDD recipe (always do it in this order) + +This order is non-negotiable. Fixture and failing test before implementation. The full suite must run between the rule going in and you committing. + +### 1. Write the fixture (two-column convention) + +A single HTML file at `tests/fixtures/antipatterns/{rule-id}.html` with two columns: left = should-flag, right = should-pass. Each test case carries a unique heading text so the test can match snippets back to expectations. + +Convention skeleton: + +```html + + + + + + +
+
+

Should flag

+ +
+
+

Should pass

+ +
+
+ + + +``` + +The script tag at the bottom is critical — it lets you load the fixture in the browser via `http://localhost:3000/fixtures/antipatterns/{rule-id}.html` (served by `server/index.js:62` route for `/fixtures/*`). + +**Should-pass cases must cover the false-positive shapes you can think of in advance.** A good fixture has 5+ pass cases. The icon-tile-stack fixture covers: round avatar, wide thumbnail, side-by-side, no-icon, too-tiny, too-huge. + +### 2. Write the failing test + +Add to `tests/detect-antipatterns-fixtures.test.mjs` in its own `describe` block. Use the snippet-substring matching pattern — the test parses heading text out of each finding's snippet and asserts membership against expected lists: + +```js +describe('detectHtml — {rule-id}', () => { + const SHOULD_FLAG = ['Heading One', 'Heading Two', /* ... */]; + const SHOULD_PASS = ['Pass Heading One', /* ... */]; + + it('{rule-id}: flags only the should-flag column', async () => { + const f = await detectHtml(path.join(FIXTURES, '{rule-id}.html')); + const flagged = new Set(); + for (const r of f) { + if (r.antipattern !== '{rule-id}') continue; + const m = (r.snippet || '').match(/"([^"]+)"/); + if (m) flagged.add(m[1]); + } + for (const text of SHOULD_FLAG) { + assert.ok(flagged.has(text), `expected "${text}" to be flagged`); + } + for (const text of SHOULD_PASS) { + assert.ok(!flagged.has(text), `"${text}" should NOT be flagged`); + } + }); +}); +``` + +For this to work, the rule's snippet **must include the heading text in quotes**. See "Snippet conventions" below. + +Run `node --test tests/detect-antipatterns-fixtures.test.mjs` and **watch it fail**. If it doesn't fail, your test is wrong. + +### 3. Add the rule definition + +Add a new entry to the `ANTIPATTERNS` array in `src/detect-antipatterns.mjs`. Place it in the right category section (slop or quality). Fill in all fields including `skillSection` and `skillGuideline`. + +### 4. Implement the pure check function + +Add a `checkXxx(opts)` function alongside the others (`checkColors`, `checkBorders`, `checkMotion`, `checkGlow`, `checkIconTile`, etc.). The pure function takes a plain options object — no DOM access — and returns an array of `{ id, snippet }`. This makes it testable and reusable across the browser/Node adapters. + +Example shape (see `checkIconTile` in src/detect-antipatterns.mjs for a real one): + +```js +function checkXxx(opts) { + const { tag, /* whatever fields the rule needs */ } = opts; + if (SAFE_TAGS.has(tag)) return []; + // ... your detection logic ... + if (matches) { + return [{ id: 'rule-id', snippet: `... "${headingText}"` }]; + } + return []; +} +``` + +### 5. Add the two adapters + +Two adapters wrap the pure function with environment-specific input gathering: + +- **`checkElementXxxDOM(el)`** — for the browser. Uses `getComputedStyle(el)` and `el.getBoundingClientRect()`. +- **`checkElementXxx(el, tag, window)`** — for jsdom (Node). Uses `window.getComputedStyle(el)` and **must read explicit pixel dimensions from `parseFloat(style.width)`** instead of bounding rects, because **jsdom does not lay out** — `getBoundingClientRect()` returns 0×0 for everything. + +If your rule needs vertical positioning info (e.g. "icon must be above heading"), that check is browser-only — gate it behind `if (headingTop && siblingBottom)` so the Node path skips it. The structural checks alone (sizes, sibling identity, classes) are enough for the fixture. + +### 6. Wire into both element-iteration loops + +Two loops iterate every element on the page. You need to add your DOM-adapter call to **both**: + +- **Browser loop** at src/detect-antipatterns.mjs:1837 (`for (const el of document.querySelectorAll('*'))` with the `findings` spread). Add a line like: + ```js + ...checkElementXxxDOM(el).map(f => ({ type: f.id, detail: f.snippet })), + ``` +- **Node (jsdom) loop** at src/detect-antipatterns.mjs:2058 (in `detectHtml`). Add a block like: + ```js + for (const f of checkElementXxx(el, tag, window)) { + findings.push(finding(f.id, filePath, f.snippet)); + } + ``` + +Forgetting one of these is the most common mistake — the test passes but the live page doesn't show anything (or vice versa). + +### 7. Add the SKILL.md DON'T line if the rule doesn't reuse an existing one + +Open `source/skills/impeccable/SKILL.md`, find the right `### Section`, and add a **DON'T** line that contains your `skillGuideline` substring verbatim. Match the style of existing DON'Ts (terse, prescriptive, 1–2 sentences max). + +If your rule reuses an existing DON'T (e.g. multiple engine rules can map to the same skill guidance, like `side-tab` and `border-accent-on-rounded` both pointing to `'thick colored border on one side'`), no skill edit is needed. + +### 8. Run the build (this regenerates everything and validates) + +```bash +bun run build && bun run build:browser && bun run build:extension +``` + +This regenerates: +- `src/detect-antipatterns-browser.js` (public-site detector) +- `extension/detector/detect.js` (extension detector) +- `extension/detector/antipatterns.json` (extension rule list, includes description) +- `public/js/generated/counts.js` (DETECTION_COUNT) + +And validates: +- Cross-checks every rule with `skillGuideline` against `source/skills/impeccable/SKILL.md` via `validateAntipatternRules()` in scripts/build.js. **Build fails if drift exists.** + +### 9. Run the test suite + +```bash +bun run test +``` + +166 unit tests + N fixture tests, including your new one. All should be green. + +### 10. Verify on a live page in the browser + +Don't skip this. The jsdom path uses `parseFloat(style.width)` and the browser path uses `getBoundingClientRect()` — they can disagree. The fixture test catches one path; manual browser verification catches the other. + +``` +http://localhost:3000/fixtures/antipatterns/{rule-id}.html +http://localhost:3000/antipattern-examples/{your-example}.html (if relevant) +http://localhost:3000/ (no false positives on real pages) +``` + +Use the chrome MCP tools (`mcp__claude-in-chrome__navigate` + `mcp__claude-in-chrome__javascript_tool`) to inject `window.impeccableScan()` and read `.impeccable-overlay` / `.impeccable-label` from the DOM to verify. Don't try to screenshot — the overlays are decorative; read them programmatically. + +## Snippet conventions + +The fixture-test convention extracts the heading text from a finding's snippet using regex `/"([^"]+)"/` — so **wrap the identifying heading text in straight double quotes** in your snippet. Examples: + +- `'80x80px icon tile above h3 "Lightning Fast"'` +- `'4.5:1 (need 4.5:1) — text #808080 on #3b82f6'` ← uses element identifiers instead, since this rule isn't anchored to a heading + +If your rule isn't naturally anchored to a heading, pick another stable identifier (a class name, the parent element's text, etc.) and document the test pattern in the test itself. + +## jsdom constraints (the most common gotcha) + +- **No layout.** `getBoundingClientRect()` returns `0×0` always. Read `parseFloat(style.width)` and `parseFloat(style.height)` instead — jsdom does honor explicit pixel widths in ` + + +
+ + +
+

Should flag

+ +
+
+ + + +
+

Lightning Fast

+

Built for speed and performance from the ground up.

+
+ +
+
+ + + +
+

Secure Storage

+

Enterprise-grade security for your peace of mind.

+
+ +
+
+ + + + +
+

Easy Setup

+

Get started in minutes with zero configuration.

+
+ +
+
+ + + + + +
+

Powerful Analytics

+

Insights that drive smarter business decisions.

+
+
+ + +
+

Should pass

+ + +
+
+ + + + +
+

Sarah Chen

+

Product designer based in Berlin.

+
+ + +
+
+

Article Headline

+

A summary of an interesting article.

+
+ + +
+
+
+ + + +
+
+

Inline Side By Side

+

Icon sits next to the heading, not above it.

+
+
+
+ + +
+

Plain Heading No Icon

+

Just a heading with body text, nothing above.

+
+ + +
+
+ + + +
+

Tiny Icon Above Me

+

The icon container is smaller than 32px.

+
+ + +
+
+

Huge Hero Image

+

The container is larger than the size cap.

+
+
+
+ + +