Skip to main content

Atomic State System

Overview

The Atomic State system is Monolex’s core terminal rendering engine. It solves a fundamental problem: traditional terminals render incomplete data, causing flickering and glitches.
╔═══════════════════════════════════════════════════════════════════════╗
║  PROBLEM SOLVED                                                       ║
╠═══════════════════════════════════════════════════════════════════════╣
║                                                                       ║
║  Traditional Terminal:                                                ║
║                                                                       ║
║    ┌───────┐     bytes     ┌─────────┐     parse      ┌────────┐      ║
║    │  PTY  │ ───────────→ │ xterm.js │ ────────────→ │ RENDER  │      ║
║    └───────┘              └─────────┘                └────────┘       ║
║                                │                                      ║
║                                ▼                                      ║
║                    ┌────────────────────────┐                         ║
║                    │  xterm parses          │                         ║
║                    │  INCOMPLETE sequences  │                         ║
║                    │        GLITCH!         │                         ║
║                    └────────────────────────┘                         ║
║                                                                       ║
╠═══════════════════════════════════════════════════════════════════════╣
║                                                                       ║
║  Atomic State System:                                                 ║
║                                                                       ║
║    ┌───────┐     bytes     ┌──────────┐    complete   ┌────────┐      ║
║    │  PTY  │ ───────────→ │ Rust VTE  │ ───────────→ │ xterm   │      ║
║    └───────┘              │ (Alacritty)│   frame     │ buffer  │      ║
║                           └──────────┘              └────────┘        ║
║                                │                          │           ║
║                    ┌───────────────────────┐        ┌────────┐        ║
║                    │ Only complete frames  │        │ RENDER │        ║
║                    │ are sent to frontend  │        └────────┘        ║
║                    └───────────────────────┘                          ║
║                                                                       ║
╚═══════════════════════════════════════════════════════════════════════╝

Architecture

╔═══════════════════════════════════════════════════════════════════════╗
║                     ATOMIC STATE ARCHITECTURE                         ║
╠═══════════════════════════════════════════════════════════════════════╣
║                                                                       ║
║  ┌─────────────────────┐                                              ║
║  │    PTY Process      │  Shell (zsh, bash, etc.)                     ║
║  │   (pty-daemon)      │  Applications (vim, htop, etc.)              ║
║  └─────────┬───────────┘                                              ║
║            │                                                          ║
║            │ Unix Socket (per session)                                ║
║            ▼                                                          ║
║  ╔═════════════════════════════════════════════════════════╗          ║
║  ║                                                         ║          ║
║  ║   RUST BACKEND (lib.rs + modules/)                      ║          ║
║  ║                                                         ║          ║
║  ║   ┌──────────────────┐      ┌───────────────────┐       ║          ║
║  ║   │  SessionActor    │─────→│   GridWorker      │       ║          ║
║  ║   │  (owns sessions) │      │   (per session)   │       ║          ║
║  ║   └──────────────────┘      └────────┬──────────┘       ║          ║
║  ║                                      │                  ║          ║
║  ║                             ┌────────▼──────────┐       ║          ║
║  ║                             │   AtomicState     │       ║          ║
║  ║                             │                   │       ║          ║
║  ║                             │ ┌───────────────┐ │       ║          ║
║  ║                             │ │ Alacritty VTE │ │       ║          ║
║  ║                             │ └───────────────┘ │       ║          ║
║  ║                             │ ┌───────────────┐ │       ║          ║
║  ║                             │ │ Line Cache    │ │       ║          ║
║  ║                             │ └───────────────┘ │       ║          ║
║  ║                             │ ┌───────────────┐ │       ║          ║
║  ║                             │ │ Diff Engine   │ │       ║          ║
║  ║                             │ └───────────────┘ │       ║          ║
║  ║                             └────────┬──────────┘       ║          ║
║  ║                                      │                  ║          ║
║  ╚═════════════════════════════════════════════════════════╝          ║
║            │                                                          ║
║            │ Tauri emit("pty-grid-{id}")                              ║
║            ▼                                                          ║
║  ╔═════════════════════════════════════════════════════════╗          ║
║  ║   TYPESCRIPT FRONTEND                                   ║          ║
║  ║                                                         ║          ║
║  ║   ┌──────────────────────────────────────────────────┐  ║          ║
║  ║   │   GridBufferInjector                             │  ║          ║
║  ║   │   (atomic-cell-injector.ts)                      │  ║          ║
║  ║   │                                                  │  ║          ║
║  ║   │   * Receives GridUpdate                          │  ║          ║
║  ║   │   * Creates buffer (H + 2V model)                │  ║          ║
║  ║   │   * Injects cells directly into xterm buffer     │  ║          ║
║  ║   │   * Sends ACK back to Rust                       │  ║          ║
║  ║   └──────────────────────────────────────────────────┘  ║          ║
║  ║                        │                                ║          ║
║  ║                        ▼                                ║          ║
║  ║   ┌──────────────────────────────────────────────────┐  ║          ║
║  ║   │   xterm.js (WebGL Renderer)                      │  ║          ║
║  ║   │   buffer.lines[0..H+2V-1] -> Direct injection    │  ║          ║
║  ║   └──────────────────────────────────────────────────┘  ║          ║
║  ╚═════════════════════════════════════════════════════════╝          ║
║                                                                       ║
╚═══════════════════════════════════════════════════════════════════════╝

