Symptom: a page refresh or tab close kills a long generation. Users expect it to continue; instead it restarts or vanishes.

Cause: by default, a client abort cancels the generation. There is no server-side stream to reconnect to, and no record of which stream belonged to the chat.

Confirmation:
1. Check the useChat options for resume: true. Absent, resumption is off by design.
2. Check the server for stream persistence (Redis via the resumable-stream package) and for POST/GET resume endpoints. Absent, there is nothing to resume from.

Fix (shape, per docs):
1. Client: useChat({ id: chatId, messages, resume: true, transport }). On mount it auto-reconnects to the active stream for the chat.
2. Server: persist the outgoing UIMessage stream with the resumable-stream package into Redis; expose a GET resume endpoint the client polls via consumeSseStream.
3. Add a dedicated stop endpoint: closing the tab is a disconnect (generation continues), while the stop button should persist the partial response, cancel the work, and clear the active stream.
4. Track the active stream id per chat in your database so resume finds the right stream.

Verification: start a long generation, refresh mid-stream, and confirm the UI reconnects and continues rather than restarting. Then use the stop button and confirm the partial response is saved and no orphan stream remains.

Trade-off: this adds Redis and endpoint complexity. Only build it for generations long enough that losing one hurts.