chore: some small ux improvements

Kujtim Hoxha created

Change summary

internal/tui/components/dialogs/commands/commands.go     |  5 -
internal/tui/components/dialogs/lazygit/lazygit.go       |  1 
internal/tui/components/dialogs/termdialog/termdialog.go | 30 ++++++++-
internal/tui/tui.go                                      |  5 +
4 files changed, 31 insertions(+), 10 deletions(-)

Detailed changes

internal/tui/components/dialogs/commands/commands.go 🔗

@@ -4,6 +4,7 @@ import (
 	"context"
 	"fmt"
 	"os"
+	"os/exec"
 	"slices"
 	"strings"
 
@@ -18,7 +19,6 @@ import (
 	"github.com/charmbracelet/crush/internal/config"
 	"github.com/charmbracelet/crush/internal/csync"
 	"github.com/charmbracelet/crush/internal/pubsub"
-	"github.com/charmbracelet/crush/internal/shell"
 	"github.com/charmbracelet/crush/internal/tui/components/chat"
 	"github.com/charmbracelet/crush/internal/tui/components/core"
 	"github.com/charmbracelet/crush/internal/tui/components/dialogs"
@@ -440,8 +440,7 @@ func (c *commandDialogCmp) defaultCommands() []Command {
 	}
 
 	// Add lazygit command if lazygit is installed.
-	sh := shell.NewShell(nil)
-	if _, _, err := sh.Exec(c.ctx, "which lazygit"); err == nil {
+	if _, err := exec.LookPath("lazygit"); err == nil {
 		commands = append(commands, Command{
 			ID:          "lazygit",
 			Title:       "Open Lazygit",

internal/tui/components/dialogs/lazygit/lazygit.go 🔗

@@ -37,6 +37,7 @@ func NewDialog(ctx context.Context, workingDir string) *termdialog.Dialog {
 		Title:      "Lazygit",
 		LoadingMsg: "Starting lazygit...",
 		Term:       terminal.New(terminal.Config{Context: ctx, Cmd: cmd}),
+		QuitHint:   "q to close",
 		OnClose: func() {
 			if themeConfig != "" {
 				if err := os.Remove(themeConfig); err != nil {

internal/tui/components/dialogs/termdialog/termdialog.go 🔗

@@ -17,8 +17,11 @@ const (
 	// headerHeight is the height of the dialog header (title + padding).
 	headerHeight = 2
 	// fullscreenWidthBreakpoint is the width below which the dialog goes
-	// fullscreen. Matches CompactModeWidthBreakpoint in chat.go.
-	fullscreenWidthBreakpoint = 120
+	// fullscreen.
+	fullscreenWidthBreakpoint = 125
+	// fullscreenHeightBreakpoint is the height below which the dialog goes
+	// fullscreen. Lazygit degrades significantly below 40 rows.
+	fullscreenHeightBreakpoint = 40
 )
 
 // Config holds configuration for a terminal dialog.
@@ -33,6 +36,8 @@ type Config struct {
 	Term *terminal.Terminal
 	// OnClose is called when the dialog is closed (optional).
 	OnClose func()
+	// QuitHint is shown in the header (e.g., "q to close"). If empty, no hint is shown.
+	QuitHint string
 }
 
 // Dialog is a dialog that embeds a terminal application.
@@ -42,6 +47,7 @@ type Dialog struct {
 	loadingMsg string
 	term       *terminal.Terminal
 	onClose    func()
+	quitHint   string
 
 	wWidth     int
 	wHeight    int
@@ -63,6 +69,7 @@ func New(cfg Config) *Dialog {
 		loadingMsg: loadingMsg,
 		term:       cfg.Term,
 		onClose:    cfg.OnClose,
+		quitHint:   cfg.QuitHint,
 	}
 }
 
@@ -102,8 +109,8 @@ func (d *Dialog) handleResize(msg tea.WindowSizeMsg) (util.Model, tea.Cmd) {
 	d.wWidth = msg.Width
 	d.wHeight = msg.Height
 
-	// Go fullscreen when window is below compact mode breakpoint.
-	d.fullscreen = msg.Width < fullscreenWidthBreakpoint
+	// Go fullscreen when window is below size breakpoints.
+	d.fullscreen = msg.Width < fullscreenWidthBreakpoint || msg.Height < fullscreenHeightBreakpoint
 
 	var outerWidth, outerHeight int
 	if d.fullscreen {
@@ -192,7 +199,20 @@ func (d *Dialog) View() string {
 		termContent = d.loadingMsg
 	}
 
-	header := t.S().Base.Padding(0, 1, 1, 1).Render(core.Title(d.title, d.width-2))
+	// Build header with title and optional quit hint on the right.
+	var header string
+	if d.quitHint != "" {
+		hintStyle := t.S().Base.Foreground(t.Secondary)
+		hint := hintStyle.Render(d.quitHint)
+		hintWidth := lipgloss.Width(hint)
+		titleWidth := d.width - 2 - hintWidth - 1 // -1 for space between title and hint
+		title := core.Title(d.title, titleWidth)
+		headerContent := title + " " + hint
+		header = t.S().Base.Padding(0, 1, 1, 1).Render(headerContent)
+	} else {
+		header = t.S().Base.Padding(0, 1, 1, 1).Render(core.Title(d.title, d.width-2))
+	}
+
 	content := lipgloss.JoinVertical(lipgloss.Left, header, termContent)
 
 	dialogStyle := t.S().Base.

internal/tui/tui.go 🔗

@@ -458,8 +458,9 @@ func (a *appModel) handleWindowResize(width, height int) tea.Cmd {
 		cmds = append(cmds, pageCmd)
 	}
 
-	// Update the dialogs
-	dialog, cmd := a.dialog.Update(tea.WindowSizeMsg{Width: width, Height: height})
+	// Update the dialogs with full window dimensions so they can overlay
+	// everything including the status bar.
+	dialog, cmd := a.dialog.Update(tea.WindowSizeMsg{Width: a.wWidth, Height: a.wHeight})
 	if model, ok := dialog.(dialogs.DialogCmp); ok {
 		a.dialog = model
 	}