Skip to content
Back to all field notes vibe · 7 min

Foundations under the house of cards: fixing a vibe-coded app without a rewrite

Everyone tells you to rewrite it properly. You don't have to. You can pour foundations under a running app, one load-bearing piece at a time.

field note 7 min

The app works, but it feels like a house of cards. You know that if you pull one card, something across the room falls over, so you have stopped pulling cards. Every change is done holding your breath. Everyone’s advice is the same: rewrite it properly, do it right this time.

You do not have to, and mostly you should not. A rewrite turns off the one thing you have that works to chase structure you can add without turning anything off. The alternative is quieter and far more effective: pour foundations under the house while it stands, one load-bearing piece at a time, so that with each piece the whole thing gets steadier instead of more fragile.

Why the app feels like cards

The fragility is not random and it is not because the code is “bad”. It is missing three specific things, and each missing piece is a source of the dread:

  • No written contract. Nobody wrote down what the app is supposed to do, so nothing can tell you when a change violated it. “Correct” lives only in your memory of clicking around.
  • No safety net. Nothing catches a break automatically. You find out things are broken the way your users do, by using the app.
  • No boundaries. Features reach directly into each other’s guts, so a change in one place surfaces somewhere unrelated. This is why pulling one card drops another.

You cannot fix all three at once, and the order you fix them in is the whole game. Do it wrong and you make the house shakier while trying to brace it.

Why rewriting is the expensive mistake

A rewrite is tempting because a blank file feels safer than a tangle you do not understand. It is a trap for three reasons.

You throw away working behavior. The messy code encodes months of small corrections, edge cases you hit and fixed, things that work for reasons you have forgotten. A rewrite discards all of it and you rediscover those edge cases one production incident at a time.

You rebuild the same gaps. If you rewrite the same way you built, by vibe, you get a new house of cards. If you rewrite carefully, you are doing months of work to reach a place you could have reached in weeks by reinforcing what you have.

And the app is off the whole time, or you are maintaining two versions at once, which is worse. Meanwhile the reinforcement approach never takes the app offline for a second.

The order that actually works

Reinforcement is safe only in one sequence. Each step is what makes the next one safe to do.

1. Write the spec that was never written

Before you touch anything, write down what the app already does, from the outside, in the present tense. Not how the code works, what the behavior is: this role can do this, cannot do that, this total always equals the sum of those parts. These are retroactive specifications, and they are the cheapest and highest-leverage thing on this list, because they define what “still working” means. Without them, every later change is judged by feel.

Doing this on an app that already exists, rather than before you build, has its own technique, and it is worth doing deliberately: the full method is in adding specs to an existing codebase without stopping the world.

2. Pin the current behavior with characterization tests

Now capture what the app does right now in tests. Not tests that assert what is ideal, tests that freeze what is actual, so that the next change speaks up the instant it moves something. These are characterization tests, and they are your safety net. The point is not correctness yet, it is detection: you want the house to tell you the moment a card shifts, before your users do.

Test the paths that would hurt if they broke first: the money math, the permission boundary, the flow everyone uses. You are not aiming for coverage, you are aiming for a tripwire on the load-bearing walls.

3. Now, and only now, reinforce the structure

With a contract and a net in place, every change is checked, so now it is safe to do the structural work you have been afraid of: put a boundary where two features currently reach into each other, extract the copy-pasted logic that multiplies every bug, add the contract that stops one feature from silently breaking another. Do it in small pieces. After each piece, the tests confirm you moved nothing you did not mean to, and the house is measurably steadier than before.

This is the step everyone wants to start with, and it is the one that is lethal without the first two. Reinforcing structure with no net is exactly how “one small refactor” takes down four things at once.

Why the order is not optional

Skip step 1 and your tests pin behavior you cannot judge, freezing bugs in place as if they were features. Skip step 2 and step 3 is blind demolition. The sequence, contract then net then structure, is not a style preference. It is the difference between the house getting steadier with each change and getting closer to falling.

It is also why “green” is not the finish line at any step. A passing build is not a correct feature, it is a build that compiled. Your evidence that the house is steadier is the behavior verified against the spec, not the absence of red.

Where PaellaDoc fits

Every step here is something you can do by hand, and on a small app you should, at least once, to feel what it buys. It stops scaling the moment the app is big enough that holding the spec, the net, and the structure in your head all at once is the actual bottleneck, which is right about when the app was worth reinforcing.

PaellaDoc is built to carry that load. It reads the running app into a model you can navigate, keeps the retroactive specs attached to the code they describe, and re-runs the app to produce evidence that a behavior still holds after a change instead of trusting a green bar. The house does not stop being yours. It stops being a house of cards, because now something other than your memory is holding the structure up. This is one instance of the larger crossing laid out in from vibe coding to production.

You built something people use. You get to keep it, and you get to stop holding your breath every time you change it.

FAQ

Can I really fix a vibe-coded app without rewriting it? Yes, and usually you should. A rewrite discards working behavior and rebuilds the same gaps. Reinforcing in place, spec then tests then structure, makes the app steadier with every change and never takes it offline.

What are characterization tests? Tests that freeze the app’s current behavior rather than assert ideal behavior. Their job is detection: they tell you the instant a change moves something that used to work, so you find breaks before your users do.

Why can’t I just start refactoring the messy parts? Because with no spec and no tests, refactoring is blind demolition. Write down what the app does, pin it with tests, and only then restructure, so every change is checked as you go.