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

Pi Web's session export uses an iterative post-processing patch to replace recursive tree rendering with a loop-based approach, preventing stack overflow crashes on deeply nested conversations.

The pi-web repository implements a robust server-side export mechanism that converts chat sessions into self-contained HTML files. When users request an export, the system must handle conversation trees of arbitrary depth—from a few messages to thousands of linear branches—without crashing the browser's JavaScript engine.

The Export Pipeline Architecture

Session export in Pi Web follows a three-stage pipeline designed for reliability at scale.

1. Session Retrieval via API Route

The export flow begins at app/api/sessions/[id]/export/route.ts. This API handler coordinates the entire conversion process:

// GET /api/sessions/[id]/export
// Located in: app/api/sessions/[id]/export/route.ts

The handler first retrieves the session data using lib/session-reader.ts, which parses the .jsonl file format that Pi Web uses for persistent storage. Each session is stored as newline-delimited JSON entries representing individual messages with parent-child relationships.

2. SDK HTML Generation

The handler delegates initial HTML production to Pi's internal SDK via exportSessionToHtml. This generates a complete HTML document including:

  • Embedded CSS for message styling
  • The raw message tree as a JavaScript data structure
  • A renderTreeRecursive function for DOM construction

The recursive renderer follows a natural but fragile pattern:

// Original SDK output (recursive, vulnerable to stack overflow)
function renderTreeRecursive(node, depth = 0) {
  renderMessage(node, depth);
  node.children.forEach(child => renderTreeRecursive(child, depth + 1));
}

For sessions with 1000+ messages in a linear chain, this exceeds typical browser call-stack limits (~10,000–50,000 frames depending on engine), causing the exported page to crash on load.

3. The Iterative Patch

Pi Web's critical innovation occurs immediately after SDK generation. The export route post-processes the HTML string to replace the vulnerable recursive implementation:

// Patched version inserted by Pi Web's export handler
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 });
    }
  }
}

This transformation maintains identical rendering behavior while keeping call-stack depth constant at O(1) regardless of tree depth. The stack array grows in heap memory—safely handling millions of nodes.

Client-Side Export Trigger

Users initiate exports through a simple fetch pattern:

// In a React component
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 server returns the fully patched HTML as a downloadable blob—no additional client-side processing required.

Key Implementation Files

File Path Responsibility
app/api/sessions/[id]/export/route.ts API endpoint, orchestrates export and applies iterative patch
lib/session-reader.ts Parses .jsonl session files into traversable objects
lib/export-html.ts Internal SDK that generates initial HTML (recursive)

Why Iterative Beats Recursive for Session Export

Recursion risks: Browser JavaScript engines impose strict call-stack limits. Deep conversation chains—common in extended research or coding assistant sessions—trigger RangeError: Maximum call stack size exceeded.

Iteration guarantees: The explicit stack pattern uses heap-allocated arrays with no engine-imposed depth limits. Memory consumption scales linearly with tree size, which is acceptable for practical session lengths.

The patch is applied server-side so exported files remain functional independently—critical for archives and shared downloads.

Summary

  • Pi Web stores sessions as .jsonl files parsed by lib/session-reader.ts
  • The export route in app/api/sessions/[id]/export/route.ts generates HTML via SDK then patches it server-side
  • The iterative renderTreeIterative replacement eliminates stack overflow risks for deep conversation trees
  • Exported HTML files are self-contained and work across all browsers regardless of session depth

Frequently Asked Questions

What file format does Pi Web use for session storage?

Pi Web uses JSON Lines (.jsonl) format, with each line representing one message entry containing content, timestamps, and parent references. This append-only structure supports efficient streaming reads during export.

Why not fix the recursion in the SDK directly?

The SDK's exportSessionToHtml is internal to Pi's broader platform. Pi Web applies the iterative patch as a consumer-side safeguard, ensuring compatibility without requiring upstream changes. This defensive approach also permits SDK updates without regression risk.

Does the iterative version change visual output?

No. The renderTreeIterative function produces identical DOM structures to the original recursive implementation. The transformation is purely algorithmic—swapping the engine's call stack for an explicit array stack while preserving traversal order.

How deep can sessions be after the patch?

The iterative approach supports millions of messages limited only by available memory. The explicit stack array grows in heap space, bypassing browser call-stack limits entirely.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →