# mi60dev/visionaire-engine [Health: Active]

**Category:** 📂 Browser Automation  
**Repository:** https://github.com/mi60dev/visionaire-engine  
**GitHub Stars:** 4  
**npm Downloads (last month):** 72  
**Views:** 2  
**Installs:** 0  
**Upvotes:** 0  
**Directory Page:** https://allmcps.com/mcp/mi60dev-visionaire-engine

## Description
Design-debugging context for AI agents from a real Chrome: which CSS rule WINS with file:line, blast radius (how many other elements that rule styles) + a scoped-fix selector, WordPress/Elementor origin attribution, interaction timelines, live CSS fix trials, and pixel/alignment/WCAG-contrast audits. No AI inside — deterministic CDP.

## Tools
Capabilities this server exposes over MCP:

- **connect** — ALWAYS the first call: start (or restart) the browser session — launch a local Chrome by default, or attach to the user's real, logged-in browser via browserUrl (e.g. http://127.0.0.1:9222, for pages behind auth like wp-admin or a dashboard). Pass url to load a page immediately. Every other tool needs a live session; if a tool reports no session or a wedged browser, call connect again to reset.
- **navigate** — Navigate the connected tab to a URL — or, with no url, hard-reload the current page. Pass bypassCache: true when a stale cached stylesheet/script keeps being served (disables the browser cache for the rest of the session). All element uids from earlier snapshots become stale — take a fresh page_snapshot afterwards.
- **set_viewport** — Emulate a viewport size (and optional deviceScaleFactor) on the connected tab, then re-inspect. Use for responsive bugs — 'it breaks on mobile', 'the menu is wrong at tablet width', anything behind a media query — since resizing can change which @media rule wins. Follow with a fresh page_snapshot / explain_styles at the new size.
- **page_snapshot** — Token-budgeted census of the rendered page — a nested, uid-keyed element tree with geometry and visibility flags. Call this FIRST after connect to orient yourself and to obtain the stable uids every other tool targets. Reach for it whenever you do not yet know the page structure or an element uid. To find one specific element by description use find_elements; to see the page visually use annotated_screenshot.
- **page_origins** — Inventory of every stylesheet (URL, byte size, origin, source-map presence) plus platform detection — WordPress version, theme/child theme, page builder (Elementor/Divi), and CSS optimizer. Use before proposing edits to learn where the CSS actually lives, when a file:line points at a generated/minified bundle and you need the true source, or to answer 'is this WordPress/Elementor?' and 'which stylesheet owns this?'.
- **inspect_element** — The 'WHAT' for one element: box model (margins/padding/border), key computed styles as authored → used values, a visibility verdict, and layout context. Use when you need an element's current rendered state — its real size, spacing, or whether it is actually visible/where it sits. These are the values the page ACTUALLY renders, so it catches the common case where the source rule you'd edit is overridden or never applied. For 'WHY it looks like this / which rule wins' use explain_styles; for 'which ancestor constrains its size or position' use inspect_ancestors.
- **explain_styles** — The core 'WHY': a per-property cascade verdict naming the winning CSS declaration and every loser with the exact reason it lost (specificity, !important, source order, inline, layer), each attributed to file:line or a WordPress/Elementor/Customizer origin. Reach for this whenever a style is wrong or 'won't apply' — wrong color/font/size/spacing, 'something is overriding my rule', 'where does this value come from', 'which rule do I edit'. Prefer this over grepping the source for a style bug: source search finds candidate rules, but only the live cascade shows which one actually WINS on a layered stack — so diagnose here before editing CSS you assume is the cause. Each winner also reports its BLAST RADIUS — how many other elements that rule styles (so you change THE button, not all buttons) — plus a scoped selector that targets just this element, with a specificity verdict. Pass an optional property (e.g. 'margin-bottom') to focus.
- **inspect_ancestors** — Walk an element's ancestor chain for ONE concern — width, height, position, overflow, or stacking — and flag the ancestor that is the binding constraint. Use when the cause lives ABOVE the element: it's too wide/narrow, clipped or cut off, won't scroll, is mysteriously positioned, or a z-index has no effect (trapped in an ancestor's stacking context). Complements explain_styles, which explains the element's own winning rules.
- **find_elements** — Deterministic search by visible text, CSS selector, ARIA role, and/or screen region → compact uid-keyed matches; anchors include their resolved href, so find_elements{role:'link'} lists the page's links WITH destinations (then navigate to browse them). Criteria are AND-combined by default; pass match:'any' for a union (OR) when over-specifying returns nothing, and visibleOnly:false to include display:none/hidden elements. Use to locate the element a user described in words ('the Subscribe button', 'the header nav') before inspecting it. Prefer this (or page_snapshot) over guessing a selector. For a point in a screenshot use node_at_point; to have the human physically click the element use pick_element.
- **node_at_point** — Map viewport coordinates (x, y) to the element there: uid, identity, and the full ancestor uid chain. Use to turn a coordinate — e.g. a spot you located in an annotated_screenshot, or pixel coords the user gave — into a concrete element and uid.
- **annotated_screenshot** — Screenshot in two modes: an overview with numbered marks burned in, where mark N equals uid eN (mark 17 = e17); or an element-scoped crop via clipTo (uid|selector|x,y) with optional padding, scale (0.5..4 zoom for tiny elements), and annotate:false for a clean unlabeled crop. Use when text tools are not enough and you need to SEE the page while keeping pixels tied to elements — spatial or visual-layout questions ('things overlap', 'the layout looks off'), to zoom in on one small element, or to confirm which element is which. Then target elements by their uid.
- **style_diff** — BEFORE/AFTER comparison for one element: record its styles into a named slot, change something, compare — only the properties that changed are reported. Reach for it whenever you ask 'did my fix actually change anything?' or need to prove what an edit / inject_css patch / viewport change / interaction altered. The loop: style_diff{mode:'record'} → apply the change → style_diff{mode:'compare'}. Confirms a fix moved exactly the property you intended, and nothing else.
- **pick_element** — Let the human point at the element: turns on a DevTools-style hover highlight in the connected tab and waits for them to click, returning the clicked element's uid and ancestor chain. Use when the user says "I'll show you" / "let me click it", or when find_elements/annotated_screenshot could not pin down the element from a description. Needs a visible browser window (connect { headless: false }).
- **get_listeners** — List the event listeners on an element — and, by default, delegated listeners up the ancestor chain, document, and window: event type, handler file:line (source-mapped, WordPress-origin-labeled), and the bug-prone flags capture/passive/once. Use to answer "which JS file handles this button?", or when a click/submit/keypress does nothing, a form won't submit, or preventDefault is ignored (often a passive listener). For what actually happens step-by-step when clicked, use record_interaction.
- **explain_animations** — Explain the animations and transitions on one element: a census of what is running right now (type, play state, timing, animated properties) plus the declared transition/animation/@keyframes rules attributed to file:line, checked against a closed ruleset of known causes. Use when an animation or transition is not smooth, does not run at all, or jumps/pops instead of animating; pass the optional property (e.g. "opacity") to check why THAT property does not animate. For a timeline of a specific click/hover (what fired, what got cancelled), use record_interaction.
- **record_interaction** — Perform one interaction (click or hover, or watch while the human interacts) and return a source-attributed causal TIMELINE — handler file:line, DOM/class mutations, animations started/cancelled, layout shifts, console errors — uid-keyed and time-ordered. Use for cause-and-effect over time: "the sidebar does not hide smoothly", "nothing/the wrong thing happens when I click", "the menu closes immediately", a modal that won't open, focus that jumps. For static listener attribution without triggering it, use get_listeners; for animation rules at rest, use explain_animations.
- **interact** — Perform ONE action (click/hover/focus) at a target and LEAVE the resulting state in place — no recording, no teardown. Use this to DRIVE the UI into a state — open a popup/menu/modal, reveal a tab or dropdown — so you can then inspect_element / annotated_screenshot / explain_styles the NEW state: "open the menu then tell me why it overflows", "click the tab and check the panel". Reports the target's post-action visibility + content box so you learn immediately whether it opened. Target by uid, selector, or x+y. For the causal TIMELINE of an interaction — which handler ran, what mutated, which transitions were cancelled — use record_interaction instead; interact only leaves you in the state, it does not explain the transition.
- **measure_element** — Deterministic rendered-pixel geometry: an element's content box (WxH @x,y) and the true TEXT INK bounding box of its glyphs (canvas measureText extents, not the advance box), plus a sub-pixel centering verdict — how far the ink sits from the content-box center on each axis, with a padding/line-height fix hint. Reach for this when the caller is fussing over VISUAL alignment that the box model can't see: "the × in the close button looks a bit high/off-center", "the icon is not quite centered", one-off pixel offsets. Pass referenceUid/referenceSelector to also get the center delta between two elements. explain_styles tells you which rule set the value; measure_element tells you whether the painted glyph actually lands where you want.
- **evaluate** — ESCAPE HATCH — run arbitrary agent-authored JavaScript in the page and get the JSON result. Use ONLY when no purpose-built tool covers the need: a custom measurement, forcing a UI state (dispatch an event / toggle a class), or reading framework/component state. Prefer explain_styles / measure_element / inspect_element / interact where they apply — reach for evaluate when the question is genuinely bespoke and none of them fits. The JS is trusted (you wrote it); its result is returned verbatim and size-capped.
- **inject_css** — Apply CSS to the LIVE page without touching source files — either declarations trialed on one element (applied !important so the trial always wins; reports which computed properties changed) or a raw page-wide rule block. THE fix-loop tool: explain_styles names the winning rule → inject_css the candidate fix → verify with measure_element/style_diff/annotated_screenshot → write the final declarations into the source once → revert:'all'. This replaces the slow edit-file → cache-bust → reload → re-snapshot cycle. Also the quick way to hide a cookie/consent overlay that occludes what you need (inject 'display:none'). Patches are trial-only: gone on navigation or revert.
- **assert_visual** — THE verification gate — call it after EVERY visual edit instead of claiming success from reading code. State verifiable rendered-geometry claims (equal_height, equal_width, aligned_edges, centered, gap_equals, spacing_equals, visible, not_clipped, not_overlapped, within_viewport, color_equals, color_near, z_above, text_not_truncated, text_not_overflowing, size_equals, positioned) and get a deterministic per-assertion PASS/FAIL with the measured pixels and the offending uids — 'FAIL: e87 412px vs e91 388px' ends the argument. Pass suite_id to register the set as a named regression suite; later call with ONLY {suite_id} to re-run it against the current render, or hand it to responsive_sweep for a per-viewport matrix. Never claim a visual fix works without a PASS from this tool.
- **visual_diff** — Deterministic screenshot diff of the CURRENT render (page or one element) against a reference image — a user-supplied mockup (reference: { image_path }) or a named pixel baseline recorded earlier with style_diff { capture_pixels: true } (reference: { baseline_slot }). Returns MATCH/DIVERGENT with divergence_pct, the worst NxN grid regions mapped back to likely element uids, and an optional diff-heatmap PNG written to disk as a file path (never inline). Reach for it on 'make it match the mockup' or to catch any unintended pixel change after an edit; tune threshold / ignore_antialiasing / mask_dynamic for environment noise. For geometry claims (heights, alignment, spacing) prefer assert_visual — it is OS-stable integer math; pixel diffing is inherently environment-sensitive.
- **impact_preview** — Blast-radius report to run BEFORE editing a shared selector: every element '.nav-item' currently matches on the open page — true match count, uids, identities, screen regions, grouped by visual role — so you see that the footer shares the class before you warp it. Pass proposed_change: { declarations: {'padding':'20px'} } for a sandboxed dry-run that predicts exactly which matched elements' computed values would change and which are protected by more specific rules. Scope honesty: current page, current viewport only (other routes/viewports/interactive states are invisible here — use responsive_sweep for viewports). Use it whenever a fix edits a class used in more than one place.
- **diagnose** — One-shot 'why is this broken': give it an element (uid/selector/x,y) and an optional symptom — clipped, overflowing, not_centered, invisible, overlapping, wrong_size, or auto — and get a ranked culprit list in plain language with deterministic measured evidence ('ancestor e2 has overflow:hidden; content exceeds it by 34px on the right'). auto runs the cheap ordered battery and reports what trips. THE tool to call when assert_visual returns FAIL and you need the cause, or when the user says 'it looks broken' without saying why. Fix the named culprit, then re-run assert_visual.
- **responsive_sweep** — Re-run a verification across viewports in ONE call and get a per-viewport verdict matrix — the cure for 'looks right at 1280, broken on mobile'. run: { suite_id } re-runs a registered assert_visual suite (selectors re-resolve at each width), run: { assertions: [...] } runs inline claims, run: { diagnose: {...} } probes a symptom per viewport. Defaults to 375/768/1280/1920; passing cells collapse to PASS, failing cells carry the failed assertions with measured values; the original viewport is restored afterwards. Run it before claiming any responsive work is done.
- **capture_proof** — Before/after evidence bundle proving a fix worked: call with phase:'before' ahead of the change and phase:'after' once assert_visual passes — each phase captures an annotated screenshot (marked with your target uids, saved to disk as file paths) and, with suite_id, attaches the suite verdict; the 'after' call returns a verdict_delta (FAIL→PASS per assertion). Use it to close the loop with humans: 'here is the before, the after, and the measured verdicts'. Bundles persist under the artifacts dir keyed by bundle_id.
- **check_alignment** — DEPRECATED — use assert_visual (aligned_edges / equal_height / equal_width / spacing_equals assertions), which adds PASS/FAIL verdicts and re-runnable suites. Still functional for one release: pixel-perfect audit for a GROUP of elements (a selector's matches or a uid list): which edges/centers align and which element is off by how many px, gap rhythm with outliers, size consistency, optional N-px grid conformance, and pixel-snap warnings.
- **pick_color** — Sample the ACTUAL painted pixel at a point or element — the composited truth that computed styles cannot give (gradients, background images, opacity stacks, blend modes) — plus the owning element's computed color/background and a WCAG contrast verdict (AA/AAA) of the text against the painted backdrop. Reach for it on 'the color looks off', 'is this the exact brand hex?', 'is this text readable on that background?'. Use at:'top-left' to sample pure background (center may hit a glyph); use explain_styles to find WHICH RULE set a wrong color.

## Claude Desktop Quick Installation
Install path detected from listing signals. Uses `npx` (confidence: high):

```json
"mcpServers": {
  "visionaire-engine": {
    "command": "npx",
    "args": ["-y","visionaire-engine"]
  }
}
```

## Documentation & README

# Visionaire Engine

> **Which rule, which file, which line — and why it wins.**

[![npm](https://img.shields.io/npm/v/visionaire-engine)](https://www.npmjs.com/package/visionaire-engine)
[![CI](https://github.com/mi60dev/visionaire-engine/actions/workflows/ci.yml/badge.svg)](https://github.com/mi60dev/visionaire-engine/actions/workflows/ci.yml)
[![License: Apache 2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)

[![visionaire-engine MCP server](https://glama.ai/mcp/servers/mi60dev/visionaire-engine/badges/card.svg)](https://glama.ai/mcp/servers/mi60dev/visionaire-engine)

![Visionaire Engine hero image](https://raw.githubusercontent.com/mi60dev/visionaire-engine/HEAD/hero.png)

**The problem:** Something looks off. You screenshot it, explain it, the LLM guesses wrong, you re-explain.

**The solution:** Visionaire reads the live page and hands the LLM the exact rule, file, and line. Right fix, first try.

![Endless re-prompting vs one pinpointed fix — the problem Visionaire solves](https://raw.githubusercontent.com/mi60dev/visionaire-engine/HEAD/hero-2.jpeg)

**You shouldn't have to write a paragraph to explain a 2px margin bug — and now you don't.** Less explaining, more fixing: built for developers, vibe coders, and anyone shipping site design changes with an LLM in the loop.

**Status: v0.7** — 28 tools, 435 tests (252 unit + 183 end-to-end on real Chrome), a 24-case seeded-bug benchmark (`npm run bench`), verified live against wordpress.org.

<details>
<summary><strong>What's new, by version</strong></summary>

- **v0.7 — the verification layer:** `assert_visual` (a 17-type assertion grammar — PASS/FAIL verdicts with measured pixels, offending uids, and re-runnable named suites), `visual_diff` (pixel diff vs a mockup or recorded baseline, divergent regions mapped to element uids), `impact_preview` (blast radius + sandboxed dry-run before editing a shared selector), `diagnose` (ranked "why is this broken" culprits with measured evidence), `responsive_sweep` (one call → per-viewport verdict matrix), `capture_proof` (before/after evidence bundles with a verdict delta); the verify-after-edit harness for Claude Code and Cursor (`npx visionaire-engine init-harness`); `style_diff { capture_pixels }` baselines; `check_alignment` deprecated in favor of `assert_visual`
- **v0.6 — the pixel-perfect pack:** `check_alignment` (group alignment / gap-rhythm / grid / pixel-snap audit) and `pick_color` (actual painted-pixel sampling + WCAG contrast verdicts)
- **v0.5:** `inject_css` — the live fix loop (trial a fix on the page, see what changed, converge, write source once); `navigate { bypassCache }` for stale-stylesheet hard reloads; blast-radius + scoped-fix reporting on `explain_styles` (change *the* button, not all buttons)
- **v0.4 (field-report items):** `interact` to drive the UI into a state and inspect it; `measure_element` for sub-pixel glyph/text-ink centering; an `evaluate` escape hatch; element-scoped crops/zoom on `annotated_screenshot`; `match:"any"` / `visibleOnly:false` on `find_elements`; zero-config cold-start Chrome discovery
- **v0.3:** the time dimension — event-listener attribution, animation diagnosis, source-attributed interaction timelines
- **Hardened for untrusted pages:** prompt-injection sanitization, fail-fast watchdog, dialog auto-dismiss

</details>

## The problem, in the wild

AI coding agents are structurally blind to what they render. They emit CSS as text and never see the pixels that come back — so they guess, insist the guess worked, and loop. The community has a name for it now: **"CSS gaslighting."** Visionaire gives the agent deterministic eyes on the real render — measured geometry, the actual cascade winner, PASS/FAIL verdicts — so the loop ends. If you want the problem in other people's words first, start here:

- **[Why AI Sucks At Front End](https://nerdy.dev/why-ai-sucks-at-front-end)** — Adam Argyle on why models can't reason about layout they can't perceive.
  > *"It's an LLM, not a rendering engine! It's notoriously bad at math, and throwing screenshots at it means very little. It's stabbing in the dark."*

- **[Is AI Causing a Repeat of Frontend's Lost Decade?](https://mastrojs.github.io/blog/2026-05-23-is-AI-causing-a-repeat-of-frontends-lost-decade/)** — Mauro Bieg on the wider cost of AI-generated frontend.
  > *"AI is enabling lots of AI slop — but this doesn't mean we don't still need people who know what they're doing."*

## The gap

Ask an LLM to fix a visual bug today and it gets one of two incomplete pictures: pixels, with no link back to code, or code, with no rendering truth about what's actually winning on screen. Existing browser MCPs make this worse for design work specifically — they ship accessibility snapshots that deliberately strip out all styling. What's missing is **explanation and attribution**:

- *Which* CSS rule wins the cascade for this property, and why did the others lose?
- *Which file, which line* does the winner live in — or which Elementor widget control, or which Customizer entry?
- *Why* is this element invisible, misaligned, or the wrong size?

Visionaire answers those questions with zero AI inside — everything is computed deterministically from the Chrome DevTools Protocol plus closed rulesets. The fuzzy part (matching "the button under the hero looks off" to an actual element) stays with the calling LLM, which gets uid-keyed snapshots, search tools, and annotated screenshots to do that cheaply.

## What the output looks like

Live against wordpress.org:

```
why color = rgb(255, 255, 255):
  WINNER  [class*=wp-block] .wp-block-button__link { color: var(--wp--custom--button--color--text) }  spec(0,2,0)
    → themes/wporg-parent-2021/build/style.css:499  [line | theme: wporg-parent-2021 — edit themes/wporg-parent-2021/build/style.css]
  lost (specificity)  :root :where(.wp-element-button, .wp-block-button__link) { color: #fff }  spec(0,1,0)
    → global-styles-inline-css:2  [db-entity | Global Styles — Site Editor → Styles (theme.json / wp_global_styles)]
  lost (origin)  a:-webkit-any-link { color: -webkit-link }  spec(0,1,1)
    → user-agent stylesheet
```

Winner, losers with the decisive loss reason, and an honest edit pointer for each — including WordPress-aware answers like "Site Editor → Styles" instead of a useless path to a generated file.

## Quick start

Requires Node ≥ 20 and Chrome/Chromium installed.

Fastest path — register straight from npm with Claude Code (no clone, no build):

```bash
claude mcp add visionaire -- npx -y visionaire-engine
```

Or run from a clone (for development or a pinned local build):

```bash
git clone https://github.com/mi60dev/visionaire-engine && cd visionaire-engine
npm install && npm run build
claude mcp add visionaire -- node "$PWD/dist/index.js"
```

Using **GitHub Copilot, Cursor, Claude Desktop, Google Antigravity**, or another client? See **[docs/clients.md](https://github.com/mi60dev/visionaire-engine/blob/HEAD/docs/clients.md)** for a copy-paste config for each, plus browser-install help for Linux/WSL/Docker.

**Run it from your project's root directory** — Visionaire is at its best when the agent has both the running site *and* its source on disk, so it can cross-reference the two. Ground before you search: take a `page_snapshot` (or read the source) to get real element names instead of guessing selectors. If a selector matches nothing, the error suggests the closest real ids/classes on the page.

Then, in a session:

1. `connect { url: "https://your-site.com" }` — launches Chrome (or `{ browserUrl: "http://127.0.0.1:9222" }` to attach to your real, logged-in browser)
2. `page_snapshot {}` — a uid-keyed census of what's visible; **target elements by their `uid`**, not invented selectors
3. `explain_styles { uid: "e17", property: "margin-bottom" }` — cascade verdict with file:line

Try it without an MCP client:

```bash
npm run demo                                              # bundled fixture
npm run demo -- https://wordpress.org --selector "a.wp-block-button__link"
```

## The 28 tools

**Session & grounding** — get connected and find the right element without guessing.

| Tool | Purpose |
|---|---|
| `connect` / `navigate` / `set_viewport` | Launch or attach to Chrome, go to a URL (`bypassCache` for hard reloads), emulate viewports |
| `page_snapshot` | Pruned, uid-keyed tree of what's visible — geometry, layout hints, invisibility reasons |
| `page_origins` | Stylesheet inventory + platform detection (WordPress version, theme, builders, optimizers) |
| `find_elements` | Deterministic search by text, selector, role, or screen region — AND-combined by default, `match:"any"` for a union, `visibleOnly:false` to include hidden elements |
| `node_at_point` | x,y → element uid + ancestor chain |
| `pick_element` | Human-in-the-loop grounding: DevTools-style hover highlight, the user clicks the element that looks wrong |

**Explanation** — the "why," with a receipt.

| Tool | Purpose |
|---|---|
| `inspect_element` | The "what": box model, computed values, visibility verdict |
| `explain_styles` | **The wedge.** Cascade winner/loser per property with file:line + origin attribution, each winner's **blast radius** (how many other elements it styles), and a scoped-fix selector for just this element |
| `inspect_ancestors` | Constraint-chain walk: which ancestor constrains width/overflow/stacking |
| `get_listeners` | Event listeners on an element + its ancestors, with handler file:line and capture/passive/once flags |
| `explain_animations` | Animations/transitions touching an element: live census, declared rules with file:line, and a closed "why is it not smooth" ruleset |

**Pixel-level checks** — new in v0.6.

| Tool | Purpose |
|---|---|
| `measure_element` | Sub-pixel rendered geometry: content box + true text-ink box (glyph extents) with a centering verdict — "is this × actually centered?" |
| `check_alignment` | *(deprecated → `assert_visual`)* Group pixel audit: which of N elements is off-alignment by how many px, gap-rhythm outliers, size consistency, N-px grid conformance, pixel-snap warnings |
| `pick_color` | The actual painted pixel (composited truth: gradients, images, opacity) + computed colors + WCAG AA/AAA contrast verdict |

**Interaction & time** — states, not just snapshots.

| Tool | Purpose |
|---|---|
| `interact` | Drive the UI into a state (open a menu/popup/modal, reveal a tab) and **leave it there** so you can inspect the new state — reports post-action visibility + box |
| `record_interaction` | One interaction → a source-attributed causal timeline: handlers, mutations, cancelled transitions, layout shifts |

**Fixing & verifying**

| Tool | Purpose |
|---|---|
| `inject_css` | The live fix loop: trial declarations on an element (or a page-wide rule) without touching source — see what changed, converge, write source once, revert |
| `style_diff` | Record styles, compare later — verify-my-fix loops |
| `evaluate` | Escape hatch: run agent-authored JavaScript in the page and get the JSON result, for the genuinely bespoke case no other tool covers |
| `annotated_screenshot` | Screenshot with numbered marks that equal snapshot uids — or an element-scoped crop via `clipTo` with `padding`/`scale` zoom and optional `annotate:false` |

**Verification & proof** — new in v0.7.

| Tool | Purpose |
|---|---|
| `assert_visual` | **The verification gate.** State rendered-geometry claims (equal heights, alignment, gaps, clipping, colors, z-order — 17 assertion types) → deterministic PASS/FAIL with measured pixels and offending uids; register named suites and re-run them after every edit |
| `visual_diff` | Pixel-diff the live page (or one element) against a mockup PNG or a recorded baseline — MATCH/DIVERGENT with divergent regions mapped back to element uids, optional heatmap artifact |
| `impact_preview` | Blast-radius report before editing a shared selector: who else matches, grouped with uids, plus a sandboxed dry-run predicting exactly which elements would change |
| `diagnose` | One-shot "why does this look broken" — ranked culprits with measured evidence for clipping, overflow, off-center, invisibility, overlap, wrong size |
| `responsive_sweep` | One verification payload across many viewports → a per-viewport verdict matrix ("fixed on desktop, still broken on mobile" caught in one call) |
| `capture_proof` | Before/after evidence bundles: annotated screenshots + suite verdicts, with a verdict delta proving the fix flipped FAIL → PASS |

Full reference: [docs/tools.md](https://github.com/mi60dev/visionaire-engine/blob/HEAD/docs/tools.md)

## The verify loop (stop the CSS gaslighting)

An agent edits a stylesheet, reads its own diff, and declares "now the cards are equal height" — without ever seeing a rendered pixel. Visionaire gives your agent deterministic eyes on rendered truth. The loop:

1. **Preview** shared-class blast radius → `impact_preview`.
2. **Edit** the smallest change.
3. **Assert** your claim → `assert_visual` (or re-run a named `suite_id`). You get PASS/FAIL + the actual measured pixels + the offending element uids.
4. **Diagnose** any FAIL → `diagnose` returns the ranked culprit with evidence.
5. **Sweep** responsive → `responsive_sweep` returns a per-viewport verdict matrix.
6. **Prove** it → `capture_proof` bundles before/after screenshots + verdict delta.

Real output — the same suite before and after a fix:

```json
{
 "verdict": "FAIL",
 "summary": "1 assertion: 0 PASS, 1 FAIL — registered as suite 'cards' (re-run with just {\"suite_id\":\"cards\"})",
 "results": [
  { "type": "equal_height", "verdict": "FAIL", "id": "cards-equal",
    "measured": { "values": [412, 388], "unit": "px", "delta": 24, "tolerance_px": 1 },
    "offending_uids": ["e1", "e2"] }
 ],
 "truncated": false, "suite_id": "cards"
}
```

…fix the CSS, re-run with just `{ "suite_id": "cards" }`:

```json
{
 "verdict": "PASS",
 "summary": "1 assertion: 1 PASS, 0 FAIL",
 "results": [
  { "type": "equal_height", "verdict": "PASS", "id": "cards-equal",
    "measured": { "values": [412, 412], "unit": "px", "delta": 0, "tolerance_px": 1 } }
 ],
 "truncated": false, "suite_id": "cards"
}
```

Run `npx visionaire-engine init-harness` from your project root to wire the included Claude Code hooks (or Cursor rule) so the agent physically cannot end a turn claiming "it's fixed" without a verification pass on record. How the markers, hooks, and Stop gate work: [docs/harness.md](https://github.com/mi60dev/visionaire-engine/blob/HEAD/docs/harness.md).

## Documentation

- [docs/clients.md](https://github.com/mi60dev/visionaire-engine/blob/HEAD/docs/clients.md) — install in Claude, Copilot, Cursor, Antigravity, and other MCP clients
- [docs/tools.md](https://github.com/mi60dev/visionaire-engine/blob/HEAD/docs/tools.md) — tool-by-tool reference with real examples
- [docs/harness.md](https://github.com/mi60dev/visionaire-engine/blob/HEAD/docs/harness.md) — the verify-after-edit harness (hooks, markers, `init-harness`)
- [docs/architecture.md](https://github.com/mi60dev/visionaire-engine/blob/HEAD/docs/architecture.md) — how the deterministic pipeline works
- [docs/wordpress.md](https://github.com/mi60dev/visionaire-engine/blob/HEAD/docs/wordpress.md) — WordPress origin resolution guide
- [docs/development.md](https://github.com/mi60dev/visionaire-engine/blob/HEAD/docs/development.md) — building, testing, extending

## Design principles

1. **No internal LLM** — deterministic, cacheable, testable, host-agnostic.
2. **Fuzzy grounding belongs to the calling LLM**; we make it cheap.
3. **Complement the incumbent browser MCPs** (same uid idiom), don't compete.
4. **Honesty ladder** on every attribution: `line > file > db-entity > component > generated > unknown`.
5. **Token-budgeted output** — a dossier is 300–800 tokens, never a dump.

## Security posture

Visionaire is pointed at arbitrary, untrusted pages, so it treats page content as hostile:

- **Prompt-injection defense.** Page-derived strings (element text, class names, ids, attribute values) are sanitized at the single choke point where they enter tool output — collapsed to one line, stripped of control and bidirectional-override characters, and length-capped. A page cannot smuggle instruction-shaped text formatted as a "system message" toward the calling LLM; such content can only appear as an inert, quoted, truncated fragment.
- **Fail-fast, never hang.** Every tool call is wrapped in a watchdog (default 60s, `VISIONAIRE_TOOL_TIMEOUT_MS` to override; `pick_element`/`record_interaction` get their declared wait plus slack). A wedged browser returns an actionable error telling you to `connect` again, instead of blocking the client.
- **No dead-locking dialogs.** Page `alert()`/`confirm()`/`prompt()` calls are auto-dismissed — otherwise they would block every evaluate-family CDP call indefinitely.

Visionaire never executes page-authored code as instructions; it only reads and attributes. The calling LLM should still treat tool output as data about a page, not as commands.

## Known limitations

- Chromium-only (CDP is the only path to matched-rule source locations; `getMatchedCSSRules` was removed from browsers years ago).
- `@layer`: unlayered-vs-layered ordering is exact; ordering between two *different* layer chains is a deterministic proxy (CDP doesn't expose layer declaration order).
- Some CDP fields we rely on (`specificity`, `layers`) are experimental; the engine feature-detects them and falls back (e.g. to its own specificity parser), and a contract smoke test in `test/e2e.test.ts` fails loudly if a Chrome update breaks the core protocol shape (the experimental fields are logged as present/absent).

## Support

Visionaire is free and open source. If it saves you time and you'd like to help keep development going, you can chip in on [Ko-fi](https://ko-fi.com/mishonyai) or [Patreon](https://www.patreon.com/cw/MishonyAI). Completely optional, and genuinely appreciated. Bug reports and field notes help just as much — see [CONTRIBUTING.md](https://github.com/mi60dev/visionaire-engine/blob/HEAD/CONTRIBUTING.md).

## License

**[Apache License 2.0](https://github.com/mi60dev/visionaire-engine/blob/HEAD/LICENSE)** — free for everyone, including commercial use, with a patent grant. Copyright © 2026 mi60dev ([NOTICE](https://github.com/mi60dev/visionaire-engine/blob/HEAD/NOTICE)).

Built and maintained by [@mi60dev](https://github.com/mi60dev). Contributions welcome under the Apache-2.0 terms — see [CONTRIBUTING.md](https://github.com/mi60dev/visionaire-engine/blob/HEAD/CONTRIBUTING.md).
