Designing for a bad connection
Most social apps assume five bars. Ours assumes a train tunnel. Here is what that actually changes.
The first thing is that nothing waits for the network before it appears. Send a photo and the bubble is in the thread immediately, drawn against a real file on your device: the bytes are written to a temporary file first, so the image bubble, the video poster and the full-screen player can all read the same URL before the upload has finished. The server's version swaps in when it lands. This sounds like a small trick and it changes the entire feel of the app, because waiting on the network first made every send feel like a stall even on a good connection.
The second thing is that a big upload has to survive more than a dropped connection.
Our chunked uploader already repairs a broken network inside a single attempt: it asks the server which parts landed and re-sends only the gaps. But that repair lives inside one call, so it covers the network dying and nothing else. Kill the app, through a crash, a swipe-up, or iOS reclaiming memory while the camera roll is exporting, and the upload id goes with it. The bytes are still in storage, the multipart is still open, and the phone has no way left to name it. The next attempt starts from part one.
That is the expensive failure. A four-gigabyte video on a phone uplink is tens of minutes, and losing it at ninety percent because the system reclaimed some memory is the difference between "try again" and "give up on posting this". So the upload id is written to disk the moment the server issues it, and cleared when the upload finishes or is abandoned. A ticket that outlives the process is what lets a later attempt at the same file pick the multipart back up.
What that deliberately does not do is finish an interrupted upload by itself at launch. Completing the multipart is only half of a post: the caption, the audience and the rest of the intent were in a screen that no longer exists. Silently finishing the file half would produce something the person never confirmed they wanted to publish, which is a worse outcome than asking.
The third thing is what happens on the way out of a screen. Read receipts are debounced, because a thread scrolling past twenty messages should not send twenty separate acknowledgements. But a debounce holds something that has not happened yet, and closing the thread is exactly when a naive implementation drops it: the timer is cancelled along with the view, and a message you definitely read stays unread on the other person's phone. So teardown flushes the pending receipt rather than cancelling it. The order of operations on the way out of a screen is its own small discipline, and it is the kind of thing that only shows up on a bad connection, where the debounce window and the disconnection overlap.
None of this is exotic. It is the same three ideas applied everywhere: show it before the network confirms it, make anything expensive resumable across process death, and treat teardown as a place where work is finished rather than abandoned.