[GH-ISSUE #255] bug: CJK char corruption in text rendering #66

Closed
opened 2026-03-02 23:44:16 +03:00 by kerem · 22 comments
Owner

Originally created by @zenyr on GitHub (Nov 3, 2025).
Original GitHub issue: https://github.com/anomalyco/opentui/issues/255

Problem

Both Solid and React reconcilers corrupt certain CJK characters during rendering. Core handles them correctly.

Expected → Actual (Solid/React)
你好世界 → 你好世ç界
中文   → 中文æ
한글   → í한글

Emoji & ASCII unaffected.

Root Cause

Encoding/displayWidth mismatch in JSX→TextNode conversion path. Likely culprit: reconciler text handling vs core displayWidth calc.

Investigation Notes

  • Core library handles all CJK/emoji correctly
  • Issue affects both Solid and React reconciler text rendering paths
  • Reproduction: specific CJK chars render as different glyphs

Next Steps

  1. Debug TextNode creation from Solid/React JSX
  2. Verify displayWidth consistency with core
  3. Check for encoding issues in reconciler text handling

Created with assistance from OpenCode & Claude Haiku 4.5

Originally created by @zenyr on GitHub (Nov 3, 2025). Original GitHub issue: https://github.com/anomalyco/opentui/issues/255 ## Problem Both Solid and React reconcilers corrupt certain CJK characters during rendering. ~Core handles them correctly.~ ``` Expected → Actual (Solid/React) 你好世界 → 你好世ç界 中文 → 中文æ 한글 → í한글 ``` Emoji & ASCII unaffected. ## Root Cause Encoding/displayWidth mismatch in JSX→TextNode conversion path. Likely culprit: reconciler text handling vs core displayWidth calc. ## Investigation Notes - Core library handles all CJK/emoji correctly - Issue affects both Solid and React reconciler text rendering paths - Reproduction: specific CJK chars render as different glyphs ## Next Steps 1. Debug TextNode creation from Solid/React JSX 2. Verify displayWidth consistency with core 3. Check for encoding issues in reconciler text handling --- *Created with assistance from OpenCode & Claude Haiku 4.5*
kerem 2026-03-02 23:44:16 +03:00
  • closed this issue
  • added the
    core
    bug
    labels
Author
Owner

@kommander commented on GitHub (Nov 3, 2025):

Thanks for looking into this. I think the root cause for something like this would rather be in the renderer.zig for the ansi output, or width calculations in utf8.zig. Setting a default background prevents text from ever being rendered on transparent terminal background.

<!-- gh-comment-id:3480021635 --> @kommander commented on GitHub (Nov 3, 2025): Thanks for looking into this. I think the root cause for something like this would rather be in the renderer.zig for the ansi output, or width calculations in utf8.zig. Setting a default background prevents text from ever being rendered on transparent terminal background.
Author
Owner

@kommander commented on GitHub (Nov 3, 2025):

Image

I can't seem to be able to reproduce that. What terminal are you using?

<!-- gh-comment-id:3480876382 --> @kommander commented on GitHub (Nov 3, 2025): <img width="334" height="123" alt="Image" src="https://github.com/user-attachments/assets/21ec2719-26fe-4287-89ea-c7b1d2e09c8c" /> I can't seem to be able to reproduce that. What terminal are you using?
Author
Owner

@zenyr commented on GitHub (Nov 4, 2025):

Oh, okay. I'll attach my system configuration here:
I also found this issue on Windows 11 Powershell / CMD environment too.
I've made a tangent PR (adding React test suite with skipped tests) on #262.

❯ fastfetch
                     ..'          jinhyeok
                 ,xNMM.           -----------------------------------------
               .OMMMMo            OS: macOS Sequoia 15.6.1 arm64
               lMM"               Host: MacBook Pro (14-inch, 2023)
     .;loddo:.  .olloddol;.       Kernel: Darwin 24.6.0
   cKMMMMMMMMMMNWMMMMMMMMMM0:     Uptime: 7 days, 19 hours, 46 mins
 .KMMMMMMMMMMMMMMMMMMMMMMMWd.     Packages: 230 (brew), 16 (brew-cask)
 XMMMMMMMMMMMMMMMMMMMMMMMX.       Shell: zsh 5.9
;MMMMMMMMMMMMMMMMMMMMMMMM:        Display (Color LCD): 3024x1964 @ 120 Hz (as 1512x982) in 14" [Built-in] *
:MMMMMMMMMMMMMMMMMMMMMMMM:        Display (RTK UHD HDR): 3360x1890 @ 60 Hz (as 1680x945) in 24" [External]
.MMMMMMMMMMMMMMMMMMMMMMMMX.       Display (LG HDR 4K): 3840x2160 @ 60 Hz (as 1920x1080) in 27" [External]
 kMMMMMMMMMMMMMMMMMMMMMMMMWd.     Display (LG HDR 4K): 3840x2160 @ 60 Hz (as 1920x1080) in 27" [External]
 'XMMMMMMMMMMMMMMMMMMMMMMMMMMk    DE: Aqua
  'XMMMMMMMMMMMMMMMMMMMMMMMMK.    WM: Quartz Compositor 278.4.7
    kMMMMMMMMMMMMMMMMMMMMMMd      WM Theme: Multicolor (Light)
     ;KMMMMMMMWXXWMMMMMMMk.       Font: .AppleSystemUIFont [System], Helvetica [User]
       "cooc*"    "*coo'"         Cursor: Fill - Black, Outline - White (32px)
                                  Terminal: tmux 3.4
                                  CPU: Apple M2 Max (12) @ 3.50 GHz
                                  GPU: Apple M2 Max (38) @ 1.40 GHz [Integrated]
❯ ghostty --version
Ghostty 1.3.0-main+3f75c66e8

Version
  - version: 1.3.0-main+3f75c66e8
  - channel: tip
Build Config
  - Zig version   : 0.15.2
  - build mode    : .ReleaseFast
  - app runtime   : .none
  - font engine   : .coretext
  - renderer      : renderer.generic.Renderer(renderer.Metal)
  - libxev        : kqueue

Update:

  • I'm not sure if this is related but this happened twice in a row for me, which is weird. (that t)
Image
<!-- gh-comment-id:3483919382 --> @zenyr commented on GitHub (Nov 4, 2025): Oh, okay. I'll attach my system configuration here: I also found this issue on `Windows 11 Powershell / CMD` environment too. I've made a tangent PR (adding React test suite with skipped tests) on #262. ``` ❯ fastfetch ..' jinhyeok ,xNMM. ----------------------------------------- .OMMMMo OS: macOS Sequoia 15.6.1 arm64 lMM" Host: MacBook Pro (14-inch, 2023) .;loddo:. .olloddol;. Kernel: Darwin 24.6.0 cKMMMMMMMMMMNWMMMMMMMMMM0: Uptime: 7 days, 19 hours, 46 mins .KMMMMMMMMMMMMMMMMMMMMMMMWd. Packages: 230 (brew), 16 (brew-cask) XMMMMMMMMMMMMMMMMMMMMMMMX. Shell: zsh 5.9 ;MMMMMMMMMMMMMMMMMMMMMMMM: Display (Color LCD): 3024x1964 @ 120 Hz (as 1512x982) in 14" [Built-in] * :MMMMMMMMMMMMMMMMMMMMMMMM: Display (RTK UHD HDR): 3360x1890 @ 60 Hz (as 1680x945) in 24" [External] .MMMMMMMMMMMMMMMMMMMMMMMMX. Display (LG HDR 4K): 3840x2160 @ 60 Hz (as 1920x1080) in 27" [External] kMMMMMMMMMMMMMMMMMMMMMMMMWd. Display (LG HDR 4K): 3840x2160 @ 60 Hz (as 1920x1080) in 27" [External] 'XMMMMMMMMMMMMMMMMMMMMMMMMMMk DE: Aqua 'XMMMMMMMMMMMMMMMMMMMMMMMMK. WM: Quartz Compositor 278.4.7 kMMMMMMMMMMMMMMMMMMMMMMd WM Theme: Multicolor (Light) ;KMMMMMMMWXXWMMMMMMMk. Font: .AppleSystemUIFont [System], Helvetica [User] "cooc*" "*coo'" Cursor: Fill - Black, Outline - White (32px) Terminal: tmux 3.4 CPU: Apple M2 Max (12) @ 3.50 GHz GPU: Apple M2 Max (38) @ 1.40 GHz [Integrated] ❯ ghostty --version Ghostty 1.3.0-main+3f75c66e8 Version - version: 1.3.0-main+3f75c66e8 - channel: tip Build Config - Zig version : 0.15.2 - build mode : .ReleaseFast - app runtime : .none - font engine : .coretext - renderer : renderer.generic.Renderer(renderer.Metal) - libxev : kqueue ``` Update: - I'm not sure if this is related but this happened twice in a row for me, which is weird. (that `t`) <img width="321" height="133" alt="Image" src="https://github.com/user-attachments/assets/f4526f01-0f0e-4601-b8e7-0ff9e8b9b970" />
Author
Owner

@kommander commented on GitHub (Nov 4, 2025):

Ahh tmux might be the culprit, it has some Unicode quirks, will try to reproduce.

The issue with the overlapping "t" is a different one, that needs flexShrink=0 and is handled in an issue on opencode.

<!-- gh-comment-id:3485998413 --> @kommander commented on GitHub (Nov 4, 2025): Ahh tmux might be the culprit, it has some Unicode quirks, will try to reproduce. The issue with the overlapping "t" is a different one, that needs flexShrink=0 and is handled in an issue on opencode.
Author
Owner

@zenyr commented on GitHub (Nov 5, 2025):

tmux might be the culprit

Hmm while I do agree with the reasoning I could 100% reproduce this on my Windows 11 device, which does not have any tmux related stuff available.

<!-- gh-comment-id:3488724017 --> @zenyr commented on GitHub (Nov 5, 2025): > tmux might be the culprit Hmm while I do agree with the reasoning I could 100% reproduce this on my `Windows 11 device`, which does not have any tmux related stuff available.
Author
Owner

@kommander commented on GitHub (Nov 5, 2025):

Yes, the windows terminals calculate Unicode width differently and not complete to the standard as well, like tmux. Proper Unicode support is lacking in many implementations unfortunately.

<!-- gh-comment-id:3491539475 --> @kommander commented on GitHub (Nov 5, 2025): Yes, the windows terminals calculate Unicode width differently and not complete to the standard as well, like tmux. Proper Unicode support is lacking in many implementations unfortunately.
Author
Owner

@kommander commented on GitHub (Nov 6, 2025):

Image I saw some of it happening. Happens at wrapping break points, it might calculate the byte offset for the chars wrong there. Similarly, for streaming content when the text is incomplete it might have bytes until the middle of a grapheme.
<!-- gh-comment-id:3494228526 --> @kommander commented on GitHub (Nov 6, 2025): <img width="49" height="40" alt="Image" src="https://github.com/user-attachments/assets/2c75169d-9f8c-4e2a-920e-d738ddd735b9" /> I saw some of it happening. Happens at wrapping break points, it might calculate the byte offset for the chars wrong there. Similarly, for streaming content when the text is incomplete it might have bytes until the middle of a grapheme.
Author
Owner

@zenyr commented on GitHub (Nov 6, 2025):

Ah sure, Windows native terminal is notorious for CJK users already :)

