I don't use AI as a magic button. I use it as a very fast colleague who needs clear boundaries and should be kept away from big decisions until it has read the room. That framing produces a concrete, repeatable loop — five steps, run over and over, from the first commit of TaskMondo to last night's bug fix.

If you haven't yet, read what vibe coding actually is — this guide assumes the supervised version, not prompt-and-pray.

Before you start: three prerequisites

  • One stack you actually understand. Mine is React, TypeScript, Vite, Tailwind, Capacitor and Firebase — the same stack for every product. AI multiplies whatever understanding you bring. It cannot replace zero.
  • An AI-native editor. I use Cursor with agents. A chat window in a browser makes you a copy-paste machine; an agent in your repo can read your structure before proposing changes.
  • Version control, always. Every AI change must be reviewable and reversible. If you can't diff it, you can't own it.

Step 1 — Describe the feature as a product, not as code

Before generating anything, I write a short brief in plain language: Who uses this? What must they be able to do? What must never happen? What data already exists?

For a leads screen in TaskMondo that might be: "A mobile-friendly overview of leads, grouped by status, with empty states, loading states, and a drawer to open each lead. Leads already live in Firestore under workspaces/leads. Nothing here may mutate booking data."

This step forces me to think before I generate. Skipping it is how you end up with a very productive machine producing debt — quickly, and with beautiful indentation.

Step 2 — Ask for a plan, not an implementation

The first prompt never says "build it." It says "propose a technical plan." In thirty seconds of reading I can tell whether the AI understood my structure — or whether it's off on a small adventure with three new dependencies and a service class nobody asked for.

Plans are cheap to reject. Implementations are not. Rejecting a bad plan costs one message; unwinding a bad implementation costs an afternoon.

Step 3 — Generate in small chunks

One component. One hook. One Firestore query. One migration. It is far easier to review 80 lines than 900 lines where half of it reads like a very enthusiastic Stack Overflow answer got translated.

Small chunks also keep the AI honest: each piece has to fit into structure that already exists, instead of the model inventing a parallel universe where your app works differently.

Step 4 — Review like you mean it (the taste pass)

Then I do the pass that actually makes it mine: rename things, move things, delete things, tighten types. Not "does it build?" but "does it make sense?" The craft lives here — in the judgment, in the deselection, in the moment you delete 40 lines of working code because they shouldn't exist.

Three things I specifically hunt for, because AI gets them wrong the most: local solutions that ignore the long-term structure, complexity hidden behind a pretty UI, and mess it didn't clean up. There's a whole guide on where vibe coding bites.

Step 5 — Test the flow, then ship

I test the actual user flow on the actual platforms — which, with one codebase going to web, iOS and Android, is three chances for something to behave differently. Then it ships. Real users beat any amount of additional polishing in the dark, and a shipped feature teaches you more than a perfect branch.

The rhythm that holds it together

Build fast, stop, tighten. Not "build fast forever" — that's how you end up with a codebase only you, two AI models and possibly a priest can understand. After every few features I take a deliberate cleanup pass: consolidate components, cache queries, narrow types that got too wide while nobody was looking.

The best use of AI is not to think less. It's to think earlier. Think the product through before you generate, the data model through before the UI gets pretty, and the failure cases through before you ship. Use AI to reach the important decisions faster — not to avoid them.

Next: the efficiency details of this loop, how I pick models in Cursor, or why I work this way at all.