Skip to content

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

Description

@N0zoM1z0

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions