The Spec Structure v0, Lovable, and Bolt Actually Want
By Sidhant Sinha on
Type "build me an invoice app for freelancers" into v0 or Lovable and you will get an app. It will have a table, a few buttons, and probably a purple gradient hero section. It will also look nothing like the app in your head, because the builder just made a few dozen decisions you never weighed in on.
The builder is not malfunctioning. It is doing exactly what it was designed to do: fill every unspecified decision with a plausible default. Which screens exist, what the entities are called, what happens on bad input, what an empty list looks like, what color the buttons are. If your prompt does not answer those questions, the model answers them from its training distribution, and the most probable answer to "what does an app look like" is, by definition, generic.
The fix is not regenerating five times or bolting "make it beautiful" onto the end. The fix is a spec that closes the important decisions before generation starts.
Why the one-liner fails
A one-line prompt specifies exactly one thing: the domain. Everything else is left open, and app builders resolve open decisions silently. You never see the fork in the road where the model chose "generic SaaS dashboard" over the dense utility you wanted. You only see the merged result, and by then the wrong choices are welded together.
This is the opposite dynamic from agentic IDE tools. Cursor and Claude Code operate inside an existing codebase, so the repo supplies most of the context, and short, direct prompts with a verification step work best. An app builder starts from an empty directory. Your prompt is the entire context. That is why builder specs should run 150 to 320 words while agent prompts stay under 160.
The skill is knowing which decisions to close. You cannot specify everything in 320 words, and you should not try. You specify the decisions that are expensive to reverse: scope, screens, data model, and the handful of behaviors that make the app yours.
The six-part skeleton
Every spec we publish for app builders covers the same six sections:
- Goal. What the app is, who uses it, and what is explicitly out of scope.
- Screens. A numbered list. Each screen names the data it shows and the actions it offers.
- Data model. Entities, fields, relationships. Actual field names.
- Key behaviors. The rules that separate your app from a CRUD template.
- Empty and error states. What renders before any data exists, and what happens on bad input.
- Styling direction. A few constraints, phrased as prohibitions where possible.
No user stories, no persona documents, no tech stack essay. Six sections, 150 to 320 words.
A full example
Here is a complete spec for a freelancer invoice tracker. It runs about 230 words and fits the skeleton exactly.
Build an invoice tracker for freelancers.
Goal: one freelancer tracks clients, invoices, and payment status.
Single user, no teams, no client login.
Screens:
1. Dashboard: total outstanding, total paid this month, list of
overdue invoices.
2. Invoices: table with client, amount, issue date, due date, status
(draft, sent, paid, overdue). Filter by status, sort by due date.
3. Invoice detail: line items (description, quantity, rate),
auto-calculated subtotal and total, buttons to change status.
4. Clients: name, email, default hourly rate, count of open invoices.
5. New invoice: pick a client, add line items, set due date. Save as
draft or mark as sent.
Data model:
- Client: id, name, email, hourly_rate
- Invoice: id, client_id, status, issue_date, due_date
- LineItem: id, invoice_id, description, quantity, unit_price
Key behaviors:
- An invoice becomes overdue automatically when due_date passes while
status is sent.
- Deleting a client with open invoices is blocked, with a message
explaining why.
- All amounts display as USD with two decimals.
Empty and error states:
- Empty invoice list: "No invoices yet" plus a create button.
- Dashboard with no data: show zeros, not blank cards.
- New invoice form: due date must be after issue date, at least one
line item required, inline errors under fields.
Styling: dense and utilitarian, neutral grays, one green accent for
paid status. No hero sections, no purple gradients, no marketing
pages. Desktop-first.
What each section is doing
Goal: draw the fence first
"Single user, no teams, no client login" does more work than anything else in the spec. Those seven words delete authentication flows, permission models, invitation emails, and sharing UI. Out-of-scope statements are the cheapest way to stop the builder from generating features you will have to rip out later.
Screens: the list is the contract
Naming five screens means the builder ships five screens. Without the list, you routinely get a marketing landing page, a settings screen, and a profile page nobody asked for. Notice that each line also names the data shown and the actions available. "Filter by status, sort by due date" is a complete UI requirement in seven words.
Data model: field names are load-bearing
If you do not name the fields, the builder invents them, and then the behaviors you wrote elsewhere stop lining up. The overdue rule references due_date and status, and those references only resolve because the data model defines them. hourly_rate lives on Client because the invoice form wants a default rate. Name your fields and the whole spec becomes internally consistent.
Key behaviors: the anti-template section
Three rules, each one checkable: automatic overdue transitions, delete protection, currency formatting. This is the section that distinguishes your app from the median CRUD scaffold, and it is the section one-line prompts skip entirely.
Empty and error states: most skipped, highest value
Builders demo beautifully with seeded data. Real apps open empty. If you do not say what an empty dashboard looks like, you get a blank white card grid on first run. Same logic for validation: "due date must be after issue date" is one clause now or a bug report later.
Styling: prohibitions beat adjectives
"Clean and modern" selects the default aesthetic, because the default is exactly what the model considers clean and modern. Negative constraints remove things: "no hero sections, no purple gradients" changes the output in a way adjectives rarely do. Say what you do not want.
Tuning the same spec per tool
The skeleton holds across builders, but the weighting shifts.
v0
v0 generates React and Next.js UI. Weight the spec toward screens, component states, and styling. The data model can stay light because you will often wire a real backend later. Add one line asking for realistic placeholder data that matches your model, so the generated UI does not fill up with lorem ipsum and $0.00.
Lovable
Lovable builds full-stack and leans on Supabase, so the data model section becomes real tables. Be explicit about relationships, and about auth: "single user, no login" versus "email login required" changes the entire scaffold. If a value should be computed rather than stored, say so, or it becomes a column.
Bolt
Bolt runs the whole stack in your browser tab. State your persistence expectation directly: local storage is fine for a prototype, a database if data needs to survive. Keep the dependency surface modest and the first version small, since you will refine it in the same session you generate it.
Replit Agent
Replit Agent plans, builds, and deploys. It responds well to an acceptance check at the end of the spec: "After building, create a client, issue an invoice with a past due date, and confirm it shows as overdue on the dashboard." That converts your key behaviors into something the agent can verify before handing the app back.
What to leave out
Cut anything that does not close a decision. "Act as a senior developer" preambles, tech stack lectures, politeness, and the full v2 feature list all dilute the constraints that matter. Mega-prompts underperform for the same reason one-liners do: the ratio of signal to decisions drops. If your spec creeps past 320 words, cut features, not detail. A five-screen app specified precisely beats a twelve-screen app specified vaguely, and you can always add screens in a follow-up prompt once the foundation matches your intent.
The skeleton is the reusable part. Goal, screens, data model, key behaviors, empty and error states, styling direction. Write those six sections for any app and every builder on the market will produce something recognizably yours on the first generation.
If you would rather start from working examples than a blank page, the v0, Lovable, Bolt, and Replit Agent hubs each have 20+ specs in this shape, free to copy and customize in the browser.