Operational context for anyone changing the WebSocket PTY server, reconnect flow, or session lifecycle.
command.New wires cfg.Context to engine teardown. The WebSocket request context is cancelled as soon as the browser disconnects. If you pass conn.Context() (or any WS-bound context), the engine cancels immediately, the PTY dies, and reconnect by session_id cannot work. Use a detached context (this codebase uses context.Background()); teardown is terminal.Terminal.Close, the output pump exiting, and session registry idle eviction.
- New PTYs are registered with an id; the Connect ack carries
session_idfor the client to store. - Reconnect: client sends
TypeConnectwith the samesession_id. Server looks up the entry, sends Connect ack, replays buffered PTY output (and optional key tail), thenAttachWriterso a single pump keeps reading the PTY and writes to the current WebSocket. - Idle TTL: when the WebSocket is gone,
noteDisconnectedsets a deadline (SessionRegistryConfig.TTL). Reconnect clears it. If the deadline passes without reconnect, the entry is evicted: log → close attached socket →session.Close()→ log. - Sweep runs on an interval;
LookupSession/Getalso evict lazily if the deadline has passed (same teardown helper as sweep). - Default idle retention in
Serveis 60s whenConfig.SessionIdleRetentionis zero; theserverCLI exposes--session-idle-retention(duration string, e.g.10s,5m) and envGO_ZOOX_TERMINAL_SESSION_IDLE_RETENTION.
host.Terminal.Close in go-zoox/command historically only closed the PTY master FD. Process.Kill() on the shell PID still leaves /bin/sh -c "…" children in the same process group alive. Teardown must signal the process group (e.g. syscall.Kill(-pid, SIGKILL) with fallback). Keep command at a version that includes that host Close behavior (tests live under command/engine/host/terminal_close_test.go).
- If the pump cannot write to the WebSocket, it clears the writer, calls
noteDisconnected, and closes the socket so the client does not stay “connected” with a dead pump. TypeKeyshould surfacesession.Writeerrors and close the connection when the PTY is gone.
- Rendering: use xterm’s default canvas renderer only — do not load
xterm-addon-webglin this page. WebGL often produces a blank/black terminal under Chrome device emulation and on many mobile GPUs. - Layout:
bodyis a column flex container and#terminalusesflex: 1; min-height: 0so the fit addon gets a non-zero size on narrow viewports (pureheight: 100%chains can collapse). After Connect,scheduleFitAfterLayout()runsfitimmediately, onrAF, and at 50/200/500 ms so DevTools viewport changes settle. - Mobile-style disconnect UI: on unclean WebSocket close, show a modal (Chinese copy + 「重新连接」) when
useMobileDisconnectOverlay()is true at that moment (not only at page load):(max-width: 768px)or(pointer: coarse)or(hover: none). This matches real phones/tablets and Chrome DevTools device emulation (oftenpointer: finebut narrow viewport /hover: none). Otherwise only the red “Connection Closed” line in xterm. - Reconnect closes the existing socket with code 1000 when needed, then opens a new WebSocket;
sessionStoragestill holdssession_idso the server can restore the PTY when within idle TTL. - Page Visibility: when
document.visibilityStatebecomesvisibleand the WebSocket isCLOSED, the page automatically callsopenWebSocket()(short debounce). Hides the mobile disconnect modal if it was open. - Reconnect UI: when opening the socket with a stored
session_idand xterm is already mounted, the client callsterm.clear()before sendingConnectso server replay does not stack on stale local scrollback.
go test ./...For reconnect and TTL behavior, prefer exercising server integration manually or extending server tests; registry unit tests cover registration, expiry, and writer binding.