DEV Community

Escape FromRussia
Escape FromRussia

Posted on

Why your AI agent recreates components instead of installing them

You find a hero block you like. You paste the link into Cursor or Claude Code and ask for "this hero on my landing page". A minute later you have a hero: different spacing, a different gradient, no entrance animation. It looks like every other AI-generated landing page.

I build a component registry for this exact workflow, and I spent a long time finding out where the design gets lost. Short version: not in the code. It gets lost in the summary.

The flow that should work

pick a component → copy an instruction → paste it into the agent
→ agent runs `npx shadcn add <url>` → your project has exactly what the preview showed
Enter fullscreen mode Exit fullscreen mode

The instruction is generated from the component's metadata, not hand-written: component ID, install command, dependencies, what to keep (motion, typography, spacing, responsive behaviour), what may change (copy, links, accent colour), and an explicit rule not to rebuild it from scratch.

On paper that's enough. In practice it broke in two places.

Failure 1: the summary eats the rules

When an agent "reads" a URL, it usually doesn't read it. The web-fetch tool passes the page through a small model and returns a digest. Commands and facts survive the digest. Prose rules — "keep the entrance animation", "don't change the grid" — are the first thing to go.

So the agent installs the component correctly… and then "improves" it, because the part that said don't never reached it.

Failure 2: the generic reflex

If the instruction describes the component ("a hero with a gradient and two buttons"), the agent writes a hero with a gradient and two buttons. Itself. A description is a spec, and an agent with a spec builds.

So the instruction should not describe the look at all. It should say: here's the command, here's the source, install it.

The fix: send the rules three ways

Putting 2–3 "keep as installed" rules in the brief is not enough. Duplicate them into places nobody summarises.

1. The brief. Full text, for the human and (best case) the agent.

2. The docs field of the registry item. The shadcn CLI prints it to the terminal after install. The agent sees it as command output, verbatim, no summary in between. Here is a real one from https://vibeui.club/r/en/hero-001.json:

Keep as installed:
- the button-016, heading-001 imports from @/components/vibeui/ — do not inline
  their code into the block or swap in your own; the parts receive props only
- light-dark() in the palette: the component has to follow the surrounding
  color-scheme rather than ship a dark theme of its own
- the transparent background by default — the component sits on the page
  background instead of laying a panel under itself
Enter fullscreen mode Exit fullscreen mode

3. A comment at the top of the file. The file is written byte for byte, so the rules end up inside the code the agent is about to edit:

"use client"

// VibeUI hero-001. Keep as installed:
// - the button-016, heading-001 imports ...
// - light-dark() in the palette ...
Enter fullscreen mode Exit fullscreen mode

One gotcha: the shadcn CLI runs files that start with a "use client" directive through ts-morph and drops comments that sit before the directive. The header has to go after it. The whole thing is about 20 lines:

export function withPreserveHeader(source: string, rules: string[], name: string) {
  if (rules.length === 0) return source
  const header = [`// VibeUI ${name}. Keep as installed:`, ...rules.map((r) => `// - ${r}`), "", ""].join("\n")
  // comments before "use client" are lost by the CLI, so the header goes after it
  const directive = /^(["']use client["'];?\r?\n)\r?\n?/.exec(source)
  return directive ? `${directive[1]}\n${header}${source.slice(directive[0].length)}` : header + source
}
Enter fullscreen mode Exit fullscreen mode

Rules don't help if the component breaks in someone else's project

The agent will drop your component into a project you've never seen. What turned out to be mandatory:

  • A local palette in --vibeui-<name>-* variables instead of the host's theme tokens. Otherwise the block gets repainted by someone else's theme and the agent "fixes" it.
  • light-dark() instead of a custom dark theme. The component follows the page's color-scheme. For next-themes / shadcn, which toggle a .dark class, one line sets color-scheme: dark under it.
  • Layout by its own width (container queries), not the viewport. Blocks often land in a column.
  • Zero npm dependencies where possible, and prefers-reduced-motion inside the component, so globals.css needs no edits.
  • Composite blocks install their parts with one command via registryDependencies instead of inlining their code. The docs field above tells the agent not to inline them either.

Checklist if you run your own registry

  1. Write the agent instruction as a command, not a description.
  2. Put the critical rules where there is no summary: CLI output (docs) and the file itself.
  3. Check that your build tool doesn't eat the comments.
  4. Make the component independent of the host theme and the parent's width.

I use this setup in VibeUI: a shadcn-compatible catalog of 2,000+ React/Tailwind components and landing blocks where every item has a "Copy for AI" brief. Previews and search are open without an account. If you've solved the "agent rewrites my component" problem differently, I'd like to hear how in the comments.

Top comments (0)