How Session Export to HTML Handles Deep Tree Traversal in Pi Web
Pi Web uses an iterative patch to replace recursive rendering functions, preventing stack overflow when exporting deeply nested chat sessions to HTML.
Pi Web is an open-source chat interface that stores conversations as .jsonl files. When users export sessions, the application must transform these structured logs into self-contained HTML documents. The challenge lies in rendering conversation trees of arbitrary depth without crashing the browser. According to the agegr/pi-web source code, the export system solves this through a server-side post-processing step that replaces recursive JavaScript with iterative equivalents.
The Deep Tree Problem in Session Export
Browser JavaScript has a call stack limit typically ranging from 10,000 to 50,000 frames depending on the engine. A recursive function walking a linear chat history of 1,000+ messages would exhaust this limit, causing a RangeError: Maximum call stack size exceeded.
The Pi SDK's default export (exportSessionToHtml) generates HTML containing a renderTreeRecursive function. For standard conversation depths, this works fine. However, Pi Web specifically targets long-form conversations where linear threads commonly exceed hundreds of messages.
How the Export Route Applies the Iterative Patch
The export flow in app/api/sessions/[id]/export/route.ts follows a four-stage pipeline:
-
Fetch the session file — the handler invokes
lib/session-reader.tsto parse the.jsonlentries for the requested session ID. -
Generate raw HTML — the Pi SDK's
exportSessionToHtmlcreates a document embeddingrenderTreeRecursivefor tree visualization. -
Apply iterative transformation — Pi Web post-processes the HTML string, substituting the recursive implementation with
renderTreeIterativebefore streaming the response. -
Return patched document — the modified HTML reaches the client with guaranteed O(1) stack depth regardless of message count.
This server-side patching strategy means clients never execute vulnerable recursive code.
The Iterative Rendering Implementation
The replacement function uses an explicit stack data structure to simulate recursion without growing the call stack:
function renderTreeIterative(root) {
const stack = [{ node: root, depth: 0 }];
while (stack.length) {
const { node, depth } = stack.pop();
// render node …
for (let i = node.children.length - 1; i >= 0; i--) {
stack.push({ node: node.children[i], depth: depth + 1 });
}
}
}
Key characteristics of this implementation:
- LIFO stack ordering — children are pushed in reverse order so they render left-to-right when popped
- Constant memory overhead — stack depth equals tree breadth, not tree depth
- No tail-call optimization dependency — works reliably across all browser engines
The patch preserves all rendering behavior while eliminating stack consumption proportional to conversation length.
Complete Export Flow Example
Triggering an export from a React component:
// Export session as downloadable HTML file
const exportSession = async (sessionId: string) => {
const res = await fetch(`/api/sessions/${sessionId}/export`);
const blob = await res.blob();
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `session-${sessionId}.html`;
a.click();
};
The fetch targets the patched endpoint in app/api/sessions/[id]/export/route.ts, ensuring the returned blob contains the iterative renderer.
Source Files and Architecture
| File | Responsibility |
|---|---|
app/api/sessions/[id]/export/route.ts |
API endpoint coordinating fetch, generation, patch, and response |
lib/session-reader.ts |
.jsonl parser extracting message entries by session ID |
lib/export-html.ts (Pi SDK) |
Raw HTML generator requiring post-processing for deep trees |
The separation of concerns allows Pi Web to upgrade SDK versions while maintaining its deep-tree safeguard through the patching layer.
Performance and Scalability
The iterative approach provides predictable performance characteristics:
- Time complexity: O(n) for n messages — identical to recursive traversal
- Space complexity: O(w) where w is maximum tree width (typically small for chat threads)
- Stack guarantee: Exactly one frame regardless of thread depth
This design intentionally trades marginal code complexity for hard reliability guarantees across all session sizes.
Summary
- Pi Web exports sessions via
app/api/sessions/[id]/export/route.tsusing the Pi SDK's HTML generator - Deep linear conversations would crash with default recursive rendering due to JavaScript stack limits
- The export route patches generated HTML to replace
renderTreeRecursivewithrenderTreeIterative - Iterative traversal uses an explicit stack and
whileloop for O(1) call stack depth - This server-side transformation ensures exported HTML works for sessions of any depth
Frequently Asked Questions
What causes stack overflow in HTML session exports?
Recursive JavaScript functions consume one stack frame per nested call. A 5,000-message linear chat creates 5,000 nested invocations, exceeding browser limits. The renderTreeRecursive function in unpatched SDK output is vulnerable to this failure mode for sufficiently deep sessions.
How does Pi Web modify the SDK's HTML output?
The export route in app/api/sessions/[id]/export/route.ts performs a string replacement on the generated HTML before streaming it to the client. This substitutes the recursive function definition with an iterative equivalent that uses a manual stack array, preserving all rendering logic while eliminating deep call stacks.
Can the iterative approach handle branching conversation trees?
Yes. The renderTreeIterative function uses a stack that processes all children of each node, making it suitable for both linear threads and complex branched conversations. The space requirement scales with tree width (number of siblings at a given level) rather than depth.
Where is the session data stored before export?
Sessions persist as .jsonl files containing one JSON object per line, read by lib/session-reader.ts. This append-only format supports efficient streaming and reconstruction of conversation history regardless of total session size.
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 →