How ChatWindow Handles Page Refreshes During Streaming in agegr/pi-web
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 and 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, determines if the current message group represents the live end of the conversation that should remain open across refreshes.
const isLiveTail =
(sessionBusy || streamState.isStreaming) &&
endIdx === messages.length &&
userIdx === lastAnchorIdx;
The calculation relies on three critical conditions:
sessionBusyorstreamState.isStreaming— indicates the agent is currently generating a responseendIdx === messages.length— confirms the group reaches the end of the current message listuserIdx === 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, this hook performs two critical operations:
- Metadata retrieval — calls GET
/api/agent/[id]to fetch the latest session metadata, including whether a run is still in progress - SSE reconnection — opens a Server-Sent Events stream to
/api/agent/[id]/eventsthat 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, guarantees the streaming bubble appears even after a full page reload.
{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
lastAnchorIdxand streaming flags to identify active conversation ends that persist across refreshes - The
useAgentSessionhook re-fetches session metadata and re-establishes SSE connections on every mount, including after browser reloads MessageViewrenders partial responses with theisStreamingflag to maintain UI consistency, ensuring users see the same streaming bubble before and after a refresh- Server-side session persistence in
lib/rpc-manager.tsensures 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. 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →