fix(tui): strip emoji from rendered titles

This commit is contained in:
Vincent Koc 2026-04-28 18:09:42 -07:00
parent 039e1927ca
commit 4f4cacedab
No known key found for this signature in database
2 changed files with 86 additions and 5 deletions

View File

@ -513,7 +513,7 @@ func (m clusterBrowserModel) detailLines(width int) []string {
bold(fmt.Sprintf("Cluster %d", cluster.ID)),
color("#5bc0eb", cluster.StableSlug),
}
lines = append(lines, wrapPlain(firstNonEmpty(cluster.RepresentativeTitle, cluster.Title, "Untitled cluster"), width)...)
lines = append(lines, wrapPlain(splitClusterTitle(cluster), width)...)
lines = append(lines,
"",
fmt.Sprintf("members: %d status: %s updated: %s", cluster.MemberCount, firstNonEmpty(cluster.Status, "unknown"), formatRelativeTime(cluster.UpdatedAt)),
@ -534,7 +534,7 @@ func (m clusterBrowserModel) detailLines(width int) []string {
dim(tuiRule(width)),
bold(fmt.Sprintf("%s #%d", kindTitle(thread.Kind), thread.Number)),
)
lines = append(lines, wrapPlain(thread.Title, width)...)
lines = append(lines, wrapPlain(renderTitleText(thread.Title), width)...)
lines = append(lines,
"",
)
@ -557,7 +557,7 @@ func (m clusterBrowserModel) detailLines(width int) []string {
neighbor.Thread.Number,
kindTitle(neighbor.Thread.Kind),
neighbor.Score*100,
neighbor.Thread.Title,
renderTitleText(neighbor.Thread.Title),
), width))
}
lines = append(lines, "")
@ -2283,7 +2283,7 @@ func (m clusterBrowserModel) memberTableRows() []table.Row {
fmt.Sprintf("#%d", thread.Number),
stateGlyph(memberDisplayState(member.member)),
formatRelativeTime(thread.UpdatedAtGitHub),
thread.Title,
renderTitleText(thread.Title),
})
}
return rows
@ -3327,7 +3327,47 @@ func layoutLabel(layout tuiLayout) string {
}
func splitClusterTitle(cluster store.ClusterSummary) string {
return firstNonEmpty(cluster.RepresentativeTitle, cluster.Title, "Untitled cluster")
return firstNonEmpty(renderTitleText(cluster.RepresentativeTitle), renderTitleText(cluster.Title), "Untitled cluster")
}
func renderTitleText(value string) string {
value = strings.TrimSpace(stripEmoji(value))
if value == "" {
return ""
}
return strings.Join(strings.Fields(value), " ")
}
func stripEmoji(value string) string {
if value == "" {
return ""
}
var out strings.Builder
out.Grow(len(value))
for _, r := range value {
if isEmojiRune(r) {
continue
}
out.WriteRune(r)
}
return out.String()
}
func isEmojiRune(r rune) bool {
switch {
case r == '\u200d' || r == '\u20e3':
return true
case r >= '\ufe00' && r <= '\ufe0f':
return true
case r >= '\U0001f000' && r <= '\U0001faff':
return true
case r >= '\u2600' && r <= '\u27bf':
return true
case r == '\u3030' || r == '\u303d' || r == '\u3297' || r == '\u3299':
return true
default:
return false
}
}
func sortedSummaryKeys(values map[string]string) []string {

View File

@ -337,6 +337,47 @@ func TestTUIClusterRowsShowReadableState(t *testing.T) {
}
}
func TestTUIRowsStripEmojiFromRenderedTitles(t *testing.T) {
model := newClusterBrowserModel(context.Background(), nil, 0, clusterBrowserPayload{
Repository: "openclaw/openclaw",
Sort: "recent",
Clusters: []store.ClusterSummary{{
ID: 1,
Status: "active",
StableSlug: "emoji-title",
RepresentativeKind: "issue",
RepresentativeTitle: "🔥 Gateway crash 🧨 after upgrade",
RepresentativeNumber: 123,
MemberCount: 3,
UpdatedAt: "2026-04-27T00:00:00Z",
}},
})
clusterRows := model.clusterRows()
if strings.ContainsAny(clusterRows[0][4], "🔥🧨") {
t.Fatalf("cluster title still contains emoji: %q", clusterRows[0][4])
}
if clusterRows[0][4] != "Gateway crash after upgrade" {
t.Fatalf("cluster title = %q, want sanitized title", clusterRows[0][4])
}
model.memberRows = []memberRow{{
selectable: true,
member: store.ClusterMemberDetail{Thread: store.Thread{
Number: 123,
State: "open",
Title: "🚨 Browser snapshot fails ✅",
UpdatedAtGitHub: "2026-04-27T00:00:00Z",
}},
}}
memberRows := model.memberTableRows()
if strings.ContainsAny(memberRows[0][3], "🚨✅") {
t.Fatalf("member title still contains emoji: %q", memberRows[0][3])
}
if memberRows[0][3] != "Browser snapshot fails" {
t.Fatalf("member title = %q, want sanitized title", memberRows[0][3])
}
}
func TestTUIRenderedRowsStyleOpenAndClosedStates(t *testing.T) {
openCluster := clusterRowStyle(store.ClusterSummary{Status: "active"}, false, false)
closedCluster := clusterRowStyle(store.ClusterSummary{Status: "closed"}, false, false)