Interface Built Right
End-to-end UI design tool for AI coding agents. Web/iOS/macOS archetype routing, /ibr:build orchestrator, custom CDP engine, deterministic rule + sensor layers.
Install
claude plugin marketplace add tyroneross/RossLabs-AI-Toolkit
claude plugin install ibr@rosslabs-ai-toolkit
The Problem
AI coding agents can render a UI without knowing whether it is the right UI. Tests pass, the page returns 200, the screenshot looks plausible — and the navbar is shifted 40px, the empty state has no CTA, the iOS app violates HIG, the form has no focus indicators. A regression checker tells you something changed. It does not tell you what to build, which patterns fit the platform, or whether the result is discoverable. IBR started as the regression checker; v1.0+ is the rest.
What I Built
IBR is an end-to-end design tool for AI coding agents — design, build, and validate interfaces across iOS, macOS, and web. The validation engine that used to be the product is now the verification layer underneath a build orchestrator, platform-aware design routers, and a custom CDP engine. Works from terminal, Codex, Claude Code slash commands, or code. Zero config.
Architecture
IBR is a deterministic tool package (npm + Claude Code/Codex plugin) that a host LLM agent calls — it is not itself an LLM caller. Every scan, resolution, and diff below runs as plain TypeScript algorithms against real browser data; the only model in the loop is the calling agent (Claude Code, Codex, or whichever assistant invoked ibr). The trace below follows one page through ibr scan / ibr ask / the /ibr:build Validate step, from browser connect to verdict.
flowchart LR
A["Host agent: Claude Code / Codex / CLI"] --> B["CDP engine: WebSocket to Chrome,<br/>or safaridriver for Safari"]
B --> C["Hydration wait + AX-tree capture<br/>+ DOM snapshot"]
C --> D{"assessUnderstanding()<br/>AX-tree quality score"}
D -->|score ≥ 0.6| E["Text-only: AX tree"]
D -->|score < 0.6| F["+ screenshot attached"]
E --> G["Element resolution:<br/>queryAXTree → Jaro-Winkler → vision-fallback flag"]
F --> G
G --> H["DOM chunking: interactive/leaf elements<br/>(60-70% fewer tokens)"]
H --> I["Tier 1: deterministic rule engine<br/>wcag-contrast, touch-targets, calm-precision"]
H --> J["Tier 2: sensor layer<br/>visualPatterns, componentCensus, contrast, nav..."]
I --> K["summarize.ts: structured report<br/>(~60% token cut)"]
J --> K
K --> L{"Baseline image exists?"}
L -->|yes| M["Pixelmatch diff (0.1 threshold)<br/>+ verdict-policy classification"]
L -->|no| N["Scan/ask result returned"]
M --> O["MATCH / EXPECTED_CHANGE /<br/>UNEXPECTED_CHANGE / LAYOUT_BROKEN"]
N --> P["Host agent"]
O --> P
P --> Q["iterate.ts: re-scan to confirm<br/>0 issues before claiming success"]
Q -.approved preferences.-> R[("Filesystem memory store<br/>summary.json + preferences/ + archive/")]
R -.injected via prompt hooks.-> A
How it works
- Entry — the host agent calls
ibr scan <url>/ibr askon the CLI, one of the MCP tools (scan,snapshot,compare,list_sessionsserved over JSON-RPC 2.0/stdio fromdist/mcp/server.js), or the/ibr:buildorchestrator’s Implement/Validate step. - Browser connect — the custom
EngineDriveropens a raw WebSocket to Chrome using Node 22’s built-inWebSocket(no Puppeteer, no Playwright dependency), or drives Safari viasafaridriver+ the macOS Accessibility API. - Capture —
waitForHydration/waitForStableTreepoll for Next.js/React hydration markers and a stable accessibility tree, then the engine pulls the AX tree and a DOM snapshot. - Modality decision —
assessUnderstanding()scores the AX tree on text quality, semantic relevance, and structural clarity; a score ≥0.6 proceeds text-only, below that a screenshot is attached to the result. - Element resolution — when a specific element is targeted by intent,
resolve()runs a 3-tier lookup:queryAXTree-first exact/near match, then Jaro-Winkler fuzzy string scoring; below 0.3 confidence it setsvisionFallback: trueso the calling agent (not IBR) knows to look at a screenshot itself. - Extraction — the full DOM is chunked down to interactive/leaf elements only, cutting 60-70% of tokens before anything reaches the model, into
EnhancedElement[]with computed styles, roles, and handler data. - Tier 1 checks — the deterministic rule engine runs
wcag-contrast,touch-targets, andcalm-precisionpresets as pure algorithms (WCAG 2.1 luminance math, 44px/24px sizing checks, Gestalt/Fitts/Hick rules) — zero LLM tokens, structuredruleId/severity/fixoutput. - Tier 2 summarization — the sensor layer (
visualPatterns,componentCensus,interactionMap,contrast,navigation,semanticState,oneLiners) condenses the element set into structured summaries;summarize.ts’s own docstring states the layer is “pure deterministic algorithms — no LLM calls, no npm dependencies.” - Visual diff (when a baseline exists) —
compare.tsruns a Pixelmatch pixel diff (0.1 color-sensitivity default) region by region, andverdict-policy.tsclassifies the result as MATCH, EXPECTED_CHANGE, UNEXPECTED_CHANGE, or LAYOUT_BROKEN against provenance-tracked thresholds (each boundary in the policy carries abasis/rationale/reviewedAt, not a bare magic number). - Result handoff and re-check — the scan/ask/compare result returns to the host agent;
ibr askcompresses this to one verdict (~600 bytes vs. ~19KB for a full scan).iterate.tsforces a re-scan before the agent is allowed to claim success (“never claim success without a second confirmation” — credited in-code to the Anthropic harness pattern). - Memory persistence — preferences the user approves are written to a filesystem-only three-tier store (
summary.jsonhot cache under 2KB,preferences/detail files,archive/for evicted entries, 50-item LRU cap), Zod-validated, and re-injected into future prompts via hooks rather than re-fetched.
Models — IBR calls no LLM or vision model itself. package.json has no Anthropic/OpenAI/LangChain SDK dependency (commander, nanoid, pixelmatch, pngjs, zod only), and the summarization/rule layers are explicitly documented in-source as pure deterministic algorithms. The “vision fallback” and “screenshot needed” signals are boolean flags computed from AX-tree heuristics — they tell the calling agent (Claude Code, Codex — already multimodal) to look at a screenshot itself; IBR never performs inference. Imagegen concept generation, when used, is invoked by the host agent as an upstream step, not by IBR. This is a deliberate design choice: every verdict IBR returns is reproducible and free to run, since none of the scan/compare/rule pipeline consumes model tokens.
Tools & Infra — Custom CDP engine over Node 22’s built-in WebSocket (no Puppeteer/Playwright — smaller footprint, direct control over AX-tree polling and hydration waits). Pixelmatch + pngjs for pixel-level visual diffing, no external image-diff service. Zod for schema validation and auto-migration across config, session, and memory/preference files. Commander for the ibr CLI; nanoid for session/preference IDs. An MCP server (JSON-RPC 2.0 over stdio) exposes scan/snapshot/compare/list_sessions so Claude Code and Codex can call IBR as a tool instead of shelling out. Safari support via safaridriver WebDriver plus a native Swift binary (ibr-ax-extract, Package.swift) for macOS Accessibility API extraction; a second Swift binary (sim-driver) drives the iOS Simulator. No database anywhere in the stack — sessions, memory, and baselines are all JSON on disk under .ibr/. Distribution is an npm package (@tyroneross/interface-built-right) plus Claude Code/Codex plugin manifests; there is no server-side hosting, everything runs in the caller’s terminal or CI against a locally launched or attached browser.
/ibr:build Orchestrator
/ibr:build <topic> runs a fixed sequence:
- Preamble — platform, scope, design mode, archetype hints, UI template, references, density
- Optional imagegen concepts — generated only when useful; require explicit approval before becoming a
visual-target - Design Director — produces
design-intent.json, specialist planning passes, target roles, and validation criteria - Brainstorm & Plan — guided exploration with platform-specific rules and a concrete implementation plan
- Implement — Calm Precision, web/iOS/macOS routers, component patterns, data-viz guidance when needed
- Validate — scan, match wireframe / visual targets, test interactions, iterate until passing
The orchestrator is the one place that knows the active design system, the reference templates, and the verification gates — so an agent calling it cannot skip the design step on the way to a passing test. Specialist passes cover flow, visual system, interaction states, content states, Mockup Gallery targets, data visualization, and validation. IBR does not spawn tiny per-atom agents; those stay inside component patterns and design tokens.
Two-Tier Architecture
Part of the verification-gated agent-work pattern (see
/toolkit/patterns/verification-gated-agents) — here: a two-tier scan (zero-token deterministic rule engine + structured sensor summaries) feeds a Pixelmatch diff at a 0.1 threshold, classified MATCH / EXPECTED_CHANGE / UNEXPECTED_CHANGE / LAYOUT_BROKEN, before an agent-driven UI change is considered done.
IBR scans return structured data, not raw element dumps. Two layers run on every scan.
Tier 1 — Deterministic Rule Engine (no LLM, zero tokens). Pure algorithms against runtime data:
| Rule Preset | What It Checks | Algorithm |
|---|---|---|
wcag-contrast | Text contrast ratios, AA and AAA | WCAG 2.1 relative luminance |
touch-targets | Interactive element sizing | 44px mobile (WCAG 2.5.5), 24px desktop (WCAG 2.5.8) |
calm-precision | Gestalt, Signal-to-Noise, Fitts, Hick, Content-Chrome, Cognitive Load | Principle-based checks |
Tier 2 — Sensor Layer (structured summaries). Pre-computed so the model focuses on judgment, not pattern re-discovery:
| Sensor | What It Produces |
|---|---|
visualPatterns | Groups elements by style fingerprint per category |
componentCensus | Tag/role counts + orphan cursor:pointer elements with no handler |
interactionMap | Which interactive-looking elements actually have handlers |
contrast | WCAG pass/fail grouped, only failures listed |
navigation | Link structure with depth and counts |
semanticState | Page intent, states, available actions |
oneLiners | 5-second scannable summary lines |
ibr scan --output summary cuts roughly 60% of tokens.
Platform Routers
Web — web-design-router classifies into seven archetypes (SaaS dashboard, data/research tool, editor/workbench, AI agent chat, commerce/checkout, content/publication, internal admin). Each archetype sets defaults for navigation, density, primary content, mobile behavior, and validation focus.
iOS — ios-design-router classifies into six archetypes — Utility, Content/Feed, Productivity, Consumer/Habit, Editorial, Tool/Pro — and routes to matching reference files. A meditation app and a developer tool both ask “what navigation pattern?” and get different correct answers without the agent guessing. Sits next to ios-design (HIG rules: navigation, color, type, motion, SF Symbols, materials, Liquid Glass) and apple-platform (architecture, SwiftData, Watch connectivity, concurrency, CI/CD, TestFlight — folded in from the standalone apple-dev skill, which is now deprecated).
Reference library — seven domain files lifted from the Calm Precision iOS design system, loaded on demand by archetype rather than all at once: navigation, lists and cards, buttons, color and typography, motion and states, task economy, and the archetype catalog the router reads from.
CDP Engine
The browser layer is a custom Chrome DevTools Protocol engine over WebSocket. No Playwright, no Puppeteer, no heavyweight automation dependency. Built-in LLM-native features the legacy stack could not do cleanly:
| Feature | What it does |
|---|---|
| queryAXTree-first resolution | Find elements by semantic name + role, not fragile CSS selectors. 3-tier: queryAXTree search → Jaro-Winkler fuzzy → vision-fallback flag (returned to the calling agent, not resolved by IBR) |
| DOM chunking | Filter to interactive/leaf elements; 60–70% fewer tokens |
| Adaptive modality | Scores AX tree quality. High → text data; low → include screenshot. Vision only when needed |
| Resolution cache | Same intent → element query twice = instant; clears on navigation |
| observe / extract / interact | Preview available actions, pull structured data, click/type by accessible name |
| Hydration waiting | Detects Next.js/React markers + polls AX tree until stable — eliminates “0 elements” on modern SPAs |
Safari support via safaridriver + macOS AX API; cross-browser diff via ibr compare-browsers.
Verification, Still
Comparison did not go away — it became a step inside the loop instead of the whole product. Capture a baseline, run the change, diff with Pixelmatch at a 0.1 threshold, classify the result as MATCH, EXPECTED_CHANGE, UNEXPECTED_CHANGE, or LAYOUT_BROKEN, and feed the verdict back into the iterate phase. Landmark detection extracts the accessibility tree on capture so the verdict carries page intent (auth form, list view, dashboard) rather than just a pixel count.
Memory
A three-tier preference store keeps the agent’s design context small and current: summary.json as a hot cache under 2KB, a preferences/ directory with full details, and an archive/ for evicted entries. Cap is 50 active preferences; the least recently used drops to archive when a 51st arrives. Prompt hooks inject relevant baselines and preferences into the agent’s context before a UI change instead of asking it to fetch them. Zod schemas validate every preference file and auto-migrate old shapes when the format changes.
Tech stack
TypeScript, custom CDP engine (Node 22 built-in WebSocket, no Puppeteer/Playwright), Pixelmatch + pngjs (visual diff), Zod (schema validation), Commander (CLI), nanoid (IDs), MCP server over JSON-RPC 2.0/stdio, Safari via safaridriver + native Swift AX extraction (macOS Accessibility API), filesystem-only session/memory store (no database), npm + Claude Code/Codex plugin distribution (no server hosting).
Results
Token reduction is the only measured efficiency figure in this build; the numbers below are the ones already reported per-feature elsewhere in this doc, consolidated here.
| Mechanism | Token Reduction |
|---|---|
ibr scan --output summary (sensor-layer summarization vs. raw dump) | ~60% |
| DOM chunking (filter to interactive/leaf elements before AX resolution) | 60–70% |
Verification accuracy (MATCH/EXPECTED_CHANGE/UNEXPECTED_CHANGE/LAYOUT_BROKEN classification correctness) and design-quality delta (pre/post Calm Precision + archetype routing) — ⚠️ no benchmark yet.
Why the Repositioning
IBR already had build orchestration, design-system enforcement, and platform skills. Calling it a testing tool described one slice and left the other three under-marketed and under-used. The new framing — design-first, verified end to end — matches what the plugin actually does on a real build, and gives the iOS work, the apple-platform integration, and the CDP engine a coherent place to live.