refactor: move inline cmds to methods

Kujtim Hoxha created

Change summary

internal/ui/AGENTS.md   |  1 
internal/ui/model/ui.go | 79 ++++++++++++++++++++++++------------------
2 files changed, 46 insertions(+), 34 deletions(-)

Detailed changes

internal/ui/AGENTS.md 🔗

@@ -59,3 +59,4 @@ Use struct embedding for shared behaviors. See `chat/messages.go` for examples o
 - Always account for padding/borders in width calculations
 - Use `tea.Batch()` when returning multiple commands
 - Pass `*common.Common` to components that need styles or app access
+- When writing tea.Cmd's prefer creating methods in the model instead of writing inline functions

internal/ui/model/ui.go 🔗

@@ -482,12 +482,7 @@ func (m *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 		m.mcpStates = mcp.GetStates()
 		// Refresh agent tools when MCP tools change.
 		if msg.Payload.Type == mcp.EventToolsListChanged {
-			mcp.RefreshTools(context.Background(), msg.Payload.Name)
-			if m.com.App.AgentCoordinator != nil {
-				if err := m.com.App.AgentCoordinator.RefreshTools(context.Background()); err != nil {
-					slog.Error("failed to refresh agent tools", "error", err)
-				}
-			}
+			cmds = append(cmds, m.refreshMCPTools(msg.Payload.Name))
 		}
 		// check if all mcps are initialized
 		initialized := true
@@ -1159,36 +1154,10 @@ func (m *UI) handleDialogMsg(msg tea.Msg) tea.Cmd {
 		cmds = append(cmds, tea.Quit)
 	case dialog.ActionEnableDockerMCP:
 		m.dialog.CloseDialog(dialog.CommandsID)
-		cmds = append(cmds, func() tea.Msg {
-			cfg := m.com.Config()
-			if err := cfg.EnableDockerMCP(); err != nil {
-				return uiutil.ReportError(err)()
-			}
-
-			// Initialize the Docker MCP client immediately.
-			ctx := context.Background()
-			if err := mcp.InitializeSingle(ctx, config.DockerMCPName, cfg); err != nil {
-				return uiutil.ReportError(fmt.Errorf("docker MCP enabled but failed to start: %w", err))()
-			}
-
-			return uiutil.NewInfoMsg("Docker MCP enabled and started successfully")
-		})
+		cmds = append(cmds, m.enableDockerMCP)
 	case dialog.ActionDisableDockerMCP:
 		m.dialog.CloseDialog(dialog.CommandsID)
-		cmds = append(cmds, func() tea.Msg {
-			// Close the Docker MCP client.
-			if err := mcp.DisableSingle(config.DockerMCPName); err != nil {
-				return uiutil.ReportError(fmt.Errorf("failed to disable docker MCP: %w", err))()
-			}
-
-			// Remove from config and persist.
-			cfg := m.com.Config()
-			if err := cfg.DisableDockerMCP(); err != nil {
-				return uiutil.ReportError(err)()
-			}
-
-			return uiutil.NewInfoMsg("Docker MCP disabled successfully")
-		})
+		cmds = append(cmds, m.disableDockerMCP)
 	case dialog.ActionInitializeProject:
 		if m.isAgentBusy() {
 			cmds = append(cmds, uiutil.ReportWarn("Agent is busy, please wait before summarizing session..."))
@@ -3052,6 +3021,48 @@ func (m *UI) copyChatHighlight() tea.Cmd {
 	)
 }
 
+func (m *UI) enableDockerMCP() tea.Msg {
+	cfg := m.com.Config()
+	if err := cfg.EnableDockerMCP(); err != nil {
+		return uiutil.ReportError(err)()
+	}
+
+	// Initialize the Docker MCP client immediately.
+	ctx := context.Background()
+	if err := mcp.InitializeSingle(ctx, config.DockerMCPName, cfg); err != nil {
+		return uiutil.ReportError(fmt.Errorf("docker MCP enabled but failed to start: %w", err))()
+	}
+
+	return uiutil.NewInfoMsg("Docker MCP enabled and started successfully")
+}
+
+func (m *UI) disableDockerMCP() tea.Msg {
+	// Close the Docker MCP client.
+	if err := mcp.DisableSingle(config.DockerMCPName); err != nil {
+		return uiutil.ReportError(fmt.Errorf("failed to disable docker MCP: %w", err))()
+	}
+
+	// Remove from config and persist.
+	cfg := m.com.Config()
+	if err := cfg.DisableDockerMCP(); err != nil {
+		return uiutil.ReportError(err)()
+	}
+
+	return uiutil.NewInfoMsg("Docker MCP disabled successfully")
+}
+
+func (m *UI) refreshMCPTools(mcpName string) tea.Cmd {
+	return func() tea.Msg {
+		mcp.RefreshTools(context.Background(), mcpName)
+		if m.com.App.AgentCoordinator != nil {
+			if err := m.com.App.AgentCoordinator.RefreshTools(context.Background()); err != nil {
+				slog.Error("failed to refresh agent tools", "error", err)
+			}
+		}
+		return nil
+	}
+}
+
 // renderLogo renders the Crush logo with the given styles and dimensions.
 func renderLogo(t *styles.Styles, compact bool, width int) string {
 	return logo.Render(version.Version, compact, logo.Opts{