diff --git a/CHANGELOG.md b/CHANGELOG.md index 7dcc889..ce09368 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 --- +## [0.2.3] - 2026-02-06 (PATCH) + +### Fixed + +- **tea**: Fix `execWithTTYControl` defer restoring cooked mode after `Resume()` on Windows + - `defer SetConsoleMode(originalMode)` fired AFTER `Resume()` → `EnterRawMode()`, undoing raw mode + - Symptoms: arrow keys broken, OS echo enabled, line buffering after `ExecProcessWithTTY` + - Fix: removed harmful defer — `Resume()` already restores correct console mode via `EnterRawMode()` + - Affected: Windows Console, Windows Terminal; not affected: MSYS/mintty (falls back to `ExecProcess`) +- **tea**: Fix `errorlint` warning — use `%w` for both errors in `execWithTTYControl` +- **examples**: Cascade `rivo/uniseg` removal from `context-menu` and `hover-highlight` go.mod + +--- + ## [0.2.2] - 2026-02-06 (PATCH) ### Fixed diff --git a/README.md b/README.md index 2393b78..93709d3 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ > Next-generation Terminal User Interface framework for Go -**Status**: ✅ v0.2.1 RELEASED (hotfix) +**Status**: ✅ v0.2.3 STABLE **Organization**: [github.com/phoenix-tui](https://github.com/phoenix-tui) **Go Version**: 1.25+ **Test Coverage**: **91.8%** (Excellent across all modules) @@ -362,15 +362,17 @@ spinner := progress.NewSpinner(progress.SpinnerDots). **Documentation**: See [components/progress/README.md](components/progress/README.md) for full API reference -## What's New in v0.2.1 (Hotfix) +## What's New in v0.2.1–v0.2.3 -**Pipe-based CancelableReader** - fixes critical stdin race on MSYS2/mintty (Git Bash): +**v0.2.3** - Fix `ExecProcessWithTTY` on Windows (defer undid raw mode after Resume) + +**v0.2.1** - Pipe-based CancelableReader (fixes stdin race on MSYS2/mintty): - Instant `Cancel()` on ALL platforms via os.Pipe relay architecture - Double-close protection via `sync.Once` -- Stabilized flaky `InputReaderRestartIdempotent` test +- Removed `rivo/uniseg` from core — `uniwidth v0.2.0` handles all width calculation - 12 new tests for pipe relay and shutdown safety -**See [CHANGELOG.md](CHANGELOG.md) for full v0.2.1 details** +**See [CHANGELOG.md](CHANGELOG.md) for full details** ## What's in v0.2.0 @@ -416,5 +418,5 @@ MIT License - see [LICENSE](LICENSE) file for details --- *Rising from the ashes of legacy TUI frameworks* 🔥 -**v0.2.1 STABLE** ⭐ -*API Quality: 9/10 | 91.8% coverage | 29,000 FPS | Pipe-based stdin for MSYS/mintty!* +**v0.2.3 STABLE** ⭐ +*API Quality: 9/10 | 91.8% coverage | 29,000 FPS | ExecProcessWithTTY fixed!* diff --git a/examples/context-menu/go.mod b/examples/context-menu/go.mod index 379d361..0a33788 100644 --- a/examples/context-menu/go.mod +++ b/examples/context-menu/go.mod @@ -23,7 +23,6 @@ require ( require ( github.com/phoenix-tui/phoenix/core v0.2.0 // indirect github.com/phoenix-tui/phoenix/terminal v0.2.0 // indirect - github.com/rivo/uniseg v0.4.7 // indirect github.com/unilibs/uniwidth v0.2.0 // indirect golang.org/x/sys v0.40.0 // indirect golang.org/x/term v0.39.0 // indirect diff --git a/examples/context-menu/go.sum b/examples/context-menu/go.sum index ed8374c..257029f 100644 --- a/examples/context-menu/go.sum +++ b/examples/context-menu/go.sum @@ -2,8 +2,6 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= -github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/unilibs/uniwidth v0.2.0 h1:HUNZ8aagjHcBIxT2Wq8ijEXoa8XXd/+f+1JnA/0wxB4= diff --git a/examples/hover-highlight/go.mod b/examples/hover-highlight/go.mod index 717ae3d..54c94d4 100644 --- a/examples/hover-highlight/go.mod +++ b/examples/hover-highlight/go.mod @@ -23,7 +23,6 @@ require ( require ( github.com/phoenix-tui/phoenix/core v0.2.0 // indirect github.com/phoenix-tui/phoenix/terminal v0.2.0 // indirect - github.com/rivo/uniseg v0.4.7 // indirect github.com/unilibs/uniwidth v0.2.0 // indirect golang.org/x/sys v0.40.0 // indirect golang.org/x/term v0.39.0 // indirect diff --git a/examples/hover-highlight/go.sum b/examples/hover-highlight/go.sum index ed8374c..257029f 100644 --- a/examples/hover-highlight/go.sum +++ b/examples/hover-highlight/go.sum @@ -2,8 +2,6 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= -github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/unilibs/uniwidth v0.2.0 h1:HUNZ8aagjHcBIxT2Wq8ijEXoa8XXd/+f+1JnA/0wxB4= diff --git a/tea/internal/application/program/tty_control_windows.go b/tea/internal/application/program/tty_control_windows.go index 1cd5da7..9c7395b 100644 --- a/tea/internal/application/program/tty_control_windows.go +++ b/tea/internal/application/program/tty_control_windows.go @@ -38,8 +38,7 @@ type TTYOptions struct { // 4. Create new process group (if requested) // 5. Setup command I/O // 6. Run child process -// 7. Restore original console mode -// 8. Resume TUI (restore raw mode, alt screen, restart input) +// 7. Resume TUI (restore raw mode, alt screen, restart input) // // Returns error if command execution or console control fails. func (p *Program[T]) execWithTTYControl(cmd *exec.Cmd, opts TTYOptions) error { @@ -70,19 +69,12 @@ func (p *Program[T]) execWithTTYControl(cmd *exec.Cmd, opts TTYOptions) error { } // STEP 4: Disable virtual terminal processing (let child control console) - newMode := originalMode &^ windows.ENABLE_VIRTUAL_TERMINAL_INPUT - if err := windows.SetConsoleMode(consoleHandle, newMode); err != nil { + childMode := originalMode &^ windows.ENABLE_VIRTUAL_TERMINAL_INPUT + if err := windows.SetConsoleMode(consoleHandle, childMode); err != nil { // Non-fatal - continue anyway log.Printf("WARNING: SetConsoleMode failed: %v - continuing with original mode", err) } - // Ensure restoration even if cmd.Run() panics - defer func() { - if err := windows.SetConsoleMode(consoleHandle, originalMode); err != nil { - log.Printf("WARNING: failed to restore console mode: %v", err) - } - }() - // STEP 5: Configure child process group (if requested) if opts.CreateProcessGroup { if cmd.SysProcAttr == nil { @@ -99,13 +91,14 @@ func (p *Program[T]) execWithTTYControl(cmd *exec.Cmd, opts TTYOptions) error { // STEP 7: Run child process (BLOCKING) cmdErr := cmd.Run() - // Console mode is restored by defer above - // STEP 8: Resume TUI (restore raw mode, alt screen, restart input, redraw) - // ALWAYS resume, even if command failed + // ALWAYS resume, even if command failed. + // NOTE: No defer SetConsoleMode(originalMode) — Resume() calls EnterRawMode() + // which sets the correct console flags. A defer would fire AFTER Resume(), + // restoring cooked-mode flags and undoing EnterRawMode (see bug report). if err := p.Resume(); err != nil { if cmdErr != nil { - return fmt.Errorf("exec: command failed (%v) and %w", cmdErr, err) + return fmt.Errorf("exec: command failed (%w) and %w", cmdErr, err) } return fmt.Errorf("exec: %w", err) }