<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Tavero]]></title><description><![CDATA[Tavero]]></description><link>https://taveroui.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Sat, 07 Nov 2026 11:35:03 GMT</lastBuildDate><atom:link href="https://taveroui.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Vibe-coded UIs drift. I built Tavero to catch it.]]></title><description><![CDATA[If you build React UI with a coding agent, you've probably seen this: every page looks fine on its own, but put them side by side and something's off. The…]]></description><link>https://taveroui.hashnode.dev/vibe-coded-uis-drift-i-built-tavero-to-catch-it</link><guid isPermaLink="true">https://taveroui.hashnode.dev/vibe-coded-uis-drift-i-built-tavero-to-catch-it</guid><category><![CDATA[React]]></category><category><![CDATA[AI]]></category><category><![CDATA[mcp]]></category><category><![CDATA[#designsystems]]></category><dc:creator><![CDATA[Tavero]]></dc:creator><pubDate>Thu, 29 Oct 2026 06:43:03 GMT</pubDate><content:encoded><![CDATA[<p>If you build React UI with a coding agent, you've probably seen this: every page looks fine on its own, but put them side by side and something's off. The Team page has a flat grey button with a 2px black border. Reports has a pill. Settings has a red one with radius 4. Billing uses <code>#4263eb</code>, which is <em>almost</em> your primary color, but not quite.</p>
<p>Nobody decided this. The agent just wrote a new button inline each time, because it didn't know (or forgot) that <code>src/components/Button.tsx</code> exists.</p>
<p>I built <strong>Tavero</strong> to catch exactly that drift and hand it back to the agent as concrete fixes. It's free and open source.</p>
<h2>What it does</h2>
<p>Tavero scans your <em>running</em> React app — what the browser actually renders — at mobile, tablet and desktop widths. It runs four rules, and every finding names the component and the source line:</p>
<ul>
<li><strong>Buttons</strong> — distinct button styles, near duplicates, and buttons written in page markup instead of the shared component.</li>
<li><strong>Cards</strong> — card-like containers that differ in background, border, radius, padding or shadow.</li>
<li><strong>Colors</strong> — colors outside your tokens, the nearest token, and where the literal is written.</li>
<li><strong>Scales</strong> — radius, spacing, font size and shadow values that aren't on the scale your tokens declare.</li>
</ul>
<p>Here's what a button finding looks like on the Acme demo (6 pages, 3 widths), which turned up 7 text button styles:</p>
<pre><code>Button "primary" · canonical · 9 renders
  src/components/Button.tsx:4  bg #3b5bdb, radius 6, padding 10/16, 14px/600
written in Team · 3 renders
  src/pages/Team.tsx:6         bg #f0f0f0, radius 0, padding 1/6, 13px/400, border 2px #000
written in Settings · 3 renders
  src/pages/Settings.tsx:12    bg #e03131, radius 4, padding 10/16, 14px/600
written in Billing · 3 renders
  src/pages/Billing.tsx:8      bg #4263eb, radius 8, padding 11/18, 14px/600
</code></pre>
<p>And a scale finding:</p>
<pre><code>radius off the scale: 4px, 8px, 14px, 999px · declared: 10px, 6px
</code></pre>
<h2>The agent closes the findings</h2>
<p>Tavero runs as an MCP server, so the agent can use it directly. The loop is:</p>
<ol>
<li><strong>Read the context</strong> — canonical components, tokens and conventions, in about 1,500 tokens.</li>
<li><strong>Open a finding</strong> — file and line, the current value, and the token or component to use.</li>
<li><strong>Fix and rescan</strong> — only the routes and the rule it touched. Seconds, not minutes.</li>
<li><strong>Check the diff</strong> — done when the finding is listed as resolved.</li>
</ol>
<p>In practice:</p>
<pre><code>› tavero_context
Action button is Button from src/components/Button.tsx (props: variant)
--acme-primary #3b5bdb · --acme-radius 10px · --acme-space 16px

› tavero_findings rule=colors
f_ff30adc07c Color #4263eb is not a project token

› tavero_finding f_ff30adc07c
src/app.css:33 literal #4263eb in source
expected --acme-primary #3b5bdb (ΔE 3.5)
edit src/app.css:33 background: #4263eb → var(--acme-primary)

› tavero_scan routes=/billing rules=colors

› tavero_diff
resolved f_ff30adc07c Color #4263eb is not a project token
resolved f_18862a0332 Hardcoded color #4263eb in 1 place(s)
</code></pre>
<p>That run was Claude Code with Opus 5.5, no shell, 29 seconds, 10 turns, on a copy of the Acme demo.</p>
<h2>You decide what's intended</h2>
<p>Not every difference is a bug. A red destructive button is probably on purpose. So there's a local panel for the person behind the agent: go through findings with the keyboard and mark each one:</p>
<ul>
<li><strong>Fix</strong> — confirmed work; the agent sees it first.</li>
<li><strong>Intended</strong> — hidden from the agent until the finding grows.</li>
<li><strong>Canonical</strong> — the reference variant, with a name, used as the expected value.</li>
</ul>
<p>You can attach a message to a finding, and each decision reaches the agent on its next call — no rescan needed.</p>
<h2>Install</h2>
<p>Add the MCP config to your client. For Claude Code (<code>.mcp.json</code>):</p>
<pre><code class="language-json">{
  "mcpServers": {
    "tavero": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "tavero", "mcp"]
    }
  }
}
</code></pre>
<p>Cursor uses the same block in <code>.cursor/mcp.json</code>. There are configs for Codex, Gemini CLI, Antigravity, VS Code and Windsurf too — <code>tavero mcp --print-config</code> prints all of them.</p>
<p>Then ask your agent to read <code>tavero_context</code> before it writes UI.</p>
<p>More, including the demo findings: <a href="https://taveroui.com">https://taveroui.com</a></p>
<p>If you try it on your app, I'd love to hear what it finds.</p>
]]></content:encoded></item></channel></rss>