BXUP Track progress & test yourself — free
Frontend & Mobile · Hands-on lab · ~5 min read
FRONTEND & MOBILE · HANDS-ON LAB

Lab 03 · Registry-Grounded Generation A/B

Ask an agent for the same data-table screen twice — once with a component-registry MCP wired, once without — then diff the outputs. The deliverable is a comparison memo that documents hallucinated props versus real component installs, plus a working on-registry screen.

Priya's team standardized on shadcn/ui a year ago. Last sprint a teammate merged an AI-generated admin table that imported @/components/ui/data-table — a module that has never existed in their repo — with a sortable prop the real Table has never had. It compiled on his machine because the agent had also helpfully invented the missing file. The PR looked plausible, reviewed plausible, and broke design consistency in three places. This lab makes that failure mode measurable: you run the same generation task grounded and ungrounded, and you count the hallucinations yourself.

The mechanism behind the hallucination is worth naming before you reproduce it. An ungrounded model answers "build a shadcn data table" from a statistical composite of every table library it has ever seen — Material UI, Ant Design, TanStack, react-table, a thousand blog tutorials — blended into a plausible average that resembles no specific library's real API. It is not lying; it is interpolating, and interpolation across libraries is exactly where invented props and lookalike modules come from. A registry MCP breaks the interpolation by giving the agent a tool to look up the ground truth — the actual installed component's props, the actual variant names, the actual install command — so it writes against one real API instead of a memory of many. The A/B you are about to run is the cleanest possible demonstration of that difference, because the only variable that changes between the two runs is whether the agent could consult reality.

Time & tier

~60 minutes hands-on · Build stage (maps to module M2, playbook PB-03) · $0 — Claude Code, Cursor, or any MCP-capable agent; shadcn registry MCP is free.

Setup

npm create vite@latest registry-lab -- --template react-ts
cd registry-lab && npm install
npx shadcn@latest init   # accept defaults (slate, CSS variables)
mkdir -p runA runB

Wire the shadcn registry MCP for run B only. For Claude Code, .mcp.json in the repo root:

{
  "mcpServers": {
    "shadcn": {
      "command": "npx",
      "args": ["shadcn@latest", "mcp"]
    }
  }
}

(Cursor: add the same server under Settings → MCP. Verify it's live by asking the agent "list available registry items" — you should see table, badge, dropdown-menu, etc.)

The task prompt — identical for both runs, save it as task.md:

Build src/OrdersTable.tsx: an orders admin table using this project's
shadcn/ui components.

Data shape:
  { id: string; customer: string; total: number;
    status: "paid" | "pending" | "refunded"; placedAt: string }

Requirements:
- columns: Order ID, Customer, Total (right-aligned, currency),
  Status (colored badge per status), Placed (relative date)
- click a column header to sort by that column
- a row-actions menu (View, Refund) on each row
- empty state when there are no orders
- seed 8 rows of demo data inline

And the memo template, comparison-memo.md:

# Registry grounding A/B — comparison memo

## Run A (ungrounded) — claims audit
| # | Claim in generated code | Real? (checked against actual API) | Class |
|---|--------------------------|-------------------------------------|-------|
Classes: HALLUCINATED-PROP / HALLUCINATED-MODULE / INVENTED-LOOKALIKE /
         WRONG-PATTERN / OK

## Run B (registry-grounded) — install audit
Components actually installed (from registry):
Props verified against installed source:

## Verdict
Compile result A vs B:
Visual/design-system consistency A vs B:
Time to working screen A vs B:
One-paragraph recommendation for the team:

Guided steps

  1. Run A — ungrounded. Disable or omit the MCP server (move .mcp.json aside). Fresh agent session, paste task.md, and let it generate without asking clarifying questions. Save the output to runA/OrdersTable.tsx plus any extra files it invents. Do not correct it, do not install anything it tells you to install unless the package genuinely exists and the agent's code needs it to compile — record every such intervention. Expected: plausible-looking code in 2–4 minutes.
  2. Audit run A against reality. Try to compile: npx tsc --noEmit. Then check every import path and every prop against the actual installed source in src/components/ui/ (for components it references but never installed, check the real shadcn docs or registry source). Fill the Run A claims table — one row per import and per non-HTML prop. Expected: 10–20 rows. Be strict: a prop that exists on some other library's Table but not on this project's is HALLUCINATED-PROP, not OK.
  3. Run B — registry-grounded. Restore .mcp.json, start a fresh session, paste the identical task.md with one added line: "Use the shadcn registry MCP to look up and install any components you need before writing code." Expected behavior worth watching: the agent queries the registry, runs real installs (npx shadcn@latest add table badge dropdown-menu), reads the installed source, then writes against the real API — sorting implemented in userland state or via @tanstack/react-table, because it discovered the primitive Table has no sortable prop.
  4. Audit run B the same way.npx tsc --noEmit, then verify a sample of props in runB/OrdersTable.tsx against the installed component source. Fill the install audit. Expected: imports resolve to files that exist because the agent installed them; prop usage matches the installed signatures.
  5. Diff the two outputs side by side.diff runA/OrdersTable.tsx runB/OrdersTable.tsx and a visual skim. In the memo, note the structural differences: where run A invented an API surface, what run B did instead with the real primitives.
  6. Make run B production-real. Wire OrdersTable into App.tsx, npm run dev, and manually exercise: sort toggles on at least two columns, the actions menu opens and is keyboard-reachable, the empty state renders when you pass []. Fix any residual issues with the agent, keeping it grounded ("check the installed dropdown-menu source before answering").
  7. Write the verdict. Complete the memo's verdict section, including the one-paragraph team recommendation: when is ungrounded generation acceptable (throwaway exploration), and what does your team gate on for production code (registry grounding + compile + prop audit)?
Pro Tip

The most dangerous run-A failure is not the import that breaks the build — it's the invented lookalike that compiles. When the agent can't find data-table, it will often write a 200-line homegrown table styled to resemble shadcn. That merges cleanly and rots your design system quietly.

Verify

  • Both raw outputs preserved unmodified in runA/ and runB/; every intervention you made is logged.
  • Run A claims table has 10+ rows, each verdict backed by a check against real installed source or registry docs — not memory.
  • Run B's screen compiles (tsc --noEmit clean) and passes the three manual checks: sorting, keyboard-reachable row actions, empty state.
  • The memo's verdict section is complete, including compile results for both runs and the team recommendation paragraph.
  • At least one INVENTED-LOOKALIKE or HALLUCINATED-MODULE finding is documented (if run A was miraculously clean, rerun it with a different model and say so in the memo).

Self-grading rubric (pass = 7/10)

  • Audit rigor (0–4): every run-A import and prop row verified against actual source, correct class assigned = 4; spot-checked only = 2.
  • Working screen (0–3): run B compiles and all three manual checks pass = 3; compiles but a check fails = 1.
  • Memo quality (0–3): verdict quantifies the difference (counts, compile results) and the recommendation draws a defensible grounded/ungrounded boundary = 3.
▸Answer key — what run A typically hallucinatesoptional deep dive — click to expand

There is no fixed seed list here — run A's defects come from your model, live — but grading is against these expected finding classes, and a competent audit of a typical ungrounded run finds four to six of them:

  • HALLUCINATED-MODULE: imports like @/components/ui/data-table or @/components/ui/table referenced without ever being installed — the path convention is real, the file is not. Confirm by ls src/components/ui/.
  • HALLUCINATED-PROP: props such as sortable, onSort, striped, pagination={{ pageSize: 10 }}, or columns={...} passed to the shadcn Table — the real primitive is a thin styled wrapper over <table> with no data-layer props at all. Any data behavior must be user-land or @tanstack/react-table. This is the highest-value finding class because it reveals the agent pattern-matched "a React table library" instead of this library.
  • INVENTED-LOOKALIKE: a bespoke DataTable or Badge implementation written from scratch and styled to imitate shadcn — compiles, looks close, ignores your tokens and variants. Classify every hand-rolled component that duplicates a registry item.
  • WRONG-PATTERN: using Menu/Dropdown names from other ecosystems (MUI, Ant) instead of shadcn's DropdownMenu composition (DropdownMenuTrigger/DropdownMenuContent/DropdownMenuItem), or importing Badge variants (variant="success") that the default registry Badge does not define (it ships default | secondary | destructive | outline).
  • OK rows matter too: standard React, the data shape, and correct shadcn usage it happened to remember. An audit that marks everything hallucinated is as miscalibrated as one that marks everything OK.

Expected run-B contrast: real npx shadcn@latest add installs visible in src/components/ui/, sorting via useState + array sort or TanStack, DropdownMenu composition for row actions, and the Badge restricted to real variants (status colors via className on top of a real variant is acceptable and worth noting as a reasonable pattern).

Scoring the memo: full marks requires (1) a hallucination count for run A with class breakdown, (2) explicit compile outcomes for both runs, and (3) a recommendation that does not say "never use ungrounded generation" — throwaway exploration is a legitimate lane; the boundary is what may merge.

Key Takeaway:

An ungrounded agent generates a plausible memory of your component library; a registry-grounded agent reads the real one. The diff between those two outputs — counted, classified, and memo'd — is the most persuasive artifact you can put in front of a team that still prompts from vibes.

This is the free reading view. Open BXUP to track your progress, run the hands-on labs, and prove it with original, exam-pattern assessments.
© Anvi Innovate · BXUP — Be Exponentially Upskilled · BXUP is an independent learning platform, not affiliated with or endorsed by AWS, Microsoft, Google, NVIDIA, IBM, Salesforce, Databricks, Oracle or Anthropic. All trademarks belong to their respective owners. · bxup.in