Hoever,I think I could reliably reproduce grapheme error, with or without chunk streaming.

For example:

Image

알겠습니다. Task 에이전트에 ktlint + detekt 검사를 위임하겠습니다.

This rendering issue persists regardless of being streamed or not.
This is another screenshot of the same session loaded without tmux: (opencode --continue)

Image
<!-- gh-comment-id:3494372266 --> @zenyr commented on GitHub (Nov 6, 2025): Ah sure, Windows native terminal is notorious for CJK users already :) Hoever,I think I could reliably reproduce grapheme error, with or without chunk streaming. For example: <img width="1100" height="187" alt="Image" src="https://github.com/user-attachments/assets/b207c56b-32e3-4e62-8642-ac9c0016fd2d" /> > 알겠습니다. Task 에이전트에 ktlint + detekt 검사를 위임하겠습니다. This rendering issue persists regardless of being streamed or not. This is another screenshot of the same session loaded `without tmux`: (opencode --continue) <img width="1852" height="1021" alt="Image" src="https://github.com/user-attachments/assets/f2946134-26ed-4d5a-bc82-cd62ca7e3a02" />
Author
Owner

@DonKongPaPa commented on GitHub (Nov 11, 2025):

check this out

https://github.com/user-attachments/assets/64da545e-368a-454d-be11-be5e229a69a9

