on main help sign in
Merge branch 'main' into crush-fantasy
commit322a2957140521f4c67ad571001598e3f3392b7c authorAndrey Nering <andreynering@users.noreply.github.com> dateThu 23 Oct 2025, 14:07 (1 year ago) parent5e1c63f14f2f9c
34 files changed+76−78
go.mod+4−4 go.sum+4−4 internal/cmd/logs.go+5 internal/cmd/root.go+1−4 internal/format/spinner.go+2−2 internal/tui/components/anim/anim.go+2−1 internal/tui/components/chat/chat.go+1−1 internal/tui/components/chat/editor/editor.go+1−1 internal/tui/components/chat/header/header.go+1−1 internal/tui/components/chat/messages/messages.go+3−3 internal/tui/components/chat/messages/tool.go+3−3 internal/tui/components/chat/sidebar/sidebar.go+1−1 internal/tui/components/chat/splash/splash.go+1−1 internal/tui/components/completions/completions.go+1−1 internal/tui/components/core/status/status.go+1−1 internal/tui/components/dialogs/commands/arguments.go+1−1 internal/tui/components/dialogs/commands/commands.go+1−1 internal/tui/components/dialogs/dialogs.go+7−3 internal/tui/components/dialogs/filepicker/filepicker.go+1−1 internal/tui/components/dialogs/models/apikey.go+2−1 internal/tui/components/dialogs/models/models.go+1−1 internal/tui/components/dialogs/permissions/permissions.go+1−1 internal/tui/components/dialogs/quit/quit.go+1−1 internal/tui/components/dialogs/reasoning/reasoning.go+1−1 internal/tui/components/dialogs/sessions/sessions.go+1−1 internal/tui/exp/list/filterable.go+2−1 internal/tui/exp/list/filterable_group.go+2−1 internal/tui/exp/list/grouped.go+1−1 internal/tui/exp/list/items.go+3−2 internal/tui/exp/list/list.go+2−2 internal/tui/exp/list/list_test.go+3−2 internal/tui/page/chat/chat.go+1−1 internal/tui/tui.go+11−26 internal/tui/util/util.go+3−2
go.mod+4−4
@@ -13,7 +13,8 @@ require (
1313 github.com/bmatcuk/doublestar/v4 v4.9.1
1414 github.com/charlievieth/fastwalk v1.0.14
1515 github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1.0.20250820203609-601216f68ee2
16− github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.4.0.20251011205917-3b687ffc1619
16+ github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.5
17+ github.com/charmbracelet/catwalk v0.7.1-0.20251023112313-048e47f1399c
1718 github.com/charmbracelet/fang v0.4.3
1819 github.com/charmbracelet/glamour/v2 v2.0.0-20250811143442-a27abb32f018
1920 github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.3.0.20250917201909-41ff0bf215ea
@@ -49,7 +50,7 @@ require (
4950 mvdan.cc/sh/v3 v3.12.1-0.20250902163504-3cf4fd5717a5
5051)
5152
52−replace charm.land/fantasy => ../../fantasy/main/
53+replace charm.land/fantasy => ../fantasy
5354
5455require (
5556 cloud.google.com/go v0.116.0 // indirect
@@ -78,10 +79,9 @@ require (
7879 github.com/bahlo/generic-list-go v0.2.0 // indirect
7980 github.com/buger/jsonparser v1.1.1 // indirect
8081 github.com/charmbracelet/anthropic-sdk-go v0.0.0-20251022202715-ec1499142678 // indirect
81− github.com/charmbracelet/catwalk v0.7.1-0.20251023112313-048e47f1399c
8282 github.com/charmbracelet/colorprofile v0.3.2
8383 github.com/charmbracelet/go-genai v0.0.0-20251021165952-9befde14ce97 // indirect
84− github.com/charmbracelet/ultraviolet v0.0.0-20250915111650-81d4262876ef
84+ github.com/charmbracelet/ultraviolet v0.0.0-20251017140847-d4ace4d6e731
8585 github.com/charmbracelet/x/cellbuf v0.0.14-0.20250811133356-e0c5dbe5ea4a // indirect
8686 github.com/charmbracelet/x/exp/slice v0.0.0-20250904123553-b4e2667e5ad5
8787 github.com/charmbracelet/x/json v0.2.0 // indirect
go.sum+4−4
@@ -78,8 +78,8 @@ github.com/charmbracelet/anthropic-sdk-go v0.0.0-20251022202715-ec1499142678 h1:
7878github.com/charmbracelet/anthropic-sdk-go v0.0.0-20251022202715-ec1499142678/go.mod h1:8TIYxZxsuCqqeJ0lga/b91tBwrbjoHDC66Sq5t8N2R4=
7979github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1.0.20250820203609-601216f68ee2 h1:973OHYuq2Jx9deyuPwe/6lsuQrDCatOsjP8uCd02URE=
8080github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1.0.20250820203609-601216f68ee2/go.mod h1:6HamsBKWqEC/FVHuQMHgQL+knPyvHH55HwJDHl/adMw=
81−github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.4.0.20251011205917-3b687ffc1619 h1:hjOhtqsxa+LVuCAkzhfA43wtusOaUPyQdSTg/wbRscw=
82−github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.4.0.20251011205917-3b687ffc1619/go.mod h1:5IzIGXU1n0foRc8bRAherC8ZuQCQURPlwx3ANLq1138=
81+github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.5 h1:oAChAeh730gtLKK/BpaTeJHzmj3KFuEfQ7AZgf2VGHM=
82+github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.5/go.mod h1:SUTLq+/pGQ5qntHgt0JswfVJFfgJgWDqyvyiSLVlmbo=
8383github.com/charmbracelet/catwalk v0.7.1-0.20251023112313-048e47f1399c h1:FVx5lHa+uGmr3GmShsQWhrpmU8RutJm0+To9rU804Ws=
8484github.com/charmbracelet/catwalk v0.7.1-0.20251023112313-048e47f1399c/go.mod h1:ReU4SdrLfe63jkEjWMdX2wlZMV3k9r11oQAmzN0m+KY=
8585github.com/charmbracelet/colorprofile v0.3.2 h1:9J27WdztfJQVAQKX2WOlSSRB+5gaKqqITmrvb1uTIiI=
@@ -94,8 +94,8 @@ github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.3.0.20250917201909-41ff0bf215ea
9494github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.3.0.20250917201909-41ff0bf215ea/go.mod h1:ngHerf1JLJXBrDXdphn5gFrBPriCL437uwukd5c93pM=
9595github.com/charmbracelet/log/v2 v2.0.0-20250226163916-c379e29ff706 h1:WkwO6Ks3mSIGnGuSdKl9qDSyfbYK50z2wc2gGMggegE=
9696github.com/charmbracelet/log/v2 v2.0.0-20250226163916-c379e29ff706/go.mod h1:mjJGp00cxcfvD5xdCa+bso251Jt4owrQvuimJtVmEmM=
97−github.com/charmbracelet/ultraviolet v0.0.0-20250915111650-81d4262876ef h1:VrWaUi2LXYLjfjCHowdSOEc6dQ9Ro14KY7Bw4IWd19M=
98−github.com/charmbracelet/ultraviolet v0.0.0-20250915111650-81d4262876ef/go.mod h1:AThRsQH1t+dfyOKIwXRoJBniYFQUkUpQq4paheHMc2o=
97+github.com/charmbracelet/ultraviolet v0.0.0-20251017140847-d4ace4d6e731 h1:Lr+igmzKpLPdb8yUZBP9noYWwCZP042z2nWPrJZTc+8=
98+github.com/charmbracelet/ultraviolet v0.0.0-20251017140847-d4ace4d6e731/go.mod h1:KfWwUa0Oe//D72YlhbOq/g40L7UiGtATrvsGI3cciG8=
9999github.com/charmbracelet/x/ansi v0.10.2 h1:ith2ArZS0CJG30cIUfID1LXN7ZFXRCww6RUvAPA+Pzw=
100100github.com/charmbracelet/x/ansi v0.10.2/go.mod h1:HbLdJjQH4UH4AqA2HpRWuWNluRE6zxJH/yteYEYCFa8=
101101github.com/charmbracelet/x/cellbuf v0.0.14-0.20250811133356-e0c5dbe5ea4a h1:zYSNtEJM9jwHbJts2k+Hroj+xQwsW1yxc4Wopdv7KaI=
internal/cmd/logs.go+5
@@ -10,8 +10,10 @@ import (
1010 "slices"
1111 "time"
1212
13+ "github.com/charmbracelet/colorprofile"
1314 "github.com/charmbracelet/crush/internal/config"
1415 "github.com/charmbracelet/log/v2"
16+ "github.com/charmbracelet/x/term"
1517 "github.com/nxadm/tail"
1618 "github.com/spf13/cobra"
1719)
@@ -45,6 +47,9 @@ var logsCmd = &cobra.Command{
4547
4648 log.SetLevel(log.DebugLevel)
4749 log.SetOutput(os.Stdout)
50+ if !term.IsTerminal(os.Stdout.Fd()) {
51+ log.SetColorProfile(colorprofile.NoTTY)
52+ }
4853
4954 cfg, err := config.Load(cwd, dataDir, false)
5055 if err != nil {
internal/cmd/root.go+1−4
@@ -83,11 +83,8 @@ crush -y
8383 // Set up the TUI.
8484 program := tea.NewProgram(
8585 tui.New(app),
86− tea.WithAltScreen(),
8786 tea.WithContext(cmd.Context()),
88− tea.WithMouseCellMotion(), // Use cell motion instead of all motion to reduce event flooding
89− tea.WithFilter(tui.MouseEventFilter), // Filter mouse events based on focus state
90− )
87+ tea.WithFilter(tui.MouseEventFilter)) // Filter mouse events based on focus state
9188
9289 go app.Subscribe(program)
9390
internal/format/spinner.go+2−2
@@ -23,8 +23,8 @@ type model struct {
2323 anim *anim.Anim
2424}
2525
26−func (m model) Init() tea.Cmd { return m.anim.Init() }
27−func (m model) View() string { return m.anim.View() }
26+func (m model) Init() tea.Cmd { return m.anim.Init() }
27+func (m model) View() tea.View { return tea.NewView(m.anim.View()) }
2828
2929// Update implements tea.Model.
3030func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
internal/tui/components/anim/anim.go+2−1
@@ -16,6 +16,7 @@ import (
1616 "github.com/lucasb-eyer/go-colorful"
1717
1818 "github.com/charmbracelet/crush/internal/csync"
19+ "github.com/charmbracelet/crush/internal/tui/util"
1920)
2021
2122const (
@@ -318,7 +319,7 @@ func (a *Anim) Init() tea.Cmd {
318319}
319320
320321// Update processes animation steps (or not).
321−func (a *Anim) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
322+func (a *Anim) Update(msg tea.Msg) (util.Model, tea.Cmd) {
322323 switch msg := msg.(type) {
323324 case StepMsg:
324325 if msg.id != a.id {
internal/tui/components/chat/chat.go+1−1
@@ -101,7 +101,7 @@ func (m *messageListCmp) Init() tea.Cmd {
101101}
102102
103103// Update handles incoming messages and updates the component state.
104−func (m *messageListCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
104+func (m *messageListCmp) Update(msg tea.Msg) (util.Model, tea.Cmd) {
105105 var cmds []tea.Cmd
106106 if m.session.ID != "" && m.app.AgentCoordinator != nil {
107107 queueSize := m.app.AgentCoordinator.QueuedPrompts(m.session.ID)
internal/tui/components/chat/editor/editor.go+1−1
@@ -172,7 +172,7 @@ func (m *editorCmp) repositionCompletions() tea.Msg {
172172 return completions.RepositionCompletionsMsg{X: x, Y: y}
173173}
174174
175−func (m *editorCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
175+func (m *editorCmp) Update(msg tea.Msg) (util.Model, tea.Cmd) {
176176 var cmd tea.Cmd
177177 var cmds []tea.Cmd
178178 switch msg := msg.(type) {
internal/tui/components/chat/header/header.go+1−1
@@ -44,7 +44,7 @@ func (h *header) Init() tea.Cmd {
4444 return nil
4545}
4646
47−func (h *header) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
47+func (h *header) Update(msg tea.Msg) (util.Model, tea.Cmd) {
4848 switch msg := msg.(type) {
4949 case pubsub.Event[session.Session]:
5050 if msg.Type == pubsub.UpdatedEvent {
internal/tui/components/chat/messages/messages.go+3−3
@@ -35,7 +35,7 @@ var ClearSelectionKey = key.NewBinding(key.WithKeys("esc", "alt+esc"), key.WithH
3535// MessageCmp defines the interface for message components in the chat interface.
3636// It combines standard UI model interfaces with message-specific functionality.
3737type MessageCmp interface {
38− util.Model // Basic Bubble Tea model interface
38+ util.Model // Basic Bubble util.Model interface
3939 layout.Sizeable // Width/height management
4040 layout.Focusable // Focus state management
4141 GetMessage() message.Message // Access to underlying message data
@@ -94,7 +94,7 @@ func (m *messageCmp) Init() tea.Cmd {
9494
9595// Update handles incoming messages and updates the component state.
9696// Manages animation updates for spinning messages and stops animation when appropriate.
97−func (m *messageCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
97+func (m *messageCmp) Update(msg tea.Msg) (util.Model, tea.Cmd) {
9898 switch msg := msg.(type) {
9999 case anim.StepMsg:
100100 m.spinning = m.shouldSpin()
@@ -387,7 +387,7 @@ func (m *assistantSectionModel) Init() tea.Cmd {
387387 return nil
388388}
389389
390−func (m *assistantSectionModel) Update(tea.Msg) (tea.Model, tea.Cmd) {
390+func (m *assistantSectionModel) Update(tea.Msg) (util.Model, tea.Cmd) {
391391 return m, nil
392392}
393393
internal/tui/components/chat/messages/tool.go+3−3
@@ -27,7 +27,7 @@ import (
2727// ToolCallCmp defines the interface for tool call components in the chat interface.
2828// It manages the display of tool execution including pending states, results, and errors.
2929type ToolCallCmp interface {
30− util.Model // Basic Bubble Tea model interface
30+ util.Model // Basic Bubble util.Model interface
3131 layout.Sizeable // Width/height management
3232 layout.Focusable // Focus state management
3333 GetToolCall() message.ToolCall // Access to tool call data
@@ -147,7 +147,7 @@ func (m *toolCallCmp) Init() tea.Cmd {
147147
148148// Update handles incoming messages and updates the component state.
149149// Manages animation updates for pending tool calls.
150−func (m *toolCallCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
150+func (m *toolCallCmp) Update(msg tea.Msg) (util.Model, tea.Cmd) {
151151 switch msg := msg.(type) {
152152 case anim.StepMsg:
153153 var cmds []tea.Cmd
@@ -160,7 +160,7 @@ func (m *toolCallCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
160160 }
161161 if m.spinning {
162162 u, cmd := m.anim.Update(msg)
163− m.anim = u.(util.Model)
163+ m.anim = u
164164 cmds = append(cmds, cmd)
165165 }
166166 return m, tea.Batch(cmds...)
internal/tui/components/chat/sidebar/sidebar.go+1−1
@@ -88,7 +88,7 @@ func (m *sidebarCmp) Init() tea.Cmd {
8888 return nil
8989}
9090
91−func (m *sidebarCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
91+func (m *sidebarCmp) Update(msg tea.Msg) (util.Model, tea.Cmd) {
9292 switch msg := msg.(type) {
9393 case SessionFilesMsg:
9494 m.files = csync.NewMap[string, SessionFile]()
internal/tui/components/chat/splash/splash.go+1−1
@@ -135,7 +135,7 @@ func (s *splashCmp) SetSize(width int, height int) tea.Cmd {
135135}
136136
137137// Update implements SplashPage.
138−func (s *splashCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
138+func (s *splashCmp) Update(msg tea.Msg) (util.Model, tea.Cmd) {
139139 switch msg := msg.(type) {
140140 case tea.WindowSizeMsg:
141141 return s, s.SetSize(msg.Width, msg.Height)
internal/tui/components/completions/completions.go+1−1
@@ -112,7 +112,7 @@ func (c *completionsCmp) Init() tea.Cmd {
112112}
113113
114114// Update implements Completions.
115−func (c *completionsCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
115+func (c *completionsCmp) Update(msg tea.Msg) (util.Model, tea.Cmd) {
116116 switch msg := msg.(type) {
117117 case tea.WindowSizeMsg:
118118 c.wWidth, c.wHeight = msg.Width, msg.Height
internal/tui/components/core/status/status.go+1−1
@@ -36,7 +36,7 @@ func (m *statusCmp) Init() tea.Cmd {
3636 return nil
3737}
3838
39−func (m *statusCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
39+func (m *statusCmp) Update(msg tea.Msg) (util.Model, tea.Cmd) {
4040 switch msg := msg.(type) {
4141 case tea.WindowSizeMsg:
4242 m.width = msg.Width
internal/tui/components/dialogs/commands/arguments.go+1−1
@@ -92,7 +92,7 @@ func (c *commandArgumentsDialogCmp) Init() tea.Cmd {
9292}
9393
9494// Update implements CommandArgumentsDialog.
95−func (c *commandArgumentsDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
95+func (c *commandArgumentsDialogCmp) Update(msg tea.Msg) (util.Model, tea.Cmd) {
9696 switch msg := msg.(type) {
9797 case tea.WindowSizeMsg:
9898 c.wWidth = msg.Width
internal/tui/components/dialogs/commands/commands.go+1−1
@@ -116,7 +116,7 @@ func (c *commandDialogCmp) Init() tea.Cmd {
116116 return c.SetCommandType(c.commandType)
117117}
118118
119−func (c *commandDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
119+func (c *commandDialogCmp) Update(msg tea.Msg) (util.Model, tea.Cmd) {
120120 switch msg := msg.(type) {
121121 case tea.WindowSizeMsg:
122122 c.wWidth = msg.Width
internal/tui/components/dialogs/dialogs.go+7−3
@@ -32,7 +32,7 @@ type CloseDialogMsg struct{}
3232
3333// DialogCmp manages a stack of dialogs with keyboard navigation.
3434type DialogCmp interface {
35− tea.Model
35+ util.Model
3636
3737 Dialogs() []DialogModel
3838 HasDialogs() bool
@@ -62,7 +62,7 @@ func (d dialogCmp) Init() tea.Cmd {
6262}
6363
6464// Update handles dialog lifecycle and forwards messages to the active dialog.
65−func (d dialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
65+func (d dialogCmp) Update(msg tea.Msg) (util.Model, tea.Cmd) {
6666 switch msg := msg.(type) {
6767 case tea.WindowSizeMsg:
6868 var cmds []tea.Cmd
@@ -98,7 +98,11 @@ func (d dialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
9898 return d, nil
9999}
100100
101−func (d dialogCmp) handleOpen(msg OpenDialogMsg) (tea.Model, tea.Cmd) {
101+func (d dialogCmp) View() string {
102+ return ""
103+}
104+
105+func (d dialogCmp) handleOpen(msg OpenDialogMsg) (util.Model, tea.Cmd) {
102106 if d.HasDialogs() {
103107 dialog := d.dialogs[len(d.dialogs)-1]
104108 if dialog.ID() == msg.Model.ID() {
internal/tui/components/dialogs/filepicker/filepicker.go+1−1
@@ -88,7 +88,7 @@ func (m *model) Init() tea.Cmd {
8888 return m.filePicker.Init()
8989}
9090
91−func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
91+func (m *model) Update(msg tea.Msg) (util.Model, tea.Cmd) {
9292 switch msg := msg.(type) {
9393 case tea.WindowSizeMsg:
9494 m.wWidth = msg.Width
internal/tui/components/dialogs/models/apikey.go+2−1
@@ -9,6 +9,7 @@ import (
99 "github.com/charmbracelet/crush/internal/config"
1010 "github.com/charmbracelet/crush/internal/home"
1111 "github.com/charmbracelet/crush/internal/tui/styles"
12+ "github.com/charmbracelet/crush/internal/tui/util"
1213 "github.com/charmbracelet/lipgloss/v2"
1314)
1415
@@ -75,7 +76,7 @@ func (a *APIKeyInput) Init() tea.Cmd {
7576 return a.spinner.Tick
7677}
7778
78−func (a *APIKeyInput) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
79+func (a *APIKeyInput) Update(msg tea.Msg) (util.Model, tea.Cmd) {
7980 switch msg := msg.(type) {
8081 case spinner.TickMsg:
8182 if a.state == APIKeyInputStateVerifying {
internal/tui/components/dialogs/models/models.go+1−1
@@ -98,7 +98,7 @@ func (m *modelDialogCmp) Init() tea.Cmd {
9898 return tea.Batch(m.modelList.Init(), m.apiKeyInput.Init())
9999}
100100
101−func (m *modelDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
101+func (m *modelDialogCmp) Update(msg tea.Msg) (util.Model, tea.Cmd) {
102102 switch msg := msg.(type) {
103103 case tea.WindowSizeMsg:
104104 m.wWidth = msg.Width
internal/tui/components/dialogs/permissions/permissions.go+1−1
@@ -95,7 +95,7 @@ func (p *permissionDialogCmp) supportsDiffView() bool {
9595 return p.permission.ToolName == tools.EditToolName || p.permission.ToolName == tools.WriteToolName || p.permission.ToolName == tools.MultiEditToolName
9696}
9797
98−func (p *permissionDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
98+func (p *permissionDialogCmp) Update(msg tea.Msg) (util.Model, tea.Cmd) {
9999 var cmds []tea.Cmd
100100
101101 switch msg := msg.(type) {
internal/tui/components/dialogs/quit/quit.go+1−1
@@ -40,7 +40,7 @@ func (q *quitDialogCmp) Init() tea.Cmd {
4040}
4141
4242// Update handles keyboard input for the quit dialog.
43−func (q *quitDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
43+func (q *quitDialogCmp) Update(msg tea.Msg) (util.Model, tea.Cmd) {
4444 switch msg := msg.(type) {
4545 case tea.WindowSizeMsg:
4646 q.wWidth = msg.Width
internal/tui/components/dialogs/reasoning/reasoning.go+1−1
@@ -168,7 +168,7 @@ func (r *reasoningDialogCmp) populateEffortOptions() tea.Cmd {
168168 return nil
169169}
170170
171−func (r *reasoningDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
171+func (r *reasoningDialogCmp) Update(msg tea.Msg) (util.Model, tea.Cmd) {
172172 switch msg := msg.(type) {
173173 case tea.WindowSizeMsg:
174174 r.wWidth = msg.Width
internal/tui/components/dialogs/sessions/sessions.go+1−1
@@ -81,7 +81,7 @@ func (s *sessionDialogCmp) Init() tea.Cmd {
8181 return tea.Sequence(cmds...)
8282}
8383
84−func (s *sessionDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
84+func (s *sessionDialogCmp) Update(msg tea.Msg) (util.Model, tea.Cmd) {
8585 switch msg := msg.(type) {
8686 case tea.WindowSizeMsg:
8787 var cmds []tea.Cmd
internal/tui/exp/list/filterable.go+2−1
@@ -9,6 +9,7 @@ import (
99 tea "github.com/charmbracelet/bubbletea/v2"
1010 "github.com/charmbracelet/crush/internal/tui/components/core/layout"
1111 "github.com/charmbracelet/crush/internal/tui/styles"
12+ "github.com/charmbracelet/crush/internal/tui/util"
1213 "github.com/charmbracelet/lipgloss/v2"
1314 "github.com/sahilm/fuzzy"
1415)
@@ -116,7 +117,7 @@ func NewFilterableList[T FilterableItem](items []T, opts ...filterableListOption
116117 return f
117118}
118119
119−func (f *filterableList[T]) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
120+func (f *filterableList[T]) Update(msg tea.Msg) (util.Model, tea.Cmd) {
120121 switch msg := msg.(type) {
121122 case tea.KeyPressMsg:
122123 switch {
internal/tui/exp/list/filterable_group.go+2−1
@@ -11,6 +11,7 @@ import (
1111 tea "github.com/charmbracelet/bubbletea/v2"
1212 "github.com/charmbracelet/crush/internal/tui/components/core/layout"
1313 "github.com/charmbracelet/crush/internal/tui/styles"
14+ "github.com/charmbracelet/crush/internal/tui/util"
1415 "github.com/charmbracelet/lipgloss/v2"
1516 "github.com/sahilm/fuzzy"
1617)
@@ -65,7 +66,7 @@ func NewFilterableGroupedList[T FilterableItem](items []Group[T], opts ...filter
6566 return f
6667}
6768
68−func (f *filterableGroupList[T]) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
69+func (f *filterableGroupList[T]) Update(msg tea.Msg) (util.Model, tea.Cmd) {
6970 switch msg := msg.(type) {
7071 case tea.KeyPressMsg:
7172 switch {
internal/tui/exp/list/grouped.go+1−1
@@ -58,7 +58,7 @@ func (g *groupedList[T]) Init() tea.Cmd {
5858 return g.render()
5959}
6060
61−func (l *groupedList[T]) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
61+func (l *groupedList[T]) Update(msg tea.Msg) (util.Model, tea.Cmd) {
6262 u, cmd := l.list.Update(msg)
6363 l.list = u.(*list[Item])
6464 return l, cmd
internal/tui/exp/list/items.go+3−2
@@ -7,6 +7,7 @@ import (
77 "github.com/charmbracelet/crush/internal/tui/components/core"
88 "github.com/charmbracelet/crush/internal/tui/components/core/layout"
99 "github.com/charmbracelet/crush/internal/tui/styles"
10+ "github.com/charmbracelet/crush/internal/tui/util"
1011 "github.com/charmbracelet/lipgloss/v2"
1112 "github.com/charmbracelet/x/ansi"
1213 "github.com/google/uuid"
@@ -97,7 +98,7 @@ func (c *completionItemCmp[T]) Init() tea.Cmd {
9798}
9899
99100// Update implements CommandItem.
100−func (c *completionItemCmp[T]) Update(tea.Msg) (tea.Model, tea.Cmd) {
101+func (c *completionItemCmp[T]) Update(tea.Msg) (util.Model, tea.Cmd) {
101102 return c, nil
102103}
103104
@@ -348,7 +349,7 @@ func (m *itemSectionModel) Init() tea.Cmd {
348349 return nil
349350}
350351
351−func (m *itemSectionModel) Update(tea.Msg) (tea.Model, tea.Cmd) {
352+func (m *itemSectionModel) Update(tea.Msg) (util.Model, tea.Cmd) {
352353 return m, nil
353354}
354355
internal/tui/exp/list/list.go+2−2
@@ -217,7 +217,7 @@ func (l *list[T]) Init() tea.Cmd {
217217}
218218
219219// Update implements List.
220−func (l *list[T]) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
220+func (l *list[T]) Update(msg tea.Msg) (util.Model, tea.Cmd) {
221221 switch msg := msg.(type) {
222222 case tea.MouseWheelMsg:
223223 if l.enableMouse {
@@ -277,7 +277,7 @@ func (l *list[T]) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
277277 return l, nil
278278}
279279
280−func (l *list[T]) handleMouseWheel(msg tea.MouseWheelMsg) (tea.Model, tea.Cmd) {
280+func (l *list[T]) handleMouseWheel(msg tea.MouseWheelMsg) (util.Model, tea.Cmd) {
281281 var cmd tea.Cmd
282282 switch msg.Button {
283283 case tea.MouseWheelDown:
internal/tui/exp/list/list_test.go+3−2
@@ -7,6 +7,7 @@ import (
77
88 tea "github.com/charmbracelet/bubbletea/v2"
99 "github.com/charmbracelet/crush/internal/tui/components/core/layout"
10+ "github.com/charmbracelet/crush/internal/tui/util"
1011 "github.com/charmbracelet/lipgloss/v2"
1112 "github.com/charmbracelet/x/exp/golden"
1213 "github.com/google/uuid"
@@ -602,7 +603,7 @@ func (s *simpleItem) Init() tea.Cmd {
602603 return nil
603604}
604605
605−func (s *simpleItem) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
606+func (s *simpleItem) Update(msg tea.Msg) (util.Model, tea.Cmd) {
606607 return s, nil
607608}
608609
@@ -644,7 +645,7 @@ func (s *selectableItem) IsFocused() bool {
644645 return s.focused
645646}
646647
647−func execCmd(m tea.Model, cmd tea.Cmd) {
648+func execCmd(m util.Model, cmd tea.Cmd) {
648649 for cmd != nil {
649650 msg := cmd()
650651 m, cmd = m.Update(msg)
internal/tui/page/chat/chat.go+1−1
@@ -163,7 +163,7 @@ func (p *chatPage) Init() tea.Cmd {
163163 )
164164}
165165
166−func (p *chatPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
166+func (p *chatPage) Update(msg tea.Msg) (util.Model, tea.Cmd) {
167167 var cmds []tea.Cmd
168168 switch msg := msg.(type) {
169169 case tea.KeyboardEnhancementsMsg:
internal/tui/tui.go+11−26
@@ -89,8 +89,6 @@ func (a appModel) Init() tea.Cmd {
8989 cmd = a.status.Init()
9090 cmds = append(cmds, cmd)
9191
92− cmds = append(cmds, tea.EnableMouseAllMotion)
93−
9492 return tea.Batch(cmds...)
9593}
9694
@@ -104,9 +102,7 @@ func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
104102 case tea.KeyboardEnhancementsMsg:
105103 for id, page := range a.pages {
106104 m, pageCmd := page.Update(msg)
107− if model, ok := m.(util.Model); ok {
108− a.pages[id] = model
109− }
105+ a.pages[id] = m
110106
111107 if pageCmd != nil {
112108 cmds = append(cmds, pageCmd)
@@ -234,9 +230,7 @@ func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
234230
235231 // Forward to view.
236232 updated, itemCmd := item.Update(msg)
237− if model, ok := updated.(util.Model); ok {
238− a.pages[a.currentPage] = model
239− }
233+ a.pages[a.currentPage] = updated
240234
241235 return a, itemCmd
242236 case pubsub.Event[permission.PermissionRequest]:
@@ -263,9 +257,7 @@ func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
263257
264258 a.isConfigured = config.HasInitialDataConfig()
265259 updated, pageCmd := item.Update(msg)
266− if model, ok := updated.(util.Model); ok {
267− a.pages[a.currentPage] = model
268− }
260+ a.pages[a.currentPage] = updated
269261
270262 cmds = append(cmds, pageCmd)
271263 return a, tea.Batch(cmds...)
@@ -285,9 +277,7 @@ func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
285277 }
286278
287279 updated, pageCmd := item.Update(msg)
288− if model, ok := updated.(util.Model); ok {
289− a.pages[a.currentPage] = model
290− }
280+ a.pages[a.currentPage] = updated
291281
292282 cmds = append(cmds, pageCmd)
293283 }
@@ -307,9 +297,7 @@ func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
307297 }
308298
309299 updated, pageCmd := item.Update(msg)
310− if model, ok := updated.(util.Model); ok {
311− a.pages[a.currentPage] = model
312− }
300+ a.pages[a.currentPage] = updated
313301
314302 cmds = append(cmds, pageCmd)
315303 }
@@ -324,9 +312,7 @@ func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
324312 }
325313
326314 updated, cmd := item.Update(msg)
327− if model, ok := updated.(util.Model); ok {
328− a.pages[a.currentPage] = model
329− }
315+ a.pages[a.currentPage] = updated
330316
331317 if a.dialog.HasDialogs() {
332318 u, dialogCmd := a.dialog.Update(msg)
@@ -362,9 +348,7 @@ func (a *appModel) handleWindowResize(width, height int) tea.Cmd {
362348 // Update the current view.
363349 for p, page := range a.pages {
364350 updated, pageCmd := page.Update(tea.WindowSizeMsg{Width: width, Height: height})
365− if model, ok := updated.(util.Model); ok {
366− a.pages[p] = model
367− }
351+ a.pages[p] = updated
368352
369353 cmds = append(cmds, pageCmd)
370354 }
@@ -467,9 +451,7 @@ func (a *appModel) handleKeyPressMsg(msg tea.KeyPressMsg) tea.Cmd {
467451 }
468452
469453 updated, cmd := item.Update(msg)
470− if model, ok := updated.(util.Model); ok {
471− a.pages[a.currentPage] = model
472− }
454+ a.pages[a.currentPage] = updated
473455 return cmd
474456 }
475457}
@@ -573,6 +555,9 @@ func (a *appModel) View() tea.View {
573555
574556 view.Layer = canvas
575557 view.Cursor = cursor
558+ view.MouseMode = tea.MouseModeCellMotion
559+ view.AltScreen = true
560+
576561 if a.app != nil && a.app.AgentCoordinator != nil && a.app.AgentCoordinator.IsBusy() {
577562 // HACK: use a random percentage to prevent ghostty from hiding it
578563 // after a timeout.
internal/tui/util/util.go+3−2
@@ -12,8 +12,9 @@ type Cursor interface {
1212}
1313
1414type Model interface {
15− tea.Model
16− tea.ViewModel
15+ Init() tea.Cmd
16+ Update(tea.Msg) (Model, tea.Cmd)
17+ View() string
1718}
1819
1920func CmdHandler(msg tea.Msg) tea.Cmd {