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.Copy
╔═══════════════════════════════════════════════════════════════════════╗
║ 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
Copy
╔═══════════════════════════════════════════════════════════════════════╗
║ 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:Copy
┌─────────────────────────────────────────────────────────────────────────┐
│ 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
Copy
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
Copy
┌─────────────────────────────────────────────────────────────────────────┐
│ 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
Copy
┌─────────────────────────────────────────────────────────────────────────┐
│ 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
Copy
╔═══════════════════════════════════════════════════════════════════════════════╗
║ 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
Copy
╔═══════════════════════════════════════════════════════════════════════╗
║ 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) ║
║ ║
╚═══════════════════════════════════════════════════════════════════════╝
Performance Comparison
Copy
╔═══════════════════════════════════════════════════════════════════════╗
║ 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.Copy
╔═══════════════════════════════════════════════════════════════════════╗
║ 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
Copy
╔═══════════════════════════════════════════════════════════════════════╗
║ 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
Copy
╔═══════════════════════════════════════════════════════════════════════╗
║ 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:Copy
╔═══════════════════════════════════════════════════════════════════════════════╗
║ 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
Copy
┌───────────────────────────────────────────────────────────────────────┐
│ 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
Copy
╔═════════════════════════════════════════════════════════════════════════╗
║ 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) │ ║
║ ║
╚═════════════════════════════════════════════════════════════════════════╝
- 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
Copy
╔═══════════════════════════════════════════════════════════════════════╗
║ 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
Copy
\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
Copy
\x1b[2J or \x1b[J detected (screen erase)
│
▼
┌───────────────────────────┐
│ implicit_syncing = true │
│ wait 8ms for more content │
└───────────────────────────┘
Performance Results
IPC Data Comparison
| Mode | Lines Sent | IPC Data | Buffer | GC Pressure |
|---|---|---|---|---|
| Full | H + V | ~50KB | Recreate | High |
| Partial | dirty only | ~0.5KB | Reuse | None |
| None | 0 | ~0.1KB | Reuse | None |
Typical Use Cases
| Use Case | DiffHint | IPC | Buffer | Render |
|---|---|---|---|---|
| Typing in shell | Partial | ~0.05KB | reused | 2.5% |
Running ls | Full | ~50KB | recreated | 100% |
| Cursor blink | None | ~0.1KB | reused | 0% |
| vim scroll | Full | ~50KB | recreated | 100% |
Epoch Ordering
The epoch system prevents stale updates after resize events.Copy
╔═══════════════════════════════════════════════════════════════════════╗
║ 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 ║
║ ║
╚═══════════════════════════════════════════════════════════════════════╝
Copy
╔═══════════════════════════════════════════════════════════════════════╗
║ EPOCH VALIDATION LOGIC ║
╠═══════════════════════════════════════════════════════════════════════╣
║ ║
║ Incoming GridUpdate ║
║ │ ║
║ ▼ ║
║ ┌────────────────────────────┐ ║
║ │ update.epoch < current? │ ║
║ └─────────────┬──────────────┘ ║
║ │ ║
║ ┌───────┴───────┐ ║
║ │ │ ║
║ YES NO ║
║ │ │ ║
║ ▼ ▼ ║
║ ┌─────────┐ ┌─────────┐ ║
║ │ REJECT │ │ ACCEPT │ ║
║ │ (stale) │ │ (apply) │ ║
║ └─────────┘ └─────────┘ ║
║ ║
╚═══════════════════════════════════════════════════════════════════════╝
Summary
Copy
╔═══════════════════════════════════════════════════════════════════════╗
║ 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