Point at something that already exists
The strongest instruction you can give is not a better adjective. It is a pointer.
Add a cancel button to BookingRow.tsx that matches the edit
button already in that file — same size, same variant, same
confirmation dialog pattern as DeleteCustomerButton.tsx.Every ambiguity in that request is resolved by something on disk. You get consistency without describing it, and the model does not have to guess your house style from the average of everybody’s.
This is the single highest-return habit in prompting for code, and it is underused because it feels like less work than writing a paragraph. It is less work. That is the point.
Sample data resolves more than prose
Three real rows tell a model things you would never think to mention.
id | customer_name | phone | starts_at | status
71 | Anita Rao | 09876543210 | 2026-09-11T14:00:00Z | confirmed
72 | S. Krishnan | | 2026-09-11T14:30:00Z | confirmed
73 | Meera D'Souza | +919876543210| 2026-09-12T09:00:00Z | cancelledFrom that the model learns that phone is sometimes empty, sometimes has a leading zero, sometimes has a country code, so it must be text, not a number. It learns that names contain apostrophes and full stops, so any string handling must survive them. It learns your timestamps are UTC.
Invent the rows if you must, but invent them honestly: include the messy ones. A sample where every field is filled and every name is Alice teaches the model that your data is clean, and it will write code that assumes so.
Show the output you want
For anything that produces a format (an API response, a CSV, an email, a summary) write the exact output you expect before asking for the code.
GET /api/bookings/71 should return exactly:
{
"id": 71,
"customer": { "name": "Anita Rao", "phone": "09876543210" },
"startsAt": "2026-09-11T14:00:00Z",
"durationMinutes": 30,
"status": "confirmed"
}
No other fields. Never include the internal notes column.That is a specification and a test in one. Compare it to “return the booking details”, which returns whatever the model felt like, usually including three fields you did not want to publish.
Screenshots, with an arrow on them
For anything visual, a picture ends the discussion. Take the screenshot, open it in any free editor (GIMP, Krita, Paint, the markup tool on your phone) draw a red arrow and three words.
“The gap under the header is too big on mobile” plus a screenshot with an arrow gets fixed on the first attempt. The same sentence alone frequently gets the wrong gap adjusted, twice.
This also works in the other direction: a screenshot of an app you admire, with a note saying this arrangement, not these colours, communicates layout intent faster than any description of columns and spacing.
Counter-examples are cheap and strong
Saying what you do not want narrows the field usefully:
Not a modal. Not a new page. Inline, under the row that was clicked.
No animation library. Do not add a date picker component;
use the native input.Each of those lines removes an option the model would otherwise have taken with about a one-in-three chance, and taking it costs you a review cycle and a dependency you did not want.
The pattern file
Once your project has one route, one form and one query you are happy with, name them in your rules file as the canonical examples:
When adding an API route, follow app/api/bookings/route.ts exactly:
same validation, same error shape, same auth check, same ordering.Now your conventions are enforced by a file rather than by your memory, and the twentieth route looks like the first. That consistency is what makes a generated codebase reviewable: you learn to read one pattern rather than twenty variations of it.
The limitation worth naming
Examples propagate whatever they contain, including mistakes. If your canonical route has a missing authorisation check, twenty routes now have it. So the file you point at should be one you have actually read, line by line, once. Pointing at a pattern is a multiplier, and multipliers work in both directions.
Every lesson here is open. Your progress is recorded once you pass the Building with AI exam.
Check your understanding
Why include a row with an empty phone number and a name containing an apostrophe in the sample data you give the model?
Pick the one you would defend. Your answer stays private.
Stuck on something?Ask Beatriz, your AI tutor
