`tea.Println` can add an extra line for a wide grapheme in the WcWidth fallback
Describe the bug
When the terminal does not answer Bubble Tea's Unicode-core mode query, the
renderer keeps the WcWidth screen-buffer method. The tea.Println path then
counts the inserted text with ansi.StringWidth, which uses grapheme-width
semantics. The two calculations disagree for some emoji sequences.
With an eight-column buffer, ️aaaaaaa fits according to the active WcWidth
buffer, but insertAbove counts it as wider than the buffer. The renderer
therefore emits one additional newline for the same visual input that the
ASCII control occupies in one line. In a real application this leaves a blank
row or shifts the managed view after tea.Println output is inserted.
Setup
- OS see attachments/environment.txt
- Shell see attachments/environment.txt
- Terminal Emulator eight-column PTY; see attachments/environment.txt
- Terminal Multiplexer [none]
The source under test is Bubble Tea v2 at audited revision
73b6d91ac1c3854dd4af046ab5f9e51d3b3b4290.
To Reproduce
From this report directory, run:
bash attachments/repro.sh
The script builds a small Bubble Tea v2 program and runs it in an eight-column
PTY. It deliberately does not answer CSI ?2027$p, then compares an ASCII
control with ️aaaaaaa. Three consecutive runs produced the same result:
PASS case=ascii default-wc insertion newlines=1
FAIL unicode grapheme changed Wc insertion offset: expected=1 actual=2
checked=2 failures=1
The complete captured traces and run hashes are in attachments/evidence.log. The terminal-query setup is recorded in attachments/environment.txt.
Source Code
The audited revision is
73b6d91ac1c3854dd4af046ab5f9e51d3b3b4290, the public Bubble Tea main
revision checked for this report on 2026-09-12.
Program.Println queues a printLineMessage in
tea.go.
The normal startup path creates a cursedRenderer and asks the terminal for
Unicode-core mode (2027) in
tea.go
and
tea.go.
If the terminal does not report that mode, the screen buffer remains on its
default WcWidth method. The method switch itself is visible in
cursed_renderer.go.
The mismatch is in insertAbove: it obtains the active buffer dimensions at
lines 765–767, splits the queued text into logical lines, and then computes the
extra wrapped-line count with ansi.StringWidth at lines 776–783. The
subsequent newline insertion uses that count at line 786:
The renderer consequently mixes the active buffer's width policy with a separate grapheme-width measurement. A terminal that successfully enables Unicode-core mode does not exercise this fallback, which is why the missing terminal response is part of the reproducer rather than an incidental detail.
Expected behavior
The line-count calculation should use the width method of the active screen buffer. In the fallback case, the Unicode input above should produce the same single inserted line as the eight-column ASCII control, rather than adding an extra blank row.
Screenshots
No screenshots were captured.
Additional context
I searched the existing Bubble Tea issues and did not find a report for this
tea.Println/insertAbove width-policy mismatch. Issues
#1736,
#1740,
#1741, and
#1742 concern flushing,
first-flush cursor movement, or related renderer sequencing. They do not cover
a stable extra newline caused by a wide grapheme in the WcWidth fallback.
The report artifacts are:
Source: charmbracelet/bubbletea