feat(ui): transparent mode (#2087)

Carlos Alexandro Becker created

optional transparent mode.

this is enabled by default on apple terminal as it doesn't reset
properly.

refs #1140
refs #1137

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

Change summary

internal/config/config.go |  1 +
internal/config/load.go   |  7 +++++++
internal/ui/model/ui.go   | 12 ++++++++++--
3 files changed, 18 insertions(+), 2 deletions(-)

Detailed changes

internal/config/config.go 🔗

@@ -204,6 +204,7 @@ type TUIOptions struct {
 	//
 
 	Completions Completions `json:"completions,omitzero" jsonschema:"description=Completions UI options"`
+	Transparent *bool       `json:"transparent,omitempty" jsonschema:"description=Enable transparent background for the TUI interface,default=false"`
 }
 
 // Completions defines options for the completions UI.

internal/config/load.go 🔗

@@ -62,6 +62,11 @@ func Load(workingDir, dataDir string, debug bool) (*Config, error) {
 		assignIfNil(&cfg.Options.TUI.Completions.MaxItems, items)
 	}
 
+	if isAppleTerminal() {
+		slog.Warn("Detected Apple Terminal, enabling transparent mode")
+		assignIfNil(&cfg.Options.TUI.Transparent, true)
+	}
+
 	// Load known providers, this loads the config from catwalk
 	providers, err := Providers(cfg)
 	if err != nil {
@@ -792,3 +797,5 @@ func GlobalSkillsDirs() []string {
 		filepath.Join(configBase, "agents", "skills"),
 	}
 }
+
+func isAppleTerminal() bool { return os.Getenv("TERM_PROGRAM") == "Apple_Terminal" }

internal/ui/model/ui.go 🔗

@@ -127,6 +127,8 @@ type UI struct {
 	height int
 	layout layout
 
+	isTransparent bool
+
 	focus uiFocusState
 	state uiState
 
@@ -296,8 +298,12 @@ func New(com *common.Common) *UI {
 	// set initial state
 	ui.setState(desiredState, desiredFocus)
 
+	opts := com.Config().Options
+
 	// disable indeterminate progress bar
-	ui.progressBarEnabled = com.Config().Options.Progress == nil || *com.Config().Options.Progress
+	ui.progressBarEnabled = opts.Progress == nil || *opts.Progress
+	// enable transparent mode
+	ui.isTransparent = opts.TUI.Transparent != nil && *opts.TUI.Transparent
 
 	return ui
 }
@@ -1884,7 +1890,9 @@ func (m *UI) Draw(scr uv.Screen, area uv.Rectangle) *tea.Cursor {
 func (m *UI) View() tea.View {
 	var v tea.View
 	v.AltScreen = true
-	v.BackgroundColor = m.com.Styles.Background
+	if !m.isTransparent {
+		v.BackgroundColor = m.com.Styles.Background
+	}
 	v.MouseMode = tea.MouseModeCellMotion
 	v.WindowTitle = "crush " + home.Short(m.com.Config().WorkingDir())