# How ChatWindow Handles Page Refreshes During Streaming in agegr/pi-web

> Discover how ChatWindow in agegr/pi-web preserves streaming sessions during page refreshes. Learn about live-tail detection and state rehydration for seamless user experience. Read now!

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

---

**The ChatWindow component preserves active streaming sessions across browser refreshes by detecting "live-tail" conversation segments, rehydrating session state via the `useAgentSession` hook on mount, and conditionally rendering partial assistant responses using the `MessageView` component.**

In the agegr/pi-web repository, the ChatWindow component ensures users never lose track of an in-flight AI response, even when reloading the page mid-stream. By combining client-side live-tail detection with server-side session persistence and Server-Sent Events (SSE) reconnection, the implementation seamlessly restores streaming states. This article examines the exact mechanisms in [`components/ChatWindow.tsx`](https://github.com/agegr/pi-web/blob/main/components/ChatWindow.tsx) and [`hooks/useAgentSession.ts`](https://github.com/agegr/pi-web/blob/main/hooks/useAgentSession.ts) that enable refresh-resilient streaming.

## Live-Tail Detection Across Browser Refreshes

The component identifies whether the conversation tail is actively streaming using the `isLiveTail` boolean calculation. This logic, found at lines 1414–1417 in [`components/ChatWindow.tsx`](https://github.com/agegr/pi-web/blob/main/components/ChatWindow.tsx), determines if the current message group represents the live end of the conversation that should remain open across refreshes.

```tsx
const isLiveTail =
  (sessionBusy || streamState.isStreaming) &&
  endIdx === messages.length &&
  userIdx === lastAnchorIdx;

```

The calculation relies on three critical conditions:

- **`sessionBusy` or `streamState.isStreaming`** — indicates the agent is currently generating a response
- **`endIdx === messages.length`** — confirms the group reaches the end of the current message list
- **`userIdx === lastAnchorIdx`** — verifies the group starts at the most recent anchor message (either a user message or compaction summary)

When `isLiveTail` evaluates to true, the UI treats the entire group as unfinished streaming content, preserving the visual state exactly as it existed before the refresh.

## Restoring Session State with useAgentSession

When the ChatWindow mounts—including after a full page refresh—the `useAgentSession` hook immediately executes to re-establish the connection to the running session. Located in [`hooks/useAgentSession.ts`](https://github.com/agegr/pi-web/blob/main/hooks/useAgentSession.ts), this hook performs two critical operations:

1. **Metadata retrieval** — calls **GET `/api/agent/[id]`** to fetch the latest session metadata, including whether a run is still in progress
2. **SSE reconnection** — opens a Server-Sent Events stream to `/api/agent/[id]/events` that pushes new tokens and state changes while the page is active

As events arrive through the SSE connection, the hook populates `streamState.isStreaming` and `streamState.streamingMessage`, ensuring the client instantly learns that a stream is active and receives the partial assistant output accumulated server-side.

## Rendering the Streaming Bubble After Reload

Once the hook confirms a stream is active, `ChatWindow` conditionally renders the partial message using the `MessageView` component with the `isStreaming` flag set to true. This block, located at lines 860–866 in [`components/ChatWindow.tsx`](https://github.com/agegr/pi-web/blob/main/components/ChatWindow.tsx), guarantees the streaming bubble appears even after a full page reload.

```tsx
{streamState.isStreaming && streamState.streamingMessage && (
  <MessageView
    message={streamState.streamingMessage as AgentMessage}
    isStreaming
    modelNames={modelNames}
    cwd={messageCwd}
    onOpenFile={onOpenFile}
  />
)}

```

The `isStreaming` prop instructs `MessageView` to display the appropriate "thinking" or loading UI, maintaining visual continuity for the user.

## Summary

- **Live-tail detection** uses `lastAnchorIdx` and streaming flags to identify active conversation ends that persist across refreshes
- The **`useAgentSession` hook** re-fetches session metadata and re-establishes SSE connections on every mount, including after browser reloads
- **`MessageView`** renders partial responses with the `isStreaming` flag to maintain UI consistency, ensuring users see the same streaming bubble before and after a refresh
- Server-side session persistence in [`lib/rpc-manager.ts`](https://github.com/agegr/pi-web/blob/main/lib/rpc-manager.ts) ensures no data is lost when the client disconnects

## Frequently Asked Questions

### What is a "live-tail" in the ChatWindow component?

A live-tail refers to the most recent message group that is actively receiving streaming content. The component calculates this state using the `isLiveTail` boolean, which checks if the conversation has reached the last anchor message index and is still marked as busy or streaming. When true, the UI preserves the group exactly as-is rather than treating it as a completed message.

### How does the application know a stream is still active after a page refresh?

The `useAgentSession` hook runs on every `ChatWindow` mount, including immediately after page refreshes. It calls the API endpoint **GET `/api/agent/[id]`** to retrieve current session metadata and opens a new SSE connection to **`/api/agent/[id]/events`** to continue receiving real-time updates. This pattern ensures the client state automatically synchronizes with the server-side streaming status.

### Where is the streaming message stored during a page reload?

The streaming message state persists server-side within the `AgentSession` instance managed by [`lib/rpc-manager.ts`](https://github.com/agegr/pi-web/blob/main/lib/rpc-manager.ts). After a refresh, the client receives this partial message through the re-established SSE connection, which populates `streamState.streamingMessage` in the `useAgentSession` hook, allowing immediate display without recreating the content client-side.

### Does the ChatWindow lose data when the browser refreshes?

No. Because the session state persists on the server and the `useAgentSession` hook re-fetches this data immediately upon remounting, the ChatWindow restores the exact streaming position and partial message content without data loss. The combination of live-tail detection and SSE reconnection ensures continuity even during extended disconnections.