# Tasks: Responsive Web Terminal ## Review Workload Forecast | Task | Estimated Lines | Stack | Risk | |------|----------------|-------|------| | T1: Protocol types + utilities | ~120 | Frontend | Low | | T2: Backend heartbeat + batching | ~200 | Backend | Medium | | T3: Backend echo detection + graceful exit | ~150 | Backend | Medium | | T4: useTerminalConnection hook | ~280 | Frontend | High | | T5: TerminalComponent rewrite | ~250 | Frontend | High | | T6: Frontend tests | ~180 | Frontend | Low | | T7: Backend tests | ~120 | Backend | Low | | **Total** | **~1,300** | | | **Review recommendation:** This exceeds the 400-line budget. Split into **3 chained PRs**: 1. **PR-1 (Backend foundation):** T1 protocol types + T2 heartbeat/batching + T3 echo/exit + T7 backend tests (~590 lines) 2. **PR-2 (Frontend connection):** T4 useTerminalConnection hook + T6 frontend hook tests (~460 lines) 3. **PR-3 (Terminal UI + integration):** T5 TerminalComponent rewrite + page integration + remaining tests (~250 lines) --- ## Task T1: Protocol Types and Utilities **Files:** - `apps/web/src/types/terminal.ts` (new) - `apps/web/src/utils/terminal-protocol.ts` (new) - `apps/web/package.json` (add `xterm-addon-serialize`) **Description:** Define TypeScript types for all WebSocket control messages. Implement encode/decode helpers that distinguish binary frames (raw terminal I/O) from JSON text frames (control messages). Add base64 encoding for the `input` control message type. Install `xterm-addon-serialize` dependency. **Acceptance:** - All message types from the design spec are represented as TypeScript types - `encodeControlMessage` and `decodeControlMessage` functions handle JSON serialization - `isControlMessage` helper correctly identifies text vs binary frames - `npm install` completes without lockfile conflicts **Depends on:** None **Estimated:** 2 hours --- ## Task T2: Backend Heartbeat and Message Batching **Files:** - `apps/api/src/services/terminal_manager.py` - `apps/api/src/api/terminal.py` **Description:** Rewrite `TerminalManager` read loop to batch small reads into single WebSocket frames (max 16ms buffering). Add heartbeat tracking: server records `last_client_message_at` timestamp, and a background task closes WebSockets idle for 60s. Update `terminal.py` endpoint to accept `ping` control messages and respond with `pong`. Handle binary input frames (not just text JSON). **Acceptance:** - Backend sends batched binary frames; `yes | head -n 10000` produces fewer WebSocket frames than before - Server responds to `ping` with matching `pong` within 100ms - Server closes idle connections after 60s of no client messages - Backend accepts both binary and text WebSocket frames for input - `make test` passes (existing backend tests still green) **Depends on:** None **Estimated:** 3 hours --- ## Task T3: Backend Echo Detection and Graceful Exit **Files:** - `apps/api/src/services/terminal_session.py` - `apps/api/src/services/terminal_manager.py` - `apps/api/src/api/terminal.py` **Description:** Add `termios` PTY inspection to detect ECHO flag state changes. Send `set_echo_state` control messages to client when echo toggles. Detect container process exit (returncode set) and send `session_ended` JSON message before closing WebSocket with code 1000. Distinguish between normal process exit, container stop, and unexpected errors. **Acceptance:** - Running `stty -echo` in terminal triggers `set_echo_state: false` message - Running `stty echo` triggers `set_echo_state: true` message - Running `exit` in shell sends `session_ended: { reason: "process_exit" }` then closes with code 1000 - Stopping container sends `session_ended: { reason: "container_stop" }` - Unexpected errors still close with code 4000 and error message **Depends on:** T2 **Estimated:** 2.5 hours --- ## Task T4: useTerminalConnection Hook **Files:** - `apps/web/src/hooks/use-terminal-connection.ts` (new) **Description:** Implement the core connection hook with: WebSocket lifecycle (open/close/reconnect with exponential backoff), heartbeat (send ping every 15s, timeout after 5s), local echo (write printable ASCII to xterm immediately, deduplicate server echo), resize debouncing (200ms, max 1/500ms), scrollback serialization on disconnect, scrollback restoration on reconnect, connection quality tracking (latency, jitter), manual reconnect bypass. **Acceptance:** - Hook exposes `state`, `sendInput`, `sendResize`, `reconnect`, `onData`, `onControl` - Reconnect backoff: 1s, 2s, 4s, 8s, then max 30s - Max 10 reconnection attempts before giving up - Local echo works for printable ASCII; disabled when echo state is false - Pending echo buffer deduplicates server echo correctly - Pending echo buffer flushes to terminal if it grows > 100 chars - Resize sends at most 1 message per 500ms - `Ctrl+Shift+R` triggers immediate reconnect when disconnected - Scrollback serialized to `sessionStorage` on disconnect, restored on reconnect with divider **Depends on:** T1 **Estimated:** 4 hours --- ## Task T5: TerminalComponent Rewrite **Files:** - `apps/web/src/components/terminal.tsx` (rewrite) - `apps/web/src/pages/terminal.tsx` (minor) - `apps/web/src/styles.css` (add terminal status styles) **Description:** Rewrite `TerminalComponent` to use `useTerminalConnection`. Integrate xterm.js with the hook's `onData` and `onControl` callbacks. Add status bar with connection quality indicator (green/yellow/red/gray dot, latency tooltip, attempt counter). Add reconnect overlay when disconnected. Wire xterm `onData` to hook's `sendInput`. Use `ResizeObserver` for container-level resize detection. Apply xterm-addon-serialize for scrollback. Update page to pass instance ID and handle close. **Acceptance:** - Terminal renders and connects on mount - Status bar shows correct dot color based on connection state - Hovering dot shows latency tooltip - Reconnect overlay appears when max retries exceeded - ResizeObserver triggers fit + resize message (debounced) - Theme colors adapt to dark/light mode - Close button works **Depends on:** T4 **Estimated:** 3 hours --- ## Task T6: Frontend Tests **Files:** - `apps/web/src/utils/terminal-protocol.test.ts` (new) - `apps/web/src/hooks/use-terminal-connection.test.ts` (new) **Description:** Write Vitest tests for protocol utilities (encode/decode all message types, base64 round-trip, frame type detection). Write tests for the connection hook using a mock WebSocket server (or manual mock). Test: reconnect backoff timing, heartbeat timeout detection, local echo deduplication, resize throttling, scrollback serialization round-trip. **Acceptance:** - Protocol tests cover all message types and edge cases - Hook tests cover connection lifecycle without real WebSocket - All tests pass: `cd apps/web && npm test` - Coverage for new code > 80% **Depends on:** T1, T4 **Estimated:** 3 hours --- ## Task T7: Backend Tests **Files:** - `apps/api/tests/unit/test_terminal_session.py` (new) - `apps/api/tests/unit/test_terminal_manager.py` (new) **Description:** Write pytest unit tests for `TerminalSession` (PTY creation, resize, echo detection, process exit detection). Write tests for `TerminalManager` (session creation, batching logic, heartbeat tracking). Use mocks for `os`, `pty`, `termios`, and `asyncio` where appropriate. **Acceptance:** - TerminalSession tests: start, resize, write, read, echo detection, close - TerminalManager tests: create session, read loop batching, heartbeat timeout - All tests pass: `make test` **Depends on:** T2, T3 **Estimated:** 2.5 hours --- ## Task Order and Dependencies ``` T1 ──► T4 ──► T5 ──► PR-3 (Frontend UI) │ └──► T6 (Frontend tests) T2 ──► T3 ──► PR-1 (Backend foundation) │ └──► T7 (Backend tests) ``` **Parallel work possible:** - T1 and T2 can be done in parallel (no dependencies) - T3 and T4 can be done in parallel (T3 depends on T2, T4 depends on T1) - T5 depends on T4 - T6 depends on T4 - T7 depends on T3 ## Chained PR Plan ### PR-1: Backend Foundation **Scope:** T1 (protocol types only) + T2 + T3 + T7 **Files touched:** `apps/api/src/services/terminal_manager.py`, `apps/api/src/services/terminal_session.py`, `apps/api/src/api/terminal.py`, new test files, `apps/web/src/types/terminal.ts`, `apps/web/src/utils/terminal-protocol.ts` **Estimated diff:** ~590 lines **Review focus:** Protocol correctness, heartbeat logic, batching efficiency ### PR-2: Frontend Connection Hook **Scope:** T4 + T6 **Files touched:** `apps/web/src/hooks/use-terminal-connection.ts`, new test files **Estimated diff:** ~460 lines **Review focus:** State machine correctness, local echo algorithm, reconnection logic ### PR-3: Terminal UI Integration **Scope:** T5 **Files touched:** `apps/web/src/components/terminal.tsx`, `apps/web/src/pages/terminal.tsx`, `apps/web/src/styles.css` **Estimated diff:** ~250 lines **Review focus:** UX, accessibility, visual polish, integration with hook **Note:** PR-2 and PR-3 can be developed in parallel if PR-1's protocol types are stable. The hook can be tested against mock protocol types before the backend is merged.