Exclusivebeta

Agent Vision — <mv-agent-vision>

See your page the way an AI agent (or a screen reader) sees it: wraps any region and splits it with a draggable scan line. Left is the human view; right is the "agent view", where every node of the accessibility tree becomes a pixel-aligned wireframe box labelled like a tree line (button "Add to cart", heading 2 "Shipping", text field "Promo code") and everything else fades into a calm grid. The flaws that make a site unreadable to agents (icon-only buttons with no name, clickable divs, images without alt, placeholder-only fields, heading skips, indistinguishable "Learn more" links, silent color dots) are flagged, scored out of 100 and listed with their fix; clicking an issue sweeps it into agent view and pulses it. Re-scans automatically on mutation, resize and scroll.

CategoryUtilities
TypeWeb Component (<mv-agent-vision>)
Statusbeta
Keywordsexclusive, accessibility, a11y, ai-agent, agent, screen-reader, accessibility-tree, audit, lint, geo, seo, wipe, splitter, before-after, devtools, semantic-html

When to use

Avoid when

Install

node scripts/add.mjs agent-vision --out ./src/marvelous

AI agent with the Marvelous UI MCP server: install_components({ slugs: ["agent-vision"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).

Files copied (dependencies included): tokens/tokens.css, core/base.css, core/dom.js, core/element.js, core/motion.js, core/observe.js, core/pointer.js, components/agent-vision/agent-vision.js, components/agent-vision/agent-vision.css.

Usage

Canonical markup — start from it and customize with attributes, data-* and CSS variables:

<div id="av-demo" style="width:min(100%,76rem);margin-inline:auto">
  <style>
    #av-demo .av-bar { display:flex; align-items:center; justify-content:space-between; gap:.75rem 1rem; flex-wrap:wrap; margin:0 0 1rem }
    #av-demo .av-bar p { margin:0; color:var(--mv-fg-muted); font-size:.8125rem; max-width:46rem }
    #av-demo .av-bar strong { color:var(--mv-fg) }
    #av-demo .av-page { container-type:inline-size; padding:1rem 1.25rem 1.25rem; border:1px solid var(--mv-border); border-radius:var(--mv-radius-xl); background:var(--mv-surface) }
    #av-demo .av-grid { display:grid; gap:1.25rem; margin-top:.875rem }
    @container (min-width:36rem) { #av-demo .av-grid { grid-template-columns:minmax(0,1fr) minmax(0,1.05fr); gap:1.75rem } }
    #av-demo .av-media { display:grid; gap:.625rem; align-content:start }
    #av-demo .av-hero-wrap { position:relative }
    #av-demo .av-hero { width:100%; aspect-ratio:4/3; object-fit:cover; border-radius:var(--mv-radius-lg) }
    #av-demo .av-tools { position:absolute; top:.625rem; right:.625rem; display:flex; gap:.375rem }
    #av-demo .av-tools .mv-button { background:var(--mv-surface-raised) }
    #av-demo .av-tools svg { width:1rem; height:1rem }
    #av-demo .av-thumbs { display:grid; grid-template-columns:repeat(3,1fr); gap:.5rem }
    #av-demo .av-thumbs img { width:100%; aspect-ratio:1; object-fit:cover; border-radius:var(--mv-radius-md) }
    #av-demo .av-thumbs img:nth-child(2) { object-position:20% 80%; transform:scaleX(-1) }
    #av-demo .av-thumbs img:nth-child(3) { object-position:85% 30% }
    #av-demo .av-info { display:grid; gap:.75rem; align-content:start }
    #av-demo .av-brand { margin:0; color:var(--mv-fg-muted); font-size:.75rem; font-weight:600; letter-spacing:.06em; text-transform:uppercase }
    #av-demo .av-info h2 { margin:-.375rem 0 0; font-size:1.5rem; line-height:1.15; letter-spacing:-.02em }
    #av-demo .av-reviews { color:var(--mv-fg-muted); font-size:.8125rem; text-decoration:none; justify-self:start }
    #av-demo .av-reviews:hover { color:var(--mv-fg); text-decoration:underline }
    #av-demo .av-reviews span { color:var(--mv-warning); letter-spacing:.08em; margin-right:.25rem }
    #av-demo .av-price { display:flex; align-items:center; gap:.625rem; margin:0; font-size:1.375rem; font-weight:700; font-variant-numeric:tabular-nums }
    #av-demo .av-dot { width:.625rem; height:.625rem; border-radius:50%; background:var(--mv-success); box-shadow:0 0 0 3px color-mix(in oklab,var(--mv-success) 22%,transparent) }
    #av-demo .av-stock { color:var(--mv-fg-muted); font-size:.8125rem; font-weight:500 }
    #av-demo .av-buy { display:grid; grid-template-columns:7rem 1fr; gap:.625rem; align-items:end }
    #av-demo .av-buy > .mv-button { width:100% }
    #av-demo .av-h { margin:.25rem 0 -.25rem; font-size:.9375rem }
    #av-demo .av-perks { display:grid; gap:.375rem; margin:0; padding:0; list-style:none; font-size:.8125rem; color:var(--mv-fg-muted) }
    #av-demo .av-perks li { display:flex; justify-content:space-between; gap:.75rem; padding:.5rem .75rem; border-radius:var(--mv-radius-md); background:var(--mv-bg-muted) }
    #av-demo .av-perks a { color:var(--mv-fg); font-weight:500; white-space:nowrap }
    #av-demo .av-promo { display:grid; gap:.375rem }
    #av-demo .av-promo-row { display:flex; gap:.5rem }
    #av-demo .av-hint { margin:.75rem 0 0; color:var(--mv-fg-muted); font-size:.75rem; text-align:center }
  </style>

  <div class="av-bar">
    <p><strong>Agent view</strong> — drag the scan line: on the right, the page as an AI agent or a screen reader perceives it. Click an issue to jump to it.</p>
    <button class="mv-button" data-variant="outline" type="button" id="av-fix">Fix the page</button>
  </div>

  <mv-agent-vision id="av-vision" split="48">
    <article class="av-page" aria-labelledby="av-title">
      <nav class="mv-breadcrumb" aria-label="Breadcrumb">
        <ol>
          <li><a href="#home">Home</a></li>
          <li><a href="#trail">Trail</a></li>
          <li><span aria-current="page">Aster 3</span></li>
        </ol>
      </nav>

      <div class="av-grid">
        <div class="av-media">
          <div class="av-hero-wrap">
            <img class="av-hero" src="data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 480 360%27%3E%3Cdefs%3E%3ClinearGradient id=%27bg%27 x1=%270%27 y1=%270%27 x2=%270%27 y2=%271%27%3E%3Cstop offset=%270%27 stop-color=%27oklch(0.95 0.015 150)%27/%3E%3Cstop offset=%271%27 stop-color=%27oklch(0.88 0.03 150)%27/%3E%3C/linearGradient%3E%3CradialGradient id=%27fl%27%3E%3Cstop offset=%270%27 stop-color=%27black%27 stop-opacity=%27.25%27/%3E%3Cstop offset=%271%27 stop-color=%27black%27 stop-opacity=%270%27/%3E%3C/radialGradient%3E%3C/defs%3E%3Crect width=%27480%27 height=%27360%27 fill=%27url(%23bg)%27/%3E%3Cellipse cx=%27246%27 cy=%27286%27 rx=%27186%27 ry=%2716%27 fill=%27url(%23fl)%27/%3E%3Cpath d=%27M70 246 Q70 276 104 280 L386 280 Q424 278 428 246 Z%27 fill=%27oklch(0.3 0.03 160)%27/%3E%3Cpath d=%27M104 280v9M140 280v9M176 280v9M212 280v9M248 280v9M284 280v9M320 280v9M356 280v9M392 280v8%27 stroke=%27oklch(0.3 0.03 160)%27 stroke-width=%2712%27/%3E%3Cpath d=%27M74 236 L426 236 Q430 254 416 262 L98 262 Q76 258 74 236Z%27 fill=%27oklch(0.96 0.01 90)%27/%3E%3Cpath d=%27M82 238 Q76 196 118 174 L192 136 Q212 98 252 94 L290 96 Q302 138 340 158 L392 182 Q428 200 426 238 Z%27 fill=%27oklch(0.66 0.16 40)%27/%3E%3Cpath d=%27M290 96 Q302 138 340 158 L392 182 Q428 200 426 238 L360 238 Q350 180 300 150 Z%27 fill=%27black%27 fill-opacity=%27.1%27/%3E%3Cpath d=%27M212 126 L262 148 M204 142 L254 164 M196 158 L246 180%27 stroke=%27oklch(0.96 0.01 90)%27 stroke-width=%277%27 stroke-linecap=%27round%27/%3E%3Cpath d=%27M104 218 Q230 200 416 220%27 stroke=%27oklch(0.3 0.03 160)%27 stroke-opacity=%27.45%27 stroke-width=%276%27 fill=%27none%27/%3E%3Cpath d=%27M120 200 Q160 186 176 150%27 stroke=%27white%27 stroke-opacity=%27.35%27 stroke-width=%275%27 fill=%27none%27 stroke-linecap=%27round%27/%3E%3Ccircle cx=%27378%27 cy=%27206%27 r=%279%27 fill=%27oklch(0.96 0.01 90)%27 fill-opacity=%27.8%27/%3E%3C/svg%3E" alt="Aster 3 trail running shoe, side view" width="480" height="360">
            <div class="av-tools">
              <button class="mv-button" data-variant="outline" data-size="icon" data-shape="round" type="button" id="av-fav">
                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 14c1.5-1.5 3-3.2 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.8 0-3 .5-4.5 2-1.5-1.5-2.7-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4 3 5.5l7 7z"/></svg>
              </button>
              <button class="mv-button" data-variant="outline" data-size="icon" data-shape="round" type="button" id="av-share">
                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><path d="m8.6 13.5 6.8 4M15.4 6.5l-6.8 4"/></svg>
              </button>
            </div>
          </div>
          <div class="av-thumbs">
            <img src="data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 480 360%27%3E%3Cdefs%3E%3ClinearGradient id=%27bg%27 x1=%270%27 y1=%270%27 x2=%270%27 y2=%271%27%3E%3Cstop offset=%270%27 stop-color=%27oklch(0.95 0.015 150)%27/%3E%3Cstop offset=%271%27 stop-color=%27oklch(0.88 0.03 150)%27/%3E%3C/linearGradient%3E%3CradialGradient id=%27fl%27%3E%3Cstop offset=%270%27 stop-color=%27black%27 stop-opacity=%27.25%27/%3E%3Cstop offset=%271%27 stop-color=%27black%27 stop-opacity=%270%27/%3E%3C/radialGradient%3E%3C/defs%3E%3Crect width=%27480%27 height=%27360%27 fill=%27url(%23bg)%27/%3E%3Cellipse cx=%27246%27 cy=%27286%27 rx=%27186%27 ry=%2716%27 fill=%27url(%23fl)%27/%3E%3Cpath d=%27M70 246 Q70 276 104 280 L386 280 Q424 278 428 246 Z%27 fill=%27oklch(0.3 0.03 160)%27/%3E%3Cpath d=%27M104 280v9M140 280v9M176 280v9M212 280v9M248 280v9M284 280v9M320 280v9M356 280v9M392 280v8%27 stroke=%27oklch(0.3 0.03 160)%27 stroke-width=%2712%27/%3E%3Cpath d=%27M74 236 L426 236 Q430 254 416 262 L98 262 Q76 258 74 236Z%27 fill=%27oklch(0.96 0.01 90)%27/%3E%3Cpath d=%27M82 238 Q76 196 118 174 L192 136 Q212 98 252 94 L290 96 Q302 138 340 158 L392 182 Q428 200 426 238 Z%27 fill=%27oklch(0.66 0.16 40)%27/%3E%3Cpath d=%27M290 96 Q302 138 340 158 L392 182 Q428 200 426 238 L360 238 Q350 180 300 150 Z%27 fill=%27black%27 fill-opacity=%27.1%27/%3E%3Cpath d=%27M212 126 L262 148 M204 142 L254 164 M196 158 L246 180%27 stroke=%27oklch(0.96 0.01 90)%27 stroke-width=%277%27 stroke-linecap=%27round%27/%3E%3Cpath d=%27M104 218 Q230 200 416 220%27 stroke=%27oklch(0.3 0.03 160)%27 stroke-opacity=%27.45%27 stroke-width=%276%27 fill=%27none%27/%3E%3Cpath d=%27M120 200 Q160 186 176 150%27 stroke=%27white%27 stroke-opacity=%27.35%27 stroke-width=%275%27 fill=%27none%27 stroke-linecap=%27round%27/%3E%3Ccircle cx=%27378%27 cy=%27206%27 r=%279%27 fill=%27oklch(0.96 0.01 90)%27 fill-opacity=%27.8%27/%3E%3C/svg%3E" alt="Aster 3, full profile" width="160" height="160">
            <img src="data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 480 360%27%3E%3Cdefs%3E%3ClinearGradient id=%27bg%27 x1=%270%27 y1=%270%27 x2=%270%27 y2=%271%27%3E%3Cstop offset=%270%27 stop-color=%27oklch(0.95 0.015 150)%27/%3E%3Cstop offset=%271%27 stop-color=%27oklch(0.88 0.03 150)%27/%3E%3C/linearGradient%3E%3CradialGradient id=%27fl%27%3E%3Cstop offset=%270%27 stop-color=%27black%27 stop-opacity=%27.25%27/%3E%3Cstop offset=%271%27 stop-color=%27black%27 stop-opacity=%270%27/%3E%3C/radialGradient%3E%3C/defs%3E%3Crect width=%27480%27 height=%27360%27 fill=%27url(%23bg)%27/%3E%3Cellipse cx=%27246%27 cy=%27286%27 rx=%27186%27 ry=%2716%27 fill=%27url(%23fl)%27/%3E%3Cpath d=%27M70 246 Q70 276 104 280 L386 280 Q424 278 428 246 Z%27 fill=%27oklch(0.3 0.03 160)%27/%3E%3Cpath d=%27M104 280v9M140 280v9M176 280v9M212 280v9M248 280v9M284 280v9M320 280v9M356 280v9M392 280v8%27 stroke=%27oklch(0.3 0.03 160)%27 stroke-width=%2712%27/%3E%3Cpath d=%27M74 236 L426 236 Q430 254 416 262 L98 262 Q76 258 74 236Z%27 fill=%27oklch(0.96 0.01 90)%27/%3E%3Cpath d=%27M82 238 Q76 196 118 174 L192 136 Q212 98 252 94 L290 96 Q302 138 340 158 L392 182 Q428 200 426 238 Z%27 fill=%27oklch(0.66 0.16 40)%27/%3E%3Cpath d=%27M290 96 Q302 138 340 158 L392 182 Q428 200 426 238 L360 238 Q350 180 300 150 Z%27 fill=%27black%27 fill-opacity=%27.1%27/%3E%3Cpath d=%27M212 126 L262 148 M204 142 L254 164 M196 158 L246 180%27 stroke=%27oklch(0.96 0.01 90)%27 stroke-width=%277%27 stroke-linecap=%27round%27/%3E%3Cpath d=%27M104 218 Q230 200 416 220%27 stroke=%27oklch(0.3 0.03 160)%27 stroke-opacity=%27.45%27 stroke-width=%276%27 fill=%27none%27/%3E%3Cpath d=%27M120 200 Q160 186 176 150%27 stroke=%27white%27 stroke-opacity=%27.35%27 stroke-width=%275%27 fill=%27none%27 stroke-linecap=%27round%27/%3E%3Ccircle cx=%27378%27 cy=%27206%27 r=%279%27 fill=%27oklch(0.96 0.01 90)%27 fill-opacity=%27.8%27/%3E%3C/svg%3E" width="160" height="160" id="av-thumb">
            <img src="data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 480 360%27%3E%3Cdefs%3E%3ClinearGradient id=%27bg%27 x1=%270%27 y1=%270%27 x2=%270%27 y2=%271%27%3E%3Cstop offset=%270%27 stop-color=%27oklch(0.95 0.015 150)%27/%3E%3Cstop offset=%271%27 stop-color=%27oklch(0.88 0.03 150)%27/%3E%3C/linearGradient%3E%3CradialGradient id=%27fl%27%3E%3Cstop offset=%270%27 stop-color=%27black%27 stop-opacity=%27.25%27/%3E%3Cstop offset=%271%27 stop-color=%27black%27 stop-opacity=%270%27/%3E%3C/radialGradient%3E%3C/defs%3E%3Crect width=%27480%27 height=%27360%27 fill=%27url(%23bg)%27/%3E%3Cellipse cx=%27246%27 cy=%27286%27 rx=%27186%27 ry=%2716%27 fill=%27url(%23fl)%27/%3E%3Cpath d=%27M70 246 Q70 276 104 280 L386 280 Q424 278 428 246 Z%27 fill=%27oklch(0.3 0.03 160)%27/%3E%3Cpath d=%27M104 280v9M140 280v9M176 280v9M212 280v9M248 280v9M284 280v9M320 280v9M356 280v9M392 280v8%27 stroke=%27oklch(0.3 0.03 160)%27 stroke-width=%2712%27/%3E%3Cpath d=%27M74 236 L426 236 Q430 254 416 262 L98 262 Q76 258 74 236Z%27 fill=%27oklch(0.96 0.01 90)%27/%3E%3Cpath d=%27M82 238 Q76 196 118 174 L192 136 Q212 98 252 94 L290 96 Q302 138 340 158 L392 182 Q428 200 426 238 Z%27 fill=%27oklch(0.66 0.16 40)%27/%3E%3Cpath d=%27M290 96 Q302 138 340 158 L392 182 Q428 200 426 238 L360 238 Q350 180 300 150 Z%27 fill=%27black%27 fill-opacity=%27.1%27/%3E%3Cpath d=%27M212 126 L262 148 M204 142 L254 164 M196 158 L246 180%27 stroke=%27oklch(0.96 0.01 90)%27 stroke-width=%277%27 stroke-linecap=%27round%27/%3E%3Cpath d=%27M104 218 Q230 200 416 220%27 stroke=%27oklch(0.3 0.03 160)%27 stroke-opacity=%27.45%27 stroke-width=%276%27 fill=%27none%27/%3E%3Cpath d=%27M120 200 Q160 186 176 150%27 stroke=%27white%27 stroke-opacity=%27.35%27 stroke-width=%275%27 fill=%27none%27 stroke-linecap=%27round%27/%3E%3Ccircle cx=%27378%27 cy=%27206%27 r=%279%27 fill=%27oklch(0.96 0.01 90)%27 fill-opacity=%27.8%27/%3E%3C/svg%3E" alt="Quick-lace detail" width="160" height="160">
          </div>
        </div>

        <div class="av-info">
          <p class="av-brand">Nomad Supply Co. · Trail</p>
          <h2 id="av-title">Aster 3 Trail Running Shoe</h2>
          <a class="av-reviews" href="#avis"><span aria-hidden="true">★★★★☆</span>Read reviews (128)</a>
          <p class="av-price">$139.00 <span class="av-dot" title="In stock" id="av-dot"></span></p>

          <div class="av-buy">
            <div class="mv-field">
              <label class="mv-label" for="av-size">Size (US)</label>
              <select class="mv-select" id="av-size">
                <option>8</option><option>9</option><option selected>10</option><option>10.5</option><option>11</option>
              </select>
            </div>
            <div class="mv-button" data-size="lg" id="av-cart" onclick="this.textContent = 'Added ✓'">Add to cart</div>
          </div>

          <h4 class="av-h" id="av-heading">Shipping &amp; returns</h4>
          <ul class="av-perks">
            <li><span>Free shipping on orders over $75</span><a href="#shipping">Learn more</a></li>
            <li><span>Free returns within 30 days</span><a href="#returns">Learn more</a></li>
            <li><span>2-year workshop warranty</span><a href="#warranty">Learn more</a></li>
          </ul>

          <form class="av-promo" aria-label="Promo code" onsubmit="event.preventDefault()">
            <div class="av-promo-row">
              <input class="mv-input" id="av-code" placeholder="Promo code" autocomplete="off">
              <button class="mv-button" data-variant="secondary" type="submit">Apply</button>
            </div>
          </form>
        </div>
      </div>
    </article>
  </mv-agent-vision>
  <p class="av-hint">Arrow keys or <kbd class="mv-kbd">Home</kbd> / <kbd class="mv-kbd">End</kbd> on the handle · “Fix the page” applies the fixes live and re-runs the scan</p>

  <script type="module">
    const vision = document.getElementById("av-vision");
    const fix = document.getElementById("av-fix");
    const $ = (id) => document.getElementById(id);
    await customElements.whenDefined("mv-agent-vision");

    // Semantic twins of the broken elements, swapped in and out.
    const fakeCart = $("av-cart");
    const realCart = Object.assign(document.createElement("button"), { type: "button", className: "mv-button", textContent: "Add to cart" });
    realCart.dataset.size = "lg";
    const badHeading = $("av-heading");
    const goodHeading = Object.assign(document.createElement("h3"), { className: "av-h", textContent: badHeading.textContent });
    const label = Object.assign(document.createElement("label"), { className: "mv-label", htmlFor: "av-code", textContent: "Promo code" });
    const stock = Object.assign(document.createElement("span"), { className: "av-stock", textContent: "In stock" });
    const links = [...document.querySelectorAll("#av-demo .av-perks a")];
    const precise = ["Learn more about shipping", "Learn more about returns", "Learn more about the warranty"];

    const apply = (fixed) => {
      const toggle = (el, name, value) => (fixed ? el.setAttribute(name, value) : el.removeAttribute(name));
      toggle($("av-fav"), "aria-label", "Add to wishlist");
      toggle($("av-share"), "aria-label", "Share");
      toggle($("av-thumb"), "alt", "Lugged outsole, bottom view");
      (fixed ? fakeCart : realCart).replaceWith(fixed ? realCart : fakeCart);
      (fixed ? badHeading : goodHeading).replaceWith(fixed ? goodHeading : badHeading);
      const dot = $("av-dot");
      if (fixed) { dot.removeAttribute("title"); dot.setAttribute("aria-hidden", "true"); dot.after(stock); }
      else { dot.title = "In stock"; dot.removeAttribute("aria-hidden"); stock.remove(); }
      links.forEach((a, i) => toggle(a, "aria-label", precise[i]));
      if (fixed) document.querySelector("#av-demo .av-promo").prepend(label); else label.remove();
      fix.dataset.fixed = String(fixed);
      fix.textContent = fixed ? "Bring the flaws back" : "Fix the page";
    };
    fix.addEventListener("click", () => apply(fix.dataset.fixed !== "true"));

    vision.addEventListener("mv-agent-scan", (e) => console.info("mv-agent-scan", e.detail.score, e.detail.issues.length));
  </script>
</div>

API

Attributes

NameTypeDefaultDescription
splitnumber (0–100)55Scan line position, as the % of the region shown in human view (the rest is agent view).
scopeCSS selectorRestricts the analysis to matching elements inside the region (default: all wrapped content).
showall | issuesallFull agent view, or only the boxes with an issue. Also driven by the toggle in the panel.

Properties

NameTypeDescription
splitnumberCurrent line position (read/write; dragging does not rewrite the attribute).
scorenumberAgent readability, 0–100: 100 − 7 per blocking issue − 3 per minor issue (a group of duplicate names counts once).
issues{ type, severity, severityLabel, title, what, note, fix, role, name, count }[]Detected issues, blockers first. severity: "blocker" | "minor". A non-enumerable elements property holds the affected nodes.
tree{ role, label, name, level, states, rect, issue }[]Perceived tree, JSON-serialisable: ARIA role, readable label, accessible name, states, rect relative to the region, issue if any.

Methods

NameDescription
scan()Re-runs the analysis now, announces the result and returns { score, issues } (the analysis also runs on its own, batched per frame).
focusIssue(index)Moves the line so the element is in agent view, scrolls it on screen if needed, selects and pulses its box, and announces the issue and its fix.

Events

NameDescription
mv-agent-scanAnalysis finished with a new result (or an explicit scan()). detail: { score, issues }.

Content structure

NameDescription
(content)One child element: the region to analyse (card, section, form, whole page…). It stays fully interactive on both sides of the line.

CSS classes

NameDescription
mv-agent-vision-layer / -box / -tagAgent view layer (aria-hidden), wireframe boxes (data-kind control | heading | media | container, data-issue blocker | minor, data-selected) and their tree line.
mv-agent-vision-rail / -handle / -scanSplitter: role=slider handle and glowing scan line.
mv-agent-vision-summary / -list / -issuePanel: readability gauge, counts, display toggle, issue list (buttons).

CSS variables

NameDefaultDescription
--mv-agent-vision-accentvar(--mv-accent)Scan line and interactive box color.
--mv-agent-vision-blockervar(--mv-danger)Blocking issue color (always paired with an icon and a label).
--mv-agent-vision-minorvar(--mv-warning)Minor issue color.
--mv-agent-vision-radiusvar(--mv-radius-xl)Agent view layer radius (match it to the region's).
--mv-agent-vision-grid16pxPitch of the grid that replaces the visual rendering.

Accessibility

The tool is accessible itself. The handle is a role="slider" (arrows ±2, Shift ±10, Page Up/Down ±10, Home = all agent view, End = all human view) with aria-valuetext "48% human view, 52% agent view". The agent view layer is aria-hidden and pointer-events: none: the region keeps its keyboard, clicks and accessibility tree intact on both sides. The issue list is a real list of buttons (Tab, or Up/Down arrows, Home/End); the active issue carries aria-current. A polite live region announces the score when the analysis changes and, when an issue is selected, its description and fix. Severity is always an icon + label ("Blocker", "Minor") on top of color. Reduced motion: no typed-in labels or pulse, instant scrolling. No loop: everything is driven by MutationObserver, ResizeObserver and events, batched into one read-then-write pass per frame.