# Proposal: High-Performance Web Terminal

## Status
**Phase:** proposal → spec → design → tasks → apply  
**Date:** 2026-06-02  
**Owner:** el Gentleman  
**Scope:** Terminal I/O latency, rendering performance, connection stability

## Problem

The current web terminal has noticeable latency on keystrokes, choppy scrolling, and poor performance during output bursts. Users report it feels "slow" compared to VS Code Server's terminal, which feels almost local.

## Goals

| Metric | Current | Target | How Measured |
|--------|---------|--------|--------------|
| Input latency (keypress → char visible) | ~110ms | < 16ms (1 frame) | `term.write()` timestamp diff |
| Output throughput (`cat /dev/zero`) | ~200KB/s | > 1 MB/s | Bytes/sec over 5s |
| Resize latency | ~200ms | < 50ms | Time from resize msg to shell SIGWINCH |
| Reconnection + replay | ~2s | < 300ms | Time from WS open to first rendered char |
| Frame drops during `yes` | Many | 0 | `requestAnimationFrame` counter |

## Non-Goals

- Changing the terminal UI/UX (chrome, controls, tabs)
- Adding new terminal features (search, multi-cursor, etc.)
- Changing authentication or session persistence model
- Supporting non-Docker container runtimes

## Constraints

- Must work with existing tool instance lifecycle (docker containers)
- Must preserve WebSocket-based architecture
- Must preserve session persistence across reconnections
- Must work in both development and production compose setups

## Solution Overview

Replace the blocking `select.select()` PTY read loop with asyncio-native event-driven I/O. Replace `docker exec` subprocess with Docker Engine API attach. Switch WebSocket to binary mode. Add output batching. Add WebGL renderer.

## Key Decisions

1. **Keep `docker exec` for now** — Docker SDK attach doesn't support PTY mode as cleanly. We can optimize the subprocess approach with proper fd handling.
2. **Use `asyncio.add_reader()`** — Native asyncio event-driven fd reading eliminates polling latency.
3. **Binary WebSocket frames** — `ws.binaryType = "arraybuffer"` eliminates Blob conversion overhead.
4. **WebGL renderer with DOM fallback** — GPU acceleration where available, graceful fallback.
5. **Output batching with 2ms window** — Collect small writes before sending to reduce frame overhead.
6. **Flow control v2** — Client acknowledges processed bytes; server pauses reads when buffer is full.

## Risks

- **Event loop blocking**: `asyncio.add_reader()` on a PTY fd may not work on all platforms (should work on Linux)
- **WebGL compatibility**: Some GPUs/drivers may fail WebGL context creation
- **Docker exec subprocess**: Still adds overhead; may revisit Docker API attach in future
- **Full rewrite**: Large change surface; thorough testing required

## Acceptance Criteria

- [ ] Input latency < 16ms measured with synthetic benchmark
- [ ] Output throughput > 1 MB/s measured with `cat /dev/zero`
- [ ] Resize latency < 50ms
- [ ] Reconnection + replay < 300ms
- [ ] No frame drops during `yes` command
- [ ] All existing terminal tests pass
- [ ] WebGL renderer loads successfully on modern browsers
- [ ] Graceful fallback to DOM renderer if WebGL fails
- [ ] Flow control prevents memory bloat on `cat /dev/urandom | base64`

## Related

- `openspec/explorations/terminal-responsiveness.md` — Detailed bottleneck analysis
