> ## Documentation Index
> Fetch the complete documentation index at: https://docs.monolex.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# 5-Tier Grid Mode Architecture

> Grid Mode v2 - Achieving significant CPU reduction through intelligent architecture

# Grid Mode v2: 5-Tier Architecture

Grid Mode v2 combines **native VTE parsing** with **xterm.js WebGL rendering** to achieve **significant CPU reduction** compared to standard xterm.js.

## Performance Comparison

```
Standard xterm.js:          │  Grid Mode v2:
PTY → JS VTE Parser (slow)  │  PTY → Native VTE (fast)
    → DOM Manipulation      │      → Cell Conversion
    → WebGL                 │      → Buffer Injection
                            │      → WebGL
Result: High CPU usage      │  Result: Lower CPU usage
```

## The 5-Tier Architecture

The five processing tiers (1–5) sit above the PTY data source (Tier 0).
Tier 0 is the input stream itself, not a processing stage — the five tiers
below are the stack that transforms raw bytes into rendered glyphs.

```
╔═════════════════════════════════════════════════════════════════════╗
║                         5-TIER ARCHITECTURE                         ║
╠═════════════════════════════════════════════════════════════════════╣
║                                                                     ║
║  TIER 0: PTY Data Stream  (source — not a processing tier)          ║
║  ─────────────────────────────────────────────────────────          ║
║  Unix socket, 4KB chunks, binary + ANSI sequences                   ║
║                                                                     ║
║  TIER 1: Native VTE Parser + AtomicParser                           ║
║  ────────────────────────────────────────────                       ║
║  AtomicParser: BSU/ESU detection, metadata extraction               ║
║  Native Rust: VTE parsing (fast native)                             ║
║                                                                     ║
║  TIER 2: ACK-Based Flow Control                                     ║
║  ───────────────────────────────                                    ║
║  Consumer-driven backpressure (waiting_for_ack, 1s fallback)        ║
║  → 100 PTY chunks → Grid updated 100x → emit 5-10x (ACK-paced)      ║
║                                                                     ║
║  TIER 3: Cell Converter (Rust)                                      ║
║  ─────────────────────────────                                      ║
║  Native Cell → xterm.js XtermCell (3×u32 per cell)                  ║
║                                                                     ║
║  TIER 4: Direct Buffer Injection                                    ║
║  ────────────────────────────────                                   ║
║  GridUpdate → xterm._core._bufferService.buffer                     ║
║                                                                     ║
║  TIER 5: xterm.js WebGL Rendering                                   ║
║  ────────────────────────────────                                   ║
║  GPU-accelerated glyph rendering                                    ║
║                                                                     ║
╚═════════════════════════════════════════════════════════════════════╝
```

## Tier 2: ACK-Based Flow Control

The key innovation that prevents crashes during high-output scenarios (LLM streaming, `cat large_file`).

### The Problem

Without flow control:

* LLM streaming outputs bulk data rapidly
* Each line triggers DOM reflow
* Browser crashes or freezes

### The Solution

Consumer-driven backpressure:

```
╔═════════════════════════════════════════════════════════════════════╗
║  ACK-BASED FLOW CONTROL                                             ║
╠═════════════════════════════════════════════════════════════════════╣
║                                                                     ║
║  let mut waiting_for_ack = false;                                   ║
║  let mut has_pending_data = false;                                  ║
║  const ACK_TIMEOUT_MS: u64 = 1000;  // 1s fallback                  ║
║                                                                     ║
║  PTY Data arrives                                                   ║
║       ↓                                                             ║
║  renderer.process(&data)  // Grid state updated immediately!        ║
║       ↓                                                             ║
║  [waiting_for_ack?]                                                 ║
║       │                                                             ║
║       ├─ YES → has_pending_data = true; continue;                   ║
║       │        (skip emit → no Frontend burden)                     ║
║       │                                                             ║
║       └─ NO → emit(grid_update); waiting_for_ack = true;            ║
║                                                                     ║
║  RAF callback fires (before inject!)                                ║
║       ↓                                                             ║
║  grid_ack() → Backend  ◀── ACK sent before inject                   ║
║       ↓                                                             ║
║  waiting_for_ack = false;                                           ║
║  if has_pending_data → request_full_update() → emit                 ║
║                                                                     ║
║  Result: 100 chunks → 5-10 emits (stable UI)                        ║
║                                                                     ║
╚═════════════════════════════════════════════════════════════════════╝
```

