# Spec: Responsive Web Terminal ## Overview Upgrade the web terminal from a fragile single-shot WebSocket into a resilient, responsive terminal that survives network blips, provides instant typing feedback, restores scrollback on reconnect, and gives users clear visibility into connection health. ## Acceptance Criteria ### AC-1: WebSocket Auto-Reconnection **GIVEN** a terminal is connected to a running instance **WHEN** the WebSocket disconnects (network hiccup, server restart, proxy timeout) **THEN** the client automatically reconnects with exponential backoff (1s, 2s, 4s, 8s, max 30s) **AND** the user sees a reconnection indicator showing attempt count and next retry time **AND** after successful reconnection, the terminal scrollback is restored **AND** a new `docker exec` session is spawned transparently **Test:** Disconnect WiFi for 5s, verify reconnect and scrollback intact. ### AC-2: Heartbeat / Ping-Pong Protocol **GIVEN** a terminal connection is established **WHEN** 15 seconds pass with no data exchanged **THEN** the client sends a `ping` control message **AND** the server responds with a `pong` within 5 seconds **AND** if no `pong` is received within 5 seconds, the client treats the connection as dead and begins reconnection **AND** the server closes WebSockets that have not sent any message (including ping) for 60 seconds **Test:** Block server responses with firewall rule, verify connection declared dead within 20s and reconnection starts. ### AC-3: Local Echo for Reduced Typing Latency **GIVEN** the terminal is in a normal interactive shell **WHEN** the user types printable ASCII characters **THEN** they appear on screen immediately (local echo) without waiting for the server round-trip **AND** when the server sends the authoritative echo back, the client reconciles (deduplicates) **AND** when the server sends a `set_echo_state` control message with `enabled: false` (e.g., for password prompts), local echo is disabled **AND** when `set_echo_state` with `enabled: true` is received, local echo is re-enabled **Test:** Type `echo hello` — characters appear instantly. Run `sudo` — local echo stops during password prompt. ### AC-4: Resize Debouncing **GIVEN** the user is resizing the browser window **WHEN** the terminal dimensions change **THEN** resize events are debounced by 200ms **AND** only the final dimensions after the user stops resizing are sent to the server **AND** at most one resize message is sent per 500ms **Test:** Rapidly resize window 10 times in 1s — verify only 1-2 resize messages sent. ### AC-5: Scrollback Serialization and Restoration **GIVEN** a terminal has been in use with output history **WHEN** a disconnect occurs **THEN** the client serializes the terminal buffer (via xterm-addon-serialize, capped at 10,000 lines) **AND** stores it in `sessionStorage` under key `hq-terminal-{instance_id}` **AND** on successful reconnection, the serialized content is written back into the terminal before new output **AND** a visual divider line indicates "--- Reconnected ---" between old and new output **Test:** Run `ls -la` 50 times, disconnect, reconnect — verify all output visible with divider. ### AC-6: Connection Quality Indicator **GIVEN** the terminal is connected **THEN** the status bar shows: - Green dot + "Connected" when healthy (latency < 100ms) - Yellow dot + "Slow" when latency is 100-500ms - Red dot + "Reconnecting (N)" during reconnection attempts - Gray dot + "Disconnected" when permanently disconnected (max retries exceeded) **AND** hovering the status dot shows a tooltip with round-trip latency (ms) and jitter **AND** the indicator updates every 5 seconds **Test:** Use network throttling in dev tools to simulate slow connection, verify indicator changes. ### AC-7: Graceful Container Exit **GIVEN** a terminal session is active **WHEN** the container process exits (shell terminates, container stops) **THEN** the terminal shows a clear message: "Session ended. The container process has exited." **AND** a "Reconnect" button is shown to spawn a new session **AND** a "Go Back" button navigates to the previous page **AND** the WebSocket closes with code 1000 (normal) instead of an error code **Test:** Run `exit` in the terminal, verify friendly message and buttons appear. ### AC-8: Backend Message Batching **GIVEN** a container process is producing output rapidly **WHEN** the backend PTY produces multiple small reads within a single event loop tick **THEN** the backend batches them into a single WebSocket binary frame **AND** batching does not add more than 16ms of latency **AND** the batch is flushed immediately when no new data is available **Test:** Run `yes | head -n 10000` and measure WebSocket frame count vs. current implementation. ### AC-9: Keyboard Shortcut for Reconnect **GIVEN** the terminal is disconnected **WHEN** the user presses `Ctrl+Shift+R` **THEN** an immediate reconnection attempt is triggered (bypassing backoff) **Test:** Disconnect terminal, press `Ctrl+Shift+R`, verify immediate reconnect attempt. ## API / Protocol Changes ### WebSocket Control Messages (JSON) ```typescript // Client → Server type ClientMessage = | { type: "ping"; id: number } | { type: "pong"; id: number } | { type: "resize"; cols: number; rows: number } | { type: "input"; data: string } // base64-encoded bytes // Server → Client type ServerMessage = | { type: "pong"; id: number } | { type: "status"; status: "connected" | "reconnected" } | { type: "set_echo_state"; enabled: boolean } | { type: "session_ended"; reason: "process_exit" | "container_stop" | "timeout" } ``` ### Binary Frames - Raw terminal output from server → client: binary WebSocket frame (no wrapping) - Raw terminal input from client → server: binary WebSocket frame (no wrapping) - Control messages (resize, ping, etc.): text JSON frames ## Dependencies ### Frontend - `xterm-addon-serialize` — scrollback serialization - `xterm-addon-webgl` (optional) — GPU rendering for smoother feel ### Backend - No new Python dependencies required - Uses existing `asyncio`, `fastapi`, `websockets` ## Non-Functional Requirements - **Latency:** Perceived typing latency < 50ms for local echo characters - **Reconnection time:** < 5 seconds for transient disconnects - **Memory:** Scrollback serialization capped at 10,000 lines (~2-5MB worst case) - **Server load:** Heartbeat interval 15s; max 4 pings/minute per terminal - **Browser support:** Chrome 90+, Firefox 88+, Safari 14+ (all support required WebSocket features) ## Open Questions 1. Should we add a "full screen" button to the terminal chrome? (Nice-to-have, out of scope for this change) 2. Should scrollback be persisted across full page reloads (via `localStorage`) or only during session (`sessionStorage`)? — **Decision:** Use `sessionStorage` to avoid leaking sensitive data. 3. Should the server echo-state detection be automatic (TIOCGWINSZ / stty inspection) or manual (client tells server)? — **Decision:** Server detects via PTY state inspection; sends `set_echo_state` to client.