Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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!*
1 change: 0 additions & 1 deletion examples/context-menu/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 0 additions & 2 deletions examples/context-menu/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
1 change: 0 additions & 1 deletion examples/hover-highlight/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 0 additions & 2 deletions examples/hover-highlight/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
23 changes: 8 additions & 15 deletions tea/internal/application/program/tty_control_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand All @@ -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)
}
Expand Down