Carsxe migrate loop
Loop until a consumer app is fully migrated onto @carsxe/design-system: grep leftover UI, migrate a slice, browser screenshot/video QA, repeat until complete.Save as
.cursor/skills/carsxe-migrate-loop/SKILL.md. Copy other files in this skill into the same folder.SKILL.md
markdown
---
name: carsxe-migrate-loop
description: >-
Loop until a consumer app is fully migrated onto @carsxe/design-system. Grep leftover
shadcn/local UI, migrate one slice, browser screenshot/video QA, repeat until complete.
Use when the user wants to migrate an app to the Carsxe design system, run a migrate
loop, or fully replace local ui/ components with @carsxe/design-system.
---
# Carsxe migrate loop
Same-session greploop: inventory leftover UI → migrate one slice → browser QA → repeat until the app is fully on `@carsxe/design-system`. This is not a timed `/loop` heartbeat.
Migration rules live in the `carsxe-design-system` skill. This skill owns the loop, inventory, and visual proof.
## Inputs
- **App root** (optional): directory to migrate. Default: current working directory.
- Do not commit or push unless the user asked. Keep work in the working tree.
## Instructions
### 0. Prerequisites
Read `carsxe-design-system/SKILL.md` and `reference.md` before changing any UI.
If those files are missing from this skills folder, copy **both** `carsxe-design-system` and `carsxe-migrate-loop` from `packages/skills` (or from the docs catalog) into the agent skills directory, then continue.
Do not confuse `@carsxe/design-system` with `@carsxe/ui`. Do not migrate widget or edit-mode behavior. Do not run `shadcn add`. Do not create wrappers around design-system primitives.
### 1. Bootstrap once
Detect the package manager from lockfiles (`bun.lock` / `bun.lockb` → bun, `pnpm-lock.yaml` → pnpm, `yarn.lock` → yarn, else npm).
```bash
bun add @carsxe/design-system
# or: pnpm add @carsxe/design-system
# or: npm install @carsxe/design-system
```
Load CSS in the app entry (layout, `main.tsx`, or global stylesheet):
- Tailwind v4 app → `@carsxe/design-system/globals.css`
- Otherwise → `@carsxe/design-system/styles.css`
Skip reinstall if the package is already a dependency. Still verify the CSS import exists.
### 2. Inventory
Read [inventory.md](inventory.md). Grep the app (exclude `node_modules`, `dist`, `.next`, `.output`, `coverage`, `build`, `.turbo`, `.carsxe-migrate-loop`).
Count **covered** leftover hits. That count is the unresolved-work number for this loop. List uncovered third-party UI separately; it is not a failure.
Record `HITS_BEFORE` at the start of every iteration.
### 3. Loop
Repeat the following cycle. **Max 20 iterations.** Stop early if an iteration does not reduce the hit count.
#### A. Pick a slice
Choose one coherent slice: a single route/page, or one component family (`Button` + usages, `Dialog` + usages). Do not migrate the whole app in one iteration.
Prefer slices that still import from `@/components/ui` or a local `components/ui` folder.
#### B. Migrate the slice
For each leftover primitive in the slice:
1. Swap imports to `@carsxe/design-system/components/<name>` (package root is allowed).
2. Keep `variant`, `size`, `className`, and native element props. Drop wrapper components.
3. Delete local source copies of covered primitives only when nothing else imports them.
4. Keep app-specific composites (page layouts, feature widgets). Those are not design-system primitives.
#### C. Typecheck / lint
If the app has `typecheck` or `lint` scripts, run them on the changed files (or the package). Fix failures caused by this slice before QA.
#### D. Visual QA
Follow [visual-qa.md](visual-qa.md). Screenshot and interact with every route touched this iteration. Record video when the tool supports it. Fail the iteration on visual regressions (rounded `--radius`, wrong tokens/fonts, broken layout or wiring).
#### E. Fix and re-inventory
Fix QA and type failures, then grep again. Set `HITS_AFTER`.
If `HITS_AFTER >= HITS_BEFORE` and covered leftovers remain, **stop** and report blockers. Do not loop without progress.
If covered leftovers remain and the count dropped, go back to **A**.
### 4. Exit conditions
Stop the loop if **any** of these are true:
- **Done:** all of the following hold:
- Covered inventory hit count is 0
- CSS import is present
- No local source copies remain for covered primitives
- Visual QA passed on every route migrated in this run
- Typecheck/lint of changed files passed, if those scripts exist
- Max iterations reached (report remaining hits)
- Zero-progress iteration (report blockers)
Uncovered UI (charts, maps, widgets, custom composites) must be listed in the report. It does not block “done”.
After a successful inventory of 0, run a **final full-app visual sweep** of remaining user-facing routes (not only the last slice) per `visual-qa.md`. If that sweep fails, treat it as remaining work and continue the loop if iterations remain.
### 5. Report
```
carsxe-migrate-loop complete.
Iterations: N
Inventory: 0 leftover hits
Routes QA'd: N
Screenshots: N
Video: yes/no
```
If not fully migrated:
```
carsxe-migrate-loop stopped after N iterations.
Inventory: M leftover hits
Routes QA'd: N
Screenshots: N
Video: yes/no
Remaining hits:
- src/components/ui/button.tsx — local primitive copy
- src/app/settings/page.tsx — import from @/components/ui/dialog
Uncovered (not blocking):
- src/components/Chart.tsx — third-party chart
```
## Additional resources
- Leftover grep patterns: [inventory.md](inventory.md)
- Screenshot, interaction, and video protocol: [visual-qa.md](visual-qa.md)
inventory.md
markdown
# Leftover UI inventory
Count **covered** hits only. Covered = a primitive that `@carsxe/design-system` ships. Uncovered third-party UI is listed in the report, not counted against done.
## Exclude
Do not search these directories:
```
node_modules
dist
.build
.output
.next
coverage
build
.turbo
.carsxe-migrate-loop
storybook-static
```
Typical ripgrep:
```bash
rg -n --glob '!node_modules/**' --glob '!dist/**' --glob '!.next/**' \
--glob '!.output/**' --glob '!coverage/**' --glob '!build/**' \
--glob '!.turbo/**' --glob '!.carsxe-migrate-loop/**' \
-e '<pattern>'
```
## Covered primitives
These names match `carsxe-design-system/reference.md`:
accordion, alert, alert-dialog, aspect-ratio, attachment, avatar, badge, breadcrumb, bubble, button, button-group, calendar, card, carousel, chart, checkbox, collapsible, combobox, command, context-menu, dialog, direction, drawer, dropdown-menu, empty, field, hover-card, input, input-group, input-otp, item, kbd, label, marker, menubar, message, message-scroller, native-select, navigation-menu, pagination, popover, progress, questionnaire, radio-group, resizable, scroll-area, select, separator, sheet, sidebar, skeleton, slider, sonner, spinner, switch, table, tabs, textarea, toast, toggle, toggle-group, tooltip
`sonner` also appears as `toast` in some shadcn apps. Treat local `sonner.tsx` / `toaster.tsx` that wrap Sonner as covered.
## Patterns to count
### Local shadcn / ui-kit imports
```
from ["']@/components/ui/
from ["']~/components/ui/
from ["']@/components/ui["']
from ["']~/components/ui["']
```
Also count relative imports into a `components/ui/` (or `src/ui/`) folder when the file is a covered primitive, for example:
```
from ["'].*/components/ui/(accordion|alert|alert-dialog|aspect-ratio|attachment|avatar|badge|breadcrumb|bubble|button|button-group|calendar|card|carousel|chart|checkbox|collapsible|combobox|command|context-menu|dialog|direction|drawer|dropdown-menu|empty|field|hover-card|input|input-group|input-otp|item|kbd|label|marker|menubar|message|message-scroller|native-select|navigation-menu|pagination|popover|progress|questionnaire|radio-group|resizable|scroll-area|select|separator|sheet|sidebar|skeleton|slider|sonner|spinner|switch|table|tabs|textarea|toast|toggle|toggle-group|tooltip|toaster)["']
```
### Local source copies
A file is a leftover copy when its path looks like:
```
**/components/ui/<primitive>.tsx
**/components/ui/<primitive>.ts
**/ui/<primitive>.tsx
```
and `<primitive>` is in the covered list (plus `toaster`). Count each such file once, in addition to import hits.
Do **not** count files under `node_modules/@carsxe/design-system`.
### shadcn CLI leftovers aimed at those primitives
Count `shadcn add` / `npx shadcn@latest add` invocations in scripts, README, or comments that add a covered primitive.
A root `components.json` that still aliases `@/components/ui` is a **flag**, not an automatic delete. Note it in the report. Only count it as a hit if the app still resolves covered primitives through that alias.
### Direct headless imports when a DS component exists
Count consumer-app imports of `@radix-ui/*`, `@base-ui/react`, or the direct dependencies used by covered primitives when they duplicate a shipped component.
Do **not** count those imports inside `node_modules`. Do not count app-specific controls whose behavior is materially different from a design-system primitive.
## Do not count (uncovered)
List these in the report under **Uncovered (not blocking)**:
- Maps, editors, and app-specific data grids
- `@carsxe/ui` widgets and edit-mode
- App-specific composites (page shells, feature cards) that compose primitives but are not themselves primitives
- Icon packages (`lucide-react`, and so on)
- `cn()` / `class-variance-authority` utilities in the app, unless they exist only to wrap a covered primitive
## Done check
Covered inventory is 0 when:
1. No import hits for the patterns above
2. No local `components/ui/<primitive>` source files remain for covered primitives
3. No `@radix-ui/*` / `@base-ui/react` hits that duplicate a shipped primitive
After deleting a local primitive, grep once more for that filename so dangling imports are not missed.
visual-qa.md
markdown
# Visual QA
Prove each migrated slice in a real browser. Screenshots are required. Video is required when the tool can record it.
## Tooling
Prefer browser automation the agent already has (Cursor browser / computer-use: navigate, snapshot, screenshot, interact).
If none is available, use Playwright as a **one-off fallback** in this session. Do not add a Playwright CI suite or check in test files unless the app already has them.
Playwright fallback (example):
```bash
npx playwright install chromium
```
Record video and screenshots to `.carsxe-migrate-loop/` (gitignored; do not commit):
```ts
const context = await browser.newContext({
recordVideo: { dir: ".carsxe-migrate-loop/video" },
})
await page.screenshot({
path: `.carsxe-migrate-loop/screenshots/${name}.png`,
fullPage: true,
})
```
If video cannot be recorded, say so in the iteration notes and keep screenshots.
## Dev server
Start or reuse the app’s existing dev command (`dev`, `start`, Vite, Next, TanStack Start). Do not start a second copy on the same port.
Wait until the server is actually serving before opening routes.
## Each iteration
1. Collect every user-facing route touched this slice.
2. Open each route in the browser.
3. If the app has a theme toggle, capture **light** and **`.dark`**.
4. Full-page screenshot of the default state.
5. Screenshot key interactive states that this slice migrated (open dialog, open dropdown, toast visible, tabs switched, select open).
6. Interact with migrated controls: click buttons, type into inputs, open/close overlays. Broken `onClick` / form wiring fails the iteration.
7. Record a short walkthrough video of the slice when recording is available.
Name artifacts:
```
.carsxe-migrate-loop/screenshots/iter-N-<route>-<theme>-<state>.png
.carsxe-migrate-loop/video/iter-N-<route>.webm
```
## Fail the iteration if
- Controls use rounded corners. `--radius` is `0`; corners are sharp. Circular controls (`Switch`, radio, slider thumbs, progress, avatars) may stay `rounded-full`.
- Brand tokens look wrong. Primary should read as `#065774` (teal), not the default shadcn zinc/slate palette.
- Fonts are not the design-system stack: Manrope (UI, body, inputs), DM Sans (headings), DM Mono (code).
- Layout overflow, clipped overlays, or controls that do not open/close.
- Default control height looks compact vs 40px (`h-10`) unless the slice intentionally passed `className` (for example `h-8` on Input).
## Final sweep
When covered inventory hits reach 0, walk remaining user-facing routes (nav links, primary flows), not only the last slice. Screenshot each. Record one walkthrough video of the main flow if video is available.
A failed final sweep is remaining work: fix and continue the loop if iterations remain.
## After QA
Note in the loop report: routes visited, screenshot count, whether video was captured, and any fail reasons. Leave artifacts on disk under `.carsxe-migrate-loop/` for the user; do not paste large binaries into chat.

