I used to default to “rewrite the whole thing” whenever an agent touched a React component. The diff looked clean. The review felt like reading a new file. Then I noticed reviews were taking longer, not shorter — because I had to re-verify every prop, every effect, every edge case the old file already got right.
The opposite habit is just as bad: endless surgical patches that leave the component a graveyard of half-migrated patterns. Somewhere between those two sits a decision I now make before I paste the prompt.
Rewrite vs patch isn’t a vibe. It’s a blast-radius call.
The two asks (say them out loud)
Patch ask: “Change only X. Keep exports, prop names, and surrounding structure. Diff should be reviewable in under five minutes.”
Rewrite ask: “Replace this component with a new implementation that meets these acceptance checks. You may rename internals. Keep the public API unless I list a breaking change.”
If I can’t pick one sentence, I don’t start the agent yet. Ambiguous prompts produce hybrid diffs — new structure with old bugs glued on.
When a patch wins
I ask for a patch when:
- The public API is fine — same props, same events, same slots; only behavior or markup inside needs a tweak
- Blast radius is one file (or one folder) — no shared hook, no design-token file, no parent layout rewrite
-
The bug or feature is localized — empty-state copy, a missing
aria-*, a wrong dependency array, one loading branch - Reviewability matters more than elegance — I need a human to verify the delta in a standup, not re-learn the component
- Tests already pin the contract — existing unit/Playwright cases should still pass with minimal fixture churn
Concrete patch jobs that work well with agents:
- Add a disabled state to a Button wrapper without touching the design-system Button
- Fix a stale closure in a
useEffectthat refetches on the wrong key - Wire an existing Spinner into a known loading branch
- Swap a hardcoded string for an i18n key the screen already uses
- Tighten a focus trap / Escape handler without redesigning the Modal
For these, I paste the file (or path), name the one outcome, and fence the rest: “Do not rename exports. Do not refactor unrelated hooks. Do not ‘clean up’ imports for fun.”
When a rewrite wins
I ask for a rewrite when:
- The shape is wrong — prop soup, god-component, three competing state machines in one file
- You’re changing the mental model — uncontrolled → controlled, local state → URL state, custom store → React Query
- Patching would fight the grain — every surgical fix needs another surgical fix next week
- You have acceptance checks ready — a11y report shape, storybook states, Playwright flows you can point at
- You’re okay reviewing a new file — timeboxed, with a checklist, not “LGTM from vibes”
Concrete rewrite jobs:
- Replace a 400-line Settings form that mixes fetch, validation, and layout with a thinner container + field components
- Migrate a class-era lifecycle tangle to hooks with an explicit data-flow diagram in the prompt
- Collapse three near-duplicate ListEmpty / TableEmpty / CardEmpty into one composable empty state
- Rebuild a dropdown that never got keyboard support so native semantics (or a known headless primitive) own focus
Rewrites still need fences. “Rewrite” is not permission to edit tokens, shared Button, or every importer. I still say: public API stays unless listed; touch only these paths; list open questions instead of inventing product behavior.
The decision checklist I actually use
Before the agent runs, I score the job:
| Question | Patch | Rewrite |
|---|---|---|
| Is the public API staying? | Yes | Maybe / no |
| Can a reviewer verify in <5 min? | Must | Nice if split |
| Are existing tests still the source of truth? | Yes | Tests may need a parallel update |
| Is the pain “one bug” or “wrong abstraction”? | Bug | Abstraction |
| Would a patch leave mixed patterns in the file? | Avoid | Prefer clean cut |
If three or more answers lean rewrite, I rewrite. If I’m unsure, I start with a patch prompt. Worst case I discard and escalate to rewrite. Starting with rewrite and then trying to “minimize the diff” usually wastes the turn.
Reviewability and PR size
Agents love large, coherent-looking files. Reviewers love small, intentional diffs. I align them like this:
- Patch PRs stay under ~100 lines when possible. One concern. Title names the behavior, not the file.
- Rewrite PRs can be larger, but I split when I can: (1) extract pure helpers / types with no behavior change, (2) swap the component, (3) delete dead paths. Three reviewable steps beat one 800-line “migration.”
- Never mix a rewrite with a drive-by token rename or eslint config tweak. That’s how rewrites become unreviewable.
I also ask the agent for a short “what stayed the same” bullet list at the top of the PR body. For patches: “Exports unchanged; only onSubmit error path edited.” For rewrites: “Props value / onChange / disabled preserved; internal state machine replaced.”
Blast radius is the real metric
Lines changed are a weak proxy. I care about:
- Call sites — did the public API move?
-
Shared modules — did tokens, theme, or
components/uimove? - Runtime surface — new effects, new network calls, new focus behavior
- Test surface — how many fixtures must change for green to mean something
A 20-line patch that edits a shared Modal default prop can out-blast a 200-line rewrite of a leaf screen. So the prompt names the ownership boundary every time, rewrite or patch.
Prompts I reuse
Patch skeleton:
Task: surgical patch only.
File(s): <paths>
Change: <one behavior>
Do not: rename exports, move files, edit tokens/, edit shared ui primitives,
refactor unrelated hooks, add deps.
Output: diff + 3-bullet “unchanged contract” summary.
Done when: <acceptance> and existing tests still apply.
Rewrite skeleton:
Task: replace implementation; keep public API unless listed.
File(s): <paths>
Public API to preserve: <props / events / slots>
Allowed to change: internals, local hooks, local subcomponents under this folder
Do not: edit tokens/, theme/, shared components/ui/, unrelated routes
Acceptance: <states / a11y / tests>
If blocked: ask — do not invent product behavior.
Same models. Different blast-radius contracts.
What I stopped doing
- “Just make it better” — produces rewrite energy with patch review budget
- Accepting a rewrite because the agent “already did it” — sunk-cost is not a merge reason
- Patching a component I’ve patched three times this month — that’s a rewrite signal I was ignoring
- Reviewing AI rewrites without a checklist — I miss the quiet API drift every time
Takeaway
Default to a patch when the contract is good and the pain is local. Escalate to a rewrite when the abstraction is the bug — and only with fences, acceptance checks, and a PR shape a human can finish reviewing.
Say the mode out loud before you prompt. The agent will follow the contract you give it. The expensive part isn’t the tokens. It’s the review you scheduled for “a small fix” that quietly became a new component.
Top comments (0)