Skip to content

Draft note composer never wraps CJK text — one-line textarea scrolls horizontally #681

Description

@IAMLEIzZ

What happened?

When typing a review note in the draft composer (press c on a hunk), CJK text never soft-wraps at the right moment. The composer stays one row high and only shows the visual row the cursor is on, so previously typed text becomes invisible while typing.
Right before the wrap threshold the box looks full but is still one row
(figure 1):
Image

After typing one more character, the view jumps to the overflow visual row and the composer appears to "lose" everything except the last typed character (figure 2). The text is not lost — the 1-row-high viewport
follows the cursor and hides the rest:
(figure 2):
Image

An English note of the same display length wraps and grows rows correctly.

Root cause — two compounding issues:

  1. draftVisualLineCount in src/ui/components/panes/AgentInlineNote.tsx estimates the composer height with String#length (UTF-16 code units) instead of terminal cells — the same bug class as Agent note text with CJK characters is truncated instead of wrapped (wrapText measures UTF-16 code units, not terminal cells) #566, in a code path that fix(ui): wrap agent note text by terminal cells #567 didn't touch. A 60-char CJK string (120 cells) in a ~76-cell box estimates to ceil(60/76) = 1 row, so the composer height stays 1 until the character count exceeds the box width (for CJK that means roughly two full visual rows of text).
  2. OpenTUI's TextareaRenderable reports virtualLineCount capped at the viewport height: a 1-row-high textarea never starts wrapping, so the max(estimate, virtualLineCount) correction in onContentChange can never grow the height either — a deadlock. The editor keeps the cursor visible by scrolling the capped viewport vertically, which is exactly what produces figure 2.

The deadlock only breaks when the character count finally exceeds the box width, the estimate grows to 2, and wrapping suddenly kicks in — which is why the composer sometimes appears to wrap after enough input.

English text escapes the deadlock because code units == cells: the estimate grows as soon as a line exceeds the box width. (A milder variant is still reachable in English with pathological word lengths: three words each slightly over half the box width word-wrap to 3 rows while the estimate says 2, and the cap hides the third row.)

Two smaller observations from instrumenting the component:

  • useEffect([draft?.body]) resets the line-count hint to the hard newline count on every keystroke, wiping the virtualLineCount-based correction from onContentChange one render later.
  • The same code-unit estimate feeds measureAgentInlineNoteHeight, so planned row heights drift from mounted heights for CJK drafts (the row-windowed stream relies on these agreeing).

Steps to reproduce

  1. hunk diff on any change (split layout).
  2. Press c to start a review note.
  3. Type a long CJK sentence without spaces (e.g. repeat 测试 ~40 times).
  4. The composer stays one row high and shows only the cursor's visual row (figures 1-2); an English sentence of similar display length wraps instead. Typing past ~2× the visual width suddenly self-heals.

Expected behavior

The draft composer wraps by terminal cells and grows a row whenever the text exceeds the box width — for CJK exactly as it already does for English, and consistent with how the saved note renders (cell-aware
wrapText since #567). The height estimate, the editor's actual wrapping, and measureAgentInlineNoteHeight should all agree.

A natural fix is to give draftVisualLineCount the same cell-aware word wrap semantics as wrapText (or share one helper), so the estimate always matches the editor's real wrapping and the height-1 deadlock never forms.

Version

0.18.0-beta.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions