architecture

Overall stack and module shape for nyxterm. Load when touching Tauri commands, PTY wiring, frontend↔backend IPC, or xterm rendering.

SKILL.md

GitHub

Architecture

Stack

  • Backend: Rust 2021+, Tauri 2, portable-pty.
  • Frontend: React 18 + TypeScript 5 + xterm.js (with fit, search, webgl, web-links, serialize addons).
  • IPC: Tauri invoke() for commands; Tauri channels for binary streams (no JSON encoding of PTY output).

Module shape (target)

nyxterm/
├── src-tauri/src/
│   ├── modules/
│   │   ├── pty/          # spawn, reader, flusher, waiter, signals, OSC filter
│   │   ├── multiplex/    # panes, splits, sessions
│   │   ├── shell/        # shell init (ZDOTDIR injection like Ghostty/Terax)
│   │   ├── fs/           # file ops for the AI harness
│   │   ├── theme/        # palette emit + theme reload
│   │   └── ai/           # provider routing, MCP client
│   ├── lib.rs            # app init, command registration
│   └── main.rs
└── src/
    └── modules/
        ├── terminal/     # TerminalPane, xterm wiring, renderer pool
        ├── multiplex/    # pane grid UI
        ├── ai/           # chat panel, tool calls
        ├── command-palette/
        └── settings/

PTY wiring (the pattern we copy from terax-ai)

Three concurrent threads per PTY session:

  • Reader: blocking read of 16 KiB chunks, push through a DA filter, buffer with backpressure cap (4 MiB).
  • Flusher: coalesces bursts (4 ms window, 50 ms idle ceiling). Emits ArrayBuffer chunks to the frontend channel.
  • Waiter: blocks on child exit, drains the reader, emits final buffer + exit code.

See pty-handling for the gotchas (SIGWINCH, ConPTY, OSC 7 trust).

IPC contract

Commands (frontend → backend):

#[tauri::command] fn pty_open(cols: u16, rows: u16, cwd: Option<String>, env: WorkspaceEnv) -> SessionId
#[tauri::command] fn pty_write(session_id: SessionId, data: String)
#[tauri::command] fn pty_resize(session_id: SessionId, cols: u16, rows: u16)
#[tauri::command] fn pty_close(session_id: SessionId)

Channels (backend → frontend):

  • data: ArrayBuffer — raw PTY bytes, no encoding.
  • exit: i32 — exit code on child termination.

Frontend receives ArrayBuffer and writes directly into xterm via term.write(bytes). Do NOT base64-encode — that doubles bandwidth and burns CPU.

Rendering

  • xterm.js with WebglAddon for GPU rendering. Handle context-loss with a 250 ms re-attach (terax-ai pattern).
  • Renderer pool: up to 5 reusable xterm instances kept off-screen. When releasing a slot, track altScreenAtRelease so we can SIGWINCH-kick the next bind for clean TUI repaint.

What lives where

Concern Layer
PTY syscalls, signals Rust (src-tauri/src/modules/pty)
Theme palette source-of-truth Rust emits, frontend listens
Pane layout state Frontend (Zustand or similar), persisted via Engram
AI tool execution (Read/Write/Edit/Bash) Rust (sandboxed where possible)
AI chat UI React
MCP client Rust

When you change this

Update this skill and AGENTS.md's skills table. Architectural changes are SDD-tracked (proposal → design → ...).