fix: revert CWD tracker from readLoop — corrupts ANSI escape sequences
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 1m4s

ProcessOutput() in the readLoop was processing every byte of SSH output,
looking for OSC 7 sequences. When these sequences split across read
boundaries (common with 32KB buffer), partial sequences leaked through
and corrupted xterm.js parser state — producing red/green color blocks
instead of text, and characters rendering at wrong widths.

Reverted readLoop to direct passthrough (v0.7.3 behavior). Also removed
shell integration injection (stty -echo + PROMPT_COMMAND) which caused
terminal mode disruption on macOS. Also removed 4px xterm padding that
could cause fitAddon cell width miscalculation.

CWD tracking will be re-implemented via a separate SSH exec channel
that polls pwd without touching the terminal data stream.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Vantz Stockwell 2026-03-17 14:14:52 -04:00
parent bce77e0932
commit c3beb6df6b
2 changed files with 6 additions and 39 deletions

View File

@ -10,7 +10,6 @@
.terminal-container .xterm {
height: 100%;
padding: 4px;
}
.terminal-container .xterm-viewport {

View File

@ -142,23 +142,10 @@ func (s *SSHService) Connect(hostname string, port int, username string, authMet
// Launch goroutine to read stdout and forward data via the output handler
go s.readLoop(sessionID, stdout)
// Inject shell integration for CWD tracking (OSC 7).
// Uses stty -echo to suppress the command from appearing in the terminal,
// then restores echo. A leading space keeps it out of shell history.
go func() {
time.Sleep(500 * time.Millisecond)
// Suppress echo, set PROMPT_COMMAND for bash (zsh uses precmd),
// restore echo, then clear the current line so no visual artifact remains.
injection := " stty -echo 2>/dev/null; " +
ShellIntegrationCommand("bash") + "; " +
"if [ -n \"$ZSH_VERSION\" ]; then " + ShellIntegrationCommand("zsh") + "; fi; " +
"stty echo 2>/dev/null\n"
sshSession.mu.Lock()
if sshSession.Stdin != nil {
_, _ = sshSession.Stdin.Write([]byte(injection))
}
sshSession.mu.Unlock()
}()
// CWD tracking disabled — ProcessOutput() in the readLoop was corrupting
// ANSI escape sequences when OSC 7 sequences split across read boundaries.
// CWD following will be re-implemented via a separate SSH exec channel
// that doesn't touch the terminal data stream.
return sessionID, nil
}
@ -216,32 +203,13 @@ func (s *SSHService) buildHostKeyCallback(hostname string, port int) ssh.HostKey
// (stripping OSC 7 sequences), and calls the output handler with cleaned data.
// It stops when the reader returns an error (typically EOF when the session closes).
func (s *SSHService) readLoop(sessionID string, reader io.Reader) {
// Grab the CWD tracker for this session (if any)
s.mu.RLock()
sess := s.sessions[sessionID]
s.mu.RUnlock()
buf := make([]byte, 32*1024)
for {
n, err := reader.Read(buf)
if n > 0 {
if n > 0 && s.outputHandler != nil {
data := make([]byte, n)
copy(data, buf[:n])
// Process CWD tracking — strips OSC 7 sequences from output
if sess != nil && sess.CWDTracker != nil {
cleaned, newCWD := sess.CWDTracker.ProcessOutput(data)
data = cleaned
// Emit CWD change event if a new path was detected
if newCWD != "" && s.cwdHandler != nil {
s.cwdHandler(sessionID, newCWD)
}
}
if len(data) > 0 && s.outputHandler != nil {
s.outputHandler(sessionID, data)
}
s.outputHandler(sessionID, data)
}
if err != nil {
break