From 28875097b0529f4a258df8be5fcd9f258fe526a6 Mon Sep 17 00:00:00 2001 From: Paul Bakaus Date: Mon, 27 Apr 2026 14:33:54 -0700 Subject: [PATCH] fix(detector): preserve percent-radius signal when width is missing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit parseRadiusToPx("50%", 0) used to return 0, and resolveBorderRadiusPx's "if (fromComputed !== null) return fromComputed" guard short-circuited with that 0 before ever consulting longhand / inline / stylesheet fallbacks. Callers that gate on `> 0` (border-accent-on-rounded and isCardLike's hasRadius) silently lost findings the old parseFloat(style.borderRadius) === 50 heuristic happened to keep. In jsdom this is reachable any time style.width resolves to "auto" or an empty string — parseFloat yields NaN, the `|| 0` fallback turns it into 0, and any percent radius collapses to nothing. Real-world cards with `width: 100%` hit this on every load. Fix: when widthPx is 0 / missing, return the raw percentage number instead. The percent-to-px conversion only makes sense with a width reference; without one, the value still serves as a positive presence signal for boolean checks. The icon-tile circle exclusion is unaffected because that rule already gates on `siblingWidth >= 32`. Caught by Cursor Bugbot on PR #115. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/detect-antipatterns-browser.js | 12 ++++++++++-- src/detect-antipatterns.mjs | 12 ++++++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/detect-antipatterns-browser.js b/src/detect-antipatterns-browser.js index f7084ae9b..b147f45fb 100644 --- a/src/detect-antipatterns-browser.js +++ b/src/detect-antipatterns-browser.js @@ -888,7 +888,12 @@ function resolveGradientStops(el, win) { // Parse a single CSS length token to pixels. Accepts "12px", "50%", a // shorthand like "12px 4px" (uses the first value), or empty / null. // Returns the pixel value, or null when the input is unparseable. -// Percentages need a `widthPx` reference to convert against. +// Percentages convert against `widthPx` when one is supplied. Without a +// usable width (jsdom returns "auto" for many real-world elements, +// which parseFloat collapses to 0), fall back to the raw percentage +// number so callers gating on `> 0` (border-accent-on-rounded, +// isCardLike's hasRadius) still see a positive value, matching the +// original parseFloat("50%") === 50 behavior. function parseRadiusToPx(value, widthPx) { if (!value || typeof value !== 'string') return null; const trimmed = value.trim(); @@ -896,7 +901,10 @@ function parseRadiusToPx(value, widthPx) { const first = trimmed.split(/\s+/)[0]; const num = parseFloat(first); if (Number.isNaN(num)) return null; - if (/%$/.test(first)) return (num / 100) * (widthPx || 0); + if (/%$/.test(first)) { + if (widthPx && widthPx > 0) return (num / 100) * widthPx; + return num; + } return num; } diff --git a/src/detect-antipatterns.mjs b/src/detect-antipatterns.mjs index 63b1edf7c..c17bf78dd 100644 --- a/src/detect-antipatterns.mjs +++ b/src/detect-antipatterns.mjs @@ -883,7 +883,12 @@ function resolveGradientStops(el, win) { // Parse a single CSS length token to pixels. Accepts "12px", "50%", a // shorthand like "12px 4px" (uses the first value), or empty / null. // Returns the pixel value, or null when the input is unparseable. -// Percentages need a `widthPx` reference to convert against. +// Percentages convert against `widthPx` when one is supplied. Without a +// usable width (jsdom returns "auto" for many real-world elements, +// which parseFloat collapses to 0), fall back to the raw percentage +// number so callers gating on `> 0` (border-accent-on-rounded, +// isCardLike's hasRadius) still see a positive value, matching the +// original parseFloat("50%") === 50 behavior. function parseRadiusToPx(value, widthPx) { if (!value || typeof value !== 'string') return null; const trimmed = value.trim(); @@ -891,7 +896,10 @@ function parseRadiusToPx(value, widthPx) { const first = trimmed.split(/\s+/)[0]; const num = parseFloat(first); if (Number.isNaN(num)) return null; - if (/%$/.test(first)) return (num / 100) * (widthPx || 0); + if (/%$/.test(first)) { + if (widthPx && widthPx > 0) return (num / 100) * widthPx; + return num; + } return num; }