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/
60 lines
3.2 KiB
Markdown
60 lines
3.2 KiB
Markdown
# 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
|