fix(tui): suspend on ctrl+z (#1371)

Bubble Tea's raw mode clears ISIG, so ctrl+z reached the TUI as a key and
was ignored instead of stopping the job. Return tea.Suspend for it on every
screen, and re-enable mouse tracking on resume, since Bubble Tea's restore
brings back the alt screen but not the mouse mode.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ian-at-strix 2026-09-28 22:44:25 -04:00 • committed by GitHub
parent d6dd9dec26
commit 0c702723aa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 37 additions and 0 deletions

View file

@ -384,7 +384,16 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.vulnerabilityCopyError = msg.err.Error()
}
return m, nil
case tea.ResumeMsg:
// Suspend turns mouse tracking off with the rest of the terminal state,
// but the restore brings back only the alt screen, so turn it back on.
return m, tea.EnableMouseCellMotion
case tea.KeyMsg:
// Raw mode clears ISIG, so ctrl+z arrives as a key instead of SIGTSTP.
// Suspend on every screen, the way a shell job would.
if msg.Type == tea.KeyCtrlZ {
return m, tea.Suspend
}
if m.showSplash {
switch msg.String() {
case "ctrl+c", "ctrl+q", "q", "esc":

View file

@ -1439,3 +1439,31 @@ func TestNarrowTerminalKeepsTheFrameIntact(t *testing.T) {
}
}
}
func TestCtrlZSuspendsFromEveryScreen(t *testing.T) {
for name, prepare := range map[string]func(*Model){
"splash": func(m *Model) { m.showSplash = true },
"modal": func(m *Model) { m.showSplash = false; m.openModal(modalHelp) },
"main": func(m *Model) { m.showSplash = false },
} {
model := New(nil)
prepare(&model)
_, cmd := model.Update(tea.KeyMsg{Type: tea.KeyCtrlZ})
if cmd == nil {
t.Fatalf("%s: ctrl+z returned no command", name)
}
if _, ok := cmd().(tea.SuspendMsg); !ok {
t.Fatalf("%s: ctrl+z did not suspend", name)
}
}
}
func TestResumeReenablesMouse(t *testing.T) {
_, cmd := New(nil).Update(tea.ResumeMsg{})
if cmd == nil {
t.Fatal("resume returned no command")
}
if msg := cmd(); msg != tea.EnableMouseCellMotion() {
t.Fatalf("resume did not re-enable mouse tracking: %#v", msg)
}
}