diff --git a/packages/cli/src/commands/layout-audit.browser.js b/packages/cli/src/commands/layout-audit.browser.js index 8693f8a678..058b649dc0 100644 --- a/packages/cli/src/commands/layout-audit.browser.js +++ b/packages/cli/src/commands/layout-audit.browser.js @@ -324,12 +324,7 @@ } function hasPaint(style) { - const backgroundColor = style.backgroundColor || ""; - const hasBackground = - backgroundColor !== "" && - backgroundColor !== "transparent" && - !backgroundColor.endsWith(", 0)") && - backgroundColor !== "rgba(0, 0, 0, 0)"; + const hasBackground = !isTransparentColor(style.backgroundColor); const hasImage = style.backgroundImage && style.backgroundImage !== "none"; const hasBorder = parsePx(style.borderTopWidth) + @@ -591,10 +586,11 @@ return element.hasAttribute("data-layout-allow-overlap"); } + // Alpha must come from colorAlpha's argument-position parse, never from a + // `", 0)"` string suffix: that suffix also matches fully-opaque 3-value rgb() + // colours whose blue channel is zero, e.g. pure red/green/yellow. function isTransparentColor(color) { - return ( - !color || color === "transparent" || color === "rgba(0, 0, 0, 0)" || color.endsWith(", 0)") - ); + return !color || color === "transparent" || colorAlpha(color) === 0; } function alphaFromParts(parts, index) { diff --git a/packages/cli/src/commands/layout-audit.browser.test.ts b/packages/cli/src/commands/layout-audit.browser.test.ts index d65dabba00..3432e040ac 100644 --- a/packages/cli/src/commands/layout-audit.browser.test.ts +++ b/packages/cli/src/commands/layout-audit.browser.test.ts @@ -2511,6 +2511,20 @@ describe("layout-audit.browser occlusion", () => { expect(occluded?.coveredFraction).toBe(1); }); + // PRINFRA-702: `rgb(r, g, 0)` ends in the same `", 0)"` as a transparent + // `rgba(..., 0)`, so a string-suffix transparency check silently exempted + // opaque red/green/yellow occluders from occlusion entirely. + it.each(["rgb(0, 255, 0)", "rgb(255, 0, 0)", "rgb(255, 255, 0)"])( + "flags an opaque %s occluder whose blue channel is zero", + (backgroundColor) => { + const issues = auditOcclusionScene({ + overlayStyle: { backgroundColor }, + topmostId: "overlay", + }); + expect(issues.some((issue) => issue.code === "text_occluded")).toBe(true); + }, + ); + // #U10: a 2-point hit on the 27-point probe grid (3 rows x 9 columns) is a // sliver of edge cover — reports ~0.07 coverage either way, but only GATES // (produces a finding) for short atomic labels; ordinary prose survives it.