pbakaus/impeccable · warning

[impeccable] visual contrast scan failed

Error message

[impeccable] visual contrast scan failed

What it means

reportVisualContrastError is the top-level handler for the visual contrast scanner in the browser extension bundle. When a contrast analysis throws (e.g. while computing contrast ratios during a lazy IntersectionObserver-driven scan), it logs '[impeccable] visual contrast scan failed' with the error via console.warn (or posts it to the extension error channel in EXTENSION_MODE) instead of crashing the scan pipeline.

Source

Thrown at browser-bundle/50-scan.js:214

  function postExtensionError(err) {
    if (!EXTENSION_MODE) return;
    window.postMessage({
      source: 'impeccable-error',
      message: err?.message || String(err),
    }, '*');
  }

  function reportVisualContrastError(err, detail = {}) {
    window.dispatchEvent(new CustomEvent('impeccable-visual-contrast-error', {
      detail: {
        ...detail,
        message: err?.message || String(err),
      },
    }));
    if (EXTENSION_MODE) {
      postExtensionError(err);
    } else {
      console.warn('[impeccable] visual contrast scan failed', err);
    }
  }

  function scheduleLazyVisualContrast(groupMap, analyses, options = {}, runtime = {}) {
    disconnectLazyVisualContrastObserver();
    if (options.visualContrastLazy === false || options.scrollOffscreen !== false) return;
    if (typeof IntersectionObserver === 'undefined') return;
    const unresolved = __lazyVisualContrastCandidates(analyses);
    if (unresolved.length === 0) return;
    const generation = runtime.generation || scanGeneration;

    lazyVisualContrastObserver = new IntersectionObserver((entries) => {
      for (const entry of entries) {
        if (!entry.isIntersecting) continue;
        const el = entry.target;
        const candidate = lazyVisualContrastPending.get(el);
        if (!candidate || lazyVisualContrastResolving.has(el)) continue;
        lazyVisualContrastObserver?.unobserve(el);

View on GitHub (pinned to 2bc2879276)

Solutions

  1. Check the logged error object's message in the console — the scanner keeps working; the failure only skips that element/group
  2. Identify the element/color that broke parsing (modern CSS color functions are the usual culprit) and reproduce on a minimal page
  3. Update/patch the color parser to handle the failing CSS color syntax
  4. If in extension mode, verify postExtensionError received the error so it is reported rather than only warned

Example fix

// before
color: 'color-mix(in srgb, red 50%, blue)' // analyzer throws, scan reports failure
// after
// parse via getComputedStyle resolved value, or guard:
const parsed = parseColor(cs.color) ?? parseColor(cs.backgroundColor);
if (!parsed) return null; // skip instead of throwing
Defensive patterns

Strategy: try-catch

Try / catch

try {
  await scanContrast(groupMap, analyses, options, runtime);
} catch (err) {
  reportVisualContrastError(err); // logs '[impeccable] visual contrast scan failed'
}

Prevention

When it happens

Trigger: Any exception inside scan/scheduleLazyVisualContrast during contrast analysis — e.g. getComputedStyle on a detached element, malformed color values (like 'color-mix(...)' the parser doesn't understand), or a DOM mutation removing observed nodes mid-analysis.

Common situations: Pages using modern CSS color functions not yet parsed by the analyzer; elements unmounted between observation and scan; shadow-DOM content the scanner can't reach.

Related errors


AI-assisted analysis of pbakaus/impeccable@2bc2879276 (2026-09-08). Data as JSON: /api/errors/7a65fe48d0a4e791. Report an issue: GitHub.