From 5d77ba75fe38e00cdbe40cda689635495ef61ae1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 26 Jul 2026 01:39:58 +0000 Subject: [PATCH] Sync generated provider output --- .../detector/detect-antipatterns-browser.js | 12 ++++++++++- .../detector/engines/regex/detect-text.mjs | 20 +++++++++++++++++-- .../scripts/detector/rules/checks.mjs | 12 ++++++++++- .../detector/detect-antipatterns-browser.js | 12 ++++++++++- .../detector/engines/regex/detect-text.mjs | 20 +++++++++++++++++-- .../scripts/detector/rules/checks.mjs | 12 ++++++++++- .../detector/detect-antipatterns-browser.js | 12 ++++++++++- .../detector/engines/regex/detect-text.mjs | 20 +++++++++++++++++-- .../scripts/detector/rules/checks.mjs | 12 ++++++++++- .../detector/detect-antipatterns-browser.js | 12 ++++++++++- .../detector/engines/regex/detect-text.mjs | 20 +++++++++++++++++-- .../scripts/detector/rules/checks.mjs | 12 ++++++++++- .../detector/detect-antipatterns-browser.js | 12 ++++++++++- .../detector/engines/regex/detect-text.mjs | 20 +++++++++++++++++-- .../scripts/detector/rules/checks.mjs | 12 ++++++++++- .../detector/detect-antipatterns-browser.js | 12 ++++++++++- .../detector/engines/regex/detect-text.mjs | 20 +++++++++++++++++-- .../scripts/detector/rules/checks.mjs | 12 ++++++++++- .../detector/detect-antipatterns-browser.js | 12 ++++++++++- .../detector/engines/regex/detect-text.mjs | 20 +++++++++++++++++-- .../scripts/detector/rules/checks.mjs | 12 ++++++++++- .../detector/detect-antipatterns-browser.js | 12 ++++++++++- .../detector/engines/regex/detect-text.mjs | 20 +++++++++++++++++-- .../scripts/detector/rules/checks.mjs | 12 ++++++++++- .../detector/detect-antipatterns-browser.js | 12 ++++++++++- .../detector/engines/regex/detect-text.mjs | 20 +++++++++++++++++-- .../scripts/detector/rules/checks.mjs | 12 ++++++++++- .../detector/detect-antipatterns-browser.js | 12 ++++++++++- .../detector/engines/regex/detect-text.mjs | 20 +++++++++++++++++-- .../scripts/detector/rules/checks.mjs | 12 ++++++++++- .../detector/detect-antipatterns-browser.js | 12 ++++++++++- .../detector/engines/regex/detect-text.mjs | 20 +++++++++++++++++-- .../scripts/detector/rules/checks.mjs | 12 ++++++++++- .../detector/detect-antipatterns-browser.js | 12 ++++++++++- .../detector/engines/regex/detect-text.mjs | 20 +++++++++++++++++-- .../scripts/detector/rules/checks.mjs | 12 ++++++++++- .../detector/detect-antipatterns-browser.js | 12 ++++++++++- .../detector/engines/regex/detect-text.mjs | 20 +++++++++++++++++-- .../scripts/detector/rules/checks.mjs | 12 ++++++++++- .../detector/detect-antipatterns-browser.js | 12 ++++++++++- .../detector/engines/regex/detect-text.mjs | 20 +++++++++++++++++-- .../scripts/detector/rules/checks.mjs | 12 ++++++++++- .../detector/detect-antipatterns-browser.js | 12 ++++++++++- .../detector/engines/regex/detect-text.mjs | 20 +++++++++++++++++-- .../scripts/detector/rules/checks.mjs | 12 ++++++++++- 45 files changed, 600 insertions(+), 60 deletions(-) diff --git a/.agents/skills/impeccable/scripts/detector/detect-antipatterns-browser.js b/.agents/skills/impeccable/scripts/detector/detect-antipatterns-browser.js index d07caa7ab..a728d4f9e 100644 --- a/.agents/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +++ b/.agents/skills/impeccable/scripts/detector/detect-antipatterns-browser.js @@ -1625,7 +1625,13 @@ function isZeroOffset(value) { // never see it — pseudo-elements aren't part of the DOM the cascade walks — // so this scans stylesheet text directly, mirroring the border rule's // gates: >= 3px thick, chromatic fill, full height against a side edge. -function scanCssTextForPseudoStripe(content) { +function scanCssTextForPseudoStripe(rawContent) { + // Blank comment bodies byte-for-byte so commented-out rules are not + // scanned as live CSS and every rule keeps its source offset (each + // finding carries `index` so line-based callers can attribute it and + // line-scoped inline ignores can match). + const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g, + (block) => block.replace(/[^\n]/g, ' ')); const customProps = collectCssCustomProps(content); const findings = []; const seen = new Set(); @@ -1734,9 +1740,13 @@ function scanCssTextForPseudoStripe(content) { if (seen.has(selector)) continue; seen.add(selector); + // The selector group absorbs whitespace trailing the previous rule; + // advance past it so `index` points at the selector itself. + const selectorStart = m.index + (m[1].length - m[1].trimStart().length); findings.push({ id: 'side-tab', snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`, + index: selectorStart, }); } return findings; diff --git a/.agents/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs b/.agents/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs index 882f76b42..c664f70b6 100644 --- a/.agents/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs +++ b/.agents/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs @@ -2,7 +2,7 @@ import { GENERIC_FONTS, OVERUSED_FONTS, EM_DASH_FLOOR, EM_DASH_CHARS_PER_DASH } import { isNeutralColor } from '../../shared/color.mjs'; import { extractGoogleFontFamilies } from '../../shared/fonts.mjs'; import { checkSourceDesignSystem } from '../../design-system.mjs'; -import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForRadialHalo } from '../../rules/checks.mjs'; +import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForPseudoStripe, scanCssTextForRadialHalo } from '../../rules/checks.mjs'; import { isFullPage } from '../../shared/page.mjs'; import { applyInlineIgnores } from '../../shared/inline-ignores.mjs'; import { finding } from '../../findings.mjs'; @@ -653,7 +653,21 @@ function detectText(content, filePath, options = {}) { profile, phase: 'source', })); - if (cssLike.has(ext)) findings.push(...scanInsetStripeCss(content, filePath)); + // Pseudo-element stripes (::before/::after absolute bars) carry the same + // side-tab silhouette without any border token, so the line matchers can't + // see them (issue #394). The shared scanner already runs on full HTML pages + // via checkHtmlPatterns; give standalone stylesheets, component style + // blocks, and CSS-in-JS templates the same coverage. Each hit carries the + // rule's source offset, so the finding gets a real line and line-scoped + // inline ignores keep working. + const pseudoStripeFindings = (text, lineOffset) => + scanCssTextForPseudoStripe(text).map(hit => + finding(hit.id, filePath, hit.snippet, lineOffset + text.slice(0, hit.index).split('\n').length)); + + if (cssLike.has(ext)) { + findings.push(...scanInsetStripeCss(content, filePath)); + findings.push(...pseudoStripeFindings(content, 0)); + } // Block-level CSS checks that need multiple declarations must run over the // complete source, not line-by-line. This covers standalone stylesheets, @@ -690,6 +704,7 @@ function detectText(content, filePath, options = {}) { // reported every selector one line low. runRegexMatchers keeps startLine - 1 // because it indexes its split lines from zero. findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 2)); + findings.push(...pseudoStripeFindings(block.content, block.startLine - 2)); } // Extract and scan CSS-in-JS template literals @@ -708,6 +723,7 @@ function detectText(content, filePath, options = {}) { phase: 'css-in-js', })); findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 1)); + findings.push(...pseudoStripeFindings(block.content, block.startLine - 1)); } if (options?.designSystem) { diff --git a/.agents/skills/impeccable/scripts/detector/rules/checks.mjs b/.agents/skills/impeccable/scripts/detector/rules/checks.mjs index 141a209a2..3e810076b 100644 --- a/.agents/skills/impeccable/scripts/detector/rules/checks.mjs +++ b/.agents/skills/impeccable/scripts/detector/rules/checks.mjs @@ -823,7 +823,13 @@ function isZeroOffset(value) { // never see it — pseudo-elements aren't part of the DOM the cascade walks — // so this scans stylesheet text directly, mirroring the border rule's // gates: >= 3px thick, chromatic fill, full height against a side edge. -function scanCssTextForPseudoStripe(content) { +function scanCssTextForPseudoStripe(rawContent) { + // Blank comment bodies byte-for-byte so commented-out rules are not + // scanned as live CSS and every rule keeps its source offset (each + // finding carries `index` so line-based callers can attribute it and + // line-scoped inline ignores can match). + const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g, + (block) => block.replace(/[^\n]/g, ' ')); const customProps = collectCssCustomProps(content); const findings = []; const seen = new Set(); @@ -932,9 +938,13 @@ function scanCssTextForPseudoStripe(content) { if (seen.has(selector)) continue; seen.add(selector); + // The selector group absorbs whitespace trailing the previous rule; + // advance past it so `index` points at the selector itself. + const selectorStart = m.index + (m[1].length - m[1].trimStart().length); findings.push({ id: 'side-tab', snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`, + index: selectorStart, }); } return findings; diff --git a/.claude/skills/impeccable/scripts/detector/detect-antipatterns-browser.js b/.claude/skills/impeccable/scripts/detector/detect-antipatterns-browser.js index d07caa7ab..a728d4f9e 100644 --- a/.claude/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +++ b/.claude/skills/impeccable/scripts/detector/detect-antipatterns-browser.js @@ -1625,7 +1625,13 @@ function isZeroOffset(value) { // never see it — pseudo-elements aren't part of the DOM the cascade walks — // so this scans stylesheet text directly, mirroring the border rule's // gates: >= 3px thick, chromatic fill, full height against a side edge. -function scanCssTextForPseudoStripe(content) { +function scanCssTextForPseudoStripe(rawContent) { + // Blank comment bodies byte-for-byte so commented-out rules are not + // scanned as live CSS and every rule keeps its source offset (each + // finding carries `index` so line-based callers can attribute it and + // line-scoped inline ignores can match). + const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g, + (block) => block.replace(/[^\n]/g, ' ')); const customProps = collectCssCustomProps(content); const findings = []; const seen = new Set(); @@ -1734,9 +1740,13 @@ function scanCssTextForPseudoStripe(content) { if (seen.has(selector)) continue; seen.add(selector); + // The selector group absorbs whitespace trailing the previous rule; + // advance past it so `index` points at the selector itself. + const selectorStart = m.index + (m[1].length - m[1].trimStart().length); findings.push({ id: 'side-tab', snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`, + index: selectorStart, }); } return findings; diff --git a/.claude/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs b/.claude/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs index 882f76b42..c664f70b6 100644 --- a/.claude/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs +++ b/.claude/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs @@ -2,7 +2,7 @@ import { GENERIC_FONTS, OVERUSED_FONTS, EM_DASH_FLOOR, EM_DASH_CHARS_PER_DASH } import { isNeutralColor } from '../../shared/color.mjs'; import { extractGoogleFontFamilies } from '../../shared/fonts.mjs'; import { checkSourceDesignSystem } from '../../design-system.mjs'; -import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForRadialHalo } from '../../rules/checks.mjs'; +import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForPseudoStripe, scanCssTextForRadialHalo } from '../../rules/checks.mjs'; import { isFullPage } from '../../shared/page.mjs'; import { applyInlineIgnores } from '../../shared/inline-ignores.mjs'; import { finding } from '../../findings.mjs'; @@ -653,7 +653,21 @@ function detectText(content, filePath, options = {}) { profile, phase: 'source', })); - if (cssLike.has(ext)) findings.push(...scanInsetStripeCss(content, filePath)); + // Pseudo-element stripes (::before/::after absolute bars) carry the same + // side-tab silhouette without any border token, so the line matchers can't + // see them (issue #394). The shared scanner already runs on full HTML pages + // via checkHtmlPatterns; give standalone stylesheets, component style + // blocks, and CSS-in-JS templates the same coverage. Each hit carries the + // rule's source offset, so the finding gets a real line and line-scoped + // inline ignores keep working. + const pseudoStripeFindings = (text, lineOffset) => + scanCssTextForPseudoStripe(text).map(hit => + finding(hit.id, filePath, hit.snippet, lineOffset + text.slice(0, hit.index).split('\n').length)); + + if (cssLike.has(ext)) { + findings.push(...scanInsetStripeCss(content, filePath)); + findings.push(...pseudoStripeFindings(content, 0)); + } // Block-level CSS checks that need multiple declarations must run over the // complete source, not line-by-line. This covers standalone stylesheets, @@ -690,6 +704,7 @@ function detectText(content, filePath, options = {}) { // reported every selector one line low. runRegexMatchers keeps startLine - 1 // because it indexes its split lines from zero. findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 2)); + findings.push(...pseudoStripeFindings(block.content, block.startLine - 2)); } // Extract and scan CSS-in-JS template literals @@ -708,6 +723,7 @@ function detectText(content, filePath, options = {}) { phase: 'css-in-js', })); findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 1)); + findings.push(...pseudoStripeFindings(block.content, block.startLine - 1)); } if (options?.designSystem) { diff --git a/.claude/skills/impeccable/scripts/detector/rules/checks.mjs b/.claude/skills/impeccable/scripts/detector/rules/checks.mjs index 141a209a2..3e810076b 100644 --- a/.claude/skills/impeccable/scripts/detector/rules/checks.mjs +++ b/.claude/skills/impeccable/scripts/detector/rules/checks.mjs @@ -823,7 +823,13 @@ function isZeroOffset(value) { // never see it — pseudo-elements aren't part of the DOM the cascade walks — // so this scans stylesheet text directly, mirroring the border rule's // gates: >= 3px thick, chromatic fill, full height against a side edge. -function scanCssTextForPseudoStripe(content) { +function scanCssTextForPseudoStripe(rawContent) { + // Blank comment bodies byte-for-byte so commented-out rules are not + // scanned as live CSS and every rule keeps its source offset (each + // finding carries `index` so line-based callers can attribute it and + // line-scoped inline ignores can match). + const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g, + (block) => block.replace(/[^\n]/g, ' ')); const customProps = collectCssCustomProps(content); const findings = []; const seen = new Set(); @@ -932,9 +938,13 @@ function scanCssTextForPseudoStripe(content) { if (seen.has(selector)) continue; seen.add(selector); + // The selector group absorbs whitespace trailing the previous rule; + // advance past it so `index` points at the selector itself. + const selectorStart = m.index + (m[1].length - m[1].trimStart().length); findings.push({ id: 'side-tab', snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`, + index: selectorStart, }); } return findings; diff --git a/.cursor/skills/impeccable/scripts/detector/detect-antipatterns-browser.js b/.cursor/skills/impeccable/scripts/detector/detect-antipatterns-browser.js index d07caa7ab..a728d4f9e 100644 --- a/.cursor/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +++ b/.cursor/skills/impeccable/scripts/detector/detect-antipatterns-browser.js @@ -1625,7 +1625,13 @@ function isZeroOffset(value) { // never see it — pseudo-elements aren't part of the DOM the cascade walks — // so this scans stylesheet text directly, mirroring the border rule's // gates: >= 3px thick, chromatic fill, full height against a side edge. -function scanCssTextForPseudoStripe(content) { +function scanCssTextForPseudoStripe(rawContent) { + // Blank comment bodies byte-for-byte so commented-out rules are not + // scanned as live CSS and every rule keeps its source offset (each + // finding carries `index` so line-based callers can attribute it and + // line-scoped inline ignores can match). + const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g, + (block) => block.replace(/[^\n]/g, ' ')); const customProps = collectCssCustomProps(content); const findings = []; const seen = new Set(); @@ -1734,9 +1740,13 @@ function scanCssTextForPseudoStripe(content) { if (seen.has(selector)) continue; seen.add(selector); + // The selector group absorbs whitespace trailing the previous rule; + // advance past it so `index` points at the selector itself. + const selectorStart = m.index + (m[1].length - m[1].trimStart().length); findings.push({ id: 'side-tab', snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`, + index: selectorStart, }); } return findings; diff --git a/.cursor/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs b/.cursor/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs index 882f76b42..c664f70b6 100644 --- a/.cursor/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs +++ b/.cursor/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs @@ -2,7 +2,7 @@ import { GENERIC_FONTS, OVERUSED_FONTS, EM_DASH_FLOOR, EM_DASH_CHARS_PER_DASH } import { isNeutralColor } from '../../shared/color.mjs'; import { extractGoogleFontFamilies } from '../../shared/fonts.mjs'; import { checkSourceDesignSystem } from '../../design-system.mjs'; -import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForRadialHalo } from '../../rules/checks.mjs'; +import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForPseudoStripe, scanCssTextForRadialHalo } from '../../rules/checks.mjs'; import { isFullPage } from '../../shared/page.mjs'; import { applyInlineIgnores } from '../../shared/inline-ignores.mjs'; import { finding } from '../../findings.mjs'; @@ -653,7 +653,21 @@ function detectText(content, filePath, options = {}) { profile, phase: 'source', })); - if (cssLike.has(ext)) findings.push(...scanInsetStripeCss(content, filePath)); + // Pseudo-element stripes (::before/::after absolute bars) carry the same + // side-tab silhouette without any border token, so the line matchers can't + // see them (issue #394). The shared scanner already runs on full HTML pages + // via checkHtmlPatterns; give standalone stylesheets, component style + // blocks, and CSS-in-JS templates the same coverage. Each hit carries the + // rule's source offset, so the finding gets a real line and line-scoped + // inline ignores keep working. + const pseudoStripeFindings = (text, lineOffset) => + scanCssTextForPseudoStripe(text).map(hit => + finding(hit.id, filePath, hit.snippet, lineOffset + text.slice(0, hit.index).split('\n').length)); + + if (cssLike.has(ext)) { + findings.push(...scanInsetStripeCss(content, filePath)); + findings.push(...pseudoStripeFindings(content, 0)); + } // Block-level CSS checks that need multiple declarations must run over the // complete source, not line-by-line. This covers standalone stylesheets, @@ -690,6 +704,7 @@ function detectText(content, filePath, options = {}) { // reported every selector one line low. runRegexMatchers keeps startLine - 1 // because it indexes its split lines from zero. findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 2)); + findings.push(...pseudoStripeFindings(block.content, block.startLine - 2)); } // Extract and scan CSS-in-JS template literals @@ -708,6 +723,7 @@ function detectText(content, filePath, options = {}) { phase: 'css-in-js', })); findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 1)); + findings.push(...pseudoStripeFindings(block.content, block.startLine - 1)); } if (options?.designSystem) { diff --git a/.cursor/skills/impeccable/scripts/detector/rules/checks.mjs b/.cursor/skills/impeccable/scripts/detector/rules/checks.mjs index 141a209a2..3e810076b 100644 --- a/.cursor/skills/impeccable/scripts/detector/rules/checks.mjs +++ b/.cursor/skills/impeccable/scripts/detector/rules/checks.mjs @@ -823,7 +823,13 @@ function isZeroOffset(value) { // never see it — pseudo-elements aren't part of the DOM the cascade walks — // so this scans stylesheet text directly, mirroring the border rule's // gates: >= 3px thick, chromatic fill, full height against a side edge. -function scanCssTextForPseudoStripe(content) { +function scanCssTextForPseudoStripe(rawContent) { + // Blank comment bodies byte-for-byte so commented-out rules are not + // scanned as live CSS and every rule keeps its source offset (each + // finding carries `index` so line-based callers can attribute it and + // line-scoped inline ignores can match). + const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g, + (block) => block.replace(/[^\n]/g, ' ')); const customProps = collectCssCustomProps(content); const findings = []; const seen = new Set(); @@ -932,9 +938,13 @@ function scanCssTextForPseudoStripe(content) { if (seen.has(selector)) continue; seen.add(selector); + // The selector group absorbs whitespace trailing the previous rule; + // advance past it so `index` points at the selector itself. + const selectorStart = m.index + (m[1].length - m[1].trimStart().length); findings.push({ id: 'side-tab', snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`, + index: selectorStart, }); } return findings; diff --git a/.gemini/skills/impeccable/scripts/detector/detect-antipatterns-browser.js b/.gemini/skills/impeccable/scripts/detector/detect-antipatterns-browser.js index d07caa7ab..a728d4f9e 100644 --- a/.gemini/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +++ b/.gemini/skills/impeccable/scripts/detector/detect-antipatterns-browser.js @@ -1625,7 +1625,13 @@ function isZeroOffset(value) { // never see it — pseudo-elements aren't part of the DOM the cascade walks — // so this scans stylesheet text directly, mirroring the border rule's // gates: >= 3px thick, chromatic fill, full height against a side edge. -function scanCssTextForPseudoStripe(content) { +function scanCssTextForPseudoStripe(rawContent) { + // Blank comment bodies byte-for-byte so commented-out rules are not + // scanned as live CSS and every rule keeps its source offset (each + // finding carries `index` so line-based callers can attribute it and + // line-scoped inline ignores can match). + const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g, + (block) => block.replace(/[^\n]/g, ' ')); const customProps = collectCssCustomProps(content); const findings = []; const seen = new Set(); @@ -1734,9 +1740,13 @@ function scanCssTextForPseudoStripe(content) { if (seen.has(selector)) continue; seen.add(selector); + // The selector group absorbs whitespace trailing the previous rule; + // advance past it so `index` points at the selector itself. + const selectorStart = m.index + (m[1].length - m[1].trimStart().length); findings.push({ id: 'side-tab', snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`, + index: selectorStart, }); } return findings; diff --git a/.gemini/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs b/.gemini/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs index 882f76b42..c664f70b6 100644 --- a/.gemini/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs +++ b/.gemini/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs @@ -2,7 +2,7 @@ import { GENERIC_FONTS, OVERUSED_FONTS, EM_DASH_FLOOR, EM_DASH_CHARS_PER_DASH } import { isNeutralColor } from '../../shared/color.mjs'; import { extractGoogleFontFamilies } from '../../shared/fonts.mjs'; import { checkSourceDesignSystem } from '../../design-system.mjs'; -import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForRadialHalo } from '../../rules/checks.mjs'; +import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForPseudoStripe, scanCssTextForRadialHalo } from '../../rules/checks.mjs'; import { isFullPage } from '../../shared/page.mjs'; import { applyInlineIgnores } from '../../shared/inline-ignores.mjs'; import { finding } from '../../findings.mjs'; @@ -653,7 +653,21 @@ function detectText(content, filePath, options = {}) { profile, phase: 'source', })); - if (cssLike.has(ext)) findings.push(...scanInsetStripeCss(content, filePath)); + // Pseudo-element stripes (::before/::after absolute bars) carry the same + // side-tab silhouette without any border token, so the line matchers can't + // see them (issue #394). The shared scanner already runs on full HTML pages + // via checkHtmlPatterns; give standalone stylesheets, component style + // blocks, and CSS-in-JS templates the same coverage. Each hit carries the + // rule's source offset, so the finding gets a real line and line-scoped + // inline ignores keep working. + const pseudoStripeFindings = (text, lineOffset) => + scanCssTextForPseudoStripe(text).map(hit => + finding(hit.id, filePath, hit.snippet, lineOffset + text.slice(0, hit.index).split('\n').length)); + + if (cssLike.has(ext)) { + findings.push(...scanInsetStripeCss(content, filePath)); + findings.push(...pseudoStripeFindings(content, 0)); + } // Block-level CSS checks that need multiple declarations must run over the // complete source, not line-by-line. This covers standalone stylesheets, @@ -690,6 +704,7 @@ function detectText(content, filePath, options = {}) { // reported every selector one line low. runRegexMatchers keeps startLine - 1 // because it indexes its split lines from zero. findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 2)); + findings.push(...pseudoStripeFindings(block.content, block.startLine - 2)); } // Extract and scan CSS-in-JS template literals @@ -708,6 +723,7 @@ function detectText(content, filePath, options = {}) { phase: 'css-in-js', })); findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 1)); + findings.push(...pseudoStripeFindings(block.content, block.startLine - 1)); } if (options?.designSystem) { diff --git a/.gemini/skills/impeccable/scripts/detector/rules/checks.mjs b/.gemini/skills/impeccable/scripts/detector/rules/checks.mjs index 141a209a2..3e810076b 100644 --- a/.gemini/skills/impeccable/scripts/detector/rules/checks.mjs +++ b/.gemini/skills/impeccable/scripts/detector/rules/checks.mjs @@ -823,7 +823,13 @@ function isZeroOffset(value) { // never see it — pseudo-elements aren't part of the DOM the cascade walks — // so this scans stylesheet text directly, mirroring the border rule's // gates: >= 3px thick, chromatic fill, full height against a side edge. -function scanCssTextForPseudoStripe(content) { +function scanCssTextForPseudoStripe(rawContent) { + // Blank comment bodies byte-for-byte so commented-out rules are not + // scanned as live CSS and every rule keeps its source offset (each + // finding carries `index` so line-based callers can attribute it and + // line-scoped inline ignores can match). + const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g, + (block) => block.replace(/[^\n]/g, ' ')); const customProps = collectCssCustomProps(content); const findings = []; const seen = new Set(); @@ -932,9 +938,13 @@ function scanCssTextForPseudoStripe(content) { if (seen.has(selector)) continue; seen.add(selector); + // The selector group absorbs whitespace trailing the previous rule; + // advance past it so `index` points at the selector itself. + const selectorStart = m.index + (m[1].length - m[1].trimStart().length); findings.push({ id: 'side-tab', snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`, + index: selectorStart, }); } return findings; diff --git a/.github/skills/impeccable/scripts/detector/detect-antipatterns-browser.js b/.github/skills/impeccable/scripts/detector/detect-antipatterns-browser.js index d07caa7ab..a728d4f9e 100644 --- a/.github/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +++ b/.github/skills/impeccable/scripts/detector/detect-antipatterns-browser.js @@ -1625,7 +1625,13 @@ function isZeroOffset(value) { // never see it — pseudo-elements aren't part of the DOM the cascade walks — // so this scans stylesheet text directly, mirroring the border rule's // gates: >= 3px thick, chromatic fill, full height against a side edge. -function scanCssTextForPseudoStripe(content) { +function scanCssTextForPseudoStripe(rawContent) { + // Blank comment bodies byte-for-byte so commented-out rules are not + // scanned as live CSS and every rule keeps its source offset (each + // finding carries `index` so line-based callers can attribute it and + // line-scoped inline ignores can match). + const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g, + (block) => block.replace(/[^\n]/g, ' ')); const customProps = collectCssCustomProps(content); const findings = []; const seen = new Set(); @@ -1734,9 +1740,13 @@ function scanCssTextForPseudoStripe(content) { if (seen.has(selector)) continue; seen.add(selector); + // The selector group absorbs whitespace trailing the previous rule; + // advance past it so `index` points at the selector itself. + const selectorStart = m.index + (m[1].length - m[1].trimStart().length); findings.push({ id: 'side-tab', snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`, + index: selectorStart, }); } return findings; diff --git a/.github/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs b/.github/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs index 882f76b42..c664f70b6 100644 --- a/.github/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs +++ b/.github/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs @@ -2,7 +2,7 @@ import { GENERIC_FONTS, OVERUSED_FONTS, EM_DASH_FLOOR, EM_DASH_CHARS_PER_DASH } import { isNeutralColor } from '../../shared/color.mjs'; import { extractGoogleFontFamilies } from '../../shared/fonts.mjs'; import { checkSourceDesignSystem } from '../../design-system.mjs'; -import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForRadialHalo } from '../../rules/checks.mjs'; +import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForPseudoStripe, scanCssTextForRadialHalo } from '../../rules/checks.mjs'; import { isFullPage } from '../../shared/page.mjs'; import { applyInlineIgnores } from '../../shared/inline-ignores.mjs'; import { finding } from '../../findings.mjs'; @@ -653,7 +653,21 @@ function detectText(content, filePath, options = {}) { profile, phase: 'source', })); - if (cssLike.has(ext)) findings.push(...scanInsetStripeCss(content, filePath)); + // Pseudo-element stripes (::before/::after absolute bars) carry the same + // side-tab silhouette without any border token, so the line matchers can't + // see them (issue #394). The shared scanner already runs on full HTML pages + // via checkHtmlPatterns; give standalone stylesheets, component style + // blocks, and CSS-in-JS templates the same coverage. Each hit carries the + // rule's source offset, so the finding gets a real line and line-scoped + // inline ignores keep working. + const pseudoStripeFindings = (text, lineOffset) => + scanCssTextForPseudoStripe(text).map(hit => + finding(hit.id, filePath, hit.snippet, lineOffset + text.slice(0, hit.index).split('\n').length)); + + if (cssLike.has(ext)) { + findings.push(...scanInsetStripeCss(content, filePath)); + findings.push(...pseudoStripeFindings(content, 0)); + } // Block-level CSS checks that need multiple declarations must run over the // complete source, not line-by-line. This covers standalone stylesheets, @@ -690,6 +704,7 @@ function detectText(content, filePath, options = {}) { // reported every selector one line low. runRegexMatchers keeps startLine - 1 // because it indexes its split lines from zero. findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 2)); + findings.push(...pseudoStripeFindings(block.content, block.startLine - 2)); } // Extract and scan CSS-in-JS template literals @@ -708,6 +723,7 @@ function detectText(content, filePath, options = {}) { phase: 'css-in-js', })); findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 1)); + findings.push(...pseudoStripeFindings(block.content, block.startLine - 1)); } if (options?.designSystem) { diff --git a/.github/skills/impeccable/scripts/detector/rules/checks.mjs b/.github/skills/impeccable/scripts/detector/rules/checks.mjs index 141a209a2..3e810076b 100644 --- a/.github/skills/impeccable/scripts/detector/rules/checks.mjs +++ b/.github/skills/impeccable/scripts/detector/rules/checks.mjs @@ -823,7 +823,13 @@ function isZeroOffset(value) { // never see it — pseudo-elements aren't part of the DOM the cascade walks — // so this scans stylesheet text directly, mirroring the border rule's // gates: >= 3px thick, chromatic fill, full height against a side edge. -function scanCssTextForPseudoStripe(content) { +function scanCssTextForPseudoStripe(rawContent) { + // Blank comment bodies byte-for-byte so commented-out rules are not + // scanned as live CSS and every rule keeps its source offset (each + // finding carries `index` so line-based callers can attribute it and + // line-scoped inline ignores can match). + const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g, + (block) => block.replace(/[^\n]/g, ' ')); const customProps = collectCssCustomProps(content); const findings = []; const seen = new Set(); @@ -932,9 +938,13 @@ function scanCssTextForPseudoStripe(content) { if (seen.has(selector)) continue; seen.add(selector); + // The selector group absorbs whitespace trailing the previous rule; + // advance past it so `index` points at the selector itself. + const selectorStart = m.index + (m[1].length - m[1].trimStart().length); findings.push({ id: 'side-tab', snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`, + index: selectorStart, }); } return findings; diff --git a/.grok/skills/impeccable/scripts/detector/detect-antipatterns-browser.js b/.grok/skills/impeccable/scripts/detector/detect-antipatterns-browser.js index d07caa7ab..a728d4f9e 100644 --- a/.grok/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +++ b/.grok/skills/impeccable/scripts/detector/detect-antipatterns-browser.js @@ -1625,7 +1625,13 @@ function isZeroOffset(value) { // never see it — pseudo-elements aren't part of the DOM the cascade walks — // so this scans stylesheet text directly, mirroring the border rule's // gates: >= 3px thick, chromatic fill, full height against a side edge. -function scanCssTextForPseudoStripe(content) { +function scanCssTextForPseudoStripe(rawContent) { + // Blank comment bodies byte-for-byte so commented-out rules are not + // scanned as live CSS and every rule keeps its source offset (each + // finding carries `index` so line-based callers can attribute it and + // line-scoped inline ignores can match). + const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g, + (block) => block.replace(/[^\n]/g, ' ')); const customProps = collectCssCustomProps(content); const findings = []; const seen = new Set(); @@ -1734,9 +1740,13 @@ function scanCssTextForPseudoStripe(content) { if (seen.has(selector)) continue; seen.add(selector); + // The selector group absorbs whitespace trailing the previous rule; + // advance past it so `index` points at the selector itself. + const selectorStart = m.index + (m[1].length - m[1].trimStart().length); findings.push({ id: 'side-tab', snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`, + index: selectorStart, }); } return findings; diff --git a/.grok/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs b/.grok/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs index 882f76b42..c664f70b6 100644 --- a/.grok/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs +++ b/.grok/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs @@ -2,7 +2,7 @@ import { GENERIC_FONTS, OVERUSED_FONTS, EM_DASH_FLOOR, EM_DASH_CHARS_PER_DASH } import { isNeutralColor } from '../../shared/color.mjs'; import { extractGoogleFontFamilies } from '../../shared/fonts.mjs'; import { checkSourceDesignSystem } from '../../design-system.mjs'; -import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForRadialHalo } from '../../rules/checks.mjs'; +import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForPseudoStripe, scanCssTextForRadialHalo } from '../../rules/checks.mjs'; import { isFullPage } from '../../shared/page.mjs'; import { applyInlineIgnores } from '../../shared/inline-ignores.mjs'; import { finding } from '../../findings.mjs'; @@ -653,7 +653,21 @@ function detectText(content, filePath, options = {}) { profile, phase: 'source', })); - if (cssLike.has(ext)) findings.push(...scanInsetStripeCss(content, filePath)); + // Pseudo-element stripes (::before/::after absolute bars) carry the same + // side-tab silhouette without any border token, so the line matchers can't + // see them (issue #394). The shared scanner already runs on full HTML pages + // via checkHtmlPatterns; give standalone stylesheets, component style + // blocks, and CSS-in-JS templates the same coverage. Each hit carries the + // rule's source offset, so the finding gets a real line and line-scoped + // inline ignores keep working. + const pseudoStripeFindings = (text, lineOffset) => + scanCssTextForPseudoStripe(text).map(hit => + finding(hit.id, filePath, hit.snippet, lineOffset + text.slice(0, hit.index).split('\n').length)); + + if (cssLike.has(ext)) { + findings.push(...scanInsetStripeCss(content, filePath)); + findings.push(...pseudoStripeFindings(content, 0)); + } // Block-level CSS checks that need multiple declarations must run over the // complete source, not line-by-line. This covers standalone stylesheets, @@ -690,6 +704,7 @@ function detectText(content, filePath, options = {}) { // reported every selector one line low. runRegexMatchers keeps startLine - 1 // because it indexes its split lines from zero. findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 2)); + findings.push(...pseudoStripeFindings(block.content, block.startLine - 2)); } // Extract and scan CSS-in-JS template literals @@ -708,6 +723,7 @@ function detectText(content, filePath, options = {}) { phase: 'css-in-js', })); findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 1)); + findings.push(...pseudoStripeFindings(block.content, block.startLine - 1)); } if (options?.designSystem) { diff --git a/.grok/skills/impeccable/scripts/detector/rules/checks.mjs b/.grok/skills/impeccable/scripts/detector/rules/checks.mjs index 141a209a2..3e810076b 100644 --- a/.grok/skills/impeccable/scripts/detector/rules/checks.mjs +++ b/.grok/skills/impeccable/scripts/detector/rules/checks.mjs @@ -823,7 +823,13 @@ function isZeroOffset(value) { // never see it — pseudo-elements aren't part of the DOM the cascade walks — // so this scans stylesheet text directly, mirroring the border rule's // gates: >= 3px thick, chromatic fill, full height against a side edge. -function scanCssTextForPseudoStripe(content) { +function scanCssTextForPseudoStripe(rawContent) { + // Blank comment bodies byte-for-byte so commented-out rules are not + // scanned as live CSS and every rule keeps its source offset (each + // finding carries `index` so line-based callers can attribute it and + // line-scoped inline ignores can match). + const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g, + (block) => block.replace(/[^\n]/g, ' ')); const customProps = collectCssCustomProps(content); const findings = []; const seen = new Set(); @@ -932,9 +938,13 @@ function scanCssTextForPseudoStripe(content) { if (seen.has(selector)) continue; seen.add(selector); + // The selector group absorbs whitespace trailing the previous rule; + // advance past it so `index` points at the selector itself. + const selectorStart = m.index + (m[1].length - m[1].trimStart().length); findings.push({ id: 'side-tab', snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`, + index: selectorStart, }); } return findings; diff --git a/.kiro/skills/impeccable/scripts/detector/detect-antipatterns-browser.js b/.kiro/skills/impeccable/scripts/detector/detect-antipatterns-browser.js index d07caa7ab..a728d4f9e 100644 --- a/.kiro/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +++ b/.kiro/skills/impeccable/scripts/detector/detect-antipatterns-browser.js @@ -1625,7 +1625,13 @@ function isZeroOffset(value) { // never see it — pseudo-elements aren't part of the DOM the cascade walks — // so this scans stylesheet text directly, mirroring the border rule's // gates: >= 3px thick, chromatic fill, full height against a side edge. -function scanCssTextForPseudoStripe(content) { +function scanCssTextForPseudoStripe(rawContent) { + // Blank comment bodies byte-for-byte so commented-out rules are not + // scanned as live CSS and every rule keeps its source offset (each + // finding carries `index` so line-based callers can attribute it and + // line-scoped inline ignores can match). + const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g, + (block) => block.replace(/[^\n]/g, ' ')); const customProps = collectCssCustomProps(content); const findings = []; const seen = new Set(); @@ -1734,9 +1740,13 @@ function scanCssTextForPseudoStripe(content) { if (seen.has(selector)) continue; seen.add(selector); + // The selector group absorbs whitespace trailing the previous rule; + // advance past it so `index` points at the selector itself. + const selectorStart = m.index + (m[1].length - m[1].trimStart().length); findings.push({ id: 'side-tab', snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`, + index: selectorStart, }); } return findings; diff --git a/.kiro/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs b/.kiro/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs index 882f76b42..c664f70b6 100644 --- a/.kiro/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs +++ b/.kiro/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs @@ -2,7 +2,7 @@ import { GENERIC_FONTS, OVERUSED_FONTS, EM_DASH_FLOOR, EM_DASH_CHARS_PER_DASH } import { isNeutralColor } from '../../shared/color.mjs'; import { extractGoogleFontFamilies } from '../../shared/fonts.mjs'; import { checkSourceDesignSystem } from '../../design-system.mjs'; -import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForRadialHalo } from '../../rules/checks.mjs'; +import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForPseudoStripe, scanCssTextForRadialHalo } from '../../rules/checks.mjs'; import { isFullPage } from '../../shared/page.mjs'; import { applyInlineIgnores } from '../../shared/inline-ignores.mjs'; import { finding } from '../../findings.mjs'; @@ -653,7 +653,21 @@ function detectText(content, filePath, options = {}) { profile, phase: 'source', })); - if (cssLike.has(ext)) findings.push(...scanInsetStripeCss(content, filePath)); + // Pseudo-element stripes (::before/::after absolute bars) carry the same + // side-tab silhouette without any border token, so the line matchers can't + // see them (issue #394). The shared scanner already runs on full HTML pages + // via checkHtmlPatterns; give standalone stylesheets, component style + // blocks, and CSS-in-JS templates the same coverage. Each hit carries the + // rule's source offset, so the finding gets a real line and line-scoped + // inline ignores keep working. + const pseudoStripeFindings = (text, lineOffset) => + scanCssTextForPseudoStripe(text).map(hit => + finding(hit.id, filePath, hit.snippet, lineOffset + text.slice(0, hit.index).split('\n').length)); + + if (cssLike.has(ext)) { + findings.push(...scanInsetStripeCss(content, filePath)); + findings.push(...pseudoStripeFindings(content, 0)); + } // Block-level CSS checks that need multiple declarations must run over the // complete source, not line-by-line. This covers standalone stylesheets, @@ -690,6 +704,7 @@ function detectText(content, filePath, options = {}) { // reported every selector one line low. runRegexMatchers keeps startLine - 1 // because it indexes its split lines from zero. findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 2)); + findings.push(...pseudoStripeFindings(block.content, block.startLine - 2)); } // Extract and scan CSS-in-JS template literals @@ -708,6 +723,7 @@ function detectText(content, filePath, options = {}) { phase: 'css-in-js', })); findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 1)); + findings.push(...pseudoStripeFindings(block.content, block.startLine - 1)); } if (options?.designSystem) { diff --git a/.kiro/skills/impeccable/scripts/detector/rules/checks.mjs b/.kiro/skills/impeccable/scripts/detector/rules/checks.mjs index 141a209a2..3e810076b 100644 --- a/.kiro/skills/impeccable/scripts/detector/rules/checks.mjs +++ b/.kiro/skills/impeccable/scripts/detector/rules/checks.mjs @@ -823,7 +823,13 @@ function isZeroOffset(value) { // never see it — pseudo-elements aren't part of the DOM the cascade walks — // so this scans stylesheet text directly, mirroring the border rule's // gates: >= 3px thick, chromatic fill, full height against a side edge. -function scanCssTextForPseudoStripe(content) { +function scanCssTextForPseudoStripe(rawContent) { + // Blank comment bodies byte-for-byte so commented-out rules are not + // scanned as live CSS and every rule keeps its source offset (each + // finding carries `index` so line-based callers can attribute it and + // line-scoped inline ignores can match). + const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g, + (block) => block.replace(/[^\n]/g, ' ')); const customProps = collectCssCustomProps(content); const findings = []; const seen = new Set(); @@ -932,9 +938,13 @@ function scanCssTextForPseudoStripe(content) { if (seen.has(selector)) continue; seen.add(selector); + // The selector group absorbs whitespace trailing the previous rule; + // advance past it so `index` points at the selector itself. + const selectorStart = m.index + (m[1].length - m[1].trimStart().length); findings.push({ id: 'side-tab', snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`, + index: selectorStart, }); } return findings; diff --git a/.opencode/skills/impeccable/scripts/detector/detect-antipatterns-browser.js b/.opencode/skills/impeccable/scripts/detector/detect-antipatterns-browser.js index d07caa7ab..a728d4f9e 100644 --- a/.opencode/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +++ b/.opencode/skills/impeccable/scripts/detector/detect-antipatterns-browser.js @@ -1625,7 +1625,13 @@ function isZeroOffset(value) { // never see it — pseudo-elements aren't part of the DOM the cascade walks — // so this scans stylesheet text directly, mirroring the border rule's // gates: >= 3px thick, chromatic fill, full height against a side edge. -function scanCssTextForPseudoStripe(content) { +function scanCssTextForPseudoStripe(rawContent) { + // Blank comment bodies byte-for-byte so commented-out rules are not + // scanned as live CSS and every rule keeps its source offset (each + // finding carries `index` so line-based callers can attribute it and + // line-scoped inline ignores can match). + const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g, + (block) => block.replace(/[^\n]/g, ' ')); const customProps = collectCssCustomProps(content); const findings = []; const seen = new Set(); @@ -1734,9 +1740,13 @@ function scanCssTextForPseudoStripe(content) { if (seen.has(selector)) continue; seen.add(selector); + // The selector group absorbs whitespace trailing the previous rule; + // advance past it so `index` points at the selector itself. + const selectorStart = m.index + (m[1].length - m[1].trimStart().length); findings.push({ id: 'side-tab', snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`, + index: selectorStart, }); } return findings; diff --git a/.opencode/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs b/.opencode/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs index 882f76b42..c664f70b6 100644 --- a/.opencode/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs +++ b/.opencode/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs @@ -2,7 +2,7 @@ import { GENERIC_FONTS, OVERUSED_FONTS, EM_DASH_FLOOR, EM_DASH_CHARS_PER_DASH } import { isNeutralColor } from '../../shared/color.mjs'; import { extractGoogleFontFamilies } from '../../shared/fonts.mjs'; import { checkSourceDesignSystem } from '../../design-system.mjs'; -import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForRadialHalo } from '../../rules/checks.mjs'; +import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForPseudoStripe, scanCssTextForRadialHalo } from '../../rules/checks.mjs'; import { isFullPage } from '../../shared/page.mjs'; import { applyInlineIgnores } from '../../shared/inline-ignores.mjs'; import { finding } from '../../findings.mjs'; @@ -653,7 +653,21 @@ function detectText(content, filePath, options = {}) { profile, phase: 'source', })); - if (cssLike.has(ext)) findings.push(...scanInsetStripeCss(content, filePath)); + // Pseudo-element stripes (::before/::after absolute bars) carry the same + // side-tab silhouette without any border token, so the line matchers can't + // see them (issue #394). The shared scanner already runs on full HTML pages + // via checkHtmlPatterns; give standalone stylesheets, component style + // blocks, and CSS-in-JS templates the same coverage. Each hit carries the + // rule's source offset, so the finding gets a real line and line-scoped + // inline ignores keep working. + const pseudoStripeFindings = (text, lineOffset) => + scanCssTextForPseudoStripe(text).map(hit => + finding(hit.id, filePath, hit.snippet, lineOffset + text.slice(0, hit.index).split('\n').length)); + + if (cssLike.has(ext)) { + findings.push(...scanInsetStripeCss(content, filePath)); + findings.push(...pseudoStripeFindings(content, 0)); + } // Block-level CSS checks that need multiple declarations must run over the // complete source, not line-by-line. This covers standalone stylesheets, @@ -690,6 +704,7 @@ function detectText(content, filePath, options = {}) { // reported every selector one line low. runRegexMatchers keeps startLine - 1 // because it indexes its split lines from zero. findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 2)); + findings.push(...pseudoStripeFindings(block.content, block.startLine - 2)); } // Extract and scan CSS-in-JS template literals @@ -708,6 +723,7 @@ function detectText(content, filePath, options = {}) { phase: 'css-in-js', })); findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 1)); + findings.push(...pseudoStripeFindings(block.content, block.startLine - 1)); } if (options?.designSystem) { diff --git a/.opencode/skills/impeccable/scripts/detector/rules/checks.mjs b/.opencode/skills/impeccable/scripts/detector/rules/checks.mjs index 141a209a2..3e810076b 100644 --- a/.opencode/skills/impeccable/scripts/detector/rules/checks.mjs +++ b/.opencode/skills/impeccable/scripts/detector/rules/checks.mjs @@ -823,7 +823,13 @@ function isZeroOffset(value) { // never see it — pseudo-elements aren't part of the DOM the cascade walks — // so this scans stylesheet text directly, mirroring the border rule's // gates: >= 3px thick, chromatic fill, full height against a side edge. -function scanCssTextForPseudoStripe(content) { +function scanCssTextForPseudoStripe(rawContent) { + // Blank comment bodies byte-for-byte so commented-out rules are not + // scanned as live CSS and every rule keeps its source offset (each + // finding carries `index` so line-based callers can attribute it and + // line-scoped inline ignores can match). + const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g, + (block) => block.replace(/[^\n]/g, ' ')); const customProps = collectCssCustomProps(content); const findings = []; const seen = new Set(); @@ -932,9 +938,13 @@ function scanCssTextForPseudoStripe(content) { if (seen.has(selector)) continue; seen.add(selector); + // The selector group absorbs whitespace trailing the previous rule; + // advance past it so `index` points at the selector itself. + const selectorStart = m.index + (m[1].length - m[1].trimStart().length); findings.push({ id: 'side-tab', snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`, + index: selectorStart, }); } return findings; diff --git a/.pi/skills/impeccable/scripts/detector/detect-antipatterns-browser.js b/.pi/skills/impeccable/scripts/detector/detect-antipatterns-browser.js index d07caa7ab..a728d4f9e 100644 --- a/.pi/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +++ b/.pi/skills/impeccable/scripts/detector/detect-antipatterns-browser.js @@ -1625,7 +1625,13 @@ function isZeroOffset(value) { // never see it — pseudo-elements aren't part of the DOM the cascade walks — // so this scans stylesheet text directly, mirroring the border rule's // gates: >= 3px thick, chromatic fill, full height against a side edge. -function scanCssTextForPseudoStripe(content) { +function scanCssTextForPseudoStripe(rawContent) { + // Blank comment bodies byte-for-byte so commented-out rules are not + // scanned as live CSS and every rule keeps its source offset (each + // finding carries `index` so line-based callers can attribute it and + // line-scoped inline ignores can match). + const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g, + (block) => block.replace(/[^\n]/g, ' ')); const customProps = collectCssCustomProps(content); const findings = []; const seen = new Set(); @@ -1734,9 +1740,13 @@ function scanCssTextForPseudoStripe(content) { if (seen.has(selector)) continue; seen.add(selector); + // The selector group absorbs whitespace trailing the previous rule; + // advance past it so `index` points at the selector itself. + const selectorStart = m.index + (m[1].length - m[1].trimStart().length); findings.push({ id: 'side-tab', snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`, + index: selectorStart, }); } return findings; diff --git a/.pi/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs b/.pi/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs index 882f76b42..c664f70b6 100644 --- a/.pi/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs +++ b/.pi/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs @@ -2,7 +2,7 @@ import { GENERIC_FONTS, OVERUSED_FONTS, EM_DASH_FLOOR, EM_DASH_CHARS_PER_DASH } import { isNeutralColor } from '../../shared/color.mjs'; import { extractGoogleFontFamilies } from '../../shared/fonts.mjs'; import { checkSourceDesignSystem } from '../../design-system.mjs'; -import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForRadialHalo } from '../../rules/checks.mjs'; +import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForPseudoStripe, scanCssTextForRadialHalo } from '../../rules/checks.mjs'; import { isFullPage } from '../../shared/page.mjs'; import { applyInlineIgnores } from '../../shared/inline-ignores.mjs'; import { finding } from '../../findings.mjs'; @@ -653,7 +653,21 @@ function detectText(content, filePath, options = {}) { profile, phase: 'source', })); - if (cssLike.has(ext)) findings.push(...scanInsetStripeCss(content, filePath)); + // Pseudo-element stripes (::before/::after absolute bars) carry the same + // side-tab silhouette without any border token, so the line matchers can't + // see them (issue #394). The shared scanner already runs on full HTML pages + // via checkHtmlPatterns; give standalone stylesheets, component style + // blocks, and CSS-in-JS templates the same coverage. Each hit carries the + // rule's source offset, so the finding gets a real line and line-scoped + // inline ignores keep working. + const pseudoStripeFindings = (text, lineOffset) => + scanCssTextForPseudoStripe(text).map(hit => + finding(hit.id, filePath, hit.snippet, lineOffset + text.slice(0, hit.index).split('\n').length)); + + if (cssLike.has(ext)) { + findings.push(...scanInsetStripeCss(content, filePath)); + findings.push(...pseudoStripeFindings(content, 0)); + } // Block-level CSS checks that need multiple declarations must run over the // complete source, not line-by-line. This covers standalone stylesheets, @@ -690,6 +704,7 @@ function detectText(content, filePath, options = {}) { // reported every selector one line low. runRegexMatchers keeps startLine - 1 // because it indexes its split lines from zero. findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 2)); + findings.push(...pseudoStripeFindings(block.content, block.startLine - 2)); } // Extract and scan CSS-in-JS template literals @@ -708,6 +723,7 @@ function detectText(content, filePath, options = {}) { phase: 'css-in-js', })); findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 1)); + findings.push(...pseudoStripeFindings(block.content, block.startLine - 1)); } if (options?.designSystem) { diff --git a/.pi/skills/impeccable/scripts/detector/rules/checks.mjs b/.pi/skills/impeccable/scripts/detector/rules/checks.mjs index 141a209a2..3e810076b 100644 --- a/.pi/skills/impeccable/scripts/detector/rules/checks.mjs +++ b/.pi/skills/impeccable/scripts/detector/rules/checks.mjs @@ -823,7 +823,13 @@ function isZeroOffset(value) { // never see it — pseudo-elements aren't part of the DOM the cascade walks — // so this scans stylesheet text directly, mirroring the border rule's // gates: >= 3px thick, chromatic fill, full height against a side edge. -function scanCssTextForPseudoStripe(content) { +function scanCssTextForPseudoStripe(rawContent) { + // Blank comment bodies byte-for-byte so commented-out rules are not + // scanned as live CSS and every rule keeps its source offset (each + // finding carries `index` so line-based callers can attribute it and + // line-scoped inline ignores can match). + const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g, + (block) => block.replace(/[^\n]/g, ' ')); const customProps = collectCssCustomProps(content); const findings = []; const seen = new Set(); @@ -932,9 +938,13 @@ function scanCssTextForPseudoStripe(content) { if (seen.has(selector)) continue; seen.add(selector); + // The selector group absorbs whitespace trailing the previous rule; + // advance past it so `index` points at the selector itself. + const selectorStart = m.index + (m[1].length - m[1].trimStart().length); findings.push({ id: 'side-tab', snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`, + index: selectorStart, }); } return findings; diff --git a/.qoder/skills/impeccable/scripts/detector/detect-antipatterns-browser.js b/.qoder/skills/impeccable/scripts/detector/detect-antipatterns-browser.js index d07caa7ab..a728d4f9e 100644 --- a/.qoder/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +++ b/.qoder/skills/impeccable/scripts/detector/detect-antipatterns-browser.js @@ -1625,7 +1625,13 @@ function isZeroOffset(value) { // never see it — pseudo-elements aren't part of the DOM the cascade walks — // so this scans stylesheet text directly, mirroring the border rule's // gates: >= 3px thick, chromatic fill, full height against a side edge. -function scanCssTextForPseudoStripe(content) { +function scanCssTextForPseudoStripe(rawContent) { + // Blank comment bodies byte-for-byte so commented-out rules are not + // scanned as live CSS and every rule keeps its source offset (each + // finding carries `index` so line-based callers can attribute it and + // line-scoped inline ignores can match). + const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g, + (block) => block.replace(/[^\n]/g, ' ')); const customProps = collectCssCustomProps(content); const findings = []; const seen = new Set(); @@ -1734,9 +1740,13 @@ function scanCssTextForPseudoStripe(content) { if (seen.has(selector)) continue; seen.add(selector); + // The selector group absorbs whitespace trailing the previous rule; + // advance past it so `index` points at the selector itself. + const selectorStart = m.index + (m[1].length - m[1].trimStart().length); findings.push({ id: 'side-tab', snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`, + index: selectorStart, }); } return findings; diff --git a/.qoder/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs b/.qoder/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs index 882f76b42..c664f70b6 100644 --- a/.qoder/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs +++ b/.qoder/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs @@ -2,7 +2,7 @@ import { GENERIC_FONTS, OVERUSED_FONTS, EM_DASH_FLOOR, EM_DASH_CHARS_PER_DASH } import { isNeutralColor } from '../../shared/color.mjs'; import { extractGoogleFontFamilies } from '../../shared/fonts.mjs'; import { checkSourceDesignSystem } from '../../design-system.mjs'; -import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForRadialHalo } from '../../rules/checks.mjs'; +import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForPseudoStripe, scanCssTextForRadialHalo } from '../../rules/checks.mjs'; import { isFullPage } from '../../shared/page.mjs'; import { applyInlineIgnores } from '../../shared/inline-ignores.mjs'; import { finding } from '../../findings.mjs'; @@ -653,7 +653,21 @@ function detectText(content, filePath, options = {}) { profile, phase: 'source', })); - if (cssLike.has(ext)) findings.push(...scanInsetStripeCss(content, filePath)); + // Pseudo-element stripes (::before/::after absolute bars) carry the same + // side-tab silhouette without any border token, so the line matchers can't + // see them (issue #394). The shared scanner already runs on full HTML pages + // via checkHtmlPatterns; give standalone stylesheets, component style + // blocks, and CSS-in-JS templates the same coverage. Each hit carries the + // rule's source offset, so the finding gets a real line and line-scoped + // inline ignores keep working. + const pseudoStripeFindings = (text, lineOffset) => + scanCssTextForPseudoStripe(text).map(hit => + finding(hit.id, filePath, hit.snippet, lineOffset + text.slice(0, hit.index).split('\n').length)); + + if (cssLike.has(ext)) { + findings.push(...scanInsetStripeCss(content, filePath)); + findings.push(...pseudoStripeFindings(content, 0)); + } // Block-level CSS checks that need multiple declarations must run over the // complete source, not line-by-line. This covers standalone stylesheets, @@ -690,6 +704,7 @@ function detectText(content, filePath, options = {}) { // reported every selector one line low. runRegexMatchers keeps startLine - 1 // because it indexes its split lines from zero. findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 2)); + findings.push(...pseudoStripeFindings(block.content, block.startLine - 2)); } // Extract and scan CSS-in-JS template literals @@ -708,6 +723,7 @@ function detectText(content, filePath, options = {}) { phase: 'css-in-js', })); findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 1)); + findings.push(...pseudoStripeFindings(block.content, block.startLine - 1)); } if (options?.designSystem) { diff --git a/.qoder/skills/impeccable/scripts/detector/rules/checks.mjs b/.qoder/skills/impeccable/scripts/detector/rules/checks.mjs index 141a209a2..3e810076b 100644 --- a/.qoder/skills/impeccable/scripts/detector/rules/checks.mjs +++ b/.qoder/skills/impeccable/scripts/detector/rules/checks.mjs @@ -823,7 +823,13 @@ function isZeroOffset(value) { // never see it — pseudo-elements aren't part of the DOM the cascade walks — // so this scans stylesheet text directly, mirroring the border rule's // gates: >= 3px thick, chromatic fill, full height against a side edge. -function scanCssTextForPseudoStripe(content) { +function scanCssTextForPseudoStripe(rawContent) { + // Blank comment bodies byte-for-byte so commented-out rules are not + // scanned as live CSS and every rule keeps its source offset (each + // finding carries `index` so line-based callers can attribute it and + // line-scoped inline ignores can match). + const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g, + (block) => block.replace(/[^\n]/g, ' ')); const customProps = collectCssCustomProps(content); const findings = []; const seen = new Set(); @@ -932,9 +938,13 @@ function scanCssTextForPseudoStripe(content) { if (seen.has(selector)) continue; seen.add(selector); + // The selector group absorbs whitespace trailing the previous rule; + // advance past it so `index` points at the selector itself. + const selectorStart = m.index + (m[1].length - m[1].trimStart().length); findings.push({ id: 'side-tab', snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`, + index: selectorStart, }); } return findings; diff --git a/.rovodev/skills/impeccable/scripts/detector/detect-antipatterns-browser.js b/.rovodev/skills/impeccable/scripts/detector/detect-antipatterns-browser.js index d07caa7ab..a728d4f9e 100644 --- a/.rovodev/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +++ b/.rovodev/skills/impeccable/scripts/detector/detect-antipatterns-browser.js @@ -1625,7 +1625,13 @@ function isZeroOffset(value) { // never see it — pseudo-elements aren't part of the DOM the cascade walks — // so this scans stylesheet text directly, mirroring the border rule's // gates: >= 3px thick, chromatic fill, full height against a side edge. -function scanCssTextForPseudoStripe(content) { +function scanCssTextForPseudoStripe(rawContent) { + // Blank comment bodies byte-for-byte so commented-out rules are not + // scanned as live CSS and every rule keeps its source offset (each + // finding carries `index` so line-based callers can attribute it and + // line-scoped inline ignores can match). + const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g, + (block) => block.replace(/[^\n]/g, ' ')); const customProps = collectCssCustomProps(content); const findings = []; const seen = new Set(); @@ -1734,9 +1740,13 @@ function scanCssTextForPseudoStripe(content) { if (seen.has(selector)) continue; seen.add(selector); + // The selector group absorbs whitespace trailing the previous rule; + // advance past it so `index` points at the selector itself. + const selectorStart = m.index + (m[1].length - m[1].trimStart().length); findings.push({ id: 'side-tab', snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`, + index: selectorStart, }); } return findings; diff --git a/.rovodev/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs b/.rovodev/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs index 882f76b42..c664f70b6 100644 --- a/.rovodev/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs +++ b/.rovodev/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs @@ -2,7 +2,7 @@ import { GENERIC_FONTS, OVERUSED_FONTS, EM_DASH_FLOOR, EM_DASH_CHARS_PER_DASH } import { isNeutralColor } from '../../shared/color.mjs'; import { extractGoogleFontFamilies } from '../../shared/fonts.mjs'; import { checkSourceDesignSystem } from '../../design-system.mjs'; -import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForRadialHalo } from '../../rules/checks.mjs'; +import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForPseudoStripe, scanCssTextForRadialHalo } from '../../rules/checks.mjs'; import { isFullPage } from '../../shared/page.mjs'; import { applyInlineIgnores } from '../../shared/inline-ignores.mjs'; import { finding } from '../../findings.mjs'; @@ -653,7 +653,21 @@ function detectText(content, filePath, options = {}) { profile, phase: 'source', })); - if (cssLike.has(ext)) findings.push(...scanInsetStripeCss(content, filePath)); + // Pseudo-element stripes (::before/::after absolute bars) carry the same + // side-tab silhouette without any border token, so the line matchers can't + // see them (issue #394). The shared scanner already runs on full HTML pages + // via checkHtmlPatterns; give standalone stylesheets, component style + // blocks, and CSS-in-JS templates the same coverage. Each hit carries the + // rule's source offset, so the finding gets a real line and line-scoped + // inline ignores keep working. + const pseudoStripeFindings = (text, lineOffset) => + scanCssTextForPseudoStripe(text).map(hit => + finding(hit.id, filePath, hit.snippet, lineOffset + text.slice(0, hit.index).split('\n').length)); + + if (cssLike.has(ext)) { + findings.push(...scanInsetStripeCss(content, filePath)); + findings.push(...pseudoStripeFindings(content, 0)); + } // Block-level CSS checks that need multiple declarations must run over the // complete source, not line-by-line. This covers standalone stylesheets, @@ -690,6 +704,7 @@ function detectText(content, filePath, options = {}) { // reported every selector one line low. runRegexMatchers keeps startLine - 1 // because it indexes its split lines from zero. findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 2)); + findings.push(...pseudoStripeFindings(block.content, block.startLine - 2)); } // Extract and scan CSS-in-JS template literals @@ -708,6 +723,7 @@ function detectText(content, filePath, options = {}) { phase: 'css-in-js', })); findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 1)); + findings.push(...pseudoStripeFindings(block.content, block.startLine - 1)); } if (options?.designSystem) { diff --git a/.rovodev/skills/impeccable/scripts/detector/rules/checks.mjs b/.rovodev/skills/impeccable/scripts/detector/rules/checks.mjs index 141a209a2..3e810076b 100644 --- a/.rovodev/skills/impeccable/scripts/detector/rules/checks.mjs +++ b/.rovodev/skills/impeccable/scripts/detector/rules/checks.mjs @@ -823,7 +823,13 @@ function isZeroOffset(value) { // never see it — pseudo-elements aren't part of the DOM the cascade walks — // so this scans stylesheet text directly, mirroring the border rule's // gates: >= 3px thick, chromatic fill, full height against a side edge. -function scanCssTextForPseudoStripe(content) { +function scanCssTextForPseudoStripe(rawContent) { + // Blank comment bodies byte-for-byte so commented-out rules are not + // scanned as live CSS and every rule keeps its source offset (each + // finding carries `index` so line-based callers can attribute it and + // line-scoped inline ignores can match). + const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g, + (block) => block.replace(/[^\n]/g, ' ')); const customProps = collectCssCustomProps(content); const findings = []; const seen = new Set(); @@ -932,9 +938,13 @@ function scanCssTextForPseudoStripe(content) { if (seen.has(selector)) continue; seen.add(selector); + // The selector group absorbs whitespace trailing the previous rule; + // advance past it so `index` points at the selector itself. + const selectorStart = m.index + (m[1].length - m[1].trimStart().length); findings.push({ id: 'side-tab', snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`, + index: selectorStart, }); } return findings; diff --git a/.trae-cn/skills/impeccable/scripts/detector/detect-antipatterns-browser.js b/.trae-cn/skills/impeccable/scripts/detector/detect-antipatterns-browser.js index d07caa7ab..a728d4f9e 100644 --- a/.trae-cn/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +++ b/.trae-cn/skills/impeccable/scripts/detector/detect-antipatterns-browser.js @@ -1625,7 +1625,13 @@ function isZeroOffset(value) { // never see it — pseudo-elements aren't part of the DOM the cascade walks — // so this scans stylesheet text directly, mirroring the border rule's // gates: >= 3px thick, chromatic fill, full height against a side edge. -function scanCssTextForPseudoStripe(content) { +function scanCssTextForPseudoStripe(rawContent) { + // Blank comment bodies byte-for-byte so commented-out rules are not + // scanned as live CSS and every rule keeps its source offset (each + // finding carries `index` so line-based callers can attribute it and + // line-scoped inline ignores can match). + const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g, + (block) => block.replace(/[^\n]/g, ' ')); const customProps = collectCssCustomProps(content); const findings = []; const seen = new Set(); @@ -1734,9 +1740,13 @@ function scanCssTextForPseudoStripe(content) { if (seen.has(selector)) continue; seen.add(selector); + // The selector group absorbs whitespace trailing the previous rule; + // advance past it so `index` points at the selector itself. + const selectorStart = m.index + (m[1].length - m[1].trimStart().length); findings.push({ id: 'side-tab', snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`, + index: selectorStart, }); } return findings; diff --git a/.trae-cn/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs b/.trae-cn/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs index 882f76b42..c664f70b6 100644 --- a/.trae-cn/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs +++ b/.trae-cn/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs @@ -2,7 +2,7 @@ import { GENERIC_FONTS, OVERUSED_FONTS, EM_DASH_FLOOR, EM_DASH_CHARS_PER_DASH } import { isNeutralColor } from '../../shared/color.mjs'; import { extractGoogleFontFamilies } from '../../shared/fonts.mjs'; import { checkSourceDesignSystem } from '../../design-system.mjs'; -import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForRadialHalo } from '../../rules/checks.mjs'; +import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForPseudoStripe, scanCssTextForRadialHalo } from '../../rules/checks.mjs'; import { isFullPage } from '../../shared/page.mjs'; import { applyInlineIgnores } from '../../shared/inline-ignores.mjs'; import { finding } from '../../findings.mjs'; @@ -653,7 +653,21 @@ function detectText(content, filePath, options = {}) { profile, phase: 'source', })); - if (cssLike.has(ext)) findings.push(...scanInsetStripeCss(content, filePath)); + // Pseudo-element stripes (::before/::after absolute bars) carry the same + // side-tab silhouette without any border token, so the line matchers can't + // see them (issue #394). The shared scanner already runs on full HTML pages + // via checkHtmlPatterns; give standalone stylesheets, component style + // blocks, and CSS-in-JS templates the same coverage. Each hit carries the + // rule's source offset, so the finding gets a real line and line-scoped + // inline ignores keep working. + const pseudoStripeFindings = (text, lineOffset) => + scanCssTextForPseudoStripe(text).map(hit => + finding(hit.id, filePath, hit.snippet, lineOffset + text.slice(0, hit.index).split('\n').length)); + + if (cssLike.has(ext)) { + findings.push(...scanInsetStripeCss(content, filePath)); + findings.push(...pseudoStripeFindings(content, 0)); + } // Block-level CSS checks that need multiple declarations must run over the // complete source, not line-by-line. This covers standalone stylesheets, @@ -690,6 +704,7 @@ function detectText(content, filePath, options = {}) { // reported every selector one line low. runRegexMatchers keeps startLine - 1 // because it indexes its split lines from zero. findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 2)); + findings.push(...pseudoStripeFindings(block.content, block.startLine - 2)); } // Extract and scan CSS-in-JS template literals @@ -708,6 +723,7 @@ function detectText(content, filePath, options = {}) { phase: 'css-in-js', })); findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 1)); + findings.push(...pseudoStripeFindings(block.content, block.startLine - 1)); } if (options?.designSystem) { diff --git a/.trae-cn/skills/impeccable/scripts/detector/rules/checks.mjs b/.trae-cn/skills/impeccable/scripts/detector/rules/checks.mjs index 141a209a2..3e810076b 100644 --- a/.trae-cn/skills/impeccable/scripts/detector/rules/checks.mjs +++ b/.trae-cn/skills/impeccable/scripts/detector/rules/checks.mjs @@ -823,7 +823,13 @@ function isZeroOffset(value) { // never see it — pseudo-elements aren't part of the DOM the cascade walks — // so this scans stylesheet text directly, mirroring the border rule's // gates: >= 3px thick, chromatic fill, full height against a side edge. -function scanCssTextForPseudoStripe(content) { +function scanCssTextForPseudoStripe(rawContent) { + // Blank comment bodies byte-for-byte so commented-out rules are not + // scanned as live CSS and every rule keeps its source offset (each + // finding carries `index` so line-based callers can attribute it and + // line-scoped inline ignores can match). + const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g, + (block) => block.replace(/[^\n]/g, ' ')); const customProps = collectCssCustomProps(content); const findings = []; const seen = new Set(); @@ -932,9 +938,13 @@ function scanCssTextForPseudoStripe(content) { if (seen.has(selector)) continue; seen.add(selector); + // The selector group absorbs whitespace trailing the previous rule; + // advance past it so `index` points at the selector itself. + const selectorStart = m.index + (m[1].length - m[1].trimStart().length); findings.push({ id: 'side-tab', snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`, + index: selectorStart, }); } return findings; diff --git a/.trae/skills/impeccable/scripts/detector/detect-antipatterns-browser.js b/.trae/skills/impeccable/scripts/detector/detect-antipatterns-browser.js index d07caa7ab..a728d4f9e 100644 --- a/.trae/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +++ b/.trae/skills/impeccable/scripts/detector/detect-antipatterns-browser.js @@ -1625,7 +1625,13 @@ function isZeroOffset(value) { // never see it — pseudo-elements aren't part of the DOM the cascade walks — // so this scans stylesheet text directly, mirroring the border rule's // gates: >= 3px thick, chromatic fill, full height against a side edge. -function scanCssTextForPseudoStripe(content) { +function scanCssTextForPseudoStripe(rawContent) { + // Blank comment bodies byte-for-byte so commented-out rules are not + // scanned as live CSS and every rule keeps its source offset (each + // finding carries `index` so line-based callers can attribute it and + // line-scoped inline ignores can match). + const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g, + (block) => block.replace(/[^\n]/g, ' ')); const customProps = collectCssCustomProps(content); const findings = []; const seen = new Set(); @@ -1734,9 +1740,13 @@ function scanCssTextForPseudoStripe(content) { if (seen.has(selector)) continue; seen.add(selector); + // The selector group absorbs whitespace trailing the previous rule; + // advance past it so `index` points at the selector itself. + const selectorStart = m.index + (m[1].length - m[1].trimStart().length); findings.push({ id: 'side-tab', snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`, + index: selectorStart, }); } return findings; diff --git a/.trae/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs b/.trae/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs index 882f76b42..c664f70b6 100644 --- a/.trae/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs +++ b/.trae/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs @@ -2,7 +2,7 @@ import { GENERIC_FONTS, OVERUSED_FONTS, EM_DASH_FLOOR, EM_DASH_CHARS_PER_DASH } import { isNeutralColor } from '../../shared/color.mjs'; import { extractGoogleFontFamilies } from '../../shared/fonts.mjs'; import { checkSourceDesignSystem } from '../../design-system.mjs'; -import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForRadialHalo } from '../../rules/checks.mjs'; +import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForPseudoStripe, scanCssTextForRadialHalo } from '../../rules/checks.mjs'; import { isFullPage } from '../../shared/page.mjs'; import { applyInlineIgnores } from '../../shared/inline-ignores.mjs'; import { finding } from '../../findings.mjs'; @@ -653,7 +653,21 @@ function detectText(content, filePath, options = {}) { profile, phase: 'source', })); - if (cssLike.has(ext)) findings.push(...scanInsetStripeCss(content, filePath)); + // Pseudo-element stripes (::before/::after absolute bars) carry the same + // side-tab silhouette without any border token, so the line matchers can't + // see them (issue #394). The shared scanner already runs on full HTML pages + // via checkHtmlPatterns; give standalone stylesheets, component style + // blocks, and CSS-in-JS templates the same coverage. Each hit carries the + // rule's source offset, so the finding gets a real line and line-scoped + // inline ignores keep working. + const pseudoStripeFindings = (text, lineOffset) => + scanCssTextForPseudoStripe(text).map(hit => + finding(hit.id, filePath, hit.snippet, lineOffset + text.slice(0, hit.index).split('\n').length)); + + if (cssLike.has(ext)) { + findings.push(...scanInsetStripeCss(content, filePath)); + findings.push(...pseudoStripeFindings(content, 0)); + } // Block-level CSS checks that need multiple declarations must run over the // complete source, not line-by-line. This covers standalone stylesheets, @@ -690,6 +704,7 @@ function detectText(content, filePath, options = {}) { // reported every selector one line low. runRegexMatchers keeps startLine - 1 // because it indexes its split lines from zero. findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 2)); + findings.push(...pseudoStripeFindings(block.content, block.startLine - 2)); } // Extract and scan CSS-in-JS template literals @@ -708,6 +723,7 @@ function detectText(content, filePath, options = {}) { phase: 'css-in-js', })); findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 1)); + findings.push(...pseudoStripeFindings(block.content, block.startLine - 1)); } if (options?.designSystem) { diff --git a/.trae/skills/impeccable/scripts/detector/rules/checks.mjs b/.trae/skills/impeccable/scripts/detector/rules/checks.mjs index 141a209a2..3e810076b 100644 --- a/.trae/skills/impeccable/scripts/detector/rules/checks.mjs +++ b/.trae/skills/impeccable/scripts/detector/rules/checks.mjs @@ -823,7 +823,13 @@ function isZeroOffset(value) { // never see it — pseudo-elements aren't part of the DOM the cascade walks — // so this scans stylesheet text directly, mirroring the border rule's // gates: >= 3px thick, chromatic fill, full height against a side edge. -function scanCssTextForPseudoStripe(content) { +function scanCssTextForPseudoStripe(rawContent) { + // Blank comment bodies byte-for-byte so commented-out rules are not + // scanned as live CSS and every rule keeps its source offset (each + // finding carries `index` so line-based callers can attribute it and + // line-scoped inline ignores can match). + const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g, + (block) => block.replace(/[^\n]/g, ' ')); const customProps = collectCssCustomProps(content); const findings = []; const seen = new Set(); @@ -932,9 +938,13 @@ function scanCssTextForPseudoStripe(content) { if (seen.has(selector)) continue; seen.add(selector); + // The selector group absorbs whitespace trailing the previous rule; + // advance past it so `index` points at the selector itself. + const selectorStart = m.index + (m[1].length - m[1].trimStart().length); findings.push({ id: 'side-tab', snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`, + index: selectorStart, }); } return findings; diff --git a/.vibe/skills/impeccable/scripts/detector/detect-antipatterns-browser.js b/.vibe/skills/impeccable/scripts/detector/detect-antipatterns-browser.js index d07caa7ab..a728d4f9e 100644 --- a/.vibe/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +++ b/.vibe/skills/impeccable/scripts/detector/detect-antipatterns-browser.js @@ -1625,7 +1625,13 @@ function isZeroOffset(value) { // never see it — pseudo-elements aren't part of the DOM the cascade walks — // so this scans stylesheet text directly, mirroring the border rule's // gates: >= 3px thick, chromatic fill, full height against a side edge. -function scanCssTextForPseudoStripe(content) { +function scanCssTextForPseudoStripe(rawContent) { + // Blank comment bodies byte-for-byte so commented-out rules are not + // scanned as live CSS and every rule keeps its source offset (each + // finding carries `index` so line-based callers can attribute it and + // line-scoped inline ignores can match). + const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g, + (block) => block.replace(/[^\n]/g, ' ')); const customProps = collectCssCustomProps(content); const findings = []; const seen = new Set(); @@ -1734,9 +1740,13 @@ function scanCssTextForPseudoStripe(content) { if (seen.has(selector)) continue; seen.add(selector); + // The selector group absorbs whitespace trailing the previous rule; + // advance past it so `index` points at the selector itself. + const selectorStart = m.index + (m[1].length - m[1].trimStart().length); findings.push({ id: 'side-tab', snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`, + index: selectorStart, }); } return findings; diff --git a/.vibe/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs b/.vibe/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs index 882f76b42..c664f70b6 100644 --- a/.vibe/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs +++ b/.vibe/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs @@ -2,7 +2,7 @@ import { GENERIC_FONTS, OVERUSED_FONTS, EM_DASH_FLOOR, EM_DASH_CHARS_PER_DASH } import { isNeutralColor } from '../../shared/color.mjs'; import { extractGoogleFontFamilies } from '../../shared/fonts.mjs'; import { checkSourceDesignSystem } from '../../design-system.mjs'; -import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForRadialHalo } from '../../rules/checks.mjs'; +import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForPseudoStripe, scanCssTextForRadialHalo } from '../../rules/checks.mjs'; import { isFullPage } from '../../shared/page.mjs'; import { applyInlineIgnores } from '../../shared/inline-ignores.mjs'; import { finding } from '../../findings.mjs'; @@ -653,7 +653,21 @@ function detectText(content, filePath, options = {}) { profile, phase: 'source', })); - if (cssLike.has(ext)) findings.push(...scanInsetStripeCss(content, filePath)); + // Pseudo-element stripes (::before/::after absolute bars) carry the same + // side-tab silhouette without any border token, so the line matchers can't + // see them (issue #394). The shared scanner already runs on full HTML pages + // via checkHtmlPatterns; give standalone stylesheets, component style + // blocks, and CSS-in-JS templates the same coverage. Each hit carries the + // rule's source offset, so the finding gets a real line and line-scoped + // inline ignores keep working. + const pseudoStripeFindings = (text, lineOffset) => + scanCssTextForPseudoStripe(text).map(hit => + finding(hit.id, filePath, hit.snippet, lineOffset + text.slice(0, hit.index).split('\n').length)); + + if (cssLike.has(ext)) { + findings.push(...scanInsetStripeCss(content, filePath)); + findings.push(...pseudoStripeFindings(content, 0)); + } // Block-level CSS checks that need multiple declarations must run over the // complete source, not line-by-line. This covers standalone stylesheets, @@ -690,6 +704,7 @@ function detectText(content, filePath, options = {}) { // reported every selector one line low. runRegexMatchers keeps startLine - 1 // because it indexes its split lines from zero. findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 2)); + findings.push(...pseudoStripeFindings(block.content, block.startLine - 2)); } // Extract and scan CSS-in-JS template literals @@ -708,6 +723,7 @@ function detectText(content, filePath, options = {}) { phase: 'css-in-js', })); findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 1)); + findings.push(...pseudoStripeFindings(block.content, block.startLine - 1)); } if (options?.designSystem) { diff --git a/.vibe/skills/impeccable/scripts/detector/rules/checks.mjs b/.vibe/skills/impeccable/scripts/detector/rules/checks.mjs index 141a209a2..3e810076b 100644 --- a/.vibe/skills/impeccable/scripts/detector/rules/checks.mjs +++ b/.vibe/skills/impeccable/scripts/detector/rules/checks.mjs @@ -823,7 +823,13 @@ function isZeroOffset(value) { // never see it — pseudo-elements aren't part of the DOM the cascade walks — // so this scans stylesheet text directly, mirroring the border rule's // gates: >= 3px thick, chromatic fill, full height against a side edge. -function scanCssTextForPseudoStripe(content) { +function scanCssTextForPseudoStripe(rawContent) { + // Blank comment bodies byte-for-byte so commented-out rules are not + // scanned as live CSS and every rule keeps its source offset (each + // finding carries `index` so line-based callers can attribute it and + // line-scoped inline ignores can match). + const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g, + (block) => block.replace(/[^\n]/g, ' ')); const customProps = collectCssCustomProps(content); const findings = []; const seen = new Set(); @@ -932,9 +938,13 @@ function scanCssTextForPseudoStripe(content) { if (seen.has(selector)) continue; seen.add(selector); + // The selector group absorbs whitespace trailing the previous rule; + // advance past it so `index` points at the selector itself. + const selectorStart = m.index + (m[1].length - m[1].trimStart().length); findings.push({ id: 'side-tab', snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`, + index: selectorStart, }); } return findings; diff --git a/plugin/skills/impeccable/scripts/detector/detect-antipatterns-browser.js b/plugin/skills/impeccable/scripts/detector/detect-antipatterns-browser.js index d07caa7ab..a728d4f9e 100644 --- a/plugin/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +++ b/plugin/skills/impeccable/scripts/detector/detect-antipatterns-browser.js @@ -1625,7 +1625,13 @@ function isZeroOffset(value) { // never see it — pseudo-elements aren't part of the DOM the cascade walks — // so this scans stylesheet text directly, mirroring the border rule's // gates: >= 3px thick, chromatic fill, full height against a side edge. -function scanCssTextForPseudoStripe(content) { +function scanCssTextForPseudoStripe(rawContent) { + // Blank comment bodies byte-for-byte so commented-out rules are not + // scanned as live CSS and every rule keeps its source offset (each + // finding carries `index` so line-based callers can attribute it and + // line-scoped inline ignores can match). + const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g, + (block) => block.replace(/[^\n]/g, ' ')); const customProps = collectCssCustomProps(content); const findings = []; const seen = new Set(); @@ -1734,9 +1740,13 @@ function scanCssTextForPseudoStripe(content) { if (seen.has(selector)) continue; seen.add(selector); + // The selector group absorbs whitespace trailing the previous rule; + // advance past it so `index` points at the selector itself. + const selectorStart = m.index + (m[1].length - m[1].trimStart().length); findings.push({ id: 'side-tab', snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`, + index: selectorStart, }); } return findings; diff --git a/plugin/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs b/plugin/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs index 882f76b42..c664f70b6 100644 --- a/plugin/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs +++ b/plugin/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs @@ -2,7 +2,7 @@ import { GENERIC_FONTS, OVERUSED_FONTS, EM_DASH_FLOOR, EM_DASH_CHARS_PER_DASH } import { isNeutralColor } from '../../shared/color.mjs'; import { extractGoogleFontFamilies } from '../../shared/fonts.mjs'; import { checkSourceDesignSystem } from '../../design-system.mjs'; -import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForRadialHalo } from '../../rules/checks.mjs'; +import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForPseudoStripe, scanCssTextForRadialHalo } from '../../rules/checks.mjs'; import { isFullPage } from '../../shared/page.mjs'; import { applyInlineIgnores } from '../../shared/inline-ignores.mjs'; import { finding } from '../../findings.mjs'; @@ -653,7 +653,21 @@ function detectText(content, filePath, options = {}) { profile, phase: 'source', })); - if (cssLike.has(ext)) findings.push(...scanInsetStripeCss(content, filePath)); + // Pseudo-element stripes (::before/::after absolute bars) carry the same + // side-tab silhouette without any border token, so the line matchers can't + // see them (issue #394). The shared scanner already runs on full HTML pages + // via checkHtmlPatterns; give standalone stylesheets, component style + // blocks, and CSS-in-JS templates the same coverage. Each hit carries the + // rule's source offset, so the finding gets a real line and line-scoped + // inline ignores keep working. + const pseudoStripeFindings = (text, lineOffset) => + scanCssTextForPseudoStripe(text).map(hit => + finding(hit.id, filePath, hit.snippet, lineOffset + text.slice(0, hit.index).split('\n').length)); + + if (cssLike.has(ext)) { + findings.push(...scanInsetStripeCss(content, filePath)); + findings.push(...pseudoStripeFindings(content, 0)); + } // Block-level CSS checks that need multiple declarations must run over the // complete source, not line-by-line. This covers standalone stylesheets, @@ -690,6 +704,7 @@ function detectText(content, filePath, options = {}) { // reported every selector one line low. runRegexMatchers keeps startLine - 1 // because it indexes its split lines from zero. findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 2)); + findings.push(...pseudoStripeFindings(block.content, block.startLine - 2)); } // Extract and scan CSS-in-JS template literals @@ -708,6 +723,7 @@ function detectText(content, filePath, options = {}) { phase: 'css-in-js', })); findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 1)); + findings.push(...pseudoStripeFindings(block.content, block.startLine - 1)); } if (options?.designSystem) { diff --git a/plugin/skills/impeccable/scripts/detector/rules/checks.mjs b/plugin/skills/impeccable/scripts/detector/rules/checks.mjs index 141a209a2..3e810076b 100644 --- a/plugin/skills/impeccable/scripts/detector/rules/checks.mjs +++ b/plugin/skills/impeccable/scripts/detector/rules/checks.mjs @@ -823,7 +823,13 @@ function isZeroOffset(value) { // never see it — pseudo-elements aren't part of the DOM the cascade walks — // so this scans stylesheet text directly, mirroring the border rule's // gates: >= 3px thick, chromatic fill, full height against a side edge. -function scanCssTextForPseudoStripe(content) { +function scanCssTextForPseudoStripe(rawContent) { + // Blank comment bodies byte-for-byte so commented-out rules are not + // scanned as live CSS and every rule keeps its source offset (each + // finding carries `index` so line-based callers can attribute it and + // line-scoped inline ignores can match). + const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g, + (block) => block.replace(/[^\n]/g, ' ')); const customProps = collectCssCustomProps(content); const findings = []; const seen = new Set(); @@ -932,9 +938,13 @@ function scanCssTextForPseudoStripe(content) { if (seen.has(selector)) continue; seen.add(selector); + // The selector group absorbs whitespace trailing the previous rule; + // advance past it so `index` points at the selector itself. + const selectorStart = m.index + (m[1].length - m[1].trimStart().length); findings.push({ id: 'side-tab', snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`, + index: selectorStart, }); } return findings;