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
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:
cursed_renderer.go
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:
attachments.zip
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.Printlnpath thencounts the inserted text with
ansi.StringWidth, which uses grapheme-widthsemantics. The two calculations disagree for some emoji sequences.
With an eight-column buffer,
🏳️🌈aaaaaaafits according to the active WcWidthbuffer, but
insertAbovecounts it as wider than the buffer. The renderertherefore 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.Printlnoutput is inserted.Setup
The source under test is Bubble Tea v2 at audited revision
73b6d91ac1c3854dd4af046ab5f9e51d3b3b4290.To Reproduce
From this report directory, run:
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 ASCIIcontrol with
🏳️🌈aaaaaaa. Three consecutive runs produced the same result: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 Teamainrevision checked for this report on 2026-09-12.
Program.Printlnqueues aprintLineMessageintea.go.
The normal startup path creates a
cursedRendererand asks the terminal forUnicode-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 atlines 765–767, splits the queued text into logical lines, and then computes the
extra wrapped-line count with
ansi.StringWidthat lines 776–783. Thesubsequent newline insertion uses that count at line 786:
cursed_renderer.go
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/insertAbovewidth-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:
attachments.zip