Version line
v2 — Go rewrite (1.x), main-v2 (active development)
Exact version
reasonix desktop-v1.3.0-44-gba094b9f
What happened?
Problem
Reasonix TUI was stopped by Unix/macOS job control with:
[1] + 16300 suspended (tty input) reasonix
After returning to the shell, the terminal showed many mouse/terminal escape fragments, for example:
70m0;22;70M32;21;70M32;20;70M...
This looks like the TUI was stopped while alt-screen / raw mode / mouse tracking were still active, leaving the shell terminal in a dirty state.
Screenshot:
Evidence
Process state showed reasonix was stopped, not crashed:
PID PPID PGID TPGID STAT TTY COMMAND
14708 14707 14708 14708 S+ ttys005 -/bin/zsh
16300 14708 16300 14708 T ttys005 reasonix
16305 16300 16300 14708 T ttys005 ...codegraph.js serve --mcp
Key points:
- reasonix was in state T, meaning stopped/suspended.
- reasonix PGID was 16300.
- terminal foreground PGID was 14708, the shell.
- This matches a background process group reading from TTY and receiving SIGTTIN.
Code context
The TUI enables alt-screen and mouse tracking in the non-Termux path:
v.AltScreen = true
v.MouseMode = tea.MouseModeCellMotion
The controller cleanup currently runs only after tea.Program.Run() returns. If the OS stops the process inside Run(), final cleanup does not run.
A targeted search found no explicit handling for:
ctrl+z
tea.Suspend
SIGTTIN
SIGTTOU
SIGTSTP
SIGCONT
Current hypothesis
Likely flow:
1. Reasonix TUI enables alt-screen / raw mode / mouse tracking.
2. The reasonix process group is no longer the terminal foreground process group.
3. Reasonix still tries to read from the TTY.
4. Unix/macOS job control sends SIGTTIN.
5. The process is stopped inside the TUI runtime.
6. Normal cleanup does not run.
7. The shell becomes foreground again while terminal modes are still dirty.
8. Mouse/terminal escape sequences become visible in the shell.
### Steps to reproduce
Proposed fix plan
I plan to split this into three small PRs.
PR 1: Ctrl+Z suspend mitigation
Map Ctrl+Z to Bubble Tea's suspend path.
Goal:
- explicit user suspend releases terminal state before stopping;
- fg can resume the TUI cleanly.
This is a mitigation only. It does not fully cover background tty-read / SIGTTIN.
PR 2: Terminal reset failsafe + idempotent cleanup
Add a conservative terminal reset fallback on final exit/error paths, and make Controller.Close() idempotent.
Goal:
- normal exit leaves the shell terminal usable;
- error exit leaves the shell terminal usable;
- final controller/plugin/job cleanup runs once;
- suspend/resume does not call Controller.Close().
This supports the full fix but does not by itself handle a process already stopped by job control.
PR 3: Unix job-control guard
Add a Unix-only job-control guard for:
SIGTSTP
SIGTTIN
SIGTTOU
SIGCONT
Goal:
- release terminal before job-control stop where possible;
- preserve shell job-control behavior;
- after SIGCONT, restore terminal only after the process group is foreground again;
- cover background tty-read / SIGTTIN cases.
This PR will be gated by a local PTY/job-control POC before merge.
Success criteria
- Ctrl+Z releases terminal before suspend.
- fg resumes TUI cleanly.
- Background tty-read / SIGTTIN does not leave shell-visible terminal escape residue.
- Normal exit and error exit leave terminal usable.
- Resume then exit performs final cleanup once.
- Controller/plugin/job cleanup does not run during suspend/resume.
### OS / platform
macos 15.7
### Relevant logs or output
```shell
Version line
v2 — Go rewrite (1.x), main-v2 (active development)
Exact version
reasonix desktop-v1.3.0-44-gba094b9f
What happened?
Problem
Reasonix TUI was stopped by Unix/macOS job control with: