Describe the bug
The truncation helpers in the TUI slice on byte offsets instead of rune boundaries, so when a cut lands in the middle of a multi-byte character the output is broken UTF-8 and the terminal shows a �. Same root cause also means CJK/emoji lines get cut short, since those runes eat 2-4 bytes of the budget but only take 1-2 columns.
Helpers doing this, all under strix/interface/tui/internal/render/:
helpers.go: truncStr, firstN, lastN
proxy.go: ptrunc (and psanitize/joinTrunc via it)
terminal.go:87
I noticed it in the proxy view, which prints captured hosts/paths/headers/bodies — those carry UTF-8 all the time (JSON APIs, non-ASCII bodies, IDN hosts), so it's easy to hit.
To Reproduce
Helpers are unexported so easiest is a test in package render (commit 1c499c5):
func TestTruncSplitsRunes(t *testing.T) {
for _, out := range []string{
firstN("中文字ABC", 4),
lastN("ABC中文字", 4),
truncStr("中文字ABC", 4),
ptrunc("中文字ABCDEF", 5),
} {
if !utf8.ValidString(out) {
t.Errorf("invalid UTF-8: %q", out)
}
}
}
All four fail, e.g. firstN("中文字ABC", 4) returns "中\xe6" — the trailing byte is half a rune.
Expected behavior
Truncation shouldn't split a rune, and the width limit should count columns, not bytes.
ansi.Truncate from charmbracelet/x/ansi does exactly this and is already a dependency (used in internal/app), so switching these helpers over would fix it and match how app/ already handles width. ansi.Truncate("中文字ABC", 4, "") → "中文".
System Information:
- OS: macOS 14 arm64 (logic is platform-independent)
- Commit:
1c499c5
- Python Version: N/A, this is the Go TUI
- LLM Used: N/A, pure rendering path
Happy to send a PR switching these to ansi.Truncate with a regression test if that's the direction you want.
Describe the bug
The truncation helpers in the TUI slice on byte offsets instead of rune boundaries, so when a cut lands in the middle of a multi-byte character the output is broken UTF-8 and the terminal shows a
�. Same root cause also means CJK/emoji lines get cut short, since those runes eat 2-4 bytes of the budget but only take 1-2 columns.Helpers doing this, all under
strix/interface/tui/internal/render/:helpers.go:truncStr,firstN,lastNproxy.go:ptrunc(andpsanitize/joinTruncvia it)terminal.go:87I noticed it in the proxy view, which prints captured hosts/paths/headers/bodies — those carry UTF-8 all the time (JSON APIs, non-ASCII bodies, IDN hosts), so it's easy to hit.
To Reproduce
Helpers are unexported so easiest is a test in
package render(commit1c499c5):All four fail, e.g.
firstN("中文字ABC", 4)returns"中\xe6"— the trailing byte is half a rune.Expected behavior
Truncation shouldn't split a rune, and the width limit should count columns, not bytes.
ansi.Truncatefromcharmbracelet/x/ansidoes exactly this and is already a dependency (used ininternal/app), so switching these helpers over would fix it and match howapp/already handles width.ansi.Truncate("中文字ABC", 4, "")→"中文".System Information:
1c499c5Happy to send a PR switching these to
ansi.Truncatewith a regression test if that's the direction you want.