Skip to content
Merged
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
8 changes: 3 additions & 5 deletions cmd/entire/cli/activity_render.go
Original file line number Diff line number Diff line change
Expand Up @@ -352,11 +352,9 @@ func renderRepoChart(w io.Writer, sty activityStyles, repos []repoContribution)
}

for _, r := range display {
name := r.Repo
if len(name) > maxNameLen {
name = name[:maxNameLen-1] + "…"
}
name = fmt.Sprintf("%-*s", maxNameLen, name)
// padOrTruncate is rune-aware; truncating r.Repo with a byte slice
// could split a multi-byte rune and emit invalid UTF-8.
name := padOrTruncate(r.Repo, maxNameLen)

bar := renderAgentBar(sty, r.Agents, maxCount, barWidth)
count := fmt.Sprintf("%*d", countWidth, r.Total)
Expand Down
25 changes: 25 additions & 0 deletions cmd/entire/cli/activity_render_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,31 @@ func TestRenderRepoChart_LimitsToFive(t *testing.T) {
}
}

func TestRenderRepoChart_UnicodeNameSafeTruncation(t *testing.T) {
t.Parallel()
var buf bytes.Buffer
sty := activityStyles{width: 60}
// A repo name long enough to force truncation and full of multi-byte
// runes, so a byte-based slice would split a rune and emit invalid UTF-8.
repos := []repoContribution{
{
Repo: strings.Repeat("é", 40),
Total: 3,
Agents: map[string]int{activityTestAgentClaude: 3},
},
}

renderRepoChart(&buf, sty, repos)
out := buf.String()

if !utf8.ValidString(out) {
t.Fatal("rendered repo chart contains invalid UTF-8")
}
if !strings.Contains(out, "…") {
t.Error("expected the long repo name to be truncated with an ellipsis")
}
}

func TestPadOrTruncate(t *testing.T) {
t.Parallel()
tests := []struct {
Expand Down
Loading