@@ -23,6 +23,7 @@ import (
2323 "github.com/charmbracelet/crush/internal/version"
2424 uv "github.com/charmbracelet/ultraviolet"
2525 "github.com/charmbracelet/ultraviolet/screen"
26+ "github.com/charmbracelet/x/ansi"
2627)
2728
2829// uiFocusState represents the current focus state of the UI.
@@ -224,7 +225,7 @@ func (m *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
224225}
225226
226227// Draw implements [tea.Layer] and draws the UI model.
227−func (m *UI) Draw(scr tea.Screen, area tea.Rectangle) {
228+func (m *UI) Draw(scr uv.Screen, area uv.Rectangle) {
228229 layout := generateLayout(m, area.Dx(), area.Dy())
229230
230231 // Clear the screen first
@@ -334,7 +335,21 @@ func (m *UI) View() tea.View {
334335 v.Cursor = cur
335336 }
336337
337− v.Content = m
338+ // TODO: Switch to lipgloss.Canvas when available
339+ canvas := uv.NewScreenBuffer(m.width, m.height)
340+ canvas.Method = ansi.GraphemeWidth
341+
342+ m.Draw(canvas, canvas.Bounds())
343+
344+ content := strings.ReplaceAll(canvas.Render(), "\r\n", "\n") // normalize newlines
345+ contentLines := strings.Split(content, "\n")
346+ for i, line := range contentLines {
347+ // Trim trailing spaces for concise rendering
348+ contentLines[i] = strings.TrimRight(line, " ")
349+ }
350+
351+ content = strings.Join(contentLines, "\n")
352+ v.Content = content
338353 if m.sendProgressBar && m.com.App != nil && m.com.App.AgentCoordinator != nil && m.com.App.AgentCoordinator.IsBusy() {
339354 // HACK: use a random percentage to prevent ghostty from hiding it
340355 // after a timeout.