Thirty photos taken at handover, one reached the office
The owner photographed a handover himself: thirty pictures, one on the server. The other twenty-nine were gone, and the app had shown him nothing was wrong.
You own a self-drive car rental business in Goa and you want to see for yourself what your drivers see. So you do a handover on the app, take thirty photographs of the car, tap Complete, and go back to the office to look at them. One photograph is there. The other twenty-nine are not on the server, not on the phone, not anywhere.
Nothing on the screen had said anything was wrong. The app accepted the tap, reported the handover done, and moved on. If the owner had not counted, nobody would have known, and the next damage dispute on that car would have been argued from one picture.
This came a few days after a different set of complaints from the drivers about the same app: photos arriving twice, thumbnails that looked cut off, a “location not available” stamp on the picture, and uploads that took too long. Both sets of problems had the same shape, which is that the app and the driver disagreed about what “done” meant.
What was actually going on
When a driver picks photos, the app shrinks them one at a time before sending. That is deliberate: doing them all at once had crashed the cheaper phones the drivers carry. While that was happening the two buttons for adding more photos were disabled. The Complete button was not.
So the owner tapped Complete while the app was still working through his thirty. The record on the phone shows the handover being submitted at 18:41:19 and the last photo finishing at 18:41:21. The booking was closed a second and a half before its photos were ready, and it went to the server with the one that was. The rest were not picked up later, because the recovery sweep that rescues stranded photos only looks at bookings still marked as pending, and that mark clears itself the moment the queue looks empty.
The fault was there before we touched anything. But the app had previously capped a gallery pick at twenty photos, and we had just removed the cap at the owner’s request, which is what made thirty easy to attempt. That part is on us.
The earlier complaints had a related cause. Tapping Complete used to freeze the screen on the whole upload, so drivers tapped again, and the same photos went twice: we confirmed it on real bookings by comparing the uploaded files byte for byte. The “cut-off” thumbnails were the small preview tile in the app trimming to a square; the stored files were a separate problem, found two days later and told elsewhere. The location stamp was missing because the app never asked the phone for permission to read it in the live flow. And uploads were slow because each photo was compressed twice and then held for an artificial pause.
What we changed
The first fix disabled Complete while photos were processing. The owner pushed back, and he was right: the entire point of processing at selection and uploading in the background is that the driver is never made to wait, and a greyed-out button is exactly the thing the design was built to avoid. So the tap is always accepted, and the submission waits quietly for the photos still in flight before it goes. Getting that right needed care, because after the wait the app was still holding the list of photos as it stood before the wait, which would have dropped precisely the ones we were waiting for.
Verified on the same booking three ways on a real phone: the old code, 1 of 30; the new code with Complete tapped after processing, 30 of 30; and the new code with Complete tapped 18 seconds into compression, the harshest case, 30 of 30, confirmed against the database as thirty rows with thirty distinct files. A first count of 29 turned out to be a query run mid-upload.
For the earlier complaints: the upload no longer blocks the screen, a guard remembers what has been queued for 48 hours so a second tap cannot resend, the preview tile shows the whole photo, the app asks for location permission and reads the position when the handover screen opens, and the double compression and the pauses were removed. Selecting fifteen or twenty photos used to freeze the screen until the whole batch was done; thumbnails now appear one by one with a “Processing 15/20” line. The app also gained its first automated tests, 29 of them, including one that reconstructs the exact duplicate-upload case.
What it did not fix
The owner’s twenty-nine photographs are gone. They were dropped on the phone and nothing copied them anywhere first.
In the thirty-photo test, one photograph arrived without its watermark: under the memory pressure of a large batch the phone occasionally fails to draw the stamp. The upload was never at risk, and the fix keeps that behaviour, retrying the stamp three times and sending the plain photo if it still fails, because a photo without a watermark is evidence and a photo not sent is not.
The release built for this also carried the app’s push-notification code, which was inert until the server side was deployed and keyed. Everything else in it worked regardless.
The mechanism, briefly
Sequential compression ran in the background while the completion handler knew nothing about it. Completion now awaits the in-flight batch, with both photo lists mirrored into refs because the arrays captured by the render closure are stale after the await. Upload queueing is genuinely non-blocking, with a persisted, 48-hour-windowed dedup label so a retry cannot re-enqueue.
Where this ends up
A handover is the moment a car’s condition becomes a matter of record, which is why Sazinga Rentals treats the photographs and the completion as one act: the booking does not close until every picture the driver took is on its way.