| name | frontend-refactoring |
| description | Use to refactor ONE existing bloated frontend module into the canonical frontend-modular-architecture layout IN A SINGLE ATOMIC CONVERSION. Refuses partial conversions โ half-converted modules are worse than untouched. Five strict phases (deep-plan โ review gate โ behavior snapshot โ atomic-execute on feature branch โ diff-verify) where conversion-verifier rolls back the entire commit if any behavior changed. Handles wizards split per-step, sub-sub-components moved into parts/component-name/ folders with own Portal primitives, dedicated Zustand stores extracted from useState soup, drawers/modals/popovers wrapped in shadcn Sheet/Dialog/Popover. Dispatches 4 agents (module-conversion-planner, module-behavior-snapshotter, atomic-module-converter, conversion-verifier). |
Frontend Refactoring โ atomic one-module conversion
The hardest problem in frontend AI work: converting a fat existing module to a sane structure WITHOUT breaking it. This skill solves it by enforcing one rule above all others:
The Iron Law
ONE MODULE PER DISPATCH.
ONE COMMIT PER CONVERSION.
ZERO BEHAVIOR CHANGE.
IF CONVERSION-VERIFIER FINDS DRIFT, THE WHOLE COMMIT REVERTS.
There is no "we'll finish the rest tomorrow" โ a half-converted module
breaks every import, every test, every running dev server. That state
MUST NEVER LAND ON A SHARED BRANCH.
When to use
- โ
A module's structure was flagged by
module-structure-auditor and needs decomposition
- โ
Before running
prototype-to-saas on a prototype with fat files (Phase B.5)
- โ
User says "this file is too big" / "this wizard is unmaintainable"
- โ
A 5+ step wizard lives in one file
- โ
A page exceeds 100 lines OR a component exceeds 200 lines
When NOT to use
- โ Multiple modules at once (one conversion per dispatch, period)
- โ Refactoring that intentionally changes behavior (use
frontend-module-builder + delete old code)
- โ Renames-only / formatting-only changes (run codemods directly)
- โ Cross-module changes (each module gets its own conversion dispatch)
How the orchestrator should use this skill
- After
module-structure-auditor returns P1 findings on a module โ dispatch frontend-refactoring for that one module
- In
prototype-to-saas pipeline โ run as Phase B.5 between discovery and rewiring. The prototype's UI must be decomposed before frontend-rewirer touches anything (rewiring a fat file just preserves the antipattern with new data wiring)
- User explicitly asks for a refactor โ dispatch directly
The 5 strict phases
โโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ PHASE 1 โ DEEP PLAN โโโโโถโ PHASE 2 โ REVIEW GATE โโโโโถโ PHASE 3 โ SNAPSHOT โ
โ module-conversion- โ โ User confirms plan โ โ module-behavior- โ
โ planner โ โ Optional 3-teammate โ โ snapshotter โ
โ โ โ completeness review โ โ โ
โ EXHAUSTIVE plan: โ โ โ โ Playwright records ALL โ
โ every new file (full โ โ REJECT if plan has any โ โ behavior: every route, โ
โ path), every split, โ โ "we'll figure step N โ โ every click, every โ
โ every store, every โ โ out later" โ vague โ โ drawer/modal/popover โ
โ Portal, every import โ โ plans are forbidden โ โ open/close, every form โ
โ update โ โ โ โ submit โ
โโโโโโโโโโฌโโโโโโโโโโโโโโโโโ โโโโโโโโโโฌโโโโโโโโโโโโโโโโโ โโโโโโโโโโฌโโโโโโโโโโโโโโโโโ
โผ โผ โผ
CONVERSION_PLAN.md approved-plan.md baseline/<feature>/
(screenshots + DOM +
HARs + console logs)
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ PHASE 4 โ ATOMIC EXECUTE โ
โ atomic-module-converter โ
โ โ
โ Single commit on feature branch. โ
โ ALL moves + splits + imports + โ
โ store extractions + Portal wraps โ
โ in ONE pass. No partial state. โ
โ โ
โ Branch: refactor/<feature>-decomposeโ
โ Commit msg: refers to CONVERSION_PLANโ
โโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ PHASE 5 โ VERIFY (no-rollback gate) โ
โ conversion-verifier + portal-correctness-auditor โ
โ โ
โ Re-run Playwright snapshot. Diff every captured โ
โ behavior against baseline. ZERO drift required. โ
โ โ
โ Also runs portal-correctness-auditor to confirm โ
โ extracted drawers/modals use shadcn Portal โ
โ primitives. โ
โ โ
โ Any drift โ git reset --hard, re-plan, restart. โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Phase 1 โ Deep plan (module-conversion-planner)
The planner reads the entire module top to bottom and produces CONVERSION_PLAN.md. The plan is EXHAUSTIVE โ every file the converter will create, move, edit, or delete, with absolute paths.
Plan format (mandatory)
# Conversion plan โ companies module โ <commit hash>
## Source inventory
- apps/web/src/modules/companies/companies.tsx (1,247 lines, 38 useState, 12 useMemo, contains 8-step create wizard inline)
- apps/web/src/modules/companies/companies-detail.tsx (612 lines)
- apps/web/src/modules/companies/api.ts (clean)
- (no stores/, no hooks/, no parts/ โ full restructure required)
## Target structure (every file listed)
### New files to create
- apps/web/src/modules/companies/pages/list-page.tsx (โค 100 lines)
- apps/web/src/modules/companies/pages/detail-page.tsx
- apps/web/src/modules/companies/pages/new-page.tsx
- apps/web/src/modules/companies/components/companies-table/index.tsx (โค 200 lines)
- apps/web/src/modules/companies/components/companies-table/columns.tsx
- apps/web/src/modules/companies/components/companies-table/row-actions.tsx
- apps/web/src/modules/companies/components/companies-table/filters.tsx
- apps/web/src/modules/companies/components/companies-table/parts/delete-confirm-dialog/index.tsx
- apps/web/src/modules/companies/components/companies-table/parts/bulk-edit-drawer/index.tsx
- apps/web/src/modules/companies/components/companies-table/parts/bulk-edit-drawer/form.tsx
- apps/web/src/modules/companies/components/companies-table/parts/bulk-edit-drawer/footer.tsx
- apps/web/src/modules/companies/components/create-wizard/index.tsx (orchestrator)
- apps/web/src/modules/companies/components/create-wizard/step-1-basics.tsx
- apps/web/src/modules/companies/components/create-wizard/step-2-contacts.tsx
- apps/web/src/modules/companies/components/create-wizard/step-3-billing.tsx
- apps/web/src/modules/companies/components/create-wizard/step-4-team.tsx
- apps/web/src/modules/companies/components/create-wizard/step-5-integrations.tsx
- apps/web/src/modules/companies/components/create-wizard/step-6-onboarding.tsx
apps/web/src/modules/companies/components/create-wizard/step-7-billing-method.tsx
apps/web/src/modules/companies/components/create-wizard/step-8-confirm.tsx
apps/web/src/modules/companies/components/create-wizard/shared/nav-buttons.tsx
apps/web/src/modules/companies/components/create-wizard/shared/progress-indicator.tsx
apps/web/src/modules/companies/stores/companies-store.ts
apps/web/src/modules/companies/stores/companies-ui-store.ts
apps/web/src/modules/companies/stores/companies-wizard-store.ts
apps/web/src/modules/companies/hooks/use-companies.ts
apps/web/src/modules/companies/hooks/use-create-company.ts
apps/web/src/modules/companies/hooks/use-update-company.ts
apps/web/src/modules/companies/hooks/use-delete-company.ts
apps/web/src/modules/companies/hooks/use-company-form.ts
apps/web/src/modules/companies/companies.tsx
apps/web/src/modules/companies/companies-detail.tsx
| Source line | Source state | Target store + property |
|---|---|---|
| companies.tsx:14 | useState([]) for selectedIds | companies-store.ts โ selectedIds + toggleSelected + clearSelection |
| companies.tsx:15 | useState('') for searchQuery | companies-store.ts โ search + setSearch |
| companies.tsx:23 | useState({}) for filters | companies-store.ts โ industryFilter + statusFilter + setFilters |
| companies.tsx:48 | useState(false) for bulkDrawerOpen | companies-ui-store.ts โ bulkDrawerOpen + openBulkDrawer + closeBulkDrawer |
| companies.tsx:67 | useState(null) for deleteConfirmFor | companies-ui-store.ts โ deleteConfirmFor + askDeleteConfirm + dismissDeleteConfirm |
| companies.tsx:124 | useState(1) for wizardStep | companies-wizard-store.ts โ step + next + prev |
| companies.tsx:125-162 | useState(...) ร 18 for wizard draft fields | companies-wizard-store.ts โ draft + patch
| (full list โ every useState accounted for) |
| Source location | Current pattern | Target |
|---|---|---|
| companies.tsx:540 | (bulk drawer) | components/companies-table/parts/bulk-edit-drawer/index.tsx using |
| companies.tsx:720 | (delete modal) | components/companies-table/parts/delete-confirm-dialog/index.tsx using |
| companies.tsx:880 | (column picker) | components/companies-table/parts/column-customizer-popover/index.tsx using |
| File | Current import | New import |
|---|---|---|
| apps/web/src/app/companies/page.tsx:1 | | |
| apps/web/src/app/companies/[id]/page.tsx:1 | | |
| apps/web/src/app/companies/new/page.tsx:1 | (used the wizard inline export) | |
| Source lines | New hook | Notes |
|---|---|---|
| companies.tsx:36-44 (TanStack Query useQuery for list) | hooks/use-companies.ts | Wraps the existing fetcher |
| companies.tsx:78-94 (useMutation for create) | hooks/use-create-company.ts | Includes invalidate ['companies'] |
| companies.tsx:200-232 (useForm + zodResolver) | hooks/use-company-form.ts | Re-export schema from contracts |
All 8 wizard steps preserve their current form fields, validation rules, submit behavior
Bulk drawer's multi-select count display unchanged
Delete dialog's destructive-action button color unchanged
Column customizer's checkbox interactions unchanged
Keyboard shortcuts (escape closes drawers) unchanged
URL params for filters unchanged
Wizard step 6 (onboarding) has a side-effect that calls 3rd-party SDK on submit โ must preserve via use-create-company hook
Bulk drawer uses a custom hook locally โ Sheet handles click-outside natively, so we drop the custom hook. Verify behavior unchanged.
Delete dialog has a 300ms delay before destruction โ preserve via mutation's onSuccess setTimeout
Create stores/ (3 files)
Create hooks/ (5 files)
Create pages/ (3 files) โ referencing components that don't exist yet (TS will fail; that's expected mid-commit)
Create components/companies-table/ (4 files + parts/)
Create components/create-wizard/ (orchestrator + 8 steps + shared)
Update apps/web/src/app/companies/
The plan is the contract. The converter executes EXACTLY what the plan says, in order. Anything the plan doesn't list MUST NOT happen.
Refuse-to-run conditions for the planner
- โ Plan contains "TBD" / "we'll figure out later" / "approximately"
- โ Plan has fewer files listed than source useState count requires (e.g., 38 useState mapped to a single store of 4 properties is wrong)
- โ Plan doesn't enumerate every Portal extraction
- โ Plan doesn't list import updates for every consumer of the old files
- โ Plan's atomic-execute order doesn't end with a typecheck step
Phase 2 โ Review gate
The user reads the plan. If high-stakes, dispatch a 3-teammate review:
Dispatch 3-teammate plan-completeness review.
Teammate A โ pessimist: hunts for what the plan FORGOT (any missing file? any orphan import?)
Teammate B โ pragmatist: questions whether each split is actually needed
Teammate C โ surgeon: validates the atomic-execute order โ would this typecheck mid-commit?
Majority verdict. Plan only proceeds if all three approve.
User then types "approve" or "revise" with specific feedback for the planner to incorporate.
Phase 3 โ Behavior snapshot (module-behavior-snapshotter)
Before the converter touches a single file, snapshot CURRENT behavior with Playwright. Every route, every interaction, every drawer/modal/popover open/close, every form submit, every keyboard shortcut.
Output: baseline/<feature>/ directory with:
- Screenshots per route ร viewport
- DOM snapshots per route (HTML structure)
- Network HARs per user flow
- Console logs per route
- Interaction trace (sequence of clicks + resulting state)
This baseline IS the source of truth for Phase 5's diff.
Phase 4 โ Atomic execute (atomic-module-converter)
Creates a feature branch refactor/<feature>-decompose and executes the plan EXACTLY in order. One commit containing ALL file moves, splits, imports, deletions.
The commit must:
- Be a single git commit (no intermediate "wip" commits)
- Pass typecheck before being created
- Reference
CONVERSION_PLAN.md in the commit body
- Touch ONLY files listed in the plan
The commit must NOT:
- Land on
main (always on a feature branch)
- Include unrelated changes ("while I'm here let me fix this lint warning")
- Touch other modules' files (cross-module changes are out of scope)
Phase 5 โ Verify (conversion-verifier)
Re-runs the same Playwright snapshot from Phase 3, this time against the converted module. Diffs everything against baseline.
| Check | Pass criteria |
|---|
| Per-route screenshot pixel-diff | โค 0.5% drift (tighter than design-fidelity-auditor's 1% because this is supposed to be byte-identical) |
| DOM structure | Identical text content; tag names may differ ONLY where a Portal was introduced (raw div โ portaled Sheet) |
| Network HAR | Identical request URLs, methods, request bodies (response handling unchanged) |
| Console logs | No new errors / warnings introduced |
| Interaction trace | Same click โ same state transition |
Plus, portal-correctness-auditor runs on the converted module โ every drawer/modal/popover must now be Portal-correct.
Rollback
If verifier returns REJECT:
git reset --hard <pre-conversion-sha>
git branch -D refactor/<feature>-decompose
The plan re-opens. Phase 1 re-runs with the verifier's findings as input ("on attempt 1, drift was X โ adjust plan to preserve Y behavior"). Phase 4 re-executes.
There's no incremental "fix the drift, keep some changes" path. Half-state is the antipattern this skill exists to prevent.
Agent teams (optional โ plan-completeness review)
For complex modules (1000+ line source, 5+ wizard steps, 3+ drawer/modal/popover extractions), the 3-teammate review in Phase 2 dramatically improves outcomes. Requires CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1.
Reference files