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:
Paul Bakaus
2026-07-01 01:29:40 -07:00
committed by GitHub
co-authored by Claude Opus 4.8
parent 5844c40177
commit f5c1bd65ae
8 changed files with 109 additions and 9 deletions
+47
View File
@@ -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.
+11
View File
@@ -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',
+36
View File
@@ -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.