Fix CLI docs and browser detector bugs

- Fix README CLI examples to use `bun bin/impeccable.mjs` instead of `npx impeccable`
- Fix classList.match() crash in Node/jsdom by converting DOMTokenList to string
- Fix overlapping page-level banners by merging all findings into a single banner

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Paul Bakaus
2026-03-20 18:15:37 -07:00
co-authored by Claude Opus 4.6
parent ce64404248
commit 122dddb686
3 changed files with 38 additions and 24 deletions
@@ -341,26 +341,27 @@ function checkColors(opts) {
// Tailwind class checks
if (classList) {
if (/\bbg-black\b/.test(classList)) {
const classStr = typeof classList === 'string' ? classList : Array.from(classList).join(' ');
if (/\bbg-black\b/.test(classStr)) {
findings.push({ id: 'pure-black-white', snippet: 'bg-black' });
}
const grayMatch = classList.match(/\btext-(?:gray|slate|zinc|neutral|stone)-\d+\b/);
const colorBgMatch = classList.match(/\bbg-(?:red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-\d+\b/);
const grayMatch = classStr.match(/\btext-(?:gray|slate|zinc|neutral|stone)-\d+\b/);
const colorBgMatch = classStr.match(/\bbg-(?:red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-\d+\b/);
if (grayMatch && colorBgMatch) {
findings.push({ id: 'gray-on-color', snippet: `${grayMatch[0]} on ${colorBgMatch[0]}` });
}
if (/\bbg-clip-text\b/.test(classList) && /\bbg-gradient-to-/.test(classList)) {
if (/\bbg-clip-text\b/.test(classStr) && /\bbg-gradient-to-/.test(classStr)) {
findings.push({ id: 'gradient-text', snippet: 'bg-clip-text + bg-gradient (Tailwind)' });
}
const purpleText = classList.match(/\btext-(?:purple|violet|indigo)-\d+\b/);
if (purpleText && (['h1', 'h2', 'h3'].includes(tag) || /\btext-(?:[2-9]xl)\b/.test(classList))) {
const purpleText = classStr.match(/\btext-(?:purple|violet|indigo)-\d+\b/);
if (purpleText && (['h1', 'h2', 'h3'].includes(tag) || /\btext-(?:[2-9]xl)\b/.test(classStr))) {
findings.push({ id: 'ai-color-palette', snippet: `${purpleText[0]} on heading` });
}
if (/\bfrom-(?:purple|violet|indigo)-\d+\b/.test(classList) && /\bto-(?:purple|violet|indigo|blue|cyan|pink|fuchsia)-\d+\b/.test(classList)) {
if (/\bfrom-(?:purple|violet|indigo)-\d+\b/.test(classStr) && /\bto-(?:purple|violet|indigo|blue|cyan|pink|fuchsia)-\d+\b/.test(classStr)) {
findings.push({ id: 'ai-color-palette', snippet: 'Purple/violet gradient (Tailwind)' });
}
}
@@ -1395,9 +1396,11 @@ if (IS_BROWSER) {
}
}
const pageLevelFindings = [];
const typoFindings = checkTypography();
if (typoFindings.length > 0) {
showPageBanner(typoFindings);
pageLevelFindings.push(...typoFindings);
allFindings.push({ el: document.body, findings: typoFindings });
}
@@ -1412,7 +1415,7 @@ if (IS_BROWSER) {
// Page-level quality checks (headings, etc.)
const qualityFindings = checkPageQualityDOM();
if (qualityFindings.length > 0) {
showPageBanner(qualityFindings);
pageLevelFindings.push(...qualityFindings);
allFindings.push({ el: document.body, findings: qualityFindings });
}
@@ -1420,10 +1423,14 @@ if (IS_BROWSER) {
const htmlPatternFindings = checkHtmlPatterns(document.documentElement.outerHTML);
if (htmlPatternFindings.length > 0) {
const mapped = htmlPatternFindings.map(f => ({ type: f.id, detail: f.snippet }));
showPageBanner(mapped);
pageLevelFindings.push(...mapped);
allFindings.push({ el: document.body, findings: mapped });
}
if (pageLevelFindings.length > 0) {
showPageBanner(pageLevelFindings);
}
printSummary(allFindings);
return allFindings;
};
+4 -4
View File
@@ -150,10 +150,10 @@ Most commands accept an optional argument to focus on a specific area:
Impeccable includes a standalone CLI for detecting anti-patterns without an AI harness:
```bash
npx impeccable detect src/ # scan a directory
npx impeccable detect index.html # scan an HTML file
npx impeccable detect https://example.com # scan a URL (Puppeteer)
npx impeccable detect --fast --json . # regex-only, JSON output
bun bin/impeccable.mjs detect src/ # scan a directory
bun bin/impeccable.mjs detect index.html # scan an HTML file
bun bin/impeccable.mjs detect https://example.com # scan a URL (Puppeteer)
bun bin/impeccable.mjs detect --fast --json . # regex-only, JSON output
```
The detector catches 25 issues across AI slop (side-tab borders, purple gradients, bounce easing, dark glows) and general design quality (line length, cramped padding, small touch targets, skipped headings, and more).
@@ -339,26 +339,27 @@ function checkColors(opts) {
// Tailwind class checks
if (classList) {
if (/\bbg-black\b/.test(classList)) {
const classStr = typeof classList === 'string' ? classList : Array.from(classList).join(' ');
if (/\bbg-black\b/.test(classStr)) {
findings.push({ id: 'pure-black-white', snippet: 'bg-black' });
}
const grayMatch = classList.match(/\btext-(?:gray|slate|zinc|neutral|stone)-\d+\b/);
const colorBgMatch = classList.match(/\bbg-(?:red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-\d+\b/);
const grayMatch = classStr.match(/\btext-(?:gray|slate|zinc|neutral|stone)-\d+\b/);
const colorBgMatch = classStr.match(/\bbg-(?:red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-\d+\b/);
if (grayMatch && colorBgMatch) {
findings.push({ id: 'gray-on-color', snippet: `${grayMatch[0]} on ${colorBgMatch[0]}` });
}
if (/\bbg-clip-text\b/.test(classList) && /\bbg-gradient-to-/.test(classList)) {
if (/\bbg-clip-text\b/.test(classStr) && /\bbg-gradient-to-/.test(classStr)) {
findings.push({ id: 'gradient-text', snippet: 'bg-clip-text + bg-gradient (Tailwind)' });
}
const purpleText = classList.match(/\btext-(?:purple|violet|indigo)-\d+\b/);
if (purpleText && (['h1', 'h2', 'h3'].includes(tag) || /\btext-(?:[2-9]xl)\b/.test(classList))) {
const purpleText = classStr.match(/\btext-(?:purple|violet|indigo)-\d+\b/);
if (purpleText && (['h1', 'h2', 'h3'].includes(tag) || /\btext-(?:[2-9]xl)\b/.test(classStr))) {
findings.push({ id: 'ai-color-palette', snippet: `${purpleText[0]} on heading` });
}
if (/\bfrom-(?:purple|violet|indigo)-\d+\b/.test(classList) && /\bto-(?:purple|violet|indigo|blue|cyan|pink|fuchsia)-\d+\b/.test(classList)) {
if (/\bfrom-(?:purple|violet|indigo)-\d+\b/.test(classStr) && /\bto-(?:purple|violet|indigo|blue|cyan|pink|fuchsia)-\d+\b/.test(classStr)) {
findings.push({ id: 'ai-color-palette', snippet: 'Purple/violet gradient (Tailwind)' });
}
}
@@ -1393,9 +1394,11 @@ if (IS_BROWSER) {
}
}
const pageLevelFindings = [];
const typoFindings = checkTypography();
if (typoFindings.length > 0) {
showPageBanner(typoFindings);
pageLevelFindings.push(...typoFindings);
allFindings.push({ el: document.body, findings: typoFindings });
}
@@ -1410,7 +1413,7 @@ if (IS_BROWSER) {
// Page-level quality checks (headings, etc.)
const qualityFindings = checkPageQualityDOM();
if (qualityFindings.length > 0) {
showPageBanner(qualityFindings);
pageLevelFindings.push(...qualityFindings);
allFindings.push({ el: document.body, findings: qualityFindings });
}
@@ -1418,10 +1421,14 @@ if (IS_BROWSER) {
const htmlPatternFindings = checkHtmlPatterns(document.documentElement.outerHTML);
if (htmlPatternFindings.length > 0) {
const mapped = htmlPatternFindings.map(f => ({ type: f.id, detail: f.snippet }));
showPageBanner(mapped);
pageLevelFindings.push(...mapped);
allFindings.push({ el: document.body, findings: mapped });
}
if (pageLevelFindings.length > 0) {
showPageBanner(pageLevelFindings);
}
printSummary(allFindings);
return allFindings;
};