Shadcn-Only Migration Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Enforce components/ui/ = shadcn/ui primitives only. Move 49 misplaced components to their proper directories and fix all 134 importing files.
Architecture: Migrate components in category batches. Each batch: (1) move files to target directory, (2) update all imports, (3) verify build passes, (4) commit. This isolates breaks and provides rollback points.
Tech Stack: Next.js 16, TypeScript, Tailwind CSS
Background
The components/ui/ directory contains 119 files. The shadcn-only rule (components/ui/.claude/rules/shadcn-only.md) allows only:
- shadcn/ui base components (
button.tsx,dialog.tsx,input.tsx, etc.) - shadcn derivatives that remain UI primitives
icon-symbol.tsx(icon exception)
Current violations: ~49 components violate this rule by being custom page/section components, feature components, or composited UI that should live in domain directories.
Duplicate crisis: Some components exist in BOTH ui/ AND their correct domain directory with DIFFERENT implementations (different sizes). This means consumers may be using the WRONG implementation.
Phase 0: Pre-Migration Audit
Before touching anything, verify build is clean and document current state.
- Step 1: Verify current build passes
cd apps/frontend && bun run build 2>&1 | tail -20Expected: Clean build with no errors.
- Step 2: Document all ui/ components and their import counts
cd apps/frontend
echo "=== ALL ui/ files with import counts ==="
for f in components/ui/*.tsx; do
name=$(basename "$f" .tsx)
count=$(grep -r "from \"~/components/ui/$name\"" --include="*.tsx" --include="*.ts" -l 2>/dev/null | wc -l | tr -d ' ')
echo "$name: $count imports"
done | sort -t: -k2 -rnSave output to docs/superpowers/plans/2026-05-11-shadcn-only-migration-AUDIT.md.
- Step 3: Identify true duplicates (same name in ui/ AND domain dirs)
cd apps/frontend
for f in components/ui/*.tsx; do
name=$(basename "$f")
for dir in marketing home booking forms layout experiences; do
if [ -f "components/$dir/$name" ]; then
echo "DUPLICATE: $name in ui/ AND $dir/"
echo " ui/: $(wc -c < components/ui/$name)"
echo " $dir/: $(wc -c < components/$dir/$name)"
fi
done
done- Step 4: Commit audit results
git add docs/superpowers/plans/2026-05-11-shadcn-only-migration-AUDIT.md
git commit -m "docs: audit shadcn-only violations before migration"Phase 1: Delete Duplicates (Remove Wrong Implementations)
Where a component exists in both ui/ and its correct domain, keep the domain version and delete the ui/ version.
Task 1: Handle page-hero duplicates
Files:
-
Delete:
components/ui/page-hero.tsx(violation + duplicate) -
Already exists:
components/marketing/page-hero.tsx -
Step 1: Verify files are identical
diff components/ui/page-hero.tsx components/marketing/page-hero.tsx && echo "IDENTICAL - safe to delete ui/ version"- Step 2: Delete ui/ version
rm components/ui/page-hero.tsx- Step 3: Verify no imports break
grep -r "from \"~/components/ui/page-hero\"" --include="*.tsx" --include="*.ts" -lExpected: should show our-evening/page.tsx (already fixed earlier).
- Step 4: Verify build
cd apps/frontend && bun run build 2>&1 | tail -10- Step 5: Commit
git add -A
git commit -m "fix: remove duplicate page-hero from ui/ (already exists in marketing/)"Task 2: Handle experience-card duplicates
Files:
-
components/ui/experience-card.tsx(10526 bytes - likely bloated wrong version) -
components/marketing/experience-card.tsx(1122 bytes - correct marketing version) -
Step 1: Compare implementations
head -50 components/ui/experience-card.tsx
echo "---"
head -50 components/marketing/experience-card.tsx-
Step 2: Identify which is the correct implementation Check which one matches the design system. The marketing/ version is 10x smaller - likely the correct abstracted version.
-
Step 3: Update imports to point to correct location If ui/ version is wrong:
# Find all imports of the WRONG implementation
grep -r "from \"~/components/ui/experience-card\"" --include="*.tsx" -lThen update each to ~/components/marketing/experience-card.
- Step 4: Delete ui/ version
rm components/ui/experience-card.tsx-
Step 5: Verify build
-
Step 6: Commit
Task 3: Handle other duplicates (contact-info-section, gallery-section, experience-option)
Same pattern as Task 2.
Repeat for each:
- Compare implementations
- Identify correct version (usually the domain dir version is correct)
- Update imports to point to correct domain dir
- Delete ui/ version
- Verify build
- Commit
Phase 2: Move Section/Showcase Components to components/marketing/sections/
Target: components/marketing/sections/ (create if not exists)
Components to move (22):
content-section.tsxcorner-accent-overlay.tsxcorner-accents.tsxform-section.tsxform-step-wrapper.tsxframed-image.tsxgallery-section.tsxgoogle-reviews-link.tsxhighlights-section.tsxicon-cards-grid.tsxicon-grid.tsximage-card-grid.tsximage-frame.tsxrecommended-by-section.tsxsection-container.tsxsection-divider.tsxsection-nav.tsxsection-title.tsxstep-nav-footer.tsxtimeline-section.tsxupcoming-shows-section.tsxvideo-frame.tsx
Task 4: Create sections/ directory
- Step 1: Create directory
mkdir -p components/marketing/sections- Step 2: Move components
cd components/ui
mv content-section.tsx corner-accent-overlay.tsx corner-accents.tsx form-section.tsx form-step-wrapper.tsx framed-image.tsx gallery-section.tsx google-reviews-link.tsx highlights-section.tsx icon-cards-grid.tsx icon-grid.tsx image-card-grid.tsx image-frame.tsx recommended-by-section.tsx section-container.tsx section-divider.tsx section-nav.tsx section-title.tsx step-nav-footer.tsx timeline-section.tsx upcoming-shows-section.tsx video-frame.tsx ../marketing/sections/- Step 3: Update ALL imports in 134 files
# Find all files importing from ui/ that should now use marketing/sections/
# This will be a large sed operation
cd ../..
find . -name "*.tsx" -o -name "*.ts" | xargs grep -l "~/components/ui/content-section" | head -20For each component, run:
find . -name "*.tsx" -o -name "*.ts" | xargs sed -i '' 's|~/components/ui/COMPONENT_NAME|~/components/marketing/sections/COMPONENT_NAME|g'IMPORTANT: Run these sed commands one component at a time, then verify build before proceeding.
- Step 4: Verify build after each component batch
bun run build 2>&1 | grep -E "error|Error|FAILED" | head -20- Step 5: Commit after full batch passes
git add -A
git commit -m "refactor: move section/showcase components from ui/ to marketing/sections/"Phase 3: Move Experience/Card Components
Target directory depends on component:
components/experiences/for experience-specific cardscomponents/booking/for booking-specific cardscomponents/marketing/for marketing cards
Components to move (12):
booking-info-row.tsx→components/booking/booking-modal.tsx→components/booking/cinema-ticket-header.tsx→components/booking/orcomponents/confirmation/experience-card.tsx→components/marketing/(already duplicate handled in Phase 1)experience-option.tsx→components/booking/(already duplicate handled in Phase 1)glass-card.tsx→components/marketing/icon-box.tsx→ needs review (could be generic primitive)journey-card.tsx→components/experiences/numbered-card.tsx→components/marketing/premium-card.tsx→components/marketing/service-card.tsx→components/marketing/why-card.tsx→components/marketing/
Task 5: Move booking components
- Move
booking-info-row.tsx,booking-modal.tsx,cinema-ticket-header.tsxtocomponents/booking/ - Update imports
- Verify build
- Commit
Task 6: Move experience components
- Move
journey-card.tsxtocomponents/experiences/ - Update imports
- Verify build
- Commit
Task 7: Move marketing cards
- Move
glass-card.tsx,numbered-card.tsx,premium-card.tsx,service-card.tsx,why-card.tsxtocomponents/marketing/ - Update imports
- Verify build
- Commit
Task 8: Review icon-box (borderline)
- Analyze
icon-box.tsxusage - If generic primitive → keep in ui/ but document why
- If domain-specific → move to appropriate directory
Phase 4: Move Info/Contact Components to components/marketing/
Components to move (7):
contact-info-block.tsx→components/marketing/contact-info-section.tsx→components/marketing/(already duplicate handled in Phase 1)design-system-nav.tsx→components/admin/orcomponents/layout/info-item.tsx→components/marketing/or keep in ui/ if genericoverline.tsx→ keep in ui/ if generic typographyrecommendation-card.tsx→components/marketing/review-card.tsx→components/marketing/
Task 9: Move info/contact components
- Move each component to appropriate directory
- Update imports
- Verify build
- Commit
Phase 5: Move Form Components to components/forms/
Components to move (6):
auto-resize-textarea.tsxfield.tsxform-button.tsxform-field.tsxform.tsxnumber-field.tsx
Task 10: Move form components
- Move all 6 to
components/forms/ - Update imports
- Verify build
- Commit
Phase 6: Move Layout/Nav Components to components/layout/
Components to move (2):
locale-link.tsxsocial-icons.tsx
Task 11: Move layout components
- Move to
components/layout/ - Update imports
- Verify build
- Commit
Phase 7: Review "Potentially OK" Components
These need manual review to determine if they belong in ui/ or should be moved:
| Component | Question |
|---|---|
hover-card.tsx | Is this a shadcn derivative or custom? |
info-row.tsx | Generic or domain-specific? |
info-tooltip.tsx | Generic or domain-specific? |
menu-book-modal.tsx | Booking-specific → components/booking/? |
modal-close-button.tsx | Generic primitive or domain? |
security-badge.tsx | Booking/trust-specific? |
social-proof.tsx | Marketing-specific? |
spinner.tsx | Generic loading indicator (OK in ui/) |
status-badge.tsx | Generic or domain-specific? |
Task 12: Review each borderline component
For each:
- Read the component source
- Determine if it's a true shadcn primitive (generic, reusable)
- If domain-specific → move to appropriate directory
- If generic → document why it stays in ui/
Phase 8: Final Verification
- Step 1: Run full build
cd apps/frontend && bun run build 2>&1 | tail -30- Step 2: Verify no imports from misplaced components
# Should return empty after all migrations
grep -r "from \"~/components/ui/(content-section|timeline-section|highlights-section|journey-card|experience-card|form-field|locale-link)\"" --include="*.tsx" components/- Step 3: Verify shadcn-only rule enforcement
echo "Remaining non-shadcn in ui/:"
ls components/ui/*.tsx | while read f; do
name=$(basename "$f" .tsx)
# Check if it's a known shadcn component
case $name in
accordion|badge|button|calendar|card|carousel|chart|checkbox|collapsible|command|confirm-dialog|dialog|dropdown-menu|input|input-otp|label|menubar|navigation-menu|popover|progress|radio-group|resizable|select|separator|sheet|skeleton|slider|sonner|switch|table|tabs|textarea|toggle|toggle-group|tooltip|icon-symbol)
# Known shadcn - OK
;;
*)
echo "REVIEW: $name"
;;
esac
done- Step 4: Final commit
git add -A
git commit -m "refactor: complete shadcn-only enforcement for components/ui/"Rollback Plan
If build breaks during migration:
# Find last good commit
git log --oneline -10
# Rollback to last good state
git reset --hard <last-good-commit-sha>
# Then restart migration from next batchNever use git push --force on main branch.
Files Created/Modified Summary
| Phase | Files Created | Files Deleted | Files Modified |
|---|---|---|---|
| Phase 0: Audit | ...-AUDIT.md | 0 | 0 |
| Phase 1: Duplicates | 0 | 6 | ~20 |
| Phase 2: Sections | 0 | 22 | ~80 |
| Phase 3: Cards | 0 | 12 | ~40 |
| Phase 4: Info/Contact | 0 | 7 | ~20 |
| Phase 5: Forms | 0 | 6 | ~15 |
| Phase 6: Layout | 0 | 2 | ~10 |
| Phase 7: Review | TBD | TBD | TBD |
| Total | 1 | 49 | 134 |
Self-Review Checklist
Before marking plan complete:
- All 49 misplaced components are accounted for
- All 134 importing files will be updated
- Each phase has rollback capability (commit after each)
- Build verification steps included in each phase
- No placeholder/TODO content in plan
- File paths are exact (no wildcards in commands)