charmbracelet / charmbracelet/bubbletea
`tea.Println` can add an extra line for a wide grapheme in the WcWidth fallback
- Dominant language
- Go
- Stars
- 44.9k
- Forks
- 1.3k
- PR merge metrics
- No merged PRs in 30d
Description
**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](attachments/environment.txt)
- Shell [see attachments/environment.txt](attachments/environment.txt)
- Terminal Emulator [eight-column PTY; see attachments/environment.txt](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:
```sh
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:
```text
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](attachments/evidence.log). The terminal-query setup
is recorded in [attachments/environment.txt](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](https://github.com/charmbracelet/bubbletea/blob/73b6d91ac1c3854dd4af046ab5f9e51d3b3b4290/tea.go#L1377-L1385).
The normal startup path creates a `cursedRenderer` and asks the terminal for
Unicode-core mode (2027) in
[tea.go](https://github.com/charmbracelet/bubbletea/blob/73b6d91ac1c3854dd4af046ab5f9e51d3b3b4290/tea.go#L1064-L1074)
and
[tea.go](https://github.com/charmbracelet/bubbletea/blob/73b6d91ac1c3854dd4af046ab5f9e51d3b3b4290/tea.go#L1118-L1124).
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](https://github.com/charmbracelet/bubbletea/blob/73b6d91ac1c3854dd4af046ab5f9e51d3b3b4290/cursed_renderer.go#L739-L753).
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](https://github.com/charmbracelet/bubbletea/blob/73b6d91ac1c3854dd4af046ab5f9e51d3b3b4290/cursed_renderer.go#L756-L786)
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](https://github.com/charmbracelet/bubbletea/issues/1736),
[#1740](https://github.com/charmbracelet/bubbletea/issues/1740),
[#1741](https://github.com/charmbracelet/bubbletea/pull/1741), and
[#1742](https://github.com/charmbracelet/bubbletea/pull/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:
- [repro.sh](attachments/repro.sh)
- [evidence.log](attachments/evidence.log)
- [environment.txt](attachments/environment.txt)
- [attachments.zip](attachments.zip)
[attachments.zip](https://github.com/user-attachments/files/32142305/attachments.zip)
Contributor guide
Research direction
Start by running attachments/repro.sh, then read insertAbove in cursed_renderer.go and the active screen buffer’s width-method selection. The issue identifies the mismatch between that method and ansi.StringWidth. Done means the eight-column WcWidth fallback produces one inserted newline for the emoji case, matching the ASCII control.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- cli
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 73/100