Data Flow
How data travels from your shell through the Rust backend to your screen.Complete Data Flow
Copy
╔═══════════════════════════════════════════════════════════════════════╗
║ COMPLETE DATA FLOW ║
╠═══════════════════════════════════════════════════════════════════════╣
║ ║
║ ┌────────────────┐ ║
║ │ Shell/App │ Output: "ls\n" -> colored file listings ║
║ │ (zsh, vim) │ ║
║ └───────┬────────┘ ║
║ │ ║
║ │ PTY master/slave pair ║
║ ▼ ║
║ ┌────────────────┐ ║
║ │ pty-daemon │ Rust daemon (sidecar binary) ║
║ │ │ Reads from PTY, writes to Unix socket ║
║ └───────┬────────┘ ║
║ │ ║
║ │ Unix Socket (per session) ║
║ ▼ ║
║ ╔════════════════════════════════════════════════════════════════╗ ║
║ ║ TAURI BACKEND (Rust) ║ ║
║ ║ ║ ║
║ ║ ┌───────────────┐ ┌───────────────┐ ║ ║
║ ║ │ SessionActor │────────▶│ GridWorker │ ║ ║
║ ║ │ │ spawn │ (tokio task) │ ║ ║
║ ║ │ - owns state │ │ │ ║ ║
║ ║ │ - routes msgs │ │ loop { │ ║ ║
║ ║ └───────────────┘ │ recv msg │ ║ ║
║ ║ │ feed data │ ║ ║
║ ║ │ emit update │ ║ ║
║ ║ │ } │ ║ ║
║ ║ └───────┬───────┘ ║ ║
║ ║ │ ║ ║
║ ║ ┌───────▼───────┐ ║ ║
║ ║ │ AtomicState │ ║ ║
║ ║ │ │ ║ ║
║ ║ │ ┌──────────┐ │ ║ ║
║ ║ │ │ Alacritty│ │◀── VTE Parser ║ ║
║ ║ │ │ Terminal │ │ ║ ║
║ ║ │ └──────────┘ │ ║ ║
║ ║ │ │ │ ║ ║
║ ║ │ ▼ │ ║ ║
║ ║ │ GridUpdate │ ║ ║
║ ║ └───────┬───────┘ ║ ║
║ ║ │ ║ ║
║ ╚════════════════════════════════════╪═══════════════════════════╝ ║
║ │ ║
║ │ Tauri emit (JSON) ║
║ ▼ ║
║ ╔════════════════════════════════════════════════════════════════╗ ║
║ ║ FRONTEND (TypeScript) ║ ║
║ ║ ║ ║
║ ║ ┌───────────────────────────────────────────┐ ║ ║
║ ║ │ GridBufferInjector │ ║ ║
║ ║ │ │ ║ ║
║ ║ │ listen(event) { │ ║ ║
║ ║ │ inject(session, term, payload); │ ║ ║
║ ║ │ invoke("grid_ack", { sessionId }); │ ║ ║
║ ║ │ } │ ║ ║
║ ║ └───────────────────────────────────────────┘ ║ ║
║ ║ │ ║ ║
║ ║ ▼ ║ ║
║ ║ ┌───────────────────────────────────────────┐ ║ ║
║ ║ │ xterm.js │ ║ ║
║ ║ │ │ ║ ║
║ ║ │ Direct buffer injection │ ║ ║
║ ║ │ refreshRows(start, end) │ ║ ║
║ ║ └───────────────────────────────────────────┘ ║ ║
║ ║ │ ║ ║
║ ║ ▼ ║ ║
║ ║ ┌───────────────────────────────────────────┐ ║ ║
║ ║ │ WebGL Renderer │ ║ ║
║ ║ │ │ ║ ║
║ ║ │ GPU -> Screen │ ║ ║
║ ║ └───────────────────────────────────────────┘ ║ ║
║ ║ ║ ║
║ ╚════════════════════════════════════════════════════════════════╝ ║
║ ║
╚═══════════════════════════════════════════════════════════════════════╝
Step-by-Step Breakdown
Step 1: Shell Output
Copy
╔═══════════════════════════════════════════════════════════════════════╗
║ STEP 1: Shell/Application Output ║
╠═══════════════════════════════════════════════════════════════════════╣
║ ║
║ User types: ls ║
║ Shell outputs: ESC[34mfile.txtESC[0m ║
║ ~~~~~~ ~~~~~~ ║
║ blue fg reset ║
║ ║
║ PTY master receives raw bytes as the output stream ║
║ ║
╚═══════════════════════════════════════════════════════════════════════╝
Step 2: GridWorker Event Loop
Copy
╔═══════════════════════════════════════════════════════════════════════╗
║ STEP 2: GridWorker Event Loop ║
╠═══════════════════════════════════════════════════════════════════════╣
║ ║
║ The GridWorker runs as a Tokio async task: ║
║ ║
║ ┌───────────────────────────────────────────────────────────────┐ ║
║ │ │ ║
║ │ loop { │ ║
║ │ select! { │ ║
║ │ // Receive PTY data │ ║
║ │ Data(data) = grid_rx.recv() => { │ ║
║ │ state.feed(&data); // Feed to VTE │ ║
║ │ } │ ║
║ │ │ ║
║ │ // Check timeouts │ ║
║ │ _ = tick_interval.tick() => { │ ║
║ │ state.tick(); │ ║
║ │ │ ║
║ │ // Pull update if ready │ ║
║ │ if let Some(update) = state.pull() { │ ║
║ │ app.emit(&event_name, &update); │ ║
║ │ } │ ║
║ │ } │ ║
║ │ │ ║
║ │ // Receive ACK │ ║
║ │ Ack = grid_rx.recv() => { │ ║
║ │ state.ack(); │ ║
║ │ } │ ║
║ │ } │ ║
║ │ } │ ║
║ │ │ ║
║ └───────────────────────────────────────────────────────────────┘ ║
║ ║
╚═══════════════════════════════════════════════════════════════════════╝
Step 3: VTE Processing
Copy
╔═══════════════════════════════════════════════════════════════════════╗
║ STEP 3: VTE Processing with Priority Detection ║
╠═══════════════════════════════════════════════════════════════════════╣
║ ║
║ Incoming data goes through priority-based sync detection: ║
║ ║
║ ┌───────────────────────────────────────────────────────────────┐ ║
║ │ │ ║
║ │ 1. Accumulate in input stream │ ║
║ │ │ ║
║ │ 2. Check for incomplete ANSI sequence │ ║
║ │ -> If incomplete, wait for more data │ ║
║ │ │ ║
║ │ 3. Process complete data: │ ║
║ │ │ ║
║ │ Priority 1: BSU (Begin Synchronized Update) │ ║
║ │ -> If detected, enter syncing mode │ ║
║ │ │ ║
║ │ Priority 2: Cursor Hide (TUI pattern) │ ║
║ │ -> If detected, wait for cursor show │ ║
║ │ │ ║
║ │ Priority 3: Implicit sync (erase pattern) │ ║
║ │ -> If detected, wait briefly │ ║
║ │ │ ║
║ │ 4. Process through Alacritty VTE Parser │ ║
║ │ -> Escape sequences interpreted │ ║
║ │ -> Grid updated with cell data │ ║
║ │ │ ║
║ │ 5. Mark pending data available │ ║
║ │ │ ║
║ └───────────────────────────────────────────────────────────────┘ ║
║ ║
╚═══════════════════════════════════════════════════════════════════════╝
Step 4: Build GridUpdate
Copy
╔═══════════════════════════════════════════════════════════════════════╗
║ STEP 4: Build GridUpdate ║
╠═══════════════════════════════════════════════════════════════════════╣
║ ║
║ The pull() method checks all gates: ║
║ ║
║ ┌───────────────────────────────────────────────────────────────┐ ║
║ │ │ ║
║ │ if waiting_ack { return None; } // Gate 1 │ ║
║ │ if syncing { return None; } // Gate 2 │ ║
║ │ if cursor_hidden { return None; } // Gate 3 │ ║
║ │ if implicit_syncing { return None; } // Gate 4 │ ║
║ │ if !pending_data { return None; } // Gate 5 │ ║
║ │ │ ║
║ │ // All gates passed -> Build and return update │ ║
║ │ waiting_ack = true; │ ║
║ │ pending_data = false; │ ║
║ │ return Some(build_update()); │ ║
║ │ │ ║
║ └───────────────────────────────────────────────────────────────┘ ║
║ ║
║ build_update() creates: ║
║ ║
║ ┌───────────────────────────────────────────────────────────────┐ ║
║ │ │ ║
║ │ GridUpdate { │ ║
║ │ lines: Vec<LineData>, // All line content │ ║
║ │ ybase: usize, // Scrollback size │ ║
║ │ total_rows: u16, // Total line count │ ║
║ │ diff_hint: DiffHint, // Full/Partial/None │ ║
║ │ cursor_row: u16, // Cursor position │ ║
║ │ cursor_col: u16, │ ║
║ │ } │ ║
║ │ │ ║
║ └───────────────────────────────────────────────────────────────┘ ║
║ ║
╚═══════════════════════════════════════════════════════════════════════╝
Step 5: Cell Conversion
Copy
╔═══════════════════════════════════════════════════════════════════════╗
║ STEP 5: Alacritty Cell -> xterm.js Cell ║
╠═══════════════════════════════════════════════════════════════════════╣
║ ║
║ Alacritty Cell: xterm.js Cell: ║
║ ┌─────────────────────────┐ ┌──────────────────┐ ║
║ │ c: char = 'f' │ │ content: u32 │ ║
║ │ fg: Color = Blue │ convert │ (width+codepoint)│ ║
║ │ bg: Color = Default │ ──────▶ │ │ ║
║ │ flags: Flags = NONE │ │ fg: u32 │ ║
║ └─────────────────────────┘ │ (mode+color) │ ║
║ │ │ ║
║ │ bg: u32 │ ║
║ │ (mode+color) │ ║
║ └──────────────────┘ ║
║ ║
║ Content Format (32-bit): ║
║ ┌──────────────────────────────────────────────────────────┐ ║
║ │ [31:24] │ [23:22] │ [21] │ [20:0] │ ║
║ │ reserved │ width │ is_combined │ codepoint │ ║
║ └──────────────────────────────────────────────────────────┘ ║
║ ║
║ Color Format (32-bit): ║
║ ┌──────────────────────────────────────────────────────────┐ ║
║ │ [31:26] │ [25:24] │ [23:0] │ ║
║ │ flags │ mode │ color value │ ║
║ └──────────────────────────────────────────────────────────┘ ║
║ ║
║ Mode: 0=Default, 1=P16, 2=P256, 3=RGB ║
║ ║
╚═══════════════════════════════════════════════════════════════════════╝
Step 6: Frontend Injection
Copy
╔═══════════════════════════════════════════════════════════════════════╗
║ STEP 6: GridBufferInjector.inject() ║
╠═══════════════════════════════════════════════════════════════════════╣
║ ║
║ ┌───────────────────────────────────────────────────────────────┐ ║
║ │ │ ║
║ │ 1. Epoch validation (reject stale updates) │ ║
║ │ -> If update is old, skip it │ ║
║ │ │ ║
║ │ 2. Ensure xterm.js buffer is sized correctly │ ║
║ │ -> Resize if needed │ ║
║ │ │ ║
║ │ 3. Inject cells directly into buffer │ ║
║ │ -> For each line: │ ║
║ │ For each cell: │ ║
║ │ data[offset] = content │ ║
║ │ data[offset + 1] = fg │ ║
║ │ data[offset + 2] = bg │ ║
║ │ │ ║
║ │ 4. Set buffer state (ybase, ydisp) │ ║
║ │ │ ║
║ │ 5. Update scroll area │ ║
║ │ │ ║
║ │ 6. Trigger render based on DiffHint: │ ║
║ │ -> Full: refreshRows(0, rows - 1) │ ║
║ │ -> Partial: refreshRows(minDirty, maxDirty) │ ║
║ │ -> None: skip refresh │ ║
║ │ │ ║
║ └───────────────────────────────────────────────────────────────┘ ║
║ ║
╚═══════════════════════════════════════════════════════════════════════╝
Timing Diagram
Copy
╔═══════════════════════════════════════════════════════════════════════╗
║ TIME -> ║
╠═══════════════════════════════════════════════════════════════════════╣
║ ║
║ PTY: ─────[data1]────────[data2]─────[data3]───────────── ║
║ │ │ │ ║
║ ▼ ▼ ▼ ║
║ feed(): ──────[*]────────────[*]──────────[*]───────────────── ║
║ │ ║
║ │ complete frame ║
║ ▼ ║
║ VTE: ────────────────────────────────────────[parse]───────── ║
║ │ ║
║ ▼ ║
║ pull(): ────────────────────────────────────────[GridUpdate]─── ║
║ │ ║
║ │ emit ║
║ ▼ ║
║ inject():─────────────────────────────────────────[*]──────────── ║
║ │ ║
║ │ ACK ║
║ ▼ ║
║ ack(): ─────────────────────────────────────────────────[*]─── ║
║ ║
║ render: ──────────────────────────────────────────────────────[*]─ ║
║ ║
║ ◀─────────────────── ~50ms (tick interval) ───────────────▶ ║
║ ║
╚═══════════════════════════════════════════════════════════════════════╝
Error Recovery
Copy
╔═══════════════════════════════════════════════════════════════════════╗
║ ERROR RECOVERY ║
╠═══════════════════════════════════════════════════════════════════════╣
║ ║
║ 1. VTE Panic Recovery ║
║ ────────────────────── ║
║ If Alacritty parser panics, catch and reset: ║
║ - Increment panic counter ║
║ - Create fresh processor ║
║ - Continue processing ║
║ ║
║ 2. ACK Timeout ║
║ ────────────── ║
║ If ACK not received within 1 second: ║
║ - Force continue (don't block forever) ║
║ - Reset waiting_ack flag ║
║ - Resume normal operation ║
║ ║
║ 3. Epoch Validation ║
║ ─────────────────── ║
║ If update epoch is older than current: ║
║ - Reject the stale update ║
║ - Don't apply to buffer ║
║ - Don't send ACK ║
║ ║
║ 4. Skip Invalid Frame ║
║ ────────────────────── ║
║ If DiffHint is 'Skip': ║
║ - Don't apply to buffer ║
║ - Don't send ACK ║
║ - Wait for valid frame ║
║ ║
╚═══════════════════════════════════════════════════════════════════════╝
Summary
Copy
╔═════════════════════════════════════════════════════════════════════╗
║ ║
║ DATA FLOW SUMMARY ║
║ ║
║ ┌───────────────────────────────────────────────────────────────┐ ║
║ │ │ ║
║ │ Shell Output │ ║
║ │ │ │ ║
║ │ ▼ │ ║
║ │ PTY Daemon (Rust sidecar) │ ║
║ │ │ │ ║
║ │ │ Unix Socket │ ║
║ │ ▼ │ ║
║ │ SessionActor -> GridWorker │ ║
║ │ │ │ ║
║ │ ▼ │ ║
║ │ AtomicState (Alacritty VTE) │ ║
║ │ │ │ ║
║ │ │ Five sync gates │ ║
║ │ ▼ │ ║
║ │ GridUpdate (JSON emit) │ ║
║ │ │ │ ║
║ │ ▼ │ ║
║ │ GridBufferInjector │ ║
║ │ │ │ ║
║ │ │ Direct buffer injection │ ║
║ │ ▼ │ ║
║ │ xterm.js + WebGL │ ║
║ │ │ │ ║
║ │ ▼ │ ║
║ │ Your Screen │ ║
║ │ │ ║
║ └───────────────────────────────────────────────────────────────┘ ║
║ ║
║ Key Technologies: ║
║ - Rust + Tokio (async processing) ║
║ - Alacritty (VTE parsing) ║
║ - MPSC channels (lock-free communication) ║
║ - Tauri (IPC) ║
║ - xterm.js + WebGL (GPU rendering) ║
║ ║
╚═════════════════════════════════════════════════════════════════════╝
MonoTerm: Shell to Screen in Microseconds