<!-- gh-comment-id:3516646814 --> @DonKongPaPa commented on GitHub (Nov 11, 2025): check this out https://github.com/user-attachments/assets/64da545e-368a-454d-be11-be5e229a69a9
Author
Owner

@kommander commented on GitHub (Nov 11, 2025):

@DonKongPaPa can you add the raw text you pasted here as well. I haven't gotten to the bottom of this yet, I suspect some grapheme byte offsets are miscalculated somewhere.

<!-- gh-comment-id:3517506732 --> @kommander commented on GitHub (Nov 11, 2025): @DonKongPaPa can you add the raw text you pasted here as well. I haven't gotten to the bottom of this yet, I suspect some grapheme byte offsets are miscalculated somewhere.
Author
Owner

@DonKongPaPa commented on GitHub (Nov 12, 2025):

@DonKongPaPa can you add the raw text you pasted here as well. I haven't gotten to the bottom of this yet, I suspect some grapheme byte offsets are miscalculated somewhere.

  1. 前后端分离 - TypeScript逻辑 + Go TUI界面
  2. 组件化设计 - 基于tview的可复用组件
  3. 渐进式交互 - 逐步披露避免信息过载
  4. 智能上下文 - 基于项目状态动态生成问题
  5. 丰富的问题类型 - 支持6种不同的交互形式
  6. 完整的验证 - 实时输入验证和错误处理
