package tui import ( "fmt" "strings" "github.com/charmbracelet/lipgloss" ) func (m Model) renderDashboard() string { colWidth := m.width / 2 if colWidth < 30 { colWidth = 30 } var left, right strings.Builder left.WriteString(renderSectionWithIcon("System", "◉")) left.WriteString("\n") if m.scanResult != nil { sysInfo := m.scanResult.System.String() left.WriteString(" ") left.WriteString(lipgloss.NewStyle().Foreground(textColor).Render(sysInfo)) } left.WriteString("\n\n") left.WriteString(renderSectionWithIcon("Installed Tools", "◆")) left.WriteString("\n") if m.scanResult != nil { installed := 0 total := len(m.scanResult.Tools) for _, t := range m.scanResult.Tools { if t.Installed { installed++ left.WriteString(" ") left.WriteString(itemOKStyle.Render("✓ ")) left.WriteString(lipgloss.NewStyle().Foreground(textColor).Render(t.Name)) left.WriteString(lipgloss.NewStyle().Foreground(dimColor).Render(fmt.Sprintf(" %s", extractVersion(t.Version)))) left.WriteString("\n") } else { left.WriteString(" ") left.WriteString(itemMissingStyle.Render("✗ ")) left.WriteString(lipgloss.NewStyle().Foreground(mutedColor).Render(t.Name)) left.WriteString(itemPendingStyle.Render(" (missing)")) left.WriteString("\n") } } barWidth := 20 pct := 0 if total > 0 { pct = (installed * barWidth) / total } bar := lipgloss.NewStyle().Foreground(primaryColor).Render(strings.Repeat("█", pct)) + lipgloss.NewStyle().Foreground(dimColor).Render(strings.Repeat("░", barWidth-pct)) left.WriteString(fmt.Sprintf("\n %s %d/%d\n", bar, installed, total)) } left.WriteString("\n") if m.installing { left.WriteString(renderSectionWithIcon("Installing", "⏳")) left.WriteString("\n") progBar := m.progressBar.View() label := "" if m.installTool != "" { label = fmt.Sprintf(" %d/%d - %s", m.installCurrent+1, m.installTotal, m.installTool) } else { label = fmt.Sprintf(" %d/%d", m.installCurrent, m.installTotal) } left.WriteString(fmt.Sprintf(" %s%s\n", progBar, label)) left.WriteString("\n") } if len(m.installLog) > 0 { left.WriteString(renderSectionWithIcon("Install Log", "📋")) left.WriteString("\n") for _, l := range m.installLog { left.WriteString(l + "\n") } left.WriteString("\n") } right.WriteString(renderSectionWithIcon("Quick Actions", "⚡")) right.WriteString("\n") actions := []struct { key string desc string color lipgloss.Color }{ {"i", "Install missing tools", primaryColor}, {"u", "Check for updates", warmColor}, {"s", "Rescan system", roseColor}, {"l", "Scan LSP servers", accentColor}, {"m", "Configure MCP servers", roseLightColor}, } for _, a := range actions { right.WriteString(fmt.Sprintf(" %s %s\n", lipgloss.NewStyle().Foreground(a.color).Bold(true).Render("["+a.key+"]"), lipgloss.NewStyle().Foreground(textColor).Render(a.desc))) } right.WriteString("\n") right.WriteString(renderSectionWithIcon("Active Agents", "◉")) right.WriteString("\n") agents := []struct { name string }{ {"Crush"}, {"Claude Code"}, } for _, a := range agents { right.WriteString(" ") right.WriteString(lipgloss.NewStyle().Foreground(dimColor).Render("● ")) right.WriteString(lipgloss.NewStyle().Foreground(mutedColor).Render(a.name + " ")) right.WriteString(itemPendingStyle.Render("stopped")) right.WriteString("\n") } right.WriteString("\n") if len(m.updateStatus) > 0 { right.WriteString(renderSectionWithIcon("Updates", "↻")) right.WriteString("\n") for _, s := range m.updateStatus { if s.NeedsUpdate { right.WriteString(" ") right.WriteString(itemWarnStyle.Render("⚠ ")) right.WriteString(fmt.Sprintf("%s: %s → %s\n", s.Tool, s.Current, s.Latest)) } else if s.Error == "" { right.WriteString(" ") right.WriteString(itemOKStyle.Render("✓ ")) right.WriteString(fmt.Sprintf("%s: up to date\n", s.Tool)) } } right.WriteString("\n") } if len(m.lspServers) > 0 { right.WriteString(renderSectionWithIcon("LSP Servers", "§")) right.WriteString("\n") lspInstalled := 0 for _, s := range m.lspServers { if s.Installed { lspInstalled++ right.WriteString(" ") right.WriteString(itemOKStyle.Render("✓ ")) right.WriteString(fmt.Sprintf("%-22s (%s)\n", s.Name, s.Language)) } else { right.WriteString(" ") right.WriteString(itemPendingStyle.Render("○ ")) right.WriteString(fmt.Sprintf("%-22s (%s)\n", s.Name, s.Language)) } } right.WriteString(fmt.Sprintf("\n %d/%d available\n", lspInstalled, len(m.lspServers))) right.WriteString("\n") } mcpStatus := itemPendingStyle.Render("○ not configured") if m.mcpConfigured { mcpStatus = itemOKStyle.Render("✓ configured") } right.WriteString(fmt.Sprintf(" MCP: %s\n", mcpStatus)) if m.daemon != nil { daemonStatus := itemPendingStyle.Render("○ stopped") if m.daemon.IsRunning() { daemonStatus = itemOKStyle.Render("✓ running") } right.WriteString(fmt.Sprintf(" Daemon: %s\n", daemonStatus)) } leftCol := lipgloss.NewStyle().Width(colWidth).Render(left.String()) rightCol := lipgloss.NewStyle().Width(colWidth).Render(right.String()) return lipgloss.JoinHorizontal(lipgloss.Top, leftCol, rightCol) } func renderSectionWithIcon(title string, icon string) string { return lipgloss.NewStyle().Foreground(primaryColor).Render(icon+" ") + sectionStyle.Render(title) }