I generate a lot of lists in Cursor.
Members. Invoices. Customers. Logs. Orders. Different products, same basic problem.
The first version is almost always strangely healthy.
There are twelve rows. I’m an admin. Every request succeeds. Names fit perfectly. Nobody has an unusual permission. There is enough data to make the table feel useful but never enough to stress it.
It looks like software.
For a while, I would keep that version and tell myself I would add the other states later.
“Later” consistently produced worse design.
The empty state became a large illustration dropped into the space where the table used to be. Errors turned into new routes. Disabled controls accumulated because removing them would disturb the composition. Long names suddenly made rows twice as tall.
Nothing was individually disastrous.
The screen just stopped feeling like one product.
I used to blame generation for some of this. I don’t anymore.
The model had designed the condition I described.
I had described a very good afternoon.
Start with the conditions that break the screen
For an invoices page, I now start with zero invoices and far too many invoices.
Zero invoices usually removes most of the interface.
There is nothing to search. Nothing to filter. Nothing to bulk select. Sorting has no meaning yet.
The job is simple:
Create an invoice.
Maybe import one.
That realization sounds obvious, but I used to keep the entire table toolbar around because removing it made the screen feel unfinished.
It was finished.
It was just empty.
Four hundred invoices teaches the opposite lesson.
Now search matters. Sorting matters. Sticky headers matter. Row density matters. Truncation matters.
INV-2026-000184 suddenly becomes a design problem.
So does a customer whose legal company name is forty-eight characters long.
At twelve rows, almost any list design works.
At four hundred, the product starts telling you what it needs.
This is also where I usually stop the model from turning everything into cards.
Cards are very easy to love when there are six of them.
Four hundred cards explain the problem much faster.
Failure is not emptiness
I used to treat empty and failed states as cousins.
Visually, they can look similar. There is not much content on the page. A message appears somewhere in the middle. Maybe there is a button.
But the jobs are completely different.
An empty invoice list says:
You have not done this yet.
A failed invoice list says:
Your work exists. I cannot show it to you.
Those should not feel the same.
On failure, I keep as much of the product in place as I can. Navigation stays. Page structure stays. Column headers can stay if they help preserve context.
“Couldn’t load invoices.”
Retry.
That is often enough.
A full-page error feels clean in a mockup. During an outage, it feels like the product threw the user out of the building.
Permissions change the interface too
Permissions were another place where my generated screens looked correct before they behaved correctly.
A viewer cannot refund an invoice.
The easy version is a disabled Refund button.
I’ve shipped versions of that idea before. I understand why it is attractive. It preserves the layout and advertises the capability.
It also puts a control in front of someone that they can never use.
I’m much more likely now to remove the action and explain the permission where explanation is useful.
“Viewers can’t refund.”
That tells the truth.
A gray button often just makes the product look broken.
The generation order matters
This is the part I underestimated.
If I generate the beautiful twelve-row admin table first, every later state inherits its assumptions.
The model tries to preserve the toolbar.
It preserves the spacing.
It preserves actions that no longer make sense.
Every new condition is forced to wear the first composition.
So I changed the order.
Zero and four hundred first.
They stress the layout.
Failure and permissions next.
They stress the product decisions.
Then I generate the ordinary state.
The ordinary state is usually easy once the difficult states have already decided the structure.
I still keep the prompt short.
Surface.
Job.
States that materially change something.
A few exact strings.
I’m not trying to describe every pixel before generation.
I’m trying to describe enough reality that the pixels have somewhere honest to go.
The happy path is no longer where I start.
It is what is left after the product survives everything around it.