<!-- gh-comment-id:3519820937 --> @DonKongPaPa commented on GitHub (Nov 12, 2025): > [@DonKongPaPa](https://github.com/DonKongPaPa) can you add the raw text you pasted here as well. I haven't gotten to the bottom of this yet, I suspect some grapheme byte offsets are miscalculated somewhere. 1. 前后端分离 - TypeScript逻辑 + Go TUI界面 2. 组件化设计 - 基于tview的可复用组件 3. 渐进式交互 - 逐步披露避免信息过载 4. 智能上下文 - 基于项目状态动态生成问题 5. 丰富的问题类型 - 支持6种不同的交互形式 6. 完整的验证 - 实时输入验证和错误处理
Author
Owner

@DonKongPaPa commented on GitHub (Nov 12, 2025):

@DonKongPaPa can you add the raw text you pasted here as well. I haven't gotten to the bottom of this yet, I suspect some grapheme byte offsets are miscalculated somewhere.

Not only that, I've also found that the cursor behavior is abnormal.

<!-- gh-comment-id:3519846616 --> @DonKongPaPa commented on GitHub (Nov 12, 2025): > [@DonKongPaPa](https://github.com/DonKongPaPa) can you add the raw text you pasted here as well. I haven't gotten to the bottom of this yet, I suspect some grapheme byte offsets are miscalculated somewhere. Not only that, I've also found that the cursor behavior is abnormal.
Author
Owner

@zenyr commented on GitHub (Nov 13, 2025):

I can confirm that the input editor now supports CJK way better than before (especially when I try to mention an agent mid-prompt)

Image 👍

But the display part issue is still somewhat there(always at the line-break?), while the majority of this issue went away (just got my hands on the latest version, needs more test).

Image
Image Image

https://opencode.ai/s/F1wEN23v

<!-- gh-comment-id:3524827199 --> @zenyr commented on GitHub (Nov 13, 2025): I can confirm that the input editor now supports CJK *way better* than before (especially when I try to mention an agent mid-prompt) <img width="202" height="47" alt="Image" src="https://github.com/user-attachments/assets/533ea2df-1ee2-492e-9d2c-43995543c08b" /> 👍 But the display part issue is still somewhat there(always at the line-break?), while the majority of this issue went away (just got my hands on the latest version, needs more test). <img width="519" height="171" alt="Image" src="https://github.com/user-attachments/assets/f381f824-d53b-468c-946b-100d931a2abc" /> --- <img width="634" height="656" alt="Image" src="https://github.com/user-attachments/assets/c365c62f-716f-4b0b-a76d-ba1cb8b073b6" /> <img width="642" height="338" alt="Image" src="https://github.com/user-attachments/assets/35f05557-9412-48dc-9f01-0c341511f8e6" /> → https://opencode.ai/s/F1wEN23v
Author
Owner

@kommander commented on GitHub (Nov 13, 2025):

I see, I think I know where to look. I'll give this another go asap.

<!-- gh-comment-id:3526565316 --> @kommander commented on GitHub (Nov 13, 2025): I see, I think I know where to look. I'll give this another go asap.
Author
Owner

@zenyr commented on GitHub (Nov 18, 2025):

Using opencode .70 and it seems very promising so far. (not seeing any glitches)

<!-- gh-comment-id:3544712734 --> @zenyr commented on GitHub (Nov 18, 2025): Using opencode .70 and it seems very promising so far. (not seeing any glitches)
Author
Owner

@zenyr commented on GitHub (Nov 18, 2025):

I could see a few consistent glitches on line-breaks.

Image

Original

▼Todo
[✓] Config-Panel 설계 워크스루 - 전체 흐름도 및 개요 정리
[✓] 주요 모듈별 상세 분석 및 예시 코드 제시
[✓] 데이터 흐름 상세 워크스루 (초기화 → 편집 → 제출)

Rendered

▼Todo
[✓] Config-Panel 설계 워크스루 - 전체 흐
흐 도 및 개요 정리
[✓] 주요 모듈별 상세 분석
 및 예시 코드 제시
[✓] 데이터 흐름 상세 워크스루 (초
초 화 → 편집 → 제출)

"흐름도" → "흐\n흐 도"
"초기화" → "초\n초 화"

Extracted with ascii sequence via tmux

▼ Todo
[✓] Config-Panel 설계 워크스루 - 전체 흐
흐 도 및 개요 정리
[✓] 주요 모듈별 상세 분석
 및 예시 코드 제시
[✓] 데이터 흐름 상세 워크스루 (초
초 화 → 편집 → 제출)

This particular case came from opencode v1.0.72 using tmux + ghostty. but I think still relevant here.

Reproduced without tmux (ghostty)

  • 플러그인 became 플러\n러 인
Image
<!-- gh-comment-id:3547155928 --> @zenyr commented on GitHub (Nov 18, 2025): I could see a few consistent glitches on line-breaks. <img width="301" height="133" alt="Image" src="https://github.com/user-attachments/assets/94540cfb-0bd6-42c8-bf53-bf88a83830c4" /> ### Original ``` ▼Todo [✓] Config-Panel 설계 워크스루 - 전체 흐름도 및 개요 정리 [✓] 주요 모듈별 상세 분석 및 예시 코드 제시 [✓] 데이터 흐름 상세 워크스루 (초기화 → 편집 → 제출) ``` ### Rendered ``` ▼Todo [✓] Config-Panel 설계 워크스루 - 전체 흐 흐 도 및 개요 정리 [✓] 주요 모듈별 상세 분석 및 예시 코드 제시 [✓] 데이터 흐름 상세 워크스루 (초 초 화 → 편집 → 제출) ``` "흐름도" → "흐\n흐 도" "초기화" → "초\n초 화" ### Extracted with ascii sequence via tmux ``` ▼ Todo [✓] Config-Panel 설계 워크스루 - 전체 흐 흐 도 및 개요 정리 [✓] 주요 모듈별 상세 분석  및 예시 코드 제시 [✓] 데이터 흐름 상세 워크스루 (초 초 화 → 편집 → 제출) ``` This particular case came from `opencode v1.0.72` using tmux + ghostty. but I think still relevant here. ### Reproduced without tmux (ghostty) - `플러그인` became `플러\n러 인` <img width="312" height="51" alt="Image" src="https://github.com/user-attachments/assets/3ba4814b-bd79-4068-a199-e0eb23fa54d3" />
Author
Owner

@kommander commented on GitHub (Nov 18, 2025):

Alright, let's do this again, I'll get it right at some point. So many edge cases.

<!-- gh-comment-id:3549904648 --> @kommander commented on GitHub (Nov 18, 2025): Alright, let's do this again, I'll get it right at some point. So many edge cases.
Author
Owner

@kommander commented on GitHub (Dec 10, 2025):

I haven't forgotten about this, just didn't get through to it yet.

<!-- gh-comment-id:3635017790 --> @kommander commented on GitHub (Dec 10, 2025): I haven't forgotten about this, just didn't get through to it yet.
Author
Owner

@kskang commented on GitHub (Jan 11, 2026):

I created a PR that may fix this issue: #512

The root cause seems to be char_offset vs col_offset mismatch in word wrap boundary detection. CJK characters have display width 2, but the code was using character count (width 1) for wrap calculations.

Would appreciate if someone could test this fix.

<!-- gh-comment-id:3734592340 --> @kskang commented on GitHub (Jan 11, 2026): I created a PR that may fix this issue: #512 The root cause seems to be `char_offset` vs `col_offset` mismatch in word wrap boundary detection. CJK characters have display width 2, but the code was using character count (width 1) for wrap calculations. Would appreciate if someone could test this fix.
Author
Owner

@kommander commented on GitHub (Jan 18, 2026):

This should be better now?

<!-- gh-comment-id:3765298498 --> @kommander commented on GitHub (Jan 18, 2026): This should be better now?
Author
Owner

@zenyr commented on GitHub (Jan 20, 2026):

https://opncd.ai/share/73TBWAXf (Last message contains a summary)

It's MUCH MUCH better now, the only edge case that I found was the Keycap emojis, which Haiku 4.5 often uses.
Other than that I think... it's fixed!

<!-- gh-comment-id:3771157189 --> @zenyr commented on GitHub (Jan 20, 2026): https://opncd.ai/share/73TBWAXf (Last message contains a summary) It's MUCH MUCH better now, the only edge case that I found was the **Keycap emojis**, which Haiku 4.5 often uses. Other than that I think... **it's fixed!**
Author
Owner

@zenyr commented on GitHub (Jan 20, 2026):

Thanks for fixing up the majority of issues. Please refer to the shared session above for the remaining edge cases. @kommander

<!-- gh-comment-id:3771196481 --> @zenyr commented on GitHub (Jan 20, 2026): Thanks for fixing up the majority of issues. Please refer to the shared session above for the remaining edge cases. @kommander
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/opentui#66
No description provided.