diff --git a/scripts/visual-diff.mjs b/scripts/visual-diff.mjs index 6b482df3..7136dd13 100644 --- a/scripts/visual-diff.mjs +++ b/scripts/visual-diff.mjs @@ -28,6 +28,39 @@ const STATS_PATH = join(ROOT, "comparison-report/visual/diff-stats.json"); mkdirSync(DIFFS_DIR, { recursive: true }); +/** + * Paint a rectangle white. Used to mask Angular-only test-env chrome + * (orange `Тестовая версия` badge top-left, `rc/2026-04-06` build tag + * top-right, chat-widget bubble bottom-right) before pixel-matching + * so those regions don't pollute the parity score. + */ +function maskRect(png, x, y, w, h) { + const x0 = Math.max(0, x); + const y0 = Math.max(0, y); + const x1 = Math.min(png.width, x + w); + const y1 = Math.min(png.height, y + h); + for (let yy = y0; yy < y1; yy++) { + for (let xx = x0; xx < x1; xx++) { + const idx = (yy * png.width + xx) * 4; + png.data[idx] = 255; + png.data[idx + 1] = 255; + png.data[idx + 2] = 255; + png.data[idx + 3] = 255; + } + } +} + +/** Apply the standard chrome masks to both PNGs in-place. */ +function applyChromeMasks(png) { + const isMobile = png.width <= 500; + // Top-left debug counter / orange "Тестовая версия" badge. + maskRect(png, 0, 0, isMobile ? 200 : 200, 90); + // Top-right `rc/...` build tag. + maskRect(png, png.width - 240, 0, 240, 50); + // Bottom-right chat widget. + maskRect(png, png.width - 90, png.height - 90, 90, 90); +} + const files = readdirSync(SRC_DIR); const reactFiles = files.filter((f) => f.startsWith("react-") && f.endsWith(".png")); @@ -70,6 +103,13 @@ for (const reactFile of reactFiles) { const aP = pad(aPng); const rP = pad(rPng); + + // Strip Angular-only chrome (orange test-env badge, build tag, chat + // widget) from both sides — those regions are environmental noise + // that shouldn't drag the parity score down. + applyChromeMasks(aP); + applyChromeMasks(rP); + const diffPng = new PNG({ width, height }); const mismatchCount = pixelmatch(