charmbracelet / charmbracelet/bubbletea

`tea.Println` can add an extra line for a wide grapheme in the WcWidth fallback

Open
#1,802 0 comments 0 reactions 0 assignees View on GitHub
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.