Prop-drilling, ad-hoc fetch effects, and God-context files are ideal AI refactor targets — mechanical, wide, and boring. But state refactors break apps silently. So AI first maps who reads and writes what, tests pin today's behavior, and only then does the migration proceed, one slice at a time.
Tier: Core · Workflow area: Component development · Tools: Claude Code, TanStack Query, Zustand / Redux Toolkit
The trap in AI-assisted refactoring is speed without a safety net: the model happily rewrites forty files in one pass, everything compiles, and a week later someone finds the cart badge no longer updates. Characterization tests — tests that pin current behavior, bugs and all — turn "it compiles" into "it still behaves."
useEffect + fetch + local loading flags copy-pasted across components, no caching, duplicate requests.Have AI produce the state map — read-only, with citations. Before touching anything:
Read-only task, modify nothing. For the checkout feature
(src/features/checkout/), map the state and data layer:
1. Every piece of client state: where it lives, every reader, every
writer — file:line for each.
2. Every server interaction: component, trigger, caching behavior
(or none), duplicate-fetch risks.
3. Prop-drilling chains 3+ levels deep, as component paths.
4. Rank the 5 riskiest spots to refactor and say why.
Output as a table I can paste into the PR description.
Spot-check three citations by opening them. A wrong map means a wrong refactor — do not skip the audit (same discipline as any comprehension pass).
Pin current behavior with characterization tests. These assert what the app does today, including its quirks:
Write characterization tests for the checkout flow pinning CURRENT
behavior — do not fix anything you find:
- add item -> cart badge count and subtotal update
- shipping option change -> total recalculates (pin the rounding!)
- refresh mid-checkout -> which state survives, which resets
- two tabs -> document the current (broken?) sync behavior as a test
with a TODO comment, not a fix
Use RTL + MSW with our existing handlers. These tests must pass on
main BEFORE any refactor commit.
npx vitest run src/features/checkout --reporter=verbose
Good output asserts through user-visible surfaces (roles, text) not implementation internals — the tests must survive the refactor unchanged; that is their entire point.
Design the target shape yourself; let AI critique it. You decide: server state → TanStack Query; client/UI state → scoped Zustand stores (or RTK slices); truly-local state stays in components. Write it as a half-page plan, then:
Here is my target architecture (attached). Against the state map,
list: state I've misclassified (server vs client vs local), places
where optimistic updates will fight query invalidation, and any
reader/writer the plan orphans.
Migrate slice by slice, tests green between every slice. One commit per slice, never "big bang":
Slice 1: migrate cart server-state to TanStack Query. Create
useCartQuery / useAddItemMutation (optimistic, rollback on error) in
src/features/checkout/api.ts, delete the useEffect fetches in
CartPanel and MiniCart, remove the drilled cartItems/setCartItems
props. Do NOT touch shipping or payment state. Characterization
tests must pass unmodified: run npx vitest run src/features/checkout
and show the output.
The loop for each slice: agent migrates → runs the pinned tests → all green → commit → next slice. If a test goes red, the agent explains the diff instead of editing the test:
git add -p && git commit -m "checkout: cart server-state -> TanStack Query (slice 1/4)"
Review the semantic risk points by hand. Compilers and pinned tests miss four things — check them personally: stale-closure captures in moved callbacks, changed re-render scope (profile a keystroke in React DevTools Profiler before/after), cache lifetime semantics (staleTime/gcTime vs. the old always-refetch behavior — deliberate, not default), and event ordering around optimistic updates.
Only now, fix the quirks you pinned. The TODO-tagged tests from step 2 (the broken tab sync, the rounding oddity) become deliberate behavior changes in their own PR, with the test updated to the new intended behavior — visible, reviewed, and attributable.
In a refactor, a failing characterization test is information and an edited characterization test is a cover-up. The agent may never "fix" a pinned test to make its refactor pass — that rule, stated in the prompt and enforced in review, is what makes AI-scale refactoring safe.
createApi replacing TanStack Query.@ngrx/signals (or NgRx) for client state, services with RxJS or TanStack Query Angular for server state; pin behavior with component-harness tests before converting subject-soup services.git log reads as a sequence of reversible steps, any of which can be reverted alone.Map first, pin behavior second, migrate in slices third — with the iron rule that the agent never edits a characterization test to go green. That sequencing is what lets you refactor at AI speed without discovering the breakage in production.