# How Pi Web Handles SSE Reconnection on Page Refresh During Active Streaming

> Discover how Pi Web seamlessly handles SSE reconnection on page refresh during active streaming. Learn how the isStreaming session flag ensures uninterrupted data flow.

- Repository: [Alex Yang/pi-web](https://github.com/agegr/pi-web)
- Tags: internals
- Published: 2026-07-25

---

**Pi Web restores interrupted Server-Sent Events streams by checking the `isStreaming` session flag on component mount and automatically reconnecting to the SSE endpoint when an active generation is detected.**

When a user refreshes the browser during an active AI generation, Pi Web (the open-source interface from `agegr/pi-web`) must seamlessly resume the stream without losing context. The application implements a robust **SSE reconnection on page refresh** strategy that coordinates between React hooks, UI components, and Next.js API routes to maintain conversational continuity.

## The Reconnection Architecture

Pi Web’s resilience relies on a stateless server design paired with a state-aware client. The server maintains the current generation status in the session store, while the client—specifically the `ChatWindow` component and `useAgentSession` hook—detects page reloads and restores connections accordingly.

## Step-by-Step Reconnection Flow

### Detecting Active Streams on Component Mount

When [`components/ChatWindow.tsx`](https://github.com/agegr/pi-web/blob/main/components/ChatWindow.tsx) mounts (or remounts after a refresh), it immediately issues a **GET `/api/agent/[id]`** request to retrieve the current session state. The response includes the boolean flag `isStreaming`, which indicates whether an agent is actively generating a response.

If `isStreaming` is `true`, the component knows it must resume the stream rather than start fresh. The same response also carries `thinkingLevel` (indicating chain-of-thought depth) and `isCompacting` (showing if memory compaction is active).

```typescript
// components/ChatWindow.tsx
useEffect(() => {
  // On mount – fetch the latest session state
  fetch(`/api/agent/${sessionId}`)
    .then(r => r.json())
    .then(state => {
      // If a generation was in progress, reconnect the SSE stream
      if (state.isStreaming) {
        startSse(sessionId);               // opens /api/agent/[id]/events
        setThinkingLevel(state.thinkingLevel);
        setIsCompacting(state.isCompacting);
      }
    });
}, [sessionId]);

```

### Restoring the SSE Connection

Upon detecting an active stream, `useAgentSession` invokes the `startSse` function, which creates a new `EventSource` connection to **GET `/api/agent/[id]/events`**. This endpoint streams `agent_message`, `agent_end`, and compaction events.

The reconnection happens transparently to the user, appending new tokens to the chat interface exactly where the previous session left off.

```typescript
// hooks/useAgentSession.ts
function startSse(id: string) {
  const evtSource = new EventSource(`/api/agent/${id}/events`);
  evtSource.onmessage = e => handleAgentEvent(JSON.parse(e.data));
  // …cleanup logic omitted…
}

```

### Synchronizing Auxiliary State

The initial state fetch ensures UI indicators reflect the true server state immediately upon reconnection. `useAgentSession` applies the `thinkingLevel` and `isCompacting` values locally so that thinking animations or compaction spinners display correctly without requiring a full page reset.

## Resilience Against Missed Events

Beyond the initial reconnection, `useAgentSession` implements defensive programming to handle edge cases. The hook runs a periodic reconciliation loop via additional calls to `GET /api/agent/[id]` and listens for browser `visibilitychange` and `online` events.

This ensures that events missed during brief disconnections or backgrounded tabs are recovered, preventing state drift between the client and server.

## Server-Side SSE Endpoint

The server-side implementation in `app/api/agent/[id]/events/route.ts` maintains the SSE stream using standard Server-Sent Events protocols. It streams JSON events including `agent_message` for token updates and `agent_end` for completion signals.

The endpoint is designed to handle multiple concurrent connections idempotently, allowing the refreshed client to establish a new stream while the server continues the ongoing generation uninterrupted. The companion endpoint in `app/api/agent/[id]/route.ts` provides the session metadata required for the client to make intelligent reconnection decisions.

## Summary

- **State Detection**: [`ChatWindow.tsx`](https://github.com/agegr/pi-web/blob/main/ChatWindow.tsx) calls `GET /api/agent/[id]` on mount to check `isStreaming` and determine if reconnection is needed.
- **Automatic Reconnection**: When `isStreaming` is `true`, `useAgentSession` initiates a new SSE connection to `/api/agent/[id]/events`.
- **State Synchronization**: The hook restores `thinkingLevel` and `isCompacting` flags to maintain accurate UI state.
- **Event Resilience**: Periodic polling and browser event listeners recover any messages lost during disconnection.
- **Source Files**: Key logic resides in [`components/ChatWindow.tsx`](https://github.com/agegr/pi-web/blob/main/components/ChatWindow.tsx), [`hooks/useAgentSession.ts`](https://github.com/agegr/pi-web/blob/main/hooks/useAgentSession.ts), `app/api/agent/[id]/events/route.ts`, and `app/api/agent/[id]/route.ts`.

## Frequently Asked Questions

### Does Pi Web lose messages if I refresh during a stream?

No. The application detects active streams via the `isStreaming` flag and immediately reconnects to the SSE endpoint. Any messages generated while the page was reloading are recovered through the initial state fetch and ongoing reconciliation loops in `useAgentSession`.

### What happens to the UI state during an SSE reconnection?

The UI state is restored using auxiliary flags returned by `GET /api/agent/[id]`. `useAgentSession` applies the `thinkingLevel` and `isCompacting` values to ensure indicators like thought depth and compaction status display correctly without requiring a full page reset.

### How does the server handle multiple SSE connections from the same session?

The server-side endpoint in `app/api/agent/[id]/events/route.ts` is designed to stream events idempotently. If a client disconnects and reconnects (such as during a refresh), the server continues emitting the same event stream, allowing the new connection to pick up the remaining tokens without duplicating or skipping messages.

### Can this reconnection logic handle browser crashes or network failures?

Yes. Beyond page refresh scenarios, `useAgentSession` implements periodic reconciliation and listens for browser `online` and `visibilitychange` events. This ensures the client recovers from network interruptions, backgrounded tabs, or crashed sessions by repeatedly synchronizing with the server state.