fix: dont oversubmit

Carlos Alexandro Becker created

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

Change summary

internal/tui/components/dialogs/commands/commands.go | 57 +++++--------
internal/tui/components/dialogs/commands/loader.go   |  2 
internal/tui/exp/list/filterable.go                  |  5 
3 files changed, 26 insertions(+), 38 deletions(-)

Detailed changes

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

@@ -3,6 +3,7 @@ package commands
 import (
 	"context"
 	"os"
+	"slices"
 	"strings"
 
 	"github.com/charmbracelet/bubbles/v2/help"
@@ -12,6 +13,7 @@ import (
 	"github.com/charmbracelet/lipgloss/v2"
 
 	"github.com/charmbracelet/crush/internal/config"
+	"github.com/charmbracelet/crush/internal/csync"
 	"github.com/charmbracelet/crush/internal/llm/agent"
 	"github.com/charmbracelet/crush/internal/llm/prompt"
 	"github.com/charmbracelet/crush/internal/pubsub"
@@ -63,10 +65,10 @@ type commandDialogCmp struct {
 	commandList  listModel
 	keyMap       CommandsDialogKeyMap
 	help         help.Model
-	selected     CommandType // Selected SystemCommands, UserCommands, or MCPPrompts
-	userCommands []Command   // User-defined commands
-	mcpPrompts   []Command   // MCP prompts
-	sessionID    string      // Current session ID
+	selected     CommandType           // Selected SystemCommands, UserCommands, or MCPPrompts
+	userCommands []Command             // User-defined commands
+	mcpPrompts   *csync.Slice[Command] // MCP prompts
+	sessionID    string                // Current session ID
 	ctx          context.Context
 	cancel       context.CancelFunc
 }
@@ -116,6 +118,7 @@ func NewCommandDialog(sessionID string) CommandsDialog {
 		help:        help,
 		selected:    SystemCommands,
 		sessionID:   sessionID,
+		mcpPrompts:  csync.NewSlice[Command](),
 	}
 }
 
@@ -125,26 +128,11 @@ func (c *commandDialogCmp) Init() tea.Cmd {
 		return util.ReportError(err)
 	}
 	c.userCommands = commands
-	c.mcpPrompts = LoadMCPPrompts()
+	c.mcpPrompts.SetSlice(loadMCPPrompts())
 
 	// Subscribe to MCP events
 	c.ctx, c.cancel = context.WithCancel(context.Background())
-	return tea.Batch(
-		c.SetCommandType(c.selected),
-		c.subscribeMCPEvents(),
-	)
-}
-
-func (c *commandDialogCmp) subscribeMCPEvents() tea.Cmd {
-	return func() tea.Msg {
-		ch := agent.SubscribeMCPEvents(c.ctx)
-		for event := range ch {
-			if event.Type == pubsub.UpdatedEvent {
-				return event
-			}
-		}
-		return nil
-	}
+	return c.setCommandType(c.selected)
 }
 
 func (c *commandDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
@@ -153,21 +141,18 @@ func (c *commandDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 		c.wWidth = msg.Width
 		c.wHeight = msg.Height
 		return c, tea.Batch(
-			c.SetCommandType(c.selected),
+			c.setCommandType(c.selected),
 			c.commandList.SetSize(c.listWidth(), c.listHeight()),
 		)
 	case pubsub.Event[agent.MCPEvent]:
 		// Reload MCP prompts when MCP state changes
 		if msg.Type == pubsub.UpdatedEvent {
-			c.mcpPrompts = LoadMCPPrompts()
+			c.mcpPrompts.SetSlice(loadMCPPrompts())
 			// If we're currently viewing MCP prompts, refresh the list
 			if c.selected == MCPPrompts {
-				return c, tea.Batch(
-					c.SetCommandType(MCPPrompts),
-					c.subscribeMCPEvents(),
-				)
+				return c, c.setCommandType(MCPPrompts)
 			}
-			return c, c.subscribeMCPEvents()
+			return c, nil
 		}
 	case tea.KeyPressMsg:
 		switch {
@@ -185,10 +170,10 @@ func (c *commandDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 				command.Handler(command),
 			)
 		case key.Matches(msg, c.keyMap.Tab):
-			if len(c.userCommands) == 0 && len(c.mcpPrompts) == 0 {
+			if len(c.userCommands) == 0 && c.mcpPrompts.Len() == 0 {
 				return c, nil
 			}
-			return c, c.SetCommandType(c.next())
+			return c, c.setCommandType(c.next())
 		case key.Matches(msg, c.keyMap.Close):
 			if c.cancel != nil {
 				c.cancel()
@@ -209,12 +194,12 @@ func (c *commandDialogCmp) next() CommandType {
 		if len(c.userCommands) > 0 {
 			return UserCommands
 		}
-		if len(c.mcpPrompts) > 0 {
+		if c.mcpPrompts.Len() > 0 {
 			return MCPPrompts
 		}
 		fallthrough
 	case UserCommands:
-		if len(c.mcpPrompts) > 0 {
+		if c.mcpPrompts.Len() > 0 {
 			return MCPPrompts
 		}
 		fallthrough
@@ -231,7 +216,7 @@ func (c *commandDialogCmp) View() string {
 	radio := c.commandTypeRadio()
 
 	header := t.S().Base.Padding(0, 1, 1, 1).Render(core.Title("Commands", c.width-lipgloss.Width(radio)-5) + " " + radio)
-	if len(c.userCommands) == 0 && len(c.mcpPrompts) == 0 {
+	if len(c.userCommands) == 0 && c.mcpPrompts.Len() == 0 {
 		header = t.S().Base.Padding(0, 1, 1, 1).Render(core.Title("Commands", c.width-4))
 	}
 	content := lipgloss.JoinVertical(
@@ -271,7 +256,7 @@ func (c *commandDialogCmp) commandTypeRadio() string {
 	if len(c.userCommands) > 0 {
 		parts = append(parts, fn(UserCommands))
 	}
-	if len(c.mcpPrompts) > 0 {
+	if c.mcpPrompts.Len() > 0 {
 		parts = append(parts, fn(MCPPrompts))
 	}
 	return t.S().Base.Foreground(t.FgHalfMuted).Render(strings.Join(parts, " "))
@@ -281,7 +266,7 @@ func (c *commandDialogCmp) listWidth() int {
 	return defaultWidth - 2 // 4 for padding
 }
 
-func (c *commandDialogCmp) SetCommandType(commandType CommandType) tea.Cmd {
+func (c *commandDialogCmp) setCommandType(commandType CommandType) tea.Cmd {
 	c.selected = commandType
 
 	var commands []Command
@@ -291,7 +276,7 @@ func (c *commandDialogCmp) SetCommandType(commandType CommandType) tea.Cmd {
 	case UserCommands:
 		commands = c.userCommands
 	case MCPPrompts:
-		commands = c.mcpPrompts
+		commands = slices.Collect(c.mcpPrompts.Seq())
 	}
 
 	commandItems := []list.CompletionItem[Command]{}

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

@@ -223,7 +223,7 @@ type CommandRunCustomMsg struct {
 	Content string
 }
 
-func LoadMCPPrompts() []Command {
+func loadMCPPrompts() []Command {
 	prompts := agent.GetMCPPrompts()
 	commands := make([]Command, 0, len(prompts))
 

internal/tui/exp/list/filterable.go 🔗

@@ -276,7 +276,10 @@ func (f *filterableList[T]) Filter(query string) tea.Cmd {
 
 func (f *filterableList[T]) SetItems(items []T) tea.Cmd {
 	f.items = items
-	return f.list.SetItems(items)
+	return tea.Sequence(
+		f.list.SetItems(items),
+		f.Filter(f.query),
+	)
 }
 
 func (f *filterableList[T]) Cursor() *tea.Cursor {