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
| Technology | Role |
|---|---|
| React 19 | Component framework for the entire UI layer |
| TypeScript | Type-safe language for all frontend code, running in strict mode |
| xterm.js | Terminal emulator rendering — handles ANSI escape sequences, cursor positioning, scrollback, and selection |
| Monaco Editor | Built-in code editor with syntax highlighting, IntelliSense, and file tabs |
| TanStack Query | Async state management for data fetching, caching, and background refetching |
| react-resizable-panels | Panel 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:
- Create session — The frontend invokes the
create_sessionTauri command with the session type and working directory. - Spawn PTY — The Rust backend uses
portable-ptyto spawn a new PTY process with the requested shell or CLI tool. The PTY is initialized with the terminal dimensions reported by the frontend. - Start reader thread — A dedicated thread begins reading output from the PTY's master file descriptor in a blocking loop.
- IPC output streaming — As the reader thread receives output, it emits Tauri events containing the output data, tagged with the session ID.
- 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.
- 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. - 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.