From a236137bc6ed3804bf06e722d11f48f45ff1baa4 Mon Sep 17 00:00:00 2001 From: Abdul Wahab Date: Sat, 22 Aug 2026 06:51:25 +0500 Subject: [PATCH 1/2] Fix: stop flagging 1D dashed rules as grid backgrounds (#615) codex-grid-background treated any 2D px background-size as a grid, so a single hairline tiled as a dash or rail false-positived. A finding now requires two hairline gradients plus a px tile. Prepared with AI assistance (Cursor agent), directed by @abdulwahabone. Co-authored-by: Cursor --- cli/engine/detect-antipatterns-browser.js | 16 ++++------ cli/engine/rules/checks.mjs | 16 ++++------ tests/detect-antipatterns.test.js | 36 ++++++++++++++++++++--- 3 files changed, 44 insertions(+), 24 deletions(-) diff --git a/cli/engine/detect-antipatterns-browser.js b/cli/engine/detect-antipatterns-browser.js index 8d5bce3f6..46f94f4be 100644 --- a/cli/engine/detect-antipatterns-browser.js +++ b/cli/engine/detect-antipatterns-browser.js @@ -1955,20 +1955,19 @@ function scanCssTextForGlow(content) { return results; } -// Decorative grid or line-field backgrounds drawn with hairline +// Decorative two-axis grid backgrounds drawn with hairline // linear-gradient layers tiled by a fixed pixel cell. Shared by the HTML // pattern pass and the regex source engine so standalone CSS, component // styles, and inline styles receive the same coverage. Both signals must // co-occur in one declaration block; unrelated rules must not add up across -// the file. Returns [{ index, snippet }], capped at one finding per source to -// match the page-level HTML check's existing behavior. +// the file. A single hairline is a line, divider, or rail, not a grid, even +// when tiled by a 2D px cell. Returns [{ index, snippet }], capped at one +// finding per source to match the page-level HTML check's existing behavior. function scanCssTextForGridBackground(content) { const hairlineRe = /\b\d{1,3}px\s*,\s*transparent\s+\d{1,3}px/gi; const invertedHairlineRe = /transparent\s+calc\(100%\s*-\s*\d{1,3}px\)/gi; const sizeDeclPxRe = /background-size\s*:[^;{}"']*\b\d{1,3}px\b/i; - const sizeDeclPxPairRe = /background-size\s*:[^;{}"']*\b\d{1,3}px\s+\d{1,3}px/i; const shorthandPxAnyRe = /\/\s*\d{1,3}px\b/; - const shorthandPxPairRe = /\/\s*\d{1,3}px\s+\d{1,3}px/; const bgDeclRe = /\bbackground(?:-image)?\s*:\s*([^;{}"']*)/gi; const blockRe = /\{([^{}]*)\}|style\s*=\s*"([^"]*)"|style\s*=\s*'([^']*)'/gi; let blk; @@ -1985,13 +1984,10 @@ function scanCssTextForGridBackground(content) { } if (hairlineCount === 0) continue; const hasPxCell = sizeDeclPxRe.test(block) || shorthandPxAnyRe.test(bgJoined); - const hasPxPairCell = sizeDeclPxPairRe.test(block) || shorthandPxPairRe.test(bgJoined); - if ((hairlineCount >= 2 && hasPxCell) || hasPxPairCell) { + if (hairlineCount >= 2 && hasPxCell) { return [{ index: blk.index, - snippet: hairlineCount >= 2 - ? 'two-axis grid-line gradient background' - : 'px-tiled hairline line-field background', + snippet: 'two-axis grid-line gradient background', }]; } } diff --git a/cli/engine/rules/checks.mjs b/cli/engine/rules/checks.mjs index 8a5654625..031501513 100644 --- a/cli/engine/rules/checks.mjs +++ b/cli/engine/rules/checks.mjs @@ -721,20 +721,19 @@ function scanCssTextForGlow(content) { return results; } -// Decorative grid or line-field backgrounds drawn with hairline +// Decorative two-axis grid backgrounds drawn with hairline // linear-gradient layers tiled by a fixed pixel cell. Shared by the HTML // pattern pass and the regex source engine so standalone CSS, component // styles, and inline styles receive the same coverage. Both signals must // co-occur in one declaration block; unrelated rules must not add up across -// the file. Returns [{ index, snippet }], capped at one finding per source to -// match the page-level HTML check's existing behavior. +// the file. A single hairline is a line, divider, or rail, not a grid, even +// when tiled by a 2D px cell. Returns [{ index, snippet }], capped at one +// finding per source to match the page-level HTML check's existing behavior. function scanCssTextForGridBackground(content) { const hairlineRe = /\b\d{1,3}px\s*,\s*transparent\s+\d{1,3}px/gi; const invertedHairlineRe = /transparent\s+calc\(100%\s*-\s*\d{1,3}px\)/gi; const sizeDeclPxRe = /background-size\s*:[^;{}"']*\b\d{1,3}px\b/i; - const sizeDeclPxPairRe = /background-size\s*:[^;{}"']*\b\d{1,3}px\s+\d{1,3}px/i; const shorthandPxAnyRe = /\/\s*\d{1,3}px\b/; - const shorthandPxPairRe = /\/\s*\d{1,3}px\s+\d{1,3}px/; const bgDeclRe = /\bbackground(?:-image)?\s*:\s*([^;{}"']*)/gi; const blockRe = /\{([^{}]*)\}|style\s*=\s*"([^"]*)"|style\s*=\s*'([^']*)'/gi; let blk; @@ -751,13 +750,10 @@ function scanCssTextForGridBackground(content) { } if (hairlineCount === 0) continue; const hasPxCell = sizeDeclPxRe.test(block) || shorthandPxAnyRe.test(bgJoined); - const hasPxPairCell = sizeDeclPxPairRe.test(block) || shorthandPxPairRe.test(bgJoined); - if ((hairlineCount >= 2 && hasPxCell) || hasPxPairCell) { + if (hairlineCount >= 2 && hasPxCell) { return [{ index: blk.index, - snippet: hairlineCount >= 2 - ? 'two-axis grid-line gradient background' - : 'px-tiled hairline line-field background', + snippet: 'two-axis grid-line gradient background', }]; } } diff --git a/tests/detect-antipatterns.test.js b/tests/detect-antipatterns.test.js index 2c7141365..66ff71e15 100644 --- a/tests/detect-antipatterns.test.js +++ b/tests/detect-antipatterns.test.js @@ -1806,11 +1806,9 @@ describe('codex-grid-background variants', () => { expect(grids(css)).toHaveLength(1); }); - test('flags single-axis hairline tiled by a px pair cell', () => { + test('keeps single-axis hairline tiled by a px pair cell legal', () => { const css = `body { background: linear-gradient(90deg, rgba(23,25,24,.035) 1px, transparent 1px) 0 0 / 40px 40px, #f4f1ea; }`; - const f = grids(css); - expect(f).toHaveLength(1); - expect(f[0].snippet).toContain('line-field'); + expect(grids(css)).toHaveLength(0); }); test('keeps percent-tiled single hairlines (data-viz track rules) legal', () => { @@ -1818,6 +1816,36 @@ describe('codex-grid-background variants', () => { expect(grids(css)).toHaveLength(0); }); + test('keeps 1D dashed dot rules legal', () => { + const css = `.dot-rule { + height: 5px; + background-image: linear-gradient(90deg, rgba(255,255,255,.75) 5px, transparent 5px); + background-size: 10px 5px; + background-repeat: repeat-x; + }`; + expect(grids(css)).toHaveLength(0); + }); + + test('keeps 1D progress rails with dash-period px pair tiles legal', () => { + const css = `.progress-rail { + background-image: linear-gradient(90deg, #eee 1px, transparent 1px); + background-size: 8px 4px; + background-repeat: repeat-x; + }`; + expect(grids(css)).toHaveLength(0); + }); + + test('regex source engine keeps 1D dot rules legal', () => { + const css = `.dot-rule { + height: 5px; + background-image: linear-gradient(90deg, rgba(255,255,255,.75) 5px, transparent 5px); + background-size: 10px 5px; + background-repeat: repeat-x; + }`; + const findings = detectText(css, 'dot-rule.css'); + expect(findings.filter(f => f.antipattern === 'codex-grid-background')).toHaveLength(0); + }); + test('classic two-axis background-size form still flags', () => { const css = `.hero { background-image: linear-gradient(#eee 1px, transparent 1px), From 7ddcd533a46a08aa15a6cbc8bb84de3fac10d3fd Mon Sep 17 00:00:00 2001 From: Abdul Wahab Date: Sat, 22 Aug 2026 06:58:36 +0500 Subject: [PATCH 2/2] Test: pin 1D grid-background pass cases in the fixture suite The unit suite already covered dashed rules; this adds an isolated HTML fixture so the page-level one-finding cap cannot hide a regression. Prepared with AI assistance (Cursor agent), directed by @abdulwahabone. Co-authored-by: Cursor --- tests/detect-antipatterns-fixtures.test.mjs | 8 +++++++ .../antipatterns/codex-grid-1d-pass.html | 21 +++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 tests/fixtures/antipatterns/codex-grid-1d-pass.html diff --git a/tests/detect-antipatterns-fixtures.test.mjs b/tests/detect-antipatterns-fixtures.test.mjs index fb9ee76f9..f7fa1a4d7 100644 --- a/tests/detect-antipatterns-fixtures.test.mjs +++ b/tests/detect-antipatterns-fixtures.test.mjs @@ -1278,6 +1278,14 @@ describe('detectHtml — generated-UI tells', () => { } }); + it('codex-grid-background: 1D dashed rules and px-pair line-fields stay legal', async () => { + const f = await detectHtml(path.join(FIXTURES, 'codex-grid-1d-pass.html')); + assert.equal( + f.filter(r => r.antipattern === 'codex-grid-background').length, 0, + `1D tiled hairlines must not flag, got: ${f.filter(r => r.antipattern === 'codex-grid-background').map(r => r.snippet).join('; ')}`, + ); + }); + it('gemini-tells: both flag cases surface by default and pass cases stay legal', async () => { const findings = await detectHtml(path.join(FIXTURES, 'gemini-tells.html')); // Two flag cases: a CSS img:hover{transform} rule and a Tailwind hover:scale on . diff --git a/tests/fixtures/antipatterns/codex-grid-1d-pass.html b/tests/fixtures/antipatterns/codex-grid-1d-pass.html new file mode 100644 index 000000000..f87942f2b --- /dev/null +++ b/tests/fixtures/antipatterns/codex-grid-1d-pass.html @@ -0,0 +1,21 @@ + + + + + codex-grid-background 1D pass cases + + + +

Dotted horizontal rule

+
+

Progress rail

+
+

Single-axis px-pair line field

+
+ +