# How Session Export to HTML Handles Deep Tree Traversal in Pi Web

> Discover how Pi Web overcomes deep tree traversal challenges when exporting chat sessions to HTML. Learn about the iterative patch that prevents stack overflow errors.

- Repository: [Alex Yang/pi-web](https://github.com/agegr/pi-web)
- Tags: deep-dive
- Published: 2026-08-15

---

**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:

1. **Fetch the session file** — the handler invokes [`lib/session-reader.ts`](https://github.com/agegr/pi-web/blob/main/lib/session-reader.ts) to parse the `.jsonl` entries for the requested session ID.

2. **Generate raw HTML** — the Pi SDK's `exportSessionToHtml` creates a document embedding `renderTreeRecursive` for tree visualization.

3. **Apply iterative transformation** — Pi Web post-processes the HTML string, substituting the recursive implementation with `renderTreeIterative` before streaming the response.

4. **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:

```javascript
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:

```typescript
// 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`](https://github.com/agegr/pi-web/blob/main/lib/session-reader.ts) | `.jsonl` parser extracting message entries by session ID |
| [`lib/export-html.ts`](https://github.com/agegr/pi-web/blob/main/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.ts` using 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 `renderTreeRecursive` with `renderTreeIterative`
- Iterative traversal uses an explicit stack and `while` loop 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`](https://github.com/agegr/pi-web/blob/main/lib/session-reader.ts). This append-only format supports efficient streaming and reconstruction of conversation history regardless of total session size.