A peer-to-peer marketplace with live buyer–seller chat — built to feel instant and never lose a message, on the real mobile networks Nigerians actually use.

Buyers and sellers negotiate in real time. If a message takes a beat to send, or silently vanishes on a dropped connection, trust goes with it. Listing something also had to be fast enough that a casual seller doesn't abandon halfway. All of this on mobile data that cuts out mid-request.
Lossy networks are the norm, not the edge case. Requests time out, sockets drop and reconnect, uploads stall. The UI had to assume this constantly.
Two sources of truth for the same data. Messages arrive both from your own optimistic send and from the socket echo — the cache has to stay correct without duplicating or losing either.
One session, three consumers. Server components, RTK Query, and the socket handshake all needed to share the same auth without three separate login states.
The RTK Query cache is the single source of truth. Messages send with a temp ID and roll back on failure; incoming socket events are de-duped against in-flight sends, so the thread never doubles up or loses a line.
A deliberate map of which mutations invalidate which queries, so an action refetches exactly what changed — not the whole screen — keeping data requests light on slow connections.
Image analysis pre-fills the listing form, with low-confidence guards that flag uncertain fields and a review-before-publish step — speed without letting the model publish something wrong.
A 45-second upload timeout race, exponential-backoff retries, and plain-language error recovery so a dropped request becomes “tap to retry,” not a dead end.
A dual-cookie session shared by server components, RTK Query, and the socket handshake — one identity, no divergent login states between the three.
Every piece pointed at the same goal: a chat that behaves like the network is perfect, on a network that never is.
You send a message: it appears instantly with a temporary ID. A moment later the server broadcasts that same message back over the socket, now with a real ID. Naively, you'd render it twice. Handle it too aggressively and you drop the real one and keep a temp that never confirms.
The fix was to treat the cache as authoritative and reconcile every inbound event against it: match the socket echo to its in-flight temp send, swap the temp ID for the real one in place, and drop the event if it's already reconciled. Same logic covers the reconnect case — when the socket comes back and replays events, nothing duplicates. That single reconciliation rule is what makes the chat feel effortless.

Messages send optimistically and reconcile cleanly across reconnects; listings go up in a few taps with AI doing the tedious part; and the whole data layer stays light on slow connections. The reconciliation and hardening patterns became the template for the app's other real-time surfaces.