Vibe Coding vs Spec-Driven Development: Two Git Logs, One Flutter Feature / Oct 30, 2026
This is part of a short series ahead of my session at FlutterBytes Conference 2026 in Lagos, 6 and 7 November. The two git logs below are illustrations. They show a pattern, not the history of a specific project.
Two ways to build the same feature
Pick a Flutter feature most apps have: a checkout screen. Show the order total, let the user pay, handle the failure.
Now build it twice. Same AI agent. Same model. The only difference is what you hand it.
On one branch, you vibe it. You describe the screen in a prompt, run what comes back, and prompt again until it looks right.
On the other branch, you spec it. You write down what the feature must do, how it fits your app and what order to build it in, then let the agent work through that.
The fastest way to see the difference isn't to read the code. It's to read the git log a month later.
Day 1: vibes win
Let's be honest about day 1, because a lot of spec-driven advice isn't.
Vibes: shipped. One prompt. A screen appears. It even looks good.
Spec: still writing. Requirements, then a design, then tasks. No screen yet.
On day 1, vibe coding wins. If you're building a demo, stop here. You're done, and writing a spec would have been wasted effort.
The problem is that most apps don't stay on day 1.
Day 30: the vibe branch
Now the app has 40 screens. Here's what the vibe branch's log tends to look like:
a41f2c0 fix pay button
9be1d07 fix pay button again
77c0e3a revert theme changes
3d9a118 why is the cart empty
e02b5f4 pls workNothing in that log is the agent being bad at Dart. Each commit is a reasonable response to the prompt that produced it. "Fix the pay button" got a fix. It just also touched the theme, so the theme got reverted. The cart broke somewhere along the way and nobody knew until it was visible.
The underlying problem: every prompt is a fresh guess about an app the agent can only partly see.
Day 30: the spec branch
Same feature, same agent. Here's the spec branch:
c19e7a2 feat: R1 show order total
5d2b8f0 feat: R2 disable Pay on tap
8a04e6d feat: R3 error state on fail
f3b91c4 test: R1–R3 widget testsIt's boring. That's the point.
Every commit points at a requirement (R1, R2, R3), and every requirement has a test. When something breaks, the test name tells you which promise broke. When you review a commit, you know what it was supposed to do before you open the diff.
What changed? The input
Not the model. Not the agent. The input.
Here's what the agent can see in each case:
| The agent can see | Vibes | Spec |
|---|---|---|
| Your latest prompt | Yes | Yes |
| The files it opens | Yes | Yes |
| What "done" means | No | Yes |
| Your architecture | No | Yes |
| Files it must not touch | No | Yes |
| A test that proves it | No | Yes |
The first two rows are the same on both branches. The agent always has your prompt and whatever files it decides to read.
The last four rows are where the git logs split:
- What "done" means. Without it, "done" is whenever the screen looks right. With acceptance criteria, it's a list you can check.
- Your architecture. Without it, the agent picks a state approach per prompt. With a design file, it uses the one you chose.
- Files it must not touch. Without it, fixing a button can rewrite a theme. With a rules
file,
lib/theme/is off limits. - A test that proves it. Without one, the cart breaks silently. With one, it breaks loudly, before merge.
None of those four things are clever. They're the context a teammate would get on their first day. The spec branch writes them down once, so the agent gets them on every task.
So which one should you use?
Honestly? Both.
Vibe it when:
- It's a prototype
- You'll delete it
- You're learning a package
Spec it when:
- Other people touch the code
- It handles money or logins
- You'll still own it next month
The question isn't "which approach is better". It's "how long does this code need to survive, and who else depends on it?" A throwaway experiment doesn't need a requirements file. A checkout flow that takes real payments does.
There is a second cost to vibe coding that the git log only hints at: a month later, can you explain why the code is the way it is? I wrote about that in Vibe Coding Builds Fast, But Can You Explain It Later?.
Where to start on the spec branch
You don't need to spec your whole app. Pick the next feature that matters and write three things before any code:
- What it must do, as short WHEN / SHALL statements. For checkout:
WHEN the user taps Pay, THE APP SHALL disable Pay and show a loader. - How it fits your app: the widgets, where state lives, which packages are allowed.
- What order to build it in: small tasks, each pointing back to a requirement.
Then let the agent take one task at a time, with a widget test per requirement. That's what produces the boring log.
If you want the full method, not just the Flutter version, start with Spec-Driven Development: From Idea to Shipped Code and Writing Requirements an AI Coding Agent Can Build From.
At FlutterBytes
At FlutterBytes 2026 in Lagos (6 and 7 November) I'll walk through the spec branch from start to finish: requirements.md, design.md, tasks.md, and widget tests that prove each requirement.
Stop prompting. Start specifying.
Frequently asked questions
- Is vibe coding bad?
- No. For a prototype, a demo or learning a new package, vibe coding is often the right call, and writing a spec would be wasted effort. It breaks down when the code has to survive: other people touch it, it handles money or logins, or you will still own it next month.
- What actually changes between vibe coding and spec-driven development?
- Not the agent or the model, only the input. A spec tells the agent what done means, how your app is built, which files it must not touch, and which test proves each requirement. Those four things are what keep the git log boring.
- Do I need to spec my whole app?
- No. Pick the next feature that matters and write three things before any code: what it must do as short WHEN and SHALL statements, how it fits your app, and the order to build it in.