From 247c1a3340d6d8bc1d3e47b68c44e6a05730a09f Mon Sep 17 00:00:00 2001 From: GitHub Copilot Date: Wed, 18 Feb 2026 15:09:31 +0000 Subject: [PATCH] Fix idle session breaking resize and input on reconnect MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two bugs introduced in the idle tracker pause commit: 1. handleOutput() short-circuited entirely when idle, skipping connector.OnData() — so terminal output never reached clients even after reconnecting. Fix: still call connector.OnData() during idle; only skip the expensive tracker.Feed(). 2. When a WebSocket client reconnects to an existing session, handleWebSocket never called UpdateConnector(), so the idle flag was never cleared and the tracker stayed paused. Fix: create a fresh connector and call session.UpdateConnector() on reconnect, which clears idle and rebuilds the tracker. --- webterm/coverage_boost_test.go | 23 ++++++++++++----------- webterm/docker_exec_session.go | 1 + webterm/server.go | 7 +++++++ webterm/terminal_session.go | 5 ++++- 4 files changed, 24 insertions(+), 12 deletions(-) diff --git a/webterm/coverage_boost_test.go b/webterm/coverage_boost_test.go index b50a31e..b7193b8 100644 --- a/webterm/coverage_boost_test.go +++ b/webterm/coverage_boost_test.go @@ -791,17 +791,18 @@ t.Fatalf("replay mismatch: %q", got) s.MarkIdle() s.idleSince.Store(time.Now().Add(-idleTrackerThreshold - time.Second).UnixNano()) -// Feed more output while idle — only replay should update -s.handleOutput([]byte(" world")) -if got := string(s.GetReplayBuffer()); got != "hello world" { -t.Fatalf("replay should accumulate while idle: %q", got) -} -conn.mu.Lock() -idleData := len(conn.data) -conn.mu.Unlock() -if idleData != 1 { -t.Fatalf("connector should NOT receive data while idle, got %d calls", idleData) -} + // Feed more output while idle — replay and connector still receive data, + // but the VT parser (tracker) is skipped. + s.handleOutput([]byte(" world")) + if got := string(s.GetReplayBuffer()); got != "hello world" { + t.Fatalf("replay should accumulate while idle: %q", got) + } + conn.mu.Lock() + idleData := len(conn.data) + conn.mu.Unlock() + if idleData != 2 { + t.Fatalf("connector should still receive data while idle, got %d calls", idleData) + } // GetScreenSnapshot should rebuild tracker on-demand snap2 := s.GetScreenSnapshot() diff --git a/webterm/docker_exec_session.go b/webterm/docker_exec_session.go index fc18353..393f112 100644 --- a/webterm/docker_exec_session.go +++ b/webterm/docker_exec_session.go @@ -154,6 +154,7 @@ func (s *DockerExecSession) handleOutput(data []byte) { if ts := s.idleSince.Load(); ts != 0 && time.Since(time.Unix(0, ts)) > idleTrackerThreshold { if len(filtered) > 0 { s.replay.Add(filtered) + connector.OnData(filtered) } return } diff --git a/webterm/server.go b/webterm/server.go index 9eab210..d4df423 100644 --- a/webterm/server.go +++ b/webterm/server.go @@ -515,6 +515,13 @@ func (s *LocalServer) handleWebSocket(w http.ResponseWriter, r *http.Request) { session := s.sessionManager.GetSession(sessionID) if session != nil && session.IsRunning() { sessionCreated = true + // Clear idle state so the output pipeline resumes fully + connector := &localClientConnector{ + server: s, + sessionID: sessionID, + routeKey: routeKey, + } + session.UpdateConnector(connector) replay := daResponsePattern.ReplaceAll(session.GetReplayBuffer(), nil) if len(replay) > 0 { s.enqueueWSFrame(routeKey, websocket.BinaryMessage, replay) diff --git a/webterm/terminal_session.go b/webterm/terminal_session.go index 4c4b377..9d716e8 100644 --- a/webterm/terminal_session.go +++ b/webterm/terminal_session.go @@ -146,9 +146,12 @@ func (s *TerminalSession) handleOutput(data []byte) { s.mu.Unlock() filtered = FilterUnsupportedModes(filtered) if ts := s.idleSince.Load(); ts != 0 && time.Since(time.Unix(0, ts)) > idleTrackerThreshold { - // No client connected — only maintain the replay buffer. + // No client connected — maintain replay buffer but skip VT parser. + // Still call the connector so data flows if a client just reconnected + // before MarkIdle was cleared. if len(filtered) > 0 { s.replay.Add(filtered) + connector.OnData(filtered) } return }