diff --git a/internal/ui/app.go b/internal/ui/app.go index 77e1b92..9ec1f77 100644 --- a/internal/ui/app.go +++ b/internal/ui/app.go @@ -315,8 +315,7 @@ func (a App) View() string { sb.WriteString("\n") } - if errorView := a.errorDisplay.View(); errorView != "" { - sb.WriteString("\n") + if errorView := a.errorDisplay.View(contentWidth); errorView != "" { sb.WriteString(errorView) } diff --git a/internal/ui/components/error_display.go b/internal/ui/components/error_display.go index ee1079f..ece2e33 100644 --- a/internal/ui/components/error_display.go +++ b/internal/ui/components/error_display.go @@ -26,7 +26,7 @@ func (e ErrorDisplay) Visible() bool { return e.visible } -func (e ErrorDisplay) View() string { +func (e ErrorDisplay) View(maxWidth int) string { if !e.visible || e.event == nil { return "" } @@ -37,9 +37,18 @@ func (e ErrorDisplay) View() string { sb.WriteString("\n") if e.event.Summary != "" { - sb.WriteString(styles.Secondary.Render("> ")) - sb.WriteString(styles.Message.Render(e.event.Summary)) - sb.WriteString("\n") + prefix := "> " + summaryWidth := maxWidth - len(prefix) + lines := softWrap(e.event.Summary, summaryWidth) + for i, line := range lines { + if i == 0 { + sb.WriteString(styles.Secondary.Render(prefix)) + } else { + sb.WriteString(strings.Repeat(" ", len(prefix))) + } + sb.WriteString(styles.Message.Render(line)) + sb.WriteString("\n") + } } if e.event.Detail != "" { @@ -50,12 +59,52 @@ func (e ErrorDisplay) View() string { if len(e.event.Actions) > 0 { sb.WriteString("\n") - for _, action := range e.event.Actions { - sb.WriteString(styles.ErrorAction.Render("⇒ " + action.Label + " ")) - sb.WriteString(styles.Message.Bold(true).Render(action.Value)) + for i, action := range e.event.Actions { + if i > 0 { + sb.WriteString(styles.SecondaryMessage.Render("⇒ " + action.Label + " " + action.Value)) + } else { + sb.WriteString(styles.ErrorAction.Render("⇒ " + action.Label + " ")) + sb.WriteString(styles.Message.Bold(true).Render(action.Value)) + } sb.WriteString("\n") } } return sb.String() } + +// softWrap breaks text into lines at word boundaries, falling back to hard +// breaks for words longer than maxWidth. +func softWrap(text string, maxWidth int) []string { + if maxWidth <= 0 || len(text) <= maxWidth { + return []string{text} + } + + words := strings.Fields(text) + if len(words) == 0 { + return []string{text} + } + + var lines []string + var current strings.Builder + + for _, word := range words { + if current.Len() == 0 { + current.WriteString(word) + continue + } + if current.Len()+1+len(word) > maxWidth { + lines = append(lines, current.String()) + current.Reset() + current.WriteString(word) + } else { + current.WriteByte(' ') + current.WriteString(word) + } + } + if current.Len() > 0 { + lines = append(lines, current.String()) + } + + return lines +} diff --git a/internal/ui/components/error_display_test.go b/internal/ui/components/error_display_test.go index 9094fdf..733f780 100644 --- a/internal/ui/components/error_display_test.go +++ b/internal/ui/components/error_display_test.go @@ -15,7 +15,7 @@ func TestErrorDisplay_ShowView(t *testing.T) { t.Fatal("expected error display to be hidden initially") } - if e.View() != "" { + if e.View(80) != "" { t.Fatal("expected empty view when error display is hidden") } @@ -32,7 +32,7 @@ func TestErrorDisplay_ShowView(t *testing.T) { t.Fatal("expected error display to be visible after Show") } - view := e.View() + view := e.View(80) if !strings.Contains(view, "Connection failed") { t.Fatalf("expected view to contain title, got: %q", view) } @@ -56,7 +56,7 @@ func TestErrorDisplay_MinimalEvent(t *testing.T) { e := NewErrorDisplay() e = e.Show(output.ErrorEvent{Title: "Something went wrong"}) - view := e.View() + view := e.View(80) if !strings.Contains(view, "Something went wrong") { t.Fatalf("expected view to contain title, got: %q", view) }