Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions internal/ui/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down
63 changes: 56 additions & 7 deletions internal/ui/components/error_display.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 ""
}
Expand All @@ -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 != "" {
Expand All @@ -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)
}
}
Comment on lines +91 to +104
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

softWrap still emits overlong words, so width limits can be violated.

Overlong tokens are appended as-is when they exceed maxWidth, so narrow views can still overflow/truncate paths and socket strings. That conflicts with the helper contract and the intent of width-constrained rendering.

Proposed fix
 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
+	flushCurrent := func() {
+		if current.Len() > 0 {
+			lines = append(lines, current.String())
+			current.Reset()
+		}
+	}

 	for _, word := range words {
+		for len(word) > maxWidth {
+			flushCurrent()
+			lines = append(lines, word[:maxWidth])
+			word = word[maxWidth:]
+		}
+
 		if current.Len() == 0 {
 			current.WriteString(word)
 			continue
 		}
 		if current.Len()+1+len(word) > maxWidth {
-			lines = append(lines, current.String())
-			current.Reset()
+			flushCurrent()
 			current.WriteString(word)
 		} else {
 			current.WriteByte(' ')
 			current.WriteString(word)
 		}
 	}
-	if current.Len() > 0 {
-		lines = append(lines, current.String())
-	}
+	flushCurrent()

 	return lines
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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)
}
}
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
flushCurrent := func() {
if current.Len() > 0 {
lines = append(lines, current.String())
current.Reset()
}
}
for _, word := range words {
for len(word) > maxWidth {
flushCurrent()
lines = append(lines, word[:maxWidth])
word = word[maxWidth:]
}
if current.Len() == 0 {
current.WriteString(word)
continue
}
if current.Len()+1+len(word) > maxWidth {
flushCurrent()
current.WriteString(word)
} else {
current.WriteByte(' ')
current.WriteString(word)
}
}
flushCurrent()
return lines
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/ui/components/error_display.go` around lines 91 - 104, The softWrap
loop currently appends overlong tokens (variable word) directly and can exceed
maxWidth; update the logic in the softWrap routine (the for _, word := range
words loop using current, maxWidth, lines) to detect when len(word) > maxWidth
and instead split that token into chunks that fit maxWidth (using rune-aware
slicing) appending each chunk as its own line (flushing current first if
non-empty), and when a chunk exactly fills a line start a new line; ensure
remaining partial chunk is written into current rather than appended raw so no
produced line exceeds maxWidth.

if current.Len() > 0 {
lines = append(lines, current.String())
}

return lines
}
6 changes: 3 additions & 3 deletions internal/ui/components/error_display_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}

Expand All @@ -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)
}
Expand All @@ -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)
}
Expand Down