Architecture

Technical architecture overview — how the Rust backend, React frontend, and Tauri IPC layer work together to power Rift Panel.

Overview

Rift Panel is a Tauri desktop application. The architecture splits into two main layers: a Rust backend that handles system-level operations (process management, file system access, native window controls) and a React frontend that renders the user interface. The two layers communicate over Tauri's built-in IPC (inter-process communication) bridge.

This design keeps performance-critical work in native Rust while leveraging the React ecosystem for a rich, interactive UI. The Tauri webview hosts the frontend, and the Rust process runs alongside it as the backend host. Unlike Electron, Tauri does not bundle Chromium — it uses the platform's native webview (WebView2 on Windows, WebKit on macOS and Linux), resulting in significantly smaller binary sizes and lower memory usage.

Backend (Rust)

The Rust backend is the core of Rift Panel's process management. It is responsible for spawning, controlling, and reading output from PTY (pseudo-terminal) processes.

Tauri Commands

All backend functionality is exposed to the frontend through Tauri commands — Rust functions annotated with #[tauri::command]. These commands are registered in the Tauri app builder and invoked from the frontend via invoke(). Commands handle session creation, input writing, session termination, file system operations, and settings management.

PTY Management

Rift Panel uses the portable-pty crate for cross-platform PTY management. When a new session is created, the backend spawns a PTY process with the requested shell or CLI tool (bash, zsh, PowerShell, claude, codex). The PTY provides a full terminal interface — the spawned process sees a real terminal with dimensions, control sequences, and job control, just as if it were running in a native terminal emulator.

Reader Threads

Each active session gets a dedicated reader thread in the Rust backend. The reader thread continuously reads output from the PTY's master file descriptor in a blocking loop. When output is available, the thread processes it and sends it to the frontend over Tauri's event system. This multi-threaded design ensures that heavy output from one session does not block reads from other sessions.

// Simplified reader thread loop
std::thread::spawn(move || {
    let mut buf = [0u8; 4096];
    loop {
        match reader.read(&mut buf) {
            Ok(n) if n > 0 => {
                let text = normalize_output(&buf[..n]);
                app_handle.emit("pty-output", (session_id, text));
            }
            _ => break,
        }
    }
});

Output Normalization

Raw PTY output is normalized before being sent to the frontend. The backend ensures all output is valid UTF-8, replacing invalid byte sequences with the Unicode replacement character. On Windows, bare \r\n line endings from PTY output are normalized to \n for consistent handling by the xterm.js terminal emulator in the frontend.

Frontend (React)

The frontend is a single-page React application rendered inside the Tauri webview. It manages the workspace UI, terminal rendering, and all user interactions.

Core Technologies

TechnologyRole
React 19Component framework for the entire UI layer
TypeScriptType-safe language for all frontend code, running in strict mode
xterm.jsTerminal emulator rendering — handles ANSI escape sequences, cursor positioning, scrollback, and selection
Monaco EditorBuilt-in code editor with syntax highlighting, IntelliSense, and file tabs
TanStack QueryAsync state management for data fetching, caching, and background refetching
react-resizable-panelsPanel layout engine providing drag-to-resize split panels with constraints

Terminal Rendering

Each terminal session is rendered by an xterm.js instance mounted inside a React component. The xterm.js instance receives output data from the Tauri event listener and writes it to the terminal buffer. User keyboard input is captured by xterm.js and sent to the Rust backend via IPC, which writes it to the PTY's input stream. This creates a full-duplex communication loop between the user and the underlying process.

IPC Communication

The frontend and backend communicate over Tauri's IPC bridge. Communication is bidirectional:

  • Frontend to Backend — The frontend calls invoke() to execute Tauri commands. These calls are asynchronous and return promises. Examples include creating a session, writing input to a PTY, resizing a PTY, and reading files.
  • Backend to Frontend — The backend emits events using Tauri's event system. The frontend registers listeners for these events. The primary use case is streaming PTY output from reader threads to xterm.js terminal instances.

Command Handlers

All Tauri command handlers are defined in commands.rs in the src-tauri/src/ directory. Each command is a standalone async or sync function that receives typed parameters, interacts with the application state (managed via Tauri's State extractor), and returns a typed result. Error handling uses Rust's Result type, with errors serialized as strings for the frontend to display.

#[tauri::command]
fn create_session(
    state: State<'_, AppState>,
    session_type: String,
    working_dir: String,
) -> Result<String, String> {
    // Spawn PTY, start reader thread, return session ID
}

Panel System

The workspace layout is represented as a recursive binary tree data structure. Each node in the tree is either a leaf (a visible panel hosting sessions) or a branch (a split point that divides space between two children).

Branch nodes store the split direction (horizontal or vertical) and the size ratio between their two children. Leaf nodes store a list of session references (tabs) and which tab is currently active. This recursive structure allows arbitrary nesting — you can split any panel regardless of how deep it is in the tree.

// Panel tree node (simplified)
type PanelNode =
  | { type: 'leaf'; sessions: Session[]; activeIndex: number }
  | { type: 'branch'; direction: 'horizontal' | 'vertical';
      ratio: number; left: PanelNode; right: PanelNode }

The react-resizable-panels library renders this tree into a nested layout of resizable containers. Each branch node maps to a PanelGroup with two Panel children separated by a PanelResizeHandle. Leaf nodes render the actual session content (terminal, editor, or integration panel).

Session Lifecycle

A session moves through a well-defined lifecycle from creation to termination:

  1. Create session — The frontend invokes the create_session Tauri command with the session type and working directory.
  2. Spawn PTY — The Rust backend uses portable-pty to spawn a new PTY process with the requested shell or CLI tool. The PTY is initialized with the terminal dimensions reported by the frontend.
  3. Start reader thread — A dedicated thread begins reading output from the PTY's master file descriptor in a blocking loop.
  4. IPC output streaming — As the reader thread receives output, it emits Tauri events containing the output data, tagged with the session ID.
  5. xterm.js rendering — The frontend's event listener receives the output and writes it to the corresponding xterm.js terminal instance, which renders it on screen.
  6. User input — Keyboard input captured by xterm.js is sent back to the Rust backend via invoke('write_to_session', ...), which writes it to the PTY's input stream.
  7. Termination — When a session is closed, the frontend invokes kill_session. The backend sends a kill signal to the PTY process, the reader thread exits its loop, and the frontend removes the session from the panel tree.

The session lifecycle is fully managed by the backend. If a PTY process exits on its own (for example, the user types exit in a shell), the reader thread detects the EOF condition and notifies the frontend, which updates the UI accordingly.