# How the Earendil π Terminal UI Achieves Differential Rendering

> Learn how the Earendil pi terminal UI uses differential rendering to minimize flicker and I/O by diffing screen states and emitting only changed rows with optimized ANSI escape sequences.

- Repository: [Earendil Works/pi](https://github.com/earendil-works/pi)
- Tags: internals
- Published: 2026-05-25

---

**The Earendil π terminal UI minimizes flicker and I/O by maintaining a `previousLines` snapshot of the screen and computing a line-by-line diff during each render cycle, emitting only the changed rows using optimized ANSI escape sequences.**

The **differential rendering** engine in the `earendil-works/pi` repository eliminates redundant terminal updates by tracking exactly what changed between frames. Located primarily in [`packages/tui/src/tui.ts`](https://github.com/earendil-works/pi/blob/main/packages/tui/src/tui.ts), the system compares new output against a cached snapshot and issues precise cursor positioning commands to update only the modified regions, dramatically reducing bandwidth and visual artifacts in interactive applications.

## The Differential Rendering Pipeline

The core algorithm resides in the `doRender` method of the `TUI` class. It follows a structured nine-step process to determine the minimal set of terminal commands required to transition from the previous frame to the current one.

### Snapshot Comparison Strategy

At the heart of the system lies the **`previousLines`** array, which stores the last rendered screen content as an array of strings. When `TUI.render()` executes, it generates a new array called `newLines` representing the current component hierarchy. The engine performs a linear scan comparing these two arrays to identify **`firstChanged`** and **`lastChanged`** indices (lines 557-574). This boundary detection determines exactly which rows require updates.

### Component Tree Rendering and Overlay Composition

Before diffing occurs, the system walks the component hierarchy to produce the base output. The `TUI.render()` method returns the initial line array (lines 699-710). Any active overlays are then composited onto these base lines via **`compositeOverlays`** (lines 558-590), ensuring that pop-ups, modals, or notifications are included in the diff calculation.

### Cursor Marker Detection

To support IME (Input Method Editor) positioning and hardware cursor placement, the renderer searches for a special zero-width escape sequence called **`CURSOR_MARKER`** within the output (lines 326-337). When found, the marker is stripped from the final output and its coordinates are stored for later use in cursor positioning commands.

### Full-Render Shortcuts

The system maintains several conditions that trigger a full buffer rewrite rather than a differential update. If the terminal dimensions changed, this is the first render cycle, or the user explicitly requested a force redraw, the entire screen is written and `previousLines` is completely replaced (lines 528-543). This ensures consistent state during disruptive events.

### Kitty Graphics Protocol Handling

When the changed region contains inline images using the Kitty graphics protocol, the renderer must manage image lifecycle. The system extracts image IDs from the affected rows using **`extractKittyImageIds`** and emits deletion commands for the old images before writing new text (lines 530-548). This prevents ghost images from persisting when content updates.

### Viewport-Aware Fallback Logic

The UI tracks the logical scrollback position via **`previousViewportTop`**. If the first changed line falls above the current viewport (indicating content scrolled out of view), the renderer falls back to a full redraw (lines 604-610). This safety measure prevents cursor positioning errors when attempting to move into regions outside the visible terminal area.

### Emitting Optimized Escape Sequences

For differential updates, the renderer wraps output in synchronized update sequences (`\x1b[?2026h` ... `\x1b[?2026l`) to prevent tearing. For each changed row between `firstChanged` and `lastChanged`, it emits a clear-line command (`\x1b[2K`) followed by the new content. The cursor is positioned using relative motion commands (`\x1b[{n}B` for down, `\x1b[{n}A` for up) to reach the start of the changed region (lines 627-657).

After writing, the system updates its internal state, storing the new snapshot, line IDs, terminal size, and viewport position for the next cycle (lines 665-679).

## Practical Implementation Example

The following TypeScript example demonstrates how to instantiate the TUI and trigger differential renders:

```typescript
import { TUI } from "./packages/tui/src/tui.ts";
import { createTerminal } from "./packages/tui/src/terminal.ts";

/* 1. Create a terminal implementation (wraps stdin/stdout) */
const term = createTerminal();

/* 2. Build the UI hierarchy */
class Hello implements Component {
  render(_: number) { return ["Hello, π!"]; }
  invalidate() {}
}
const tui = new TUI(term);
tui.addChild(new Hello());

/* 3. Start the interactive loop */
tui.start();

/* 4. Request a render whenever the UI changes */
function updateMessage(msg: string) {
  // Mutate component state, then...
  tui.requestRender();   // triggers differential rendering on the next tick
}

```

When `updateMessage()` calls `tui.requestRender()`, the system executes the differential rendering pipeline. If only one line of text changed, only that line receives a `\x1b[2K` (clear) plus new content, dramatically reducing terminal traffic compared to full-screen redraws.

## Key Source Files

The differential rendering system spans several files in the `packages/tui/src` directory:

- **[`packages/tui/src/tui.ts`](https://github.com/earendil-works/pi/blob/main/packages/tui/src/tui.ts)**: Contains the core `TUI` class and the `doRender` method implementing the differential rendering loop and overlay composition.
- **[`packages/tui/src/utils.ts`](https://github.com/earendil-works/pi/blob/main/packages/tui/src/utils.ts)**: Provides text measurement utilities including `visibleWidth` and `sliceByColumn` for ANSI-aware string manipulation.
- **[`packages/tui/src/terminal-image.ts`](https://github.com/earendil-works/pi/blob/main/packages/tui/src/terminal-image.ts)**: Implements Kitty protocol support with `extractKittyImageIds` and `deleteKittyImage` functions used during image diffing.
- **[`packages/tui/test/tui-render.test.ts`](https://github.com/earendil-works/pi/blob/main/packages/tui/test/tui-render.test.ts)**: Houses the test suite verifying differential rendering behavior and full-redraw counts.

## Summary

- The **Earendil π terminal UI** achieves differential rendering by caching the previous screen state in `previousLines` and comparing it against new output.
- The algorithm identifies the first and last changed rows, emitting terminal updates only for that range using optimized ANSI escape sequences.
- **Full redraws** occur when the terminal resizes, on first render, when forcing refresh, or when changes occur outside the current viewport.
- **Kitty graphics protocol** images are explicitly tracked and deleted before text updates to prevent visual artifacts.
- The system uses **synchronized output** wrappers and relative cursor positioning to minimize flicker and I/O overhead.

## Frequently Asked Questions

### What triggers a full redraw instead of differential rendering?

A full redraw occurs in four specific scenarios: when the terminal dimensions change, during the initial render cycle, when the user explicitly forces a refresh, or when the first changed line lies above the current viewport (as tracked by `previousViewportTop`). In these cases, the system writes the entire buffer and replaces the `previousLines` snapshot completely.

### How does the π TUI handle inline images during differential updates?

When changed rows contain Kitty graphics protocol sequences, the renderer extracts image IDs using `extractKittyImageIds` from [`packages/tui/src/terminal-image.ts`](https://github.com/earendil-works/pi/blob/main/packages/tui/src/terminal-image.ts). It emits deletion commands for the old images before writing the new text content, ensuring that outdated images do not persist on screen when the underlying content changes.

### Why does the renderer use a CURSOR_MARKER constant?

The `CURSOR_MARKER` is a special zero-width escape sequence that marks the desired hardware cursor position within the output buffer. During the rendering cycle (lines 326-337 in [`tui.ts`](https://github.com/earendil-works/pi/blob/main/tui.ts)), the system locates and removes this marker, storing its coordinates to position the actual terminal cursor correctly. This mechanism supports IME input and precise cursor placement without visible artifacts.

### What escape sequences optimize the differential rendering output?

The renderer utilizes several ANSI sequences: synchronized update wrappers (`\x1b[?2026h` and `\x1b[?2026l`) to prevent tearing, clear-line commands (`\x1b[2K`) to erase changed rows, and relative cursor motion (`\x1b[{n}B` or `\x1b[{n}A`) to position the cursor at the start of the changed region before writing new content.