fix(ui): subscribe to `app.LSPEvent` instead of `workspace.LSPEvent` (#2565)

Chris Chen created

Fix LSP UI display broken in v0.55.0. The UI was still subscribing
to workspace.LSPEvent after the event type was moved to the app
package, causing LSP status to never update in the UI.

Changes:
- internal/ui/model/ui.go: Subscribe to app.LSPEvent; use app.GetLSPStates()
- internal/ui/model/lsp.go: Use app.LSPClientInfo type for LSPInfo embedding
  and sorting (fields are identical to workspace.LSPClientInfo)

Fixes charmbracelet/crush#2560

Change summary

internal/ui/model/lsp.go | 6 +++---
internal/ui/model/ui.go  | 9 +++++----
2 files changed, 8 insertions(+), 7 deletions(-)

Detailed changes

internal/ui/model/lsp.go 🔗

@@ -7,16 +7,16 @@ import (
 	"strings"
 
 	"charm.land/lipgloss/v2"
+	"github.com/charmbracelet/crush/internal/app"
 	"github.com/charmbracelet/crush/internal/lsp"
 	"github.com/charmbracelet/crush/internal/ui/common"
 	"github.com/charmbracelet/crush/internal/ui/styles"
-	"github.com/charmbracelet/crush/internal/workspace"
 	"github.com/charmbracelet/x/powernap/pkg/lsp/protocol"
 )
 
 // LSPInfo wraps LSP client information with diagnostic counts by severity.
 type LSPInfo struct {
-	workspace.LSPClientInfo
+	app.LSPClientInfo
 	Diagnostics map[protocol.DiagnosticSeverity]int
 }
 
@@ -25,7 +25,7 @@ type LSPInfo struct {
 func (m *UI) lspInfo(width, maxItems int, isSection bool) string {
 	t := m.com.Styles
 
-	states := slices.SortedFunc(maps.Values(m.lspStates), func(a, b workspace.LSPClientInfo) int {
+	states := slices.SortedFunc(maps.Values(m.lspStates), func(a, b app.LSPClientInfo) int {
 		return strings.Compare(a.Name, b.Name)
 	})
 

internal/ui/model/ui.go 🔗

@@ -28,6 +28,7 @@ import (
 	"github.com/charmbracelet/crush/internal/agent/notify"
 	agenttools "github.com/charmbracelet/crush/internal/agent/tools"
 	"github.com/charmbracelet/crush/internal/agent/tools/mcp"
+	"github.com/charmbracelet/crush/internal/app"
 	"github.com/charmbracelet/crush/internal/commands"
 	"github.com/charmbracelet/crush/internal/config"
 	"github.com/charmbracelet/crush/internal/fsext"
@@ -213,7 +214,7 @@ type UI struct {
 	}
 
 	// lsp
-	lspStates map[string]workspace.LSPClientInfo
+	lspStates map[string]app.LSPClientInfo
 
 	// mcp
 	mcpStates map[string]mcp.ClientInfo
@@ -315,7 +316,7 @@ func New(com *common.Common, initialSessionID string, continueLast bool) *UI {
 		completions:         comp,
 		attachments:         attachments,
 		todoSpinner:         todoSpinner,
-		lspStates:           make(map[string]workspace.LSPClientInfo),
+		lspStates:           make(map[string]app.LSPClientInfo),
 		mcpStates:           make(map[string]mcp.ClientInfo),
 		notifyBackend:       notification.NoopBackend{},
 		notifyWindowFocused: true,
@@ -615,8 +616,8 @@ func (m *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 		m.renderPills()
 	case pubsub.Event[history.File]:
 		cmds = append(cmds, m.handleFileEvent(msg.Payload))
-	case pubsub.Event[workspace.LSPEvent]:
-		m.lspStates = m.com.Workspace.LSPGetStates()
+	case pubsub.Event[app.LSPEvent]:
+		m.lspStates = app.GetLSPStates()
 	case pubsub.Event[mcp.Event]:
 		switch msg.Payload.Type {
 		case mcp.EventStateChanged: