Lesson 1 — The exit code is not the output
Here is a thing that happened while building the tool that made this very course. The build printed a cheerful line — built the package (24,873 bytes, 7 files) — and exited zero. The archive opened. The page looked right. Three separate things in it were wrong.
None of them were the kind of wrong that throws an exception. That is the whole problem, and it is the specific problem you inherit the moment you start building faster than you read.
Four moves that turn ‘it ran’ into ‘it works’
Say what success looks like, before you build
Out loud, in one sentence, in the terms of the output rather than the code: every question has a visible prompt. If you cannot say it, you cannot check it, and you will accept whatever appears.
Render the thing, examine and admire it
Not the source. Not the summary of the source. The artefact a person would actually receive: the page, the file, the row in the database.
Most silent failures are visible here and nowhere else.
Grep for the claim, not the vibe
You said every question has a prompt. So count them. Four of five empty is a number; ‘looks fine’ is not. Ask the specific question whose answer would embarrass you.
Pin it so it cannot come back
The bug you just found by eye is one refactor away from returning, and next time you will not be looking. A test is how you stop paying for the same lesson twice.
Lesson 2 — Four bugs that passed every test
These are real. Every one was found in this codebase, and every one had already survived a full green suite. Read them as a taxonomy rather than a list of mistakes: the shapes repeat.
| What went wrong | Why nothing failed | What caught it |
|---|---|---|
| Four of five questions rendered an empty label | An empty string is a valid string; the page looked | Counting the labels instead of glancing at the page |
| A timeline field named marker was written as data | The number was plausible and nothing measured it | A test that computed it and disagreed |
| A path renamed everywhere except one selector string | The selector matched nothing and returned undefined | Grepping for the old name after assuming the rename was done |
Isn’t this just an argument for writing more tests?
Partly, but tests you write are shaped by what you already thought of. Three of the four above were invisible to a suite of four hundred passing
tests, because every fixture in it had been built by someone who knew the field names. Real content is the cheapest way to get input you did not
design.
If the AI wrote the bug, is checking it my job?
Yes, and this is the actual deal. You are trading typing for reviewing. If you skip the reviewing you have not saved time, you have moved the cost to whomever finds it later, usually with interest.
How much checking is enough?
Check the claim you would be most embarrassed to have got wrong. That is usually one command. The contrast bug took one line to disprove
and had already justified a code change built on it.
Lesson 3 — Build it anyway
None of this is an argument for slowing down. The package that had three bugs in it was still built in an afternoon, and the bugs were found and pinned in minutes each. That is a good trade, and it is only available if you actually take the second half of it.
First pass
Build the obvious thing
Merge two codebases, port the layer worth keeping, run the suite. Green. Genuinely fast, and genuinely not finished.
First check
Look at the output
Render a real lesson instead of a fixture. Four empty labels appear immediately, none of which any test had an opinion about.
The awkward bit
Check your own claims too
A contrast ratio asserted from memory turned out to be wrong, and a code change had already been made on the strength of it. Reverted, remeasured, pinned.
Last
Write the tripwires
Each bug becomes a test and a line in the traps file. The point is not the fix; it is that the next person cannot make the same mistake quietly.
- Prefer refusing to warning.
A warning is read once. A gate is read every time, because it stops you.
- Measure the number you were about to state.
Especially when you feel sure. Certainty is not evidence, and it is cheap to check.
- Feed it input you did not design.
Your own fixtures agree with your own assumptions. Real content does not.
- Fix the layer, not the instance.
Ask whether the same shape of bug can occur elsewhere, and gate it there.
- The Vibe Coding Studio (opens in a new tab)
Where the loop this lab leans on is taught properly.
- Andrej Karpathy’s original “vibe coding” tweet (opens in a new tab)
The tweet that named it, February 2025.
- WCAG: Labels or Instructions (SC 3.3.2) (opens in a new tab)
Why an empty label is a defect and not a cosmetic issue.
