fix(tui): clip sidebar lines with ellipses
- commit
- a2c470e3988efeb4258f06a9817171c0a9b1e3e5
- author
- brice · · 2026-08-05 13:39 UTC
- parent
- 071b798
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +47 -55
M packages/tui/rightbar.go +32 -41
| @@ -60,8 +60,8 @@ func RightBarColumns(cols int) (mainWidth, rightBarWidth int, ok bool) { | |||
| 60 | 60 | // RenderRightBar renders a bounded, full-height side rail without adding a | |
| 61 | 61 | // second frame around the host-owned separator. Widgets are sorted by | |
| 62 | 62 | // extension name and ID so output stays deterministic even when extension | |
| 63 | - | // frames arrive concurrently. Long content wraps to the rail width, and | |
| 64 | - | // content that does not fit is replaced by a compact marker. | |
| 63 | + | // frames arrive concurrently. Long lines are clipped to the rail width with | |
| 64 | + | // a three-dot ellipsis. | |
| 65 | 65 | func RenderRightBar(th Theme, widgets []RightBarWidget, width, height int) []string { | |
| 66 | 66 | if width <= 0 || height <= 0 { | |
| 67 | 67 | return nil | |
| @@ -78,7 +78,7 @@ func RenderRightBar(th Theme, widgets []RightBarWidget, width, height int) []str | |||
| 78 | 78 | content := make([]string, 0, height) | |
| 79 | 79 | truncated := false | |
| 80 | 80 | padLine := func(text string) string { | |
| 81 | - | text = truncateToWidth(text, width) | |
| 81 | + | text = truncateRightBarLine(text, width) | |
| 82 | 82 | if visible := visibleWidth(text); visible < width { | |
| 83 | 83 | text += strings.Repeat(" ", width-visible) | |
| 84 | 84 | } | |
| @@ -116,18 +116,12 @@ func RenderRightBar(th Theme, widgets []RightBarWidget, width, height int) []str | |||
| 116 | 116 | } | |
| 117 | 117 | appendContent := func(text string, color int, dim, phase bool) bool { | |
| 118 | 118 | text = strings.ReplaceAll(text, "\r", "") | |
| 119 | - | for _, physicalLine := range strings.Split(text, "\n") { | |
| 120 | - | wrapped := rightBarWrapLine(physicalLine, width) | |
| 121 | - | if len(wrapped) == 0 { | |
| 122 | - | wrapped = []string{""} | |
| 123 | - | } | |
| 124 | - | for _, line := range wrapped { | |
| 125 | - | if len(content) >= height { | |
| 126 | - | truncated = true | |
| 127 | - | return false | |
| 128 | - | } | |
| 129 | - | content = append(content, paintLine(line, color, dim, phase)) | |
| 119 | + | for _, line := range strings.Split(text, "\n") { | |
| 120 | + | if len(content) >= height { | |
| 121 | + | truncated = true | |
| 122 | + | return false | |
| 130 | 123 | } | |
| 124 | + | content = append(content, paintLine(line, color, dim, phase)) | |
| 131 | 125 | } | |
| 132 | 126 | return true | |
| 133 | 127 | } | |
| @@ -197,33 +191,14 @@ func rightBarChecklistMarker(text string) (start, end int, marker byte, ok bool) | |||
| 197 | 191 | } | |
| 198 | 192 | } | |
| 199 | 193 | ||
| 200 | - | func rightBarWrapLine(text string, width int) []string { | |
| 201 | - | _, end, _, ok := rightBarChecklistMarker(text) | |
| 202 | - | if !ok { | |
| 203 | - | return WrapANSILine(text, width) | |
| 194 | + | func truncateRightBarLine(text string, width int) string { | |
| 195 | + | if width <= 0 || visibleWidth(text) <= width { | |
| 196 | + | return text | |
| 204 | 197 | } | |
| 205 | - | ||
| 206 | - | bodyStart := end | |
| 207 | - | if bodyStart < len(text) && text[bodyStart] == ' ' { | |
| 208 | - | bodyStart++ | |
| 209 | - | } | |
| 210 | - | prefix := text[:bodyStart] | |
| 211 | - | prefixWidth := visibleWidth(prefix) | |
| 212 | - | bodyWidth := width - prefixWidth | |
| 213 | - | if bodyWidth < 1 { | |
| 214 | - | return WrapANSILine(text, width) | |
| 215 | - | } | |
| 216 | - | bodyLines := WrapANSILine(text[bodyStart:], bodyWidth) | |
| 217 | - | continuation := strings.Repeat(" ", prefixWidth) | |
| 218 | - | wrapped := make([]string, len(bodyLines)) | |
| 219 | - | for i, bodyLine := range bodyLines { | |
| 220 | - | if i == 0 { | |
| 221 | - | wrapped[i] = prefix + bodyLine | |
| 222 | - | } else { | |
| 223 | - | wrapped[i] = continuation + bodyLine | |
| 224 | - | } | |
| 198 | + | if width <= 3 { | |
| 199 | + | return truncateToWidth("...", width) | |
| 225 | 200 | } | |
| 226 | - | return wrapped | |
| 201 | + | return truncateToWidth(text, width-3) + "..." | |
| 227 | 202 | } | |
| 228 | 203 | ||
| 229 | 204 | func rightBarChecklistIndent(lines []string) int { | |
| @@ -261,12 +236,12 @@ func rightBarChecklistPhaseName(text string) (start, end int, ok bool) { | |||
| 261 | 236 | suffix := text[start:trimmedEnd] | |
| 262 | 237 | separator := strings.LastIndex(suffix, " ") | |
| 263 | 238 | if separator < 0 { | |
| 264 | - | return 0, 0, false | |
| 239 | + | return rightBarFallbackPhaseName(text, start, trimmedEnd) | |
| 265 | 240 | } | |
| 266 | 241 | nameEnd := start + separator | |
| 267 | 242 | progress := strings.TrimSpace(text[nameEnd:trimmedEnd]) | |
| 268 | 243 | if !rightBarProgressToken(progress) { | |
| 269 | - | return 0, 0, false | |
| 244 | + | return rightBarFallbackPhaseName(text, start, trimmedEnd) | |
| 270 | 245 | } | |
| 271 | 246 | for nameEnd > start && text[nameEnd-1] == ' ' { | |
| 272 | 247 | nameEnd-- | |
| @@ -277,6 +252,22 @@ func rightBarChecklistPhaseName(text string) (start, end int, ok bool) { | |||
| 277 | 252 | return start, nameEnd, true | |
| 278 | 253 | } | |
| 279 | 254 | ||
| 255 | + | func rightBarFallbackPhaseName(text string, start, end int) (int, int, bool) { | |
| 256 | + | for end > start && text[end-1] == ' ' { | |
| 257 | + | end-- | |
| 258 | + | } | |
| 259 | + | if end-start >= 3 && text[end-3:end] == "..." { | |
| 260 | + | end -= 3 | |
| 261 | + | } | |
| 262 | + | for end > start && text[end-1] == ' ' { | |
| 263 | + | end-- | |
| 264 | + | } | |
| 265 | + | if end <= start { | |
| 266 | + | return 0, 0, false | |
| 267 | + | } | |
| 268 | + | return start, end, true | |
| 269 | + | } | |
| 270 | + | ||
| 280 | 271 | func rightBarProgressToken(value string) bool { | |
| 281 | 272 | separator := strings.IndexByte(value, '/') | |
| 282 | 273 | if separator <= 0 || separator == len(value)-1 { | |
| @@ -60,8 +60,8 @@ func RightBarColumns(cols int) (mainWidth, rightBarWidth int, ok bool) { | |||||
| 60 | // RenderRightBar renders a bounded, full-height side rail without adding a | 60 | // RenderRightBar renders a bounded, full-height side rail without adding a | ||
| 61 | // second frame around the host-owned separator. Widgets are sorted by | 61 | // second frame around the host-owned separator. Widgets are sorted by | ||
| 62 | // extension name and ID so output stays deterministic even when extension | 62 | // extension name and ID so output stays deterministic even when extension | ||
| 63 | - | // frames arrive concurrently. Long content wraps to the rail width, and | 63 | + | // frames arrive concurrently. Long lines are clipped to the rail width with |
| 64 | - | // content that does not fit is replaced by a compact marker. | 64 | + | // a three-dot ellipsis. |
| 65 | func RenderRightBar(th Theme, widgets []RightBarWidget, width, height int) []string { | 65 | func RenderRightBar(th Theme, widgets []RightBarWidget, width, height int) []string { | ||
| 66 | if width <= 0 || height <= 0 { | 66 | if width <= 0 || height <= 0 { | ||
| 67 | return nil | 67 | return nil | ||
| @@ -78,7 +78,7 @@ func RenderRightBar(th Theme, widgets []RightBarWidget, width, height int) []str | |||||
| 78 | content := make([]string, 0, height) | 78 | content := make([]string, 0, height) | ||
| 79 | truncated := false | 79 | truncated := false | ||
| 80 | padLine := func(text string) string { | 80 | padLine := func(text string) string { | ||
| 81 | - | text = truncateToWidth(text, width) | 81 | + | text = truncateRightBarLine(text, width) |
| 82 | if visible := visibleWidth(text); visible < width { | 82 | if visible := visibleWidth(text); visible < width { | ||
| 83 | text += strings.Repeat(" ", width-visible) | 83 | text += strings.Repeat(" ", width-visible) | ||
| 84 | } | 84 | } | ||
| @@ -116,18 +116,12 @@ func RenderRightBar(th Theme, widgets []RightBarWidget, width, height int) []str | |||||
| 116 | } | 116 | } | ||
| 117 | appendContent := func(text string, color int, dim, phase bool) bool { | 117 | appendContent := func(text string, color int, dim, phase bool) bool { | ||
| 118 | text = strings.ReplaceAll(text, "\r", "") | 118 | text = strings.ReplaceAll(text, "\r", "") | ||
| 119 | - | for _, physicalLine := range strings.Split(text, "\n") { | 119 | + | for _, line := range strings.Split(text, "\n") { |
| 120 | - | wrapped := rightBarWrapLine(physicalLine, width) | 120 | + | if len(content) >= height { |
| 121 | - | if len(wrapped) == 0 { | 121 | + | truncated = true |
| 122 | - | wrapped = []string{""} | 122 | + | return false |
| 123 | - | } | |||
| 124 | - | for _, line := range wrapped { | |||
| 125 | - | if len(content) >= height { | |||
| 126 | - | truncated = true | |||
| 127 | - | return false | |||
| 128 | - | } | |||
| 129 | - | content = append(content, paintLine(line, color, dim, phase)) | |||
| 130 | } | 123 | } | ||
| 124 | + | content = append(content, paintLine(line, color, dim, phase)) | |||
| 131 | } | 125 | } | ||
| 132 | return true | 126 | return true | ||
| 133 | } | 127 | } | ||
| @@ -197,33 +191,14 @@ func rightBarChecklistMarker(text string) (start, end int, marker byte, ok bool) | |||||
| 197 | } | 191 | } | ||
| 198 | } | 192 | } | ||
| 199 | 193 | ||||
| 200 | - | func rightBarWrapLine(text string, width int) []string { | 194 | + | func truncateRightBarLine(text string, width int) string { |
| 201 | - | _, end, _, ok := rightBarChecklistMarker(text) | 195 | + | if width <= 0 || visibleWidth(text) <= width { |
| 202 | - | if !ok { | 196 | + | return text |
| 203 | - | return WrapANSILine(text, width) | |||
| 204 | } | 197 | } | ||
| 205 | - | 198 | + | if width <= 3 { | |
| 206 | - | bodyStart := end | 199 | + | return truncateToWidth("...", width) |
| 207 | - | if bodyStart < len(text) && text[bodyStart] == ' ' { | |||
| 208 | - | bodyStart++ | |||
| 209 | - | } | |||
| 210 | - | prefix := text[:bodyStart] | |||
| 211 | - | prefixWidth := visibleWidth(prefix) | |||
| 212 | - | bodyWidth := width - prefixWidth | |||
| 213 | - | if bodyWidth < 1 { | |||
| 214 | - | return WrapANSILine(text, width) | |||
| 215 | - | } | |||
| 216 | - | bodyLines := WrapANSILine(text[bodyStart:], bodyWidth) | |||
| 217 | - | continuation := strings.Repeat(" ", prefixWidth) | |||
| 218 | - | wrapped := make([]string, len(bodyLines)) | |||
| 219 | - | for i, bodyLine := range bodyLines { | |||
| 220 | - | if i == 0 { | |||
| 221 | - | wrapped[i] = prefix + bodyLine | |||
| 222 | - | } else { | |||
| 223 | - | wrapped[i] = continuation + bodyLine | |||
| 224 | - | } | |||
| 225 | } | 200 | } | ||
| 226 | - | return wrapped | 201 | + | return truncateToWidth(text, width-3) + "..." |
| 227 | } | 202 | } | ||
| 228 | 203 | ||||
| 229 | func rightBarChecklistIndent(lines []string) int { | 204 | func rightBarChecklistIndent(lines []string) int { | ||
| @@ -261,12 +236,12 @@ func rightBarChecklistPhaseName(text string) (start, end int, ok bool) { | |||||
| 261 | suffix := text[start:trimmedEnd] | 236 | suffix := text[start:trimmedEnd] | ||
| 262 | separator := strings.LastIndex(suffix, " ") | 237 | separator := strings.LastIndex(suffix, " ") | ||
| 263 | if separator < 0 { | 238 | if separator < 0 { | ||
| 264 | - | return 0, 0, false | 239 | + | return rightBarFallbackPhaseName(text, start, trimmedEnd) |
| 265 | } | 240 | } | ||
| 266 | nameEnd := start + separator | 241 | nameEnd := start + separator | ||
| 267 | progress := strings.TrimSpace(text[nameEnd:trimmedEnd]) | 242 | progress := strings.TrimSpace(text[nameEnd:trimmedEnd]) | ||
| 268 | if !rightBarProgressToken(progress) { | 243 | if !rightBarProgressToken(progress) { | ||
| 269 | - | return 0, 0, false | 244 | + | return rightBarFallbackPhaseName(text, start, trimmedEnd) |
| 270 | } | 245 | } | ||
| 271 | for nameEnd > start && text[nameEnd-1] == ' ' { | 246 | for nameEnd > start && text[nameEnd-1] == ' ' { | ||
| 272 | nameEnd-- | 247 | nameEnd-- | ||
| @@ -277,6 +252,22 @@ func rightBarChecklistPhaseName(text string) (start, end int, ok bool) { | |||||
| 277 | return start, nameEnd, true | 252 | return start, nameEnd, true | ||
| 278 | } | 253 | } | ||
| 279 | 254 | ||||
| 255 | + | func rightBarFallbackPhaseName(text string, start, end int) (int, int, bool) { | |||
| 256 | + | for end > start && text[end-1] == ' ' { | |||
| 257 | + | end-- | |||
| 258 | + | } | |||
| 259 | + | if end-start >= 3 && text[end-3:end] == "..." { | |||
| 260 | + | end -= 3 | |||
| 261 | + | } | |||
| 262 | + | for end > start && text[end-1] == ' ' { | |||
| 263 | + | end-- | |||
| 264 | + | } | |||
| 265 | + | if end <= start { | |||
| 266 | + | return 0, 0, false | |||
| 267 | + | } | |||
| 268 | + | return start, end, true | |||
| 269 | + | } | |||
| 270 | + | ||||
| 280 | func rightBarProgressToken(value string) bool { | 271 | func rightBarProgressToken(value string) bool { | ||
| 281 | separator := strings.IndexByte(value, '/') | 272 | separator := strings.IndexByte(value, '/') | ||
| 282 | if separator <= 0 || separator == len(value)-1 { | 273 | if separator <= 0 || separator == len(value)-1 { | ||
M packages/tui/rightbar_test.go +15 -14
| @@ -51,7 +51,7 @@ func TestRenderRightBarSortsWidgetsAndBoundsRows(t *testing.T) { | |||
| 51 | 51 | } | |
| 52 | 52 | } | |
| 53 | 53 | ||
| 54 | - | func TestRenderRightBarWrapsLongWidgetLines(t *testing.T) { | |
| 54 | + | func TestRenderRightBarClipsLongWidgetLinesWithEllipsis(t *testing.T) { | |
| 55 | 55 | lines := RenderRightBar(Dark, []RightBarWidget{{ | |
| 56 | 56 | Extension: "plan", | |
| 57 | 57 | Lines: []string{"one two three four five six"}, | |
| @@ -61,17 +61,17 @@ func TestRenderRightBarWrapsLongWidgetLines(t *testing.T) { | |||
| 61 | 61 | t.Fatalf("right bar rows = %d, want 8", len(lines)) | |
| 62 | 62 | } | |
| 63 | 63 | plain := stripANSI(strings.Join(lines, "\n")) | |
| 64 | - | if !strings.Contains(plain, "one two three") || !strings.Contains(plain, "four five six") { | |
| 65 | - | t.Fatalf("long widget line was not wrapped: %q", plain) | |
| 64 | + | if !strings.Contains(plain, " one two thre...") || strings.Contains(plain, "four five six") { | |
| 65 | + | t.Fatalf("long widget line was not clipped with an ellipsis: %q", plain) | |
| 66 | 66 | } | |
| 67 | 67 | for idx, line := range lines { | |
| 68 | 68 | if width := visibleWidth(line); width != 16 { | |
| 69 | - | t.Fatalf("wrapped right bar line %d width = %d, want 16: %q", idx, width, line) | |
| 69 | + | t.Fatalf("clipped right bar line %d width = %d, want 16: %q", idx, width, line) | |
| 70 | 70 | } | |
| 71 | 71 | } | |
| 72 | 72 | } | |
| 73 | 73 | ||
| 74 | - | func TestRenderRightBarUsesChecklistIndentHangingWrapAndBoldPhases(t *testing.T) { | |
| 74 | + | func TestRenderRightBarUsesChecklistIndentEllipsisAndBoldPhases(t *testing.T) { | |
| 75 | 75 | lines := RenderRightBar(Dark, []RightBarWidget{{ | |
| 76 | 76 | Extension: "tasked-phases", | |
| 77 | 77 | Title: "p 0/1 | t 0/1", | |
| @@ -81,14 +81,16 @@ func TestRenderRightBarUsesChecklistIndentHangingWrapAndBoldPhases(t *testing.T) | |||
| 81 | 81 | }, | |
| 82 | 82 | }}, 36, 8) | |
| 83 | 83 | plain := stripANSI(strings.Join(lines, "\n")) | |
| 84 | - | for _, want := range []string{ | |
| 85 | - | " [>] Copper Lantern Setup 0/1", | |
| 86 | - | " [ ] Sort the leftover star", | |
| 87 | - | " stickers", | |
| 88 | - | } { | |
| 89 | - | if !strings.Contains(plain, want) { | |
| 90 | - | t.Fatalf("right-bar checklist layout missing %q: %q", want, plain) | |
| 91 | - | } | |
| 84 | + | if !strings.Contains(plain, " [>] Copper Lantern Setup 0/1") { | |
| 85 | + | t.Fatalf("phase row has the wrong indentation: %q", plain) | |
| 86 | + | } | |
| 87 | + | plainLines := strings.Split(plain, "\n") | |
| 88 | + | if len(plainLines) < 4 { | |
| 89 | + | t.Fatalf("checklist rows are missing: %q", plain) | |
| 90 | + | } | |
| 91 | + | expectedTask := truncateRightBarLine(" [ ] Sort the leftover star stickers", 36) | |
| 92 | + | if got := strings.TrimRight(plainLines[3], " "); got != expectedTask { | |
| 93 | + | t.Fatalf("task row = %q, want clipped row %q", got, expectedTask) | |
| 92 | 94 | } | |
| 93 | 95 | if !strings.Contains(strings.Join(lines, "\n"), Bold("Copper Lantern Setup")) { | |
| 94 | 96 | t.Fatalf("phase name is not bold: %q", lines) | |
| @@ -96,7 +98,6 @@ func TestRenderRightBarUsesChecklistIndentHangingWrapAndBoldPhases(t *testing.T) | |||
| 96 | 98 | if strings.Contains(strings.Join(lines, "\n"), Bold("Sort the leftover")) { | |
| 97 | 99 | t.Fatalf("task text should not be bold: %q", lines) | |
| 98 | 100 | } | |
| 99 | - | plainLines := strings.Split(plain, "\n") | |
| 100 | 101 | if len(plainLines) < 2 || !strings.HasPrefix(plainLines[0], "[tasked-phases] p 0/1 | t 0/1") || strings.TrimSpace(plainLines[1]) != "" { | |
| 101 | 102 | t.Fatalf("widget title is not followed by an empty row: %q", plain) | |
| 102 | 103 | } | |
| @@ -51,7 +51,7 @@ func TestRenderRightBarSortsWidgetsAndBoundsRows(t *testing.T) { | |||||
| 51 | } | 51 | } | ||
| 52 | } | 52 | } | ||
| 53 | 53 | ||||
| 54 | - | func TestRenderRightBarWrapsLongWidgetLines(t *testing.T) { | 54 | + | func TestRenderRightBarClipsLongWidgetLinesWithEllipsis(t *testing.T) { |
| 55 | lines := RenderRightBar(Dark, []RightBarWidget{{ | 55 | lines := RenderRightBar(Dark, []RightBarWidget{{ | ||
| 56 | Extension: "plan", | 56 | Extension: "plan", | ||
| 57 | Lines: []string{"one two three four five six"}, | 57 | Lines: []string{"one two three four five six"}, | ||
| @@ -61,17 +61,17 @@ func TestRenderRightBarWrapsLongWidgetLines(t *testing.T) { | |||||
| 61 | t.Fatalf("right bar rows = %d, want 8", len(lines)) | 61 | t.Fatalf("right bar rows = %d, want 8", len(lines)) | ||
| 62 | } | 62 | } | ||
| 63 | plain := stripANSI(strings.Join(lines, "\n")) | 63 | plain := stripANSI(strings.Join(lines, "\n")) | ||
| 64 | - | if !strings.Contains(plain, "one two three") || !strings.Contains(plain, "four five six") { | 64 | + | if !strings.Contains(plain, " one two thre...") || strings.Contains(plain, "four five six") { |
| 65 | - | t.Fatalf("long widget line was not wrapped: %q", plain) | 65 | + | t.Fatalf("long widget line was not clipped with an ellipsis: %q", plain) |
| 66 | } | 66 | } | ||
| 67 | for idx, line := range lines { | 67 | for idx, line := range lines { | ||
| 68 | if width := visibleWidth(line); width != 16 { | 68 | if width := visibleWidth(line); width != 16 { | ||
| 69 | - | t.Fatalf("wrapped right bar line %d width = %d, want 16: %q", idx, width, line) | 69 | + | t.Fatalf("clipped right bar line %d width = %d, want 16: %q", idx, width, line) |
| 70 | } | 70 | } | ||
| 71 | } | 71 | } | ||
| 72 | } | 72 | } | ||
| 73 | 73 | ||||
| 74 | - | func TestRenderRightBarUsesChecklistIndentHangingWrapAndBoldPhases(t *testing.T) { | 74 | + | func TestRenderRightBarUsesChecklistIndentEllipsisAndBoldPhases(t *testing.T) { |
| 75 | lines := RenderRightBar(Dark, []RightBarWidget{{ | 75 | lines := RenderRightBar(Dark, []RightBarWidget{{ | ||
| 76 | Extension: "tasked-phases", | 76 | Extension: "tasked-phases", | ||
| 77 | Title: "p 0/1 | t 0/1", | 77 | Title: "p 0/1 | t 0/1", | ||
| @@ -81,14 +81,16 @@ func TestRenderRightBarUsesChecklistIndentHangingWrapAndBoldPhases(t *testing.T) | |||||
| 81 | }, | 81 | }, | ||
| 82 | }}, 36, 8) | 82 | }}, 36, 8) | ||
| 83 | plain := stripANSI(strings.Join(lines, "\n")) | 83 | plain := stripANSI(strings.Join(lines, "\n")) | ||
| 84 | - | for _, want := range []string{ | 84 | + | if !strings.Contains(plain, " [>] Copper Lantern Setup 0/1") { |
| 85 | - | " [>] Copper Lantern Setup 0/1", | 85 | + | t.Fatalf("phase row has the wrong indentation: %q", plain) |
| 86 | - | " [ ] Sort the leftover star", | 86 | + | } |
| 87 | - | " stickers", | 87 | + | plainLines := strings.Split(plain, "\n") |
| 88 | - | } { | 88 | + | if len(plainLines) < 4 { |
| 89 | - | if !strings.Contains(plain, want) { | 89 | + | t.Fatalf("checklist rows are missing: %q", plain) |
| 90 | - | t.Fatalf("right-bar checklist layout missing %q: %q", want, plain) | 90 | + | } |
| 91 | - | } | 91 | + | expectedTask := truncateRightBarLine(" [ ] Sort the leftover star stickers", 36) |
| 92 | + | if got := strings.TrimRight(plainLines[3], " "); got != expectedTask { | |||
| 93 | + | t.Fatalf("task row = %q, want clipped row %q", got, expectedTask) | |||
| 92 | } | 94 | } | ||
| 93 | if !strings.Contains(strings.Join(lines, "\n"), Bold("Copper Lantern Setup")) { | 95 | if !strings.Contains(strings.Join(lines, "\n"), Bold("Copper Lantern Setup")) { | ||
| 94 | t.Fatalf("phase name is not bold: %q", lines) | 96 | t.Fatalf("phase name is not bold: %q", lines) | ||
| @@ -96,7 +98,6 @@ func TestRenderRightBarUsesChecklistIndentHangingWrapAndBoldPhases(t *testing.T) | |||||
| 96 | if strings.Contains(strings.Join(lines, "\n"), Bold("Sort the leftover")) { | 98 | if strings.Contains(strings.Join(lines, "\n"), Bold("Sort the leftover")) { | ||
| 97 | t.Fatalf("task text should not be bold: %q", lines) | 99 | t.Fatalf("task text should not be bold: %q", lines) | ||
| 98 | } | 100 | } | ||
| 99 | - | plainLines := strings.Split(plain, "\n") | |||
| 100 | if len(plainLines) < 2 || !strings.HasPrefix(plainLines[0], "[tasked-phases] p 0/1 | t 0/1") || strings.TrimSpace(plainLines[1]) != "" { | 101 | if len(plainLines) < 2 || !strings.HasPrefix(plainLines[0], "[tasked-phases] p 0/1 | t 0/1") || strings.TrimSpace(plainLines[1]) != "" { | ||
| 101 | t.Fatalf("widget title is not followed by an empty row: %q", plain) | 102 | t.Fatalf("widget title is not followed by an empty row: %q", plain) | ||
| 102 | } | 103 | } | ||