A success screen should describe a completed operation. Mine described an intention—and then asked the user to press another button to actually save it.
I found this in Wimemo, the travel-photo app I build. In one clean-install check, photo scanning returned 184 candidates: 149 suggested and 35 excluded. The celebration announced 149 trips and 149 cities. After the final button triggered persistence, the Atlas showed 139 trips and 53 cities.
That difference alone did not prove missing photos. Candidates can legitimately merge during import, and visiting one city on several trips should not create several cities. The broken promise was that the screen celebrated before saving, using candidate counts as if they described the saved library.
The change we merged puts the ending under the save operation’s control:
The regression tests focus on those boundaries: failure, retry, repeated taps, candidate merging and city identity. They do not simply assert that this particular import must always produce 139 trips.
The fix is merged and the unit-test check passed; I have not yet verified it in a public App Store release. I also have no conversion or retention result to attach to it.
My takeaway: audit the past-tense verbs in your UI—saved, imported, sent—and identify the result that makes each one true. A polished ending can otherwise conceal unfinished work.
How do you make the difference between accepted, saving and saved clear in your product?
Longer incident write-up:
https://medium.com/@msss.jerry.li/our-success-screen-arrived-before-the-save-1c70e90f0775
thanks for sharing this. catching the success screen before the save — that was a sharp catch.
149 celebrated, then 139 / 53 after. that number lands.
I'm on a side too, still no first payment. grateful you wrote it this clearly.
I learned the same lesson with internal import jobs: the success screen should not celebrate the batch id, it should celebrate the saved thing the user can inspect afterwards. I now keep accepted, running, and committed as separate labels, and keep the retry payload intact so a failed save does not make people redo the judgement work.