The H + 2V Buffer Model

The H + 2V formula is the mathematical foundation for scroll consistency.

The Problem

xterm.js calculates scroll position using buffer size. If buffer size does not match the scroll area, you get desync:
┌─────────────────────────────────────────────────────────────────────────┐
│  xterm.js Viewport.ts calculates:                                       │
│                                                                         │
│    scrollAreaHeight = buffer.lines.length x rowHeight                   │
│    maxScrollTop = scrollAreaHeight - viewportHeight                     │
│                                                                         │
│  If buffer.lines.length does not match scrollArea:                      │
│                                                                         │
│    ┌──────────────────┐      ┌──────────────────┐                       │
│    │ buffer.lines = X │  !=  │ scrollArea = Y   │                       │
│    └──────────────────┘      └──────────────────┘                       │
│               │                       │                                 │
│               └───────────┬───────────┘                                 │
│                           ▼                                             │
│                    SCROLL DESYNC!                                       │
│                    - Scroll jumps                                       │
│                    - Thumb size wrong                                   │
│                    - Position calculation broken                        │
└─────────────────────────────────────────────────────────────────────────┘

The Formula

H = history_size (ybase in xterm.js)
V = viewport rows (term.rows)

From Rust:
  total_rows = H + V
  ybase = H

Formula:
  buffer.lines.length = H + 2V

Derivation:
  neededLength = 2 x total_rows - ybase
               = 2 x (H + V) - H
               = 2H + 2V - H
               = H + 2V

Visual Buffer Structure

┌─────────────────────────────────────────────────────────────────────────┐
│  BUFFER STRUCTURE (H + 2V)                                              │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  Index:  0         H-1   H      H+V-1  H+V    H+2V-1                    │
│          │         │     │      │      │      │                         │
│          ▼         ▼     ▼      ▼      ▼      ▼                         │
│  ┌───────┬─────────┬─────┬──────┬──────┬──────┬───────┐                 │
│  │ ##################### │/////////////│@@@@@@@@@@@@@ │                 │
│  │    HISTORY (H)        │  VIEWPORT   │   PADDING    │                 │
│  │    scrollback         │    (V)      │     (V)      │                 │
│  │    actual data        │ actual data │  empty lines │                 │
│  └───────────────────────┴─────────────┴──────────────┘                 │
│                                                                         │
│  Legend:                                                                │
│    #### = History lines (scrollback content)                            │
│    //// = Viewport lines (visible screen content)                       │
│    @@@@ = Padding lines (empty, for scroll consistency)                 │
│                                                                         │
│  Actual content: H + V lines (from Rust)                                │
│  Buffer size:    H + 2V lines (with padding)                            │
│  Padding:        V lines (constant, regardless of H)                    │
└─────────────────────────────────────────────────────────────────────────┘

Safety Proof

┌─────────────────────────────────────────────────────────────────────────┐
│  ARRAY BOUNDS SAFETY PROOF                                              │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  Maximum access pattern in xterm.js:                                    │
│                                                                         │
│    buffer.lines[ydisp + y]                                              │
│    where: ydisp = ybase = H  (at bottom)                                │
│           y = 0..V-1         (viewport rows)                            │
│                                                                         │
│  Maximum index accessed:                                                │
│    max_index = H + (V - 1) = H + V - 1                                  │
│                                                                         │
│  Buffer length:                                                         │
│    buffer.lines.length = H + 2V                                         │
│                                                                         │
│  Safety check:                                                          │
│    H + V - 1 < H + 2V                                                   │
│    V - 1 < 2V                                                           │
│    -1 < V              [ALWAYS TRUE for V > 0]                          │
│                                                                         │
│  Q.E.D: Buffer access is always within bounds                           │
└─────────────────────────────────────────────────────────────────────────┘

Understanding Coordinates: Buffer vs Viewport

╔═══════════════════════════════════════════════════════════════════════════════╗
║  TWO WAYS TO POINT AT THE SAME LINE                                           ║
╠═══════════════════════════════════════════════════════════════════════════════╣
║                                                                               ║
║  Think of a terminal like a very long scroll:                                 ║
║                                                                               ║
║  ┌───────────────────────────────────────────────────────────────────────┐    ║
║  │                                                                       │    ║
║  │  The WHOLE scroll (Buffer)           Your WINDOW (Viewport)           │    ║
║  │  ═════════════════════════           ════════════════════════         │    ║
║  │                                                                       │    ║
║  │  ┌─────────────────────┐                                              │    ║
║  │  │ Line 0              │  ← oldest history                            │    ║
║  │  │ Line 1              │                                              │    ║
║  │  │ Line 2              │                                              │    ║
║  │  │ ...                 │                                              │    ║
║  │  │ Line 999            │                                              │    ║
║  │  ├─────────────────────┤         ┌─────────────────────┐              │    ║
║  │  │ Line 1000           │ ════════│ Row 0 (what you see)│              │    ║
║  │  │ Line 1001           │ ════════│ Row 1               │              │    ║
║  │  │ Line 1002           │ ════════│ Row 2               │              │    ║
║  │  │ ...                 │         │ ...                 │              │    ║
║  │  │ Line 1039           │ ════════│ Row 39              │              │    ║
║  │  └─────────────────────┘         └─────────────────────┘              │    ║
║  │                                                                       │    ║
║  │  BUFFER INDEX                    VIEWPORT ROW                         │    ║
║  │  (absolute position              (what row on your screen)            │    ║
║  │   in the scroll)                                                      │    ║
║  │                                                                       │    ║
║  └───────────────────────────────────────────────────────────────────────┘    ║
║                                                                               ║
╠═══════════════════════════════════════════════════════════════════════════════╣
║                                                                               ║
║  SAME LINE, DIFFERENT NUMBERS:                                                ║
║  ─────────────────────────────                                                ║
║                                                                               ║
║  ┌─────────────────────────────────────────────────────────────────────────┐  ║
║  │                                                                         │  ║
║  │  You're looking at "$ ls -la" on your screen                            │  ║
║  │                                                                         │  ║
║  │  Where is it?                                                           │  ║
║  │                                                                         │  ║
║  │    BUFFER: "It's at line 1005 in the whole scroll"                      │  ║
║  │                                                                         │  ║
║  │    VIEWPORT: "It's at row 5 on screen (6th row you see)"                │  ║
║  │                                                                         │  ║
║  │  Both are correct! Just different reference points.                     │  ║
║  │                                                                         │  ║
║  └─────────────────────────────────────────────────────────────────────────┘  ║
║                                                                               ║
╠═══════════════════════════════════════════════════════════════════════════════╣
║                                                                               ║
║  THE CONVERSION:                                                              ║
║  ───────────────                                                              ║
║                                                                               ║
║  ┌─────────────────────────────────────────────────────────────────────────┐  ║
║  │                                                                         │  ║
║  │     Viewport Row  =  Buffer Index  -  Scroll Position                   │  ║
║  │                                                                         │  ║
║  │     Example:                                                            │  ║
║  │       Buffer Index: 1005                                                │  ║
║  │       Scroll Position: 1000 (you've scrolled 1000 lines up)             │  ║
║  │       Viewport Row: 1005 - 1000 = 5                                     │  ║
║  │                                                                         │  ║
║  └─────────────────────────────────────────────────────────────────────────┘  ║
║                                                                               ║
╠═══════════════════════════════════════════════════════════════════════════════╣
║                                                                               ║
║  WHY THIS MATTERS FOR RENDERING:                                              ║
║  ───────────────────────────────                                              ║
║                                                                               ║
║  When MonoTerm says "refresh rows 5 to 10", it means:                         ║
║                                                                               ║
║    ✓ VIEWPORT rows 5-10 (what you see on screen)                              ║
║    ✗ NOT buffer lines 5-10 (that's ancient history!)                          ║
║                                                                               ║
║  ┌─────────────────────────────────────────────────────────────────────────┐  ║
║  │                                                                         │  ║
║  │  Your screen (40 rows):                                                 │  ║
║  │                                                                         │  ║
║  │    Row 0:  $ cd projects                                                │  ║
║  │    Row 1:  $ git status                                                 │  ║
║  │    Row 2:  On branch main                                               │  ║
║  │    Row 3:  nothing to commit                                            │  ║
║  │    Row 4:  $                                                            │  ║
║  │    Row 5:  █  ← You just typed here (CHANGED)                           │  ║
║  │    Row 6:                                                               │  ║
║  │    ...                                                                  │  ║
║  │    Row 39:                                                              │  ║
║  │                                                                         │  ║
║  │  MonoTerm: "Refresh viewport rows 5-5 only!"                            │  ║
║  │  Result: Only the line you typed on gets redrawn                        │  ║
║  │                                                                         │  ║
║  └─────────────────────────────────────────────────────────────────────────┘  ║
║                                                                               ║
╚═══════════════════════════════════════════════════════════════════════════════╝

Line Fingerprinting (FNV-1a)

The core mechanism for efficient change detection.

How Fingerprinting Works

╔═══════════════════════════════════════════════════════════════════════╗
║  LINE CACHING MECHANISM                                               ║
╠═══════════════════════════════════════════════════════════════════════╣
║                                                                       ║
║   Each line is stored with a precomputed hash:                        ║
║                                                                       ║
║   ┌─────────────────────────────────────────────────────────────┐     ║
║   │  Line Data                                                  │     ║
║   │  ┌──────────┬──────────┬──────────┬──────────┬──────────┐   │     ║
║   │  │ Content  │ FG Color │ BG Color │ Attrs    │ Wrapped? │   │     ║
║   │  └────┬─────┴────┬─────┴────┬─────┴────┬─────┴────┬─────┘   │     ║
║   │       │          │          │          │          │         │     ║
║   │       └──────────┴──────────┴──────────┴──────────┘         │     ║
║   │                          │                                  │     ║
║   │                          ▼                                  │     ║
║   │                   ┌────────────┐                            │     ║
║   │                   │ Hash: u64  │  ← O(1) comparison         │     ║
║   │                   └────────────┘                            │     ║
║   └─────────────────────────────────────────────────────────────┘     ║
║                                                                       ║
║   Comparison: hash1 == hash2  (single u64 comparison)                 ║
║                                                                       ║
╚═══════════════════════════════════════════════════════════════════════╝
Hash includes: Content, colors, text attributes (bold, underline), wrap flag

Performance Comparison

╔═══════════════════════════════════════════════════════════════════════╗
║  COMPARISON ALGORITHM                                                 ║
╠═══════════════════════════════════════════════════════════════════════╣
║                                                                       ║
║  Previous State:              New State:                              ║
║                                                                       ║
║  Line 0: hash=0xABC  =======  Line 0: hash=0xABC   -> SAME            ║
║  Line 1: hash=0xDEF  =======  Line 1: hash=0xDEF   -> SAME            ║
║  Line 2: hash=0x123  =/=/=/=  Line 2: hash=0x456   -> CHANGED (row 2) ║
║  Line 3: hash=0x789  =======  Line 3: hash=0x789   -> SAME            ║
║  Line 4: hash=0xAAA  =/=/=/=  Line 4: hash=0xBBB   -> CHANGED (row 4) ║
║                                                                       ║
║  Result: Partial { start_row: 2, end_row: 4 }                         ║
║                                                                       ║
╠═══════════════════════════════════════════════════════════════════════╣
║  PERFORMANCE GAIN                                                     ║
╠═══════════════════════════════════════════════════════════════════════╣
║                                                                       ║
║  Without hash:                                                        ║
║    40 rows x 120 cols x 3 fields = 14,400 comparisons                 ║
║                                                                       ║
║  With hash:                                                           ║
║    40 rows x 1 u64 = 40 comparisons                                   ║
║                                                                       ║
║  Speedup: 360x faster                                                 ║
╚═══════════════════════════════════════════════════════════════════════╝

DiffHint Enum

The DiffHint tells the frontend exactly how to handle each update.
╔═══════════════════════════════════════════════════════════════════════╗
║  DiffHint: UPDATE TYPE CLASSIFICATION                                 ║
╠═══════════════════════════════════════════════════════════════════════╣
║                                                                       ║
║   ┌─────────────┬────────────────────────────────────────────────┐    ║
║   │  DiffHint   │  Description                                   │    ║
║   ├─────────────┼────────────────────────────────────────────────┤    ║
║   │  Full       │  Rebuild entire buffer (resize, first frame)   │    ║
║   │  Partial    │  Only specific rows changed                    │    ║
║   │  ScrollOnly │  New history lines added (scroll optimization) │    ║
║   │  None       │  No changes, cursor update only                │    ║
║   │  Skip       │  Inconsistent data - do NOT apply              │    ║
║   └─────────────┴────────────────────────────────────────────────┘    ║
║                                                                       ║
╚═══════════════════════════════════════════════════════════════════════╝

Visual Guide

╔═══════════════════════════════════════════════════════════════════════╗
║  DiffHint Visual Guide                                                ║
╠═══════════════════════════════════════════════════════════════════════╣
║                                                                       ║
║  ┌─────────┐   ┌─────────┐   ┌─────────┐   ┌─────────┐                ║
║  │#########│   │.........│   │.........│   │ X X X X │                ║
║  │#########│   │..##.....│   │.........│   │ X X X X │                ║
║  │#########│   │.........│   │...._....│   │ X X X X │                ║
║  │#########│   │.........│   │.........│   │ X X X X │                ║
║  └─────────┘   └─────────┘   └─────────┘   └─────────┘                ║
║     Full        Partial         None          Skip                    ║
║  Render ALL   Render SOME   Cursor only   Discard                     ║
║                                                                       ║
╚═══════════════════════════════════════════════════════════════════════╝

Decision Flow

╔═══════════════════════════════════════════════════════════════════════╗
║  compute_diff() Decision Flow                                         ║
╠═══════════════════════════════════════════════════════════════════════╣
║                                                                       ║
║  Start                                                                ║
║    │                                                                  ║
║    ▼                                                                  ║
║  ┌──────────────────┐                                                 ║
║  │  First frame?    │───── Yes ────────────────────────→ Full         ║
║  └────────┬─────────┘                                                 ║
║           │ No                                                        ║
║           ▼                                                           ║
║  ┌──────────────────┐                                                 ║
║  │  Size mismatch?  │───── Yes ────────────────────────→ Full         ║
║  └────────┬─────────┘                                                 ║
║           │ No                                                        ║
║           ▼                                                           ║
║  ┌──────────────────┐                                                 ║
║  │  History changed?│───── Yes ────────────────────────→ Full         ║
║  └────────┬─────────┘                                                 ║
║           │ No                                                        ║
║           ▼                                                           ║
║  ┌──────────────────┐                                                 ║
║  │Ybase oscillation?│───── Yes ────────────────────────→ Skip         ║
║  │ (glitch detected)│                                                 ║
║  └────────┬─────────┘                                                 ║
║           │ No                                                        ║
║           ▼                                                           ║
║  ┌──────────────────┐                                                 ║
║  │ Compare lines    │                                                 ║
║  │ by hash          │                                                 ║
║  └────────┬─────────┘                                                 ║
║           │                                                           ║
║           ├── No changes ───────────────────────────────  None        ║
║           │                                                           ║
║           ├── < 50% rows changed ───────────────────────  Partial     ║
║           │                                                           ║
║           └── >= 50% rows changed ──────────────────────  Full        ║
║                                                                       ║
╚═══════════════════════════════════════════════════════════════════════╝

Frontend Processing

When the frontend receives a GridUpdate, it follows this decision tree to determine how to display it:
╔═══════════════════════════════════════════════════════════════════════════════╗
║  FRONTEND DISPLAY DECISION FLOW                                               ║
╠═══════════════════════════════════════════════════════════════════════════════╣
║                                                                               ║
║                          ┌────────────────────────┐                           ║
║                          │   Update Received      │                           ║
║                          │   from Backend         │                           ║
║                          └───────────┬────────────┘                           ║
║                                      │                                        ║
║                                      ▼                                        ║
║                    ┌─────────────────────────────────────┐                    ║
║                    │  Is this a corrupted frame?         │                    ║
║                    │  (Skip mode)                        │                    ║
║                    └─────────────────┬───────────────────┘                    ║
║                              yes │         │ no                               ║
║                                  ▼         ▼                                  ║
║                    ┌─────────────────┐  ┌─────────────────────────────┐       ║
║                    │                 │  │  Store data in memory       │       ║
║                    │  DISCARD        │  │  (prepare for display)      │       ║
║                    │  Don't display  │  └─────────────┬───────────────┘       ║
║                    │  Don't confirm  │                │                       ║
║                    │                 │                ▼                       ║
║                    └─────────────────┘  ┌─────────────────────────────┐       ║
║                                         │  Is this a full refresh?    │       ║
║                                         │  (Full mode)                │       ║
║                                         └─────────────┬───────────────┘       ║
║                                               yes │         │ no              ║
║                                                   ▼         ▼                 ║
║                              ┌─────────────────────┐  ┌─────────────────────┐ ║
║                              │                     │  │  Partial update?    │ ║
║                              │  REFRESH ALL        │  │                     │ ║
║                              │  Redraw entire      │  └──────────┬──────────┘ ║
║                              │  screen             │       yes │       │ no   ║
║                              │                     │           ▼       ▼      ║
║                              └─────────────────────┘  ┌─────────┐ ┌─────────┐ ║
║                                                       │ Check   │ │         │ ║
║                                                       │ scroll  │ │ CURSOR  │ ║
║                                                       │ position│ │ ONLY    │ ║
║                                                       └────┬────┘ │ No      │ ║
║                                                            │      │ redraw  │ ║
║                                                   ┌────────┴────┐ └─────────┘ ║
║                                                   │             │             ║
║                                             ┌─────▼─────┐ ┌─────▼─────┐       ║
║                                             │ User is   │ │ User is   │       ║
║                                             │ viewing   │ │ viewing   │       ║
║                                             │ RECENT    │ │ OLD       │       ║
║                                             │ content   │ │ history   │       ║
║                                             └─────┬─────┘ └─────┬─────┘       ║
║                                                   │             │             ║
║                                                   ▼             ▼             ║
║                                             ┌───────────┐ ┌───────────┐       ║
║                                             │           │ │           │       ║
║                                             │ REFRESH   │ │ SKIP      │       ║
║                                             │ Changed   │ │ No redraw │       ║
║                                             │ rows only │ │ (data is  │       ║
║                                             │           │ │ stored)   │       ║
║                                             └───────────┘ └───────────┘       ║
║                                                                               ║
╠═══════════════════════════════════════════════════════════════════════════════╣
║                                                                               ║
║  WHY CHECK SCROLL POSITION?                                                   ║
║  ─────────────────────────────────────────────────────────────────────────    ║
║                                                                               ║
║  If you scrolled up to read old history:                                      ║
║    • New content arrives at the BOTTOM                                        ║
║    • You're NOT looking at the bottom                                         ║
║    • Refreshing would be wasted work                                          ║
║    • Data is still saved (visible when you scroll back down)                  ║
║                                                                               ║
║  ┌───────────────────────────────────────────────────────────────────────┐    ║
║  │                                                                       │    ║
║  │  ┌─────────────────────┐      ┌─────────────────────┐                 │    ║
║  │  │  You are HERE       │      │  New data arrives   │                 │    ║
║  │  │  (reading old logs) │      │  HERE (at bottom)   │                 │    ║
║  │  │         👀           │      │                     │                │    ║
║  │  │  ████████████████   │      │                     │                 │    ║
║  │  │  ████████████████   │      │                     │                 │    ║
║  │  │  ████████████████   │      │                     │                 │    ║
║  │  │                     │      │  ████████████████   │                 │    ║
║  │  │                     │      │  ████████████████   │ ← new content   │    ║
║  │  └─────────────────────┘      └─────────────────────┘                 │    ║
║  │                                                                       │    ║
║  │  Result: Skip refresh (you can't see it anyway!)                      │    ║
║  │          Data is stored, visible when you scroll down                 │    ║
║  │                                                                       │    ║
║  └───────────────────────────────────────────────────────────────────────┘    ║
║                                                                               ║
╚═══════════════════════════════════════════════════════════════════════════════╝

Feed/Pull Cycle

The AtomicState operates on a simple feed/pull cycle with ACK-based flow control.

Public API

┌───────────────────────────────────────────────────────────────────────┐
│  ATOMICSTATE PUBLIC METHODS                                           │
├───────────────────────────────────────────────────────────────────────┤
│                                                                       │
│  feed(&mut self, data: &[u8])                                         │
│       └──► Feed PTY bytes → Simulation layer                          │
│                                                                       │
│  tick(&mut self)                                                      │
│       └──► Check timeouts                                             │
│                                                                       │
│  pull(&mut self) → Option<GridUpdate>                                 │
│       └──► Get update if ready                                        │
│           Blocked when waiting_ack                                    │
│                                                                       │
│  ack(&mut self)                                                       │
│       └──► Acknowledge receipt                                        │
│           Clears waiting_ack flag                                     │
│                                                                       │
│  resize(&mut self, cols, rows, epoch)                                 │
│       └──► Handle resize with epoch ordering                          │
│                                                                       │
└───────────────────────────────────────────────────────────────────────┘

ACK Flow Control

╔═════════════════════════════════════════════════════════════════════════╗
║  ACK HANDSHAKE - BACKPRESSURE MECHANISM                                 ║
╠═════════════════════════════════════════════════════════════════════════╣
║                                                                         ║
║     Rust (GridWorker)                 Frontend (GridBufferInjector)     ║
║            │                                      │                     ║
║            │   ═══════════ GridUpdate ══════════→ │                     ║
║            │        (via Tauri emit)              │                     ║
║            │                                      │                     ║
║            │   ┌────────────────────────┐         │                     ║
║            │   │ waiting_ack = true     │         │   inject()          ║
║            │   └────────────────────────┘         │                     ║
║            │                                      │                     ║
║            │   ←════════════ ACK ═══════════════  │                     ║
║            │        (via Tauri invoke)            │                     ║
║            │                                      │                     ║
║            │   ┌────────────────────────┐         │                     ║
║            │   │ waiting_ack = false    │         │                     ║
║            │   └────────────────────────┘         │                     ║
║            │                                      │                     ║
║            │   (ready for next update)            │                     ║
║                                                                         ║
╚═════════════════════════════════════════════════════════════════════════╝
Why ACK?
  • Prevents flooding frontend with updates
  • Ensures each frame is processed before next
  • Timeout (1s) prevents deadlock if frontend stuck

Sync Detection Mechanisms

AtomicState uses multiple mechanisms to detect frame boundaries:

Priority 1: BSU/ESU

╔═══════════════════════════════════════════════════════════════════════╗
║  BSU/ESU (Begin/End Synchronized Update)                              ║
╠═══════════════════════════════════════════════════════════════════════╣
║                                                                       ║
║  BSU markers:                                                         ║
║    * DCS format:  \x1bP=1s\x1b\\   (ESC P = 1 s ESC \)                ║
║    * DEC mode:    \x1b[?2026h      (CSI ? 2026 h)                     ║
║                                                                       ║
║  ESU markers:                                                         ║
║    * DCS format:  \x1bP=2s\x1b\\   (ESC P = 2 s ESC \)                ║
║    * DEC mode:    \x1b[?2026l      (CSI ? 2026 l)                     ║
║                                                                       ║
║  Timeout: 16ms (one frame at 60Hz)                                    ║
╚═══════════════════════════════════════════════════════════════════════╝

Priority 2: Cursor Hide Pattern

\x1b[?25l ... redraw content ... \x1b[?25h
   │                                  │
   ▼                                  ▼
cursor_hidden = true           cursor_hidden = false

Used by: vim, htop, Ink-based apps
Timeout: 8ms

Priority 3: Implicit Sync

\x1b[2J or \x1b[J detected (screen erase)


┌───────────────────────────┐
│ implicit_syncing = true   │
│ wait 8ms for more content │
└───────────────────────────┘

Performance Results

IPC Data Comparison

ModeLines SentIPC DataBufferGC Pressure
FullH + V~50KBRecreateHigh
Partialdirty only~0.5KBReuseNone
None0~0.1KBReuseNone

Typical Use Cases

Use CaseDiffHintIPCBufferRender
Typing in shellPartial~0.05KBreused2.5%
Running lsFull~50KBrecreated100%
Cursor blinkNone~0.1KBreused0%
vim scrollFull~50KBrecreated100%

Epoch Ordering

The epoch system prevents stale updates after resize events.
╔═══════════════════════════════════════════════════════════════════════╗
║  EPOCH USE CASE: Resize Race Condition Prevention                     ║
╠═══════════════════════════════════════════════════════════════════════╣
║                                                                       ║
║  Time    Event                              Epoch                     ║
║  ────    ─────                              ─────                     ║
║  T1      User triggers resize                                         ║
║  T2      Frontend: currentEpoch = 5           5                       ║
║  T3      Backend: receives resize(epoch=5)                            ║
║  T4      Old GridUpdate arrives (epoch=4)                             ║
║          --> REJECTED (4 < 5)                <── order guarantee      ║
║  T5      New GridUpdate (epoch=5) arrives                             ║
║          --> ACCEPTED (5 >= 5)               OK                       ║
║                                                                       ║
╚═══════════════════════════════════════════════════════════════════════╝
╔═══════════════════════════════════════════════════════════════════════╗
║  EPOCH VALIDATION LOGIC                                               ║
╠═══════════════════════════════════════════════════════════════════════╣
║                                                                       ║
║   Incoming GridUpdate                                                 ║
║        │                                                              ║
║        ▼                                                              ║
║   ┌────────────────────────────┐                                      ║
║   │  update.epoch < current?   │                                      ║
║   └─────────────┬──────────────┘                                      ║
║                 │                                                     ║
║        ┌───────┴───────┐                                              ║
║        │               │                                              ║
║       YES             NO                                              ║
║        │               │                                              ║
║        ▼               ▼                                              ║
║   ┌─────────┐    ┌─────────┐                                          ║
║   │ REJECT  │    │ ACCEPT  │                                          ║
║   │ (stale) │    │ (apply) │                                          ║
║   └─────────┘    └─────────┘                                          ║
║                                                                       ║
╚═══════════════════════════════════════════════════════════════════════╝

Summary

╔═══════════════════════════════════════════════════════════════════════╗
║  ATOMIC STATE SYSTEM SUMMARY                                          ║
╠═══════════════════════════════════════════════════════════════════════╣
║                                                                       ║
║  [OK] FLICKER-FREE RENDERING                                          ║
║       Only complete frames reach the screen                           ║
║                                                                       ║
║  [OK] H + 2V BUFFER MODEL                                             ║
║       Buffer = Content (H+V) + Padding (V)                            ║
║       Padding is constant (~40 lines), not proportional to history    ║
║                                                                       ║
║  [OK] HASH-BASED DIFF                                                 ║
║       O(1) per-line comparison via precomputed hash                   ║
║       360x faster than full cell comparison                           ║
║                                                                       ║
║  [OK] DIFFHINT OPTIMIZATION                                           ║
║       Full, Partial, None, Skip modes                                 ║
║       99.9% IPC reduction for typical typing                          ║
║                                                                       ║
║  [OK] ACK FLOW CONTROL                                                ║
║       Backpressure prevents frontend flooding                         ║
║       Epoch ordering prevents stale updates                           ║
║                                                                       ║
╚═══════════════════════════════════════════════════════════════════════╝

Last updated: 2026-01-17