diff --git a/apps/tui-go/internal/app/overlays.go b/apps/tui-go/internal/app/overlays.go index 1ca71185..46b40b20 100644 --- a/apps/tui-go/internal/app/overlays.go +++ b/apps/tui-go/internal/app/overlays.go @@ -440,8 +440,9 @@ func (m Model) artifactContentBodyH() int { return h } -// artifactContentLines splits the selected artifact's content into display lines. -func (m Model) artifactContentLines() []string { +// artifactContentLines splits the selected artifact's content into display lines, +// wrapping each line to the specified width. +func (m Model) artifactContentLines(w int) []string { if m.artifactsIndex < 0 || m.artifactsIndex >= len(m.artifacts) { return nil } @@ -449,12 +450,13 @@ func (m Model) artifactContentLines() []string { if c == nil { return []string{"(no content stored)"} } - return strings.Split(strings.ReplaceAll(*c, "\r\n", "\n"), "\n") + lines := strings.Split(strings.ReplaceAll(*c, "\r\n", "\n"), "\n") + return wrapLines(lines, w) } // artifactContentMaxScroll keeps the last page of content anchored to the body bottom. -func (m Model) artifactContentMaxScroll() int { - max := len(m.artifactContentLines()) - m.artifactContentBodyH() +func (m Model) artifactContentMaxScroll(w int) int { + max := len(m.artifactContentLines(w)) - m.artifactContentBodyH() if max < 0 { max = 0 } @@ -515,11 +517,13 @@ func (m Model) artifactsModal() string { } // Selected artifact content, scrollable with PgUp/PgDn. - lines := m.artifactContentLines() + // Width available for content: w - 2 (left padding) - 2 (box borders) - 2 (right padding) = w - 6 + contentW := w - 6 + lines := m.artifactContentLines(contentW) bodyH := m.artifactContentBodyH() coff := m.artifactScroll - if coff > m.artifactContentMaxScroll() { - coff = m.artifactContentMaxScroll() + if coff > m.artifactContentMaxScroll(contentW) { + coff = m.artifactContentMaxScroll(contentW) } if coff < 0 { coff = 0 @@ -534,7 +538,7 @@ func (m Model) artifactsModal() string { cend = len(lines) } for i := coff; i < cend; i++ { - b.WriteString(mbg(t, " "+truncate(lines[i], w-6), t.P.Fg) + "\n") + b.WriteString(mbg(t, " "+lines[i], t.P.Fg) + "\n") } b.WriteString("\n" + modalHints(t, [][2]string{{"↑↓", "select"}, {"PgUp/Dn", "scroll"}, {"v/esc", "close"}})) @@ -582,3 +586,37 @@ func truncate(s string, w int) string { } return string(r[:w-1]) + "…" } + +// wrapLines wraps each line in the input slice to the specified width using hard rune-boundary wrapping. +func wrapLines(lines []string, w int) []string { + if w < 1 { + w = 1 + } + var result []string + for _, line := range lines { + wrapped := wrapLine(line, w) + result = append(result, wrapped...) + } + return result +} + +// wrapLine wraps a single line to the specified width using hard rune-boundary wrapping. +func wrapLine(s string, w int) []string { + if w < 1 { + w = 1 + } + runes := []rune(s) + if len(runes) <= w { + return []string{s} + } + var lines []string + for len(runes) > 0 { + if len(runes) <= w { + lines = append(lines, string(runes)) + break + } + lines = append(lines, string(runes[:w])) + runes = runes[w:] + } + return lines +} diff --git a/apps/tui-go/internal/app/update.go b/apps/tui-go/internal/app/update.go index 2ac91e89..59353c9b 100644 --- a/apps/tui-go/internal/app/update.go +++ b/apps/tui-go/internal/app/update.go @@ -401,7 +401,8 @@ func (m Model) handleOverlayKey(k tea.KeyMsg) (tea.Model, tea.Cmd) { m.artifactScroll-- } case k.Type == tea.KeyPgDown: - if m.artifactScroll < m.artifactContentMaxScroll() { + contentW := m.modalWidth() - 6 + if m.artifactScroll < m.artifactContentMaxScroll(contentW) { m.artifactScroll++ } case runeIs(k, "v"):