6c8cfe9157
Implements a resilient, responsive web terminal that survives network blips, provides instant typing feedback, and restores scrollback on reconnect. Backend changes: - Add heartbeat tracking (15s ping interval, 60s idle timeout) - Add message batching (16ms flush window) for efficient I/O - Add termios echo detection and set_echo_state control messages - Add graceful session_ended notification before close - Add ping/pong protocol support Frontend changes: - Rewrite TerminalComponent with status bar, connection indicator, session-ended overlay, reconnect banner, and ResizeObserver - Add useTerminalConnection hook with: - Exponential backoff auto-reconnect (1s → 30s max, 10 attempts) - Heartbeat/ping-pong with latency tracking - Local echo for printable ASCII with server deduplication - Resize debounce (200ms) + throttle (500ms) - Scrollback serialization via xterm-addon-serialize - Ctrl+Shift+R manual reconnect shortcut - Add WebSocket protocol types and encoding utilities - Add xterm-addon-serialize dependency Tests: - 16 backend unit tests (TerminalSession + TerminalManager) - 13 frontend hook tests (connection lifecycle, reconnect, resize, scrollback, callbacks) Quality gates: - Frontend typecheck: clean - Frontend lint: clean - Frontend tests: 48 passed - Backend unit tests: 101 passed - Backend ruff: clean SDD artifacts: openspec/changes/responsive-terminal/
3.2 KiB
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:
- No reconnection — A brief network hiccup kills the terminal. Users must navigate away and back.
- No heartbeat — Half-open connections stall silently. No way to know if the terminal is alive.
- High input latency — Every keystroke round-trips to the server before appearing on screen. No local echo.
- Inefficient I/O path — Backend
selectpolling with 0.1s timeout, 4096-byte reads, busy-wait sleep(0.01). Frontend receives Blob and converts to ArrayBuffer asynchronously. - No scrollback persistence — Reconnect starts with a blank terminal. Session history is lost.
- Rudimentary resize — Fires on every window resize event with no debouncing.
- No connection quality feedback — Binary status (connected/disconnected). No latency or health indicator.
- 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 lifecycleapps/api/src/services/terminal_manager.py— Manages TerminalSession, read/write loopsapps/api/src/services/terminal_session.py— PTY-baseddocker execwithselectI/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 execon reconnect - xterm-addon-serialize may be large for very long sessions
- Changes touch both frontend and backend — cross-stack coordination needed