StarBuild
Blog
Language
Open the app
← Back to the blog

Writing user stories an AI app builder can actually use

Published · 6 min

A good user story already holds most of what an AI app builder needs: who the user is, what they want and why. What is usually missing is the data, the unhappy paths and a definition of done you can check by clicking.

The same story, before and after

Here is the kind of story that passes a quick grooming session and then disappoints everyone.

As a manager, I want to see my team's vacation requests.

Hand that to a human developer and they walk over to ask you six questions. Hand it to a builder and it answers all six itself, silently, with guesses. It will pick the fields, invent the statuses and decide what 'see' means. Some guesses will be fine. You will not know which until you click.

Now the version with the questions already answered:

As a team manager, I want a list of my team's vacation requests, so that I can approve or reject them before the end of the week.

Data and fields

Name every field and say what values it can take. This is the single biggest improvement you can make, because the data model is the part that is expensive to change later. A tool like StarBuild creates a real Postgres table from what you describe, so a vague field becomes a vague column.

  • Employee name, text, required.
  • Start date and end date, dates, end not before start.
  • Type: annual leave or unpaid leave.
  • Status: pending, approved or rejected. Starts as pending. Nothing else.
  • Decision note, text, optional, filled in when the manager rejects.
  • Created at, set by the system, not typed by anyone.

Sample rows help more than you would think. Three made-up requests, one of each status, tell the builder how long a name gets and what a realistic date range looks like.

Empty, loading and error states

These are where a prototype starts to feel fake, so say what you want in a sentence each. Empty: 'No requests yet. When someone on your team asks for time off, it will show up here.' Loading: a few grey placeholder rows, not a spinner on a blank page. Error: 'We could not load the requests. Try again', with a retry button, and the list stays as it was if an approval fails.

That last case matters. If the manager clicks Approve and the save fails, the badge must not turn green. Say so. Otherwise you will find out in a demo.

Permissions and out of scope

Unstated rules are the quiet killers. Who can see the page? A manager sees only their own team, not the whole company. An employee can create a request but cannot approve their own. Write these as plain sentences. If the app has sign-in, the builder needs to know which role gets which screen.

Then draw the fence. Out of scope for this story: calendar view, email notifications, vacation balance calculation, public holidays, importing from another system. A short 'not now' list prevents a lot of enthusiastic extra work that you then have to review and delete.

Acceptance criteria you can check by clicking

A criterion is good if a person who did not write it can tell, in under a minute and with only a mouse, whether it is true. Compare 'approval works well' with 'when I click Approve on a pending request, the row shows Approved without a page reload, and after I refresh it is still Approved.' The second one is a test. The first is a mood.

  • Given three pending requests, the list shows them with the oldest start date first.
  • When I choose the filter Rejected, only rejected rows remain, and the count in the header matches.
  • When I reject a request, a note field appears, and I cannot confirm with the note empty.
  • When there are no requests at all, I see the empty message and no table headers.
  • When I sign in as an employee, I do not see Approve or Reject buttons.

StarBuild runs typecheck, lint, a build and a browser test after each change and shows you a receipt, which tells you the thing runs. It cannot tell you the thing is right. Your criteria do that part, so write them as if you were the one who has to sign off.

Iterate in the chat

Treat the first version as a draft. Open the preview, walk through your criteria one by one, and write each correction the way you would in a review: 'the status badge should be green for approved and grey for pending.' One change per message is easier to check and easier to undo. Every version is kept, so you can step back when an experiment goes wrong.

A realistic second round looks like this. You click through and notice that approved requests drop to the bottom, which hides them from the manager who wants to double check last week. You write: 'keep pending first, but sort the rest by start date, newest on top.' Then you notice that a rejection saves without a note, which breaks your own criterion. You write that down too, as a separate message. Two small corrections, two small diffs, and you always know which one caused a surprise.

When the builder gets something wrong, resist the urge to rewrite the whole story. Quote the criterion it missed and say what you saw instead. That is a bug report, and builders handle bug reports well.

How these requests usually go wrong

  • Too vague. 'A nice dashboard for HR' gives the builder permission to invent everything. You will get something plausible and wrong.
  • Too many stories at once. Approving, calendars, balances and notifications in one message produce a pile you cannot review. One story, look at it, then the next.
  • Unstated rules. Everything the team 'just knows', such as notice periods or who covers for whom, has to be written, because the builder was not in your planning meeting.
  • Criteria about feelings. Anything you cannot check with a click goes back for rewriting.
  • Skipping the sad paths. The happy path always works in a demo. Ask for the failed save and the empty list on purpose.

None of this is new. It is what refinement has always been for. The difference is that a human teammate would have asked, and a builder will not, so the questions have to be answered before you send the message.