### Key Properties

| Property            | Description                                                    |
| ------------------- | -------------------------------------------------------------- |
| **Consumer-driven** | RAF callback start triggers next emit (not render completion!) |
| **No data loss**    | Grid processes ALL data immediately, only emit is controlled   |
| **1s fallback**     | Prevents deadlock if ACK is lost                               |
| **O(1) memory**     | Uses boolean flags only, no frame queue                        |

> **Note:** ACK is sent at RAF callback start, BEFORE inject(). This provides
> processing-slot backpressure, not frame-level render synchronization. The \~15ms gap between
> ACK and actual render is a deliberate trade-off to prevent GridWorker blocking.

## Tier 3: Cell Format Conversion

Grid Mode v2 converts between two different cell representations:

### Native Cell Structure

```
╔═════════════════════════════════════════════════════════════════╗
║  NATIVE CELL STRUCTURE                                          ║
╠═════════════════════════════════════════════════════════════════╣
║                                                                 ║
║  Cell:                                                          ║
║  ├── Character      Unicode code point (4 bytes)                ║
║  ├── Foreground     Color value                                 ║
║  ├── Background     Color value                                 ║
║  ├── Flags          Cell attributes (bold, italic, etc.)        ║
║  └── Extra          Wide characters, hyperlinks                 ║
║                                                                 ║
║  Color Types:                                                   ║
║  ├── Named          0-15 (black, red, green, etc.)              ║
║  ├── Indexed        16-255 (256-color palette)                  ║
║  └── RGB            True color (24-bit)                         ║
║                                                                 ║
╚═════════════════════════════════════════════════════════════════╝
```

### xterm.js BufferLine Format

```
BufferLine._data = Uint32Array(cols × 3)

Cell 0         Cell 1         Cell 2         ...
┌───┬───┬───┐ ┌───┬───┬───┐ ┌───┬───┬───┐
│ C │ F │ B │ │ C │ F │ B │ │ C │ F │ ...
└───┴───┴───┘ └───┴───┴───┘ └───┴───┴───┘
[0] [1] [2]   [3] [4] [5]   [6] [7] [8]

C = Content (codepoint + width)
F = Foreground (color + flags)
B = Background (color + flags)
```

### Bit-Packing Format

```
Slot 0 - CONTENT:
┌─────────────────────────────────┐
│ codepoint[0:20] │combined│width │
│   21 bits       │  1 bit │2 bits│
└─────────────────────────────────┘

Slot 1 - FG (Foreground):
┌─────────────────────────────────┐
│  color[0:23]  │CM[24:25]│flags  │
│   24 bits     │ 2 bits  │6 bits │
└─────────────────────────────────┘
Flags: INVERSE, BOLD, UNDERLINE, BLINK, INVISIBLE, STRIKETHROUGH

Slot 2 - BG (Background):
Same structure, BG flags: ITALIC, DIM, HAS_EXTENDED, PROTECTED, OVERLINE
```

## Tier 4: Direct Buffer Injection

Instead of using `term.write()`, we inject directly into xterm's internal buffer:

```
╔═════════════════════════════════════════════════════════════════╗
║  DIRECT BUFFER INJECTION STEPS                                  ║
╠═════════════════════════════════════════════════════════════════╣
║                                                                 ║
║  Step 1: Sync Scrollback Position                               ║
║  ────────────────────────────────                               ║
║  Set ybase to match backend scrollback count                    ║
║                                                                 ║
║  Step 2: Inject Cells Directly                                  ║
║  ──────────────────────────────                                 ║
║  For each viewport row:                                         ║
║    For each column:                                             ║
║      ├── Write content (Slot 0)                                 ║
║      ├── Write foreground (Slot 1)                              ║
║      └── Write background (Slot 2)                              ║
║                                                                 ║
║  Step 3: Update Cursor                                          ║
║  ───────────────────────                                        ║
║  Set cursor X and Y from GridUpdate                             ║
║                                                                 ║
║  Step 4: Trigger WebGL Render                                   ║
║  ───────────────────────────────                                ║
║  Call _renderRows(0, rows-1) for immediate repaint              ║
║  (sync render - no 1-frame delay)                               ║
║                                                                 ║
╚═════════════════════════════════════════════════════════════════╝
```

<Note>
  MonoTerm uses `_renderRows()` for synchronous rendering instead of the async `refresh()` API. See [Synchronous Rendering](/terminal/rendering/sync-rendering).
</Note>

### Why Not term.write()?

```
╔═════════════════════════════════════════════════════════════════╗
║  PATH COMPARISON                                                ║
╠═════════════════════════════════════════════════════════════════╣
║                                                                 ║
║  Standard xterm.js:           Grid Mode v2:                     ║
║  ─────────────────            ─────────────                     ║
║  term.write(ansiString)       GridUpdate (from Rust)            ║
║       │                            │                            ║
║       ▼                            ▼                            ║
║  WriteBuffer                   Buffer Injector                  ║
║       │                            │                            ║
║       ▼                            ├── Direct memory write      ║
║  InputHandler.parse()              │   (Content, FG, BG slots)  ║
║  ❌ DUPLICATE PARSING!             │                            ║
║       │                            ▼                            ║
║       ▼                        term.refresh()                   ║
║  BufferSet.update()                │                            ║
║       │                            ▼                            ║
║       ▼                        WebGL Render                     ║
║  RenderService.refresh()                                        ║
║       │                                                         ║
║       ▼                                                         ║
║  WebGL Render                                                   ║
║                                                                 ║
║  ✅ VTE parsing already done in native backend                  ║
║  ✅ No double parsing                                           ║
║  ✅ Direct memory access                                        ║
║                                                                 ║
╚═════════════════════════════════════════════════════════════════╝
```

## Epoch-Based Size Synchronization

Prevents race conditions during resize:

```
╔═════════════════════════════════════════════════════════════════╗
║  EPOCH-BASED SIZE SYNCHRONIZATION                               ║
╠═════════════════════════════════════════════════════════════════╣
║                                                                 ║
║  PROBLEM: Resize Race Condition                                 ║
║  ──────────────────────────────                                 ║
║  User resizes (120×40 → 150×50)                                 ║
║       ↓                                                         ║
║  Old GridUpdates arrive with old size                           ║
║       ↓                                                         ║
║  Injection corruption (data/size mismatch)                      ║
║                                                                 ║
║  SOLUTION: Epoch Counter                                        ║
║  ─────────────────────────                                      ║
║  1. Frontend increments epoch on resize                         ║
║       ↓                                                         ║
║  2. Backend stores epoch with each GridUpdate                   ║
║       ↓                                                         ║
║  3. Injection validates: update.epoch ≥ currentEpoch?           ║
║       │                                                         ║
║       ├── YES → Inject normally                                 ║
║       └── NO → Discard as STALE                                 ║
║                                                                 ║
╚═════════════════════════════════════════════════════════════════╝
```

## Architecture Benefits

| Operation       | Standard xterm     | Grid Mode v2             |
| --------------- | ------------------ | ------------------------ |
| VTE Parsing     | JavaScript (slow)  | Native Rust (fast)       |
| Cell Conversion | N/A                | Direct format conversion |
| Buffer Update   | JS internal        | Direct injection         |
| WebGL Render    | GPU-accelerated    | GPU-accelerated          |
| **Overall**     | **High CPU usage** | **Lower CPU usage**      |

The key advantage is bypassing the JavaScript VTE parser entirely, using a native Rust implementation instead.

## Key Components

```
╔═════════════════════════════════════════════════════════════════╗
║  GRID MODE v2 COMPONENTS                                        ║
╠═════════════════════════════════════════════════════════════════╣
║                                                                 ║
║  Component          Purpose                                     ║
║  ─────────────────  ────────────────────────────────────────    ║
║  AtomicParser       BSU/ESU detection, metadata extraction      ║
║  Cell Converter     Native cell → xterm.js format conversion    ║
║  Buffer Injector    Direct xterm.js buffer manipulation         ║
║  VTE Parser         ANSI/escape sequence parsing (Alacritty)    ║
║                                                                 ║
╚═════════════════════════════════════════════════════════════════╝
```
