Files
headquarter/openspec/changes/responsive-terminal/explore.md
T
2026-06-03 08:51:02 +00:00

3.2 KiB

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