slopware 101 · step 7 of 35
What does “it works” mean?
by Damla Su Bilge · 3 min read
Working once and working are not the same thing. The first is an event: you clicked, and the right thing happened. The second is a claim about many clicks, by people who are not you, on machines that are not yours, on days when you are not watching.
Take the to-do app again and one feature, adding a task. You ask a model for it, the model says done, you type buy thread, press Enter, and it appears. That is one observation. It is a real observation of one case; the remaining criteria tell you what else needs checking.
The earlier chapters have already given you the questions it leaves open. Is the task still there after a refresh? On another device? Did it travel through a request, and what came back? What happens when the field is empty, or very long, or when two tabs add a task at the same time? What happens when the server is not running?
An outcome you can check
A prompt describes what you want. An acceptance criterion describes how you will know you have it. The difference matters, because a model will happily satisfy a prompt in a way you did not mean, and a written criterion gives you a specific observation to make instead of relying on its summary.
An acceptance criterion is an observable statement with an expected result. For adding a task in a version that keeps tasks in localStorage, it might look like this:
- After adding 'buy thread' and refreshing, 'buy thread' is still in the list.
- Pressing Enter in an empty field adds nothing and logs no error in the console.
- A 300 character task is saved whole and wraps instead of overflowing.
- With two tabs open, a task added in one appears in the other after a refresh.
- Tasks do not appear on a second device. This is intended for now.
Each line is specific enough that two people watching the same screen would agree whether it passed. At least one line describes something the app should refuse, because refusing is half of behaving correctly and the half that a quick try never exercises. The last line records an intentional limitation, so that when it happens nobody mistakes it for a bug.
If the app had a server, the criteria would reach into the Network panel too: adding a task sends one POST to /api/tasks, the response is 201, and the new task comes back with an id.
Using them
Write the criteria before you ask for the feature, and paste them into the request. The conversation changes. The model now has something concrete to aim at, and you have something concrete to check.
When it says done, check each line yourself. A model's summary of what it did is a description, not an observation. It might be accurate. You find out by refreshing the page, opening a second tab and pressing Enter on an empty field, which together take about a minute.
A criterion that fails is not a disaster. It is the first precise thing you know about the feature, and it already contains the two sentences the next chapter asks for: what you expected, and what you saw instead.
Your own project
Pick the feature in your project you care about most and look at it with everything so far.
Start with where it runs and who can reach it, and whether the address in your bar would open for anyone else. Open the Network panel while you use the feature and read the requests it makes, noting which machine decides each answer. Find where the data it creates is stored and what would erase it. Check whether anything that should be secret has reached the browser.
Then write three acceptance criteria for that feature, including one refusal, and check them by hand. Keep the list in the repository next to the code, so the next change can be checked against the same lines.
Those criteria give us a way to check an outcome. There is also the time between pressing a button and learning what happened. Take saving: a green message may appear even though the app has not yet heard back from the service. We need to follow that interval before treating the message as evidence.


