mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-11 21:57:14 +03:00
Add codex-grid-background detector rule (#328)
* Add codex-grid-background detector rule Detects the Codex two-axis grid-line background tell: a single background value carrying two or more hairline `linear-gradient(... 1px, transparent 1px)` layers (one per axis), usually paired with a repeating `background-size` cell. Gated behind --gpt like the sibling codex tells, off by default. Counts hairline stops within a single background declaration (not across the page) so unrelated single-axis ruled lines don't add up to a false flag, and matches the stop directly rather than parsing whole gradient layers, since colors like oklch(...) carry nested parens. Extends the gpt-tells fixture with one flag case and two pass cases (single-axis rule, two-color blend), regenerates the browser detector bundle, and bumps the rule count 44 -> 45. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Require tiling background-size for codex-grid-background Address review: two hairline gradients alone draw a fixed crosshair, not a grid. Scope detection to a single style block (CSS rule body or inline style attr) and require both >=2 hairline stops AND a tiling `background-size` px cell in the same block, matching the skill rule's "plus background-size" wording. Add a crosshair-without-tiling pass case. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Scope codex-grid-background hairline count to background values Address review: count hairline stops only inside background/background-image declaration values, not the whole style block, so a hairline in an unrelated property (mask-image, border-image) can't stand in for the grid's second axis. Add a bg+mask-image hairline pass case. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
5844c40177
commit
f5c1bd65ae
@@ -1,6 +1,6 @@
|
||||
# Impeccable
|
||||
|
||||
Design guidance for AI coding agents. 1 skill, 23 commands, live browser iteration, and 44 deterministic detector rules for AI-generated frontend design.
|
||||
Design guidance for AI coding agents. 1 skill, 23 commands, live browser iteration, and 45 deterministic detector rules for AI-generated frontend design.
|
||||
|
||||
> **Quick start:** From your project root, run `npx impeccable install`, then run `/impeccable init` inside your AI coding tool. Full docs: [impeccable.style](https://impeccable.style).
|
||||
|
||||
@@ -13,7 +13,7 @@ Every model trained on the same SaaS templates. Skip the guidance and you get th
|
||||
Impeccable adds:
|
||||
- **One setup flow.** `/impeccable init` writes `PRODUCT.md` and offers `DESIGN.md`, so later commands know the audience, brand/product lane, voice, anti-references, colors, type, and components.
|
||||
- **23 commands.** A shared design vocabulary with your AI: `polish`, `audit`, `critique`, `distill`, `animate`, `bolder`, `quieter`, and more.
|
||||
- **44 deterministic detector rules** plus LLM-only critique checks. The CLI and browser extension run the deterministic rules with no LLM and no API key.
|
||||
- **45 deterministic detector rules** plus LLM-only critique checks. The CLI and browser extension run the deterministic rules with no LLM and no API key.
|
||||
|
||||
## What's Included
|
||||
|
||||
@@ -341,7 +341,7 @@ npx impeccable ignores add-file "src/legacy/**"
|
||||
npx impeccable ignores add-value overused-font Inter --reason "Brand font"
|
||||
```
|
||||
|
||||
The detector catches 44 deterministic 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).
|
||||
The detector catches 45 deterministic 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).
|
||||
|
||||
By default, `detect` respects the same `.impeccable/config.json` and `.impeccable/config.local.json` detector config as the design hook: `detector.ignoreRules`, `detector.ignoreFiles`, `detector.ignoreValues`, and `detector.designSystem.enabled`. Hook lifecycle settings such as `hook.enabled` only affect automatic hook execution.
|
||||
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
# Impeccable CLI
|
||||
|
||||
Detect UI anti-patterns and design quality issues from the command line. Scans HTML, CSS, JSX, TSX, Vue, and Svelte files for 44 deterministic rules, including AI-generated UI tells, accessibility violations, and general design quality problems.
|
||||
Detect UI anti-patterns and design quality issues from the command line. Scans HTML, CSS, JSX, TSX, Vue, and Svelte files for 45 deterministic rules, including AI-generated UI tells, accessibility violations, and general design quality problems.
|
||||
|
||||
## Quick Start
|
||||
|
||||
@@ -56,7 +56,7 @@ npx impeccable detect --fast src/
|
||||
|
||||
**Quality**: tiny body text, cramped padding, long line lengths, small touch targets
|
||||
|
||||
44 deterministic detector rules in total. See the full catalog at [impeccable.style/slop](https://impeccable.style/slop).
|
||||
45 deterministic detector rules in total. See the full catalog at [impeccable.style/slop](https://impeccable.style/slop).
|
||||
|
||||
## Exit Codes
|
||||
|
||||
|
||||
@@ -478,6 +478,17 @@ const ANTIPATTERNS = [
|
||||
skillSection: 'Visual Details',
|
||||
skillGuideline: 'repeating-gradient decorative stripes',
|
||||
},
|
||||
{
|
||||
id: 'codex-grid-background',
|
||||
category: 'slop',
|
||||
severity: 'advisory',
|
||||
gated: 'gpt',
|
||||
name: 'Decorative grid-line background',
|
||||
description:
|
||||
'A two-axis grid drawn with hairline linear-gradient layers ("1px, transparent 1px" on both axes) is a recurring generated-UI signature. Reserve grid overlays for actual canvas, map, blueprint, or measurement surfaces; elsewhere use product structure or a plain surface.',
|
||||
skillSection: 'Visual Details',
|
||||
skillGuideline: 'two-axis grid-line gradient background',
|
||||
},
|
||||
{
|
||||
id: 'theater-slop-phrase',
|
||||
category: 'slop',
|
||||
@@ -1172,6 +1183,42 @@ function checkHtmlPatterns(html) {
|
||||
findings.push({ id: 'repeating-stripes-gradient', snippet: 'repeating-gradient decorative stripes' });
|
||||
}
|
||||
|
||||
// --- Provider tells (gated): two-axis grid-line background (Codex/GPT) ---
|
||||
// The Codex grid tell is two hairline `linear-gradient(... <color> 1px,
|
||||
// transparent 1px)` layers (one per axis) tiled by a repeating
|
||||
// `background-size` cell. Both signals must co-occur in the SAME style block
|
||||
// (a CSS rule body or one inline `style="..."`): two hairline stops WITHOUT a
|
||||
// tiling background-size is a fixed crosshair, not a grid, and a single
|
||||
// hairline is a legitimate ruled line. Scoping to one block also stops
|
||||
// unrelated single-axis rules on separate elements from adding up across the
|
||||
// page. Count hairlines only inside `background`/`background-image` values so
|
||||
// a hairline in an unrelated property (mask-image, border-image) can't stand
|
||||
// in for the second axis. Colors like `oklch(96% 0.012 82 / 0.055)` carry
|
||||
// nested parens, so match the hairline stop directly rather than parsing
|
||||
// whole gradient layers.
|
||||
{
|
||||
const hairlineRe = /\b\d{1,3}px\s*,\s*transparent\s+\d{1,3}px/gi;
|
||||
const gridSizeRe = /background-size\s*:[^;{}"']*\b\d{1,3}px\b/i;
|
||||
const bgDeclRe = /\bbackground(?:-image)?\s*:\s*([^;{}"']*)/gi;
|
||||
const blockRe = /\{([^{}]*)\}|style\s*=\s*"([^"]*)"|style\s*=\s*'([^']*)'/gi;
|
||||
let blk;
|
||||
while ((blk = blockRe.exec(html)) !== null) {
|
||||
const block = blk[1] || blk[2] || blk[3] || '';
|
||||
if (!gridSizeRe.test(block)) continue;
|
||||
let hairlineCount = 0;
|
||||
let bm;
|
||||
bgDeclRe.lastIndex = 0;
|
||||
while ((bm = bgDeclRe.exec(block)) !== null) {
|
||||
const stops = bm[1].match(hairlineRe);
|
||||
if (stops) hairlineCount += stops.length;
|
||||
}
|
||||
if (hairlineCount >= 2) {
|
||||
findings.push({ id: 'codex-grid-background', snippet: 'two-axis grid-line gradient background' });
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Provider tells (gated): "X theater" framing copy (GPT) ---
|
||||
// Lives here (regex-on-HTML) rather than in the text-content analyzers so it
|
||||
// runs in the bundled browser path too, not just the CLI/static path.
|
||||
|
||||
@@ -376,6 +376,17 @@ const ANTIPATTERNS = [
|
||||
skillSection: 'Visual Details',
|
||||
skillGuideline: 'repeating-gradient decorative stripes',
|
||||
},
|
||||
{
|
||||
id: 'codex-grid-background',
|
||||
category: 'slop',
|
||||
severity: 'advisory',
|
||||
gated: 'gpt',
|
||||
name: 'Decorative grid-line background',
|
||||
description:
|
||||
'A two-axis grid drawn with hairline linear-gradient layers ("1px, transparent 1px" on both axes) is a recurring generated-UI signature. Reserve grid overlays for actual canvas, map, blueprint, or measurement surfaces; elsewhere use product structure or a plain surface.',
|
||||
skillSection: 'Visual Details',
|
||||
skillGuideline: 'two-axis grid-line gradient background',
|
||||
},
|
||||
{
|
||||
id: 'theater-slop-phrase',
|
||||
category: 'slop',
|
||||
|
||||
@@ -573,6 +573,42 @@ function checkHtmlPatterns(html) {
|
||||
findings.push({ id: 'repeating-stripes-gradient', snippet: 'repeating-gradient decorative stripes' });
|
||||
}
|
||||
|
||||
// --- Provider tells (gated): two-axis grid-line background (Codex/GPT) ---
|
||||
// The Codex grid tell is two hairline `linear-gradient(... <color> 1px,
|
||||
// transparent 1px)` layers (one per axis) tiled by a repeating
|
||||
// `background-size` cell. Both signals must co-occur in the SAME style block
|
||||
// (a CSS rule body or one inline `style="..."`): two hairline stops WITHOUT a
|
||||
// tiling background-size is a fixed crosshair, not a grid, and a single
|
||||
// hairline is a legitimate ruled line. Scoping to one block also stops
|
||||
// unrelated single-axis rules on separate elements from adding up across the
|
||||
// page. Count hairlines only inside `background`/`background-image` values so
|
||||
// a hairline in an unrelated property (mask-image, border-image) can't stand
|
||||
// in for the second axis. Colors like `oklch(96% 0.012 82 / 0.055)` carry
|
||||
// nested parens, so match the hairline stop directly rather than parsing
|
||||
// whole gradient layers.
|
||||
{
|
||||
const hairlineRe = /\b\d{1,3}px\s*,\s*transparent\s+\d{1,3}px/gi;
|
||||
const gridSizeRe = /background-size\s*:[^;{}"']*\b\d{1,3}px\b/i;
|
||||
const bgDeclRe = /\bbackground(?:-image)?\s*:\s*([^;{}"']*)/gi;
|
||||
const blockRe = /\{([^{}]*)\}|style\s*=\s*"([^"]*)"|style\s*=\s*'([^']*)'/gi;
|
||||
let blk;
|
||||
while ((blk = blockRe.exec(html)) !== null) {
|
||||
const block = blk[1] || blk[2] || blk[3] || '';
|
||||
if (!gridSizeRe.test(block)) continue;
|
||||
let hairlineCount = 0;
|
||||
let bm;
|
||||
bgDeclRe.lastIndex = 0;
|
||||
while ((bm = bgDeclRe.exec(block)) !== null) {
|
||||
const stops = bm[1].match(hairlineRe);
|
||||
if (stops) hairlineCount += stops.length;
|
||||
}
|
||||
if (hairlineCount >= 2) {
|
||||
findings.push({ id: 'codex-grid-background', snippet: 'two-axis grid-line gradient background' });
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Provider tells (gated): "X theater" framing copy (GPT) ---
|
||||
// Lives here (regex-on-HTML) rather than in the text-content analyzers so it
|
||||
// runs in the bundled browser path too, not just the CLI/static path.
|
||||
|
||||
@@ -521,7 +521,7 @@ import '../styles/testimonials.css';
|
||||
<article class="ks-bento-tile ks-bento-tile--span-6" id="why-ci">
|
||||
<span class="ks-bento-num" data-color="patina">06</span>
|
||||
<h3 class="why-panel-title">Block slop before it ships.</h3>
|
||||
<p class="why-panel-body">A detector you can wire into PR checks. 44 deterministic rules, no LLM, exit codes the build can read.</p>
|
||||
<p class="why-panel-body">A detector you can wire into PR checks. 45 deterministic rules, no LLM, exit codes the build can read.</p>
|
||||
<div class="why-visual why-visual--ci">
|
||||
<div class="why-ci-window">
|
||||
<div class="why-ci-header">
|
||||
@@ -799,7 +799,7 @@ import '../styles/testimonials.css';
|
||||
</li>
|
||||
<li>
|
||||
<strong>CLI for CI</strong>
|
||||
<span><code>npx impeccable detect src/</code> in a PR check. 44 deterministic rules. JSON output, exit codes for build gates.</span>
|
||||
<span><code>npx impeccable detect src/</code> in a PR check. 45 deterministic rules. JSON output, exit codes for build gates.</span>
|
||||
<a href="https://www.npmjs.com/package/impeccable" target="_blank" rel="noopener">View on npm →</a>
|
||||
</li>
|
||||
<li>
|
||||
|
||||
@@ -744,7 +744,7 @@ describe('detectHtml — cream-palette', () => {
|
||||
});
|
||||
|
||||
describe('detectHtml — gated provider tells (--gpt / --gemini)', () => {
|
||||
const GPT_IDS = ['gpt-thin-border-wide-shadow', 'repeating-stripes-gradient', 'theater-slop-phrase'];
|
||||
const GPT_IDS = ['gpt-thin-border-wide-shadow', 'repeating-stripes-gradient', 'codex-grid-background', 'theater-slop-phrase'];
|
||||
|
||||
it('gpt-tells: gated OFF by default — none of the GPT idioms surface', async () => {
|
||||
const f = await detectHtml(path.join(FIXTURES, 'gpt-tells.html'));
|
||||
@@ -756,7 +756,7 @@ describe('detectHtml — gated provider tells (--gpt / --gemini)', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('gpt-tells: with providers:[gpt], flag column triggers all three, pass column adds none', async () => {
|
||||
it('gpt-tells: with providers:[gpt], each flag case triggers once, pass column adds none', async () => {
|
||||
const f = await detectHtml(path.join(FIXTURES, 'gpt-tells.html'), { providers: ['gpt'] });
|
||||
for (const id of GPT_IDS) {
|
||||
assert.equal(
|
||||
|
||||
+6
@@ -9,6 +9,7 @@
|
||||
.col { padding: 16px; }
|
||||
.card { padding: 24px; margin: 0 0 24px; border-radius: 12px; }
|
||||
.stripes { height: 80px; margin: 0 0 24px; border-radius: 12px; }
|
||||
.grid { height: 80px; margin: 0 0 24px; border-radius: 12px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -19,6 +20,7 @@
|
||||
Thin hairline border paired with a wide diffuse shadow.
|
||||
</div>
|
||||
<div class="stripes flag-repeating-stripes" style="background: repeating-linear-gradient(45deg, #eee, #eee 10px, #fafafa 10px, #fafafa 20px)"></div>
|
||||
<div class="grid flag-codex-grid" style="background: linear-gradient(90deg, oklch(96% 0.012 82 / 0.055) 1px, transparent 1px), linear-gradient(180deg, oklch(96% 0.012 82 / 0.05) 1px, transparent 1px); background-size: 78px 78px, 78px 78px;"></div>
|
||||
<p class="flag-theater">We retired the growth theater and shipped something that actually works.</p>
|
||||
</section>
|
||||
|
||||
@@ -34,6 +36,10 @@
|
||||
OKLCH shadow hue values should not be mistaken for shadow blur.
|
||||
</div>
|
||||
<div class="stripes pass-plain-gradient" style="background: linear-gradient(180deg, #f3f4f6, #ffffff)"></div>
|
||||
<div class="grid pass-single-axis-rule" style="background: linear-gradient(180deg, rgba(15, 23, 42, 0.06) 1px, transparent 1px); background-size: 100% 32px"></div>
|
||||
<div class="grid pass-two-color-blend" style="background: linear-gradient(90deg, #f3f4f6, #e5e7eb), linear-gradient(180deg, #eef2ff, #ffffff)"></div>
|
||||
<div class="grid pass-crosshair-no-tiling" style="background: linear-gradient(90deg, rgba(15, 23, 42, 0.06) 1px, transparent 1px), linear-gradient(180deg, rgba(15, 23, 42, 0.06) 1px, transparent 1px)"></div>
|
||||
<div class="grid pass-bg-plus-mask-hairline" style="background: linear-gradient(90deg, rgba(15, 23, 42, 0.06) 1px, transparent 1px); background-size: 32px 32px; mask-image: linear-gradient(180deg, #000 1px, transparent 1px)"></div>
|
||||
<p class="pass-no-theater">We measured real outcomes for the people who use this every day.</p>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user