# Explore: Responsive Web Terminal ## Problem Statement The current web terminal feels sluggish and fragile compared to a local terminal session. Key pain points: 1. **No reconnection** — A brief network hiccup kills the terminal. Users must navigate away and back. 2. **No heartbeat** — Half-open connections stall silently. No way to know if the terminal is alive. 3. **High input latency** — Every keystroke round-trips to the server before appearing on screen. No local echo. 4. **Inefficient I/O path** — Backend `select` polling with 0.1s timeout, 4096-byte reads, busy-wait sleep(0.01). Frontend receives Blob and converts to ArrayBuffer asynchronously. 5. **No scrollback persistence** — Reconnect starts with a blank terminal. Session history is lost. 6. **Rudimentary resize** — Fires on every window resize event with no debouncing. 7. **No connection quality feedback** — Binary status (connected/disconnected). No latency or health indicator. 8. **No graceful container exit handling** — Process death closes WebSocket with a generic error. ## Current Architecture ### Frontend - `apps/web/src/components/terminal.tsx` — xterm.js v5.3.0 with FitAddon and WebLinksAddon - WebSocket to `/ws/tool-instances/{instance_id}/terminal` - Receives Blob (binary) and string (JSON control) messages - Sends raw bytes for input, JSON for resize - Basic status: connecting | connected | disconnected | error ### Backend - `apps/api/src/api/terminal.py` — FastAPI WebSocket endpoint, auth, session lifecycle - `apps/api/src/services/terminal_manager.py` — Manages TerminalSession, read/write loops - `apps/api/src/services/terminal_session.py` — PTY-based `docker exec` with `select` I/O - Protocol: raw bytes for terminal I/O, JSON for resize control messages ### Gaps vs. Local Terminal Feel | Aspect | Local Terminal | Current Web Terminal | |--------|---------------|----------------------| | Keystroke feedback | Immediate (kernel TTY) | Round-trip (~50-200ms) | | Network resilience | N/A (local) | Dies on any disconnect | | Scrollback | Persistent | Lost on reconnect | | Resize | Instant | Undebounced, may spam | | Health visibility | Always local | Binary connected/disconnected | | Large output | Buffered by kernel | Select polling, 4KB chunks | ## Opportunities - **WebSocket reconnection with exponential backoff** and session token for continuity - **Heartbeat/ping-pong** to detect half-open connections within seconds - **Local echo optimization** for printable characters (with server-side authoritative sync) - **Message batching** on backend to reduce WebSocket frame overhead - **Scrollback serialization** via xterm-addon-serialize to restore on reconnect - **Resize debouncing** to avoid flooding the server - **Connection quality indicator** (latency, jitter) in the terminal chrome - **Graceful handling** of container exit with clear user messaging ## Risks - Adding heartbeat may increase server load with many concurrent terminals - Local echo requires careful handling of password prompts and special modes - Reconnecting to a docker exec PTY is not natively resumable — new `docker exec` on reconnect - xterm-addon-serialize may be large for very long sessions - Changes touch both frontend and backend — cross-stack coordination needed