Terminal block: WebGL renderer shows garbled CJK chars (atlas slot misalignment on wide chars)
メンテナーはふだん 1 日以内に返信
まだ誰も着手していません。
評価
- 難易度
- 4/5
- 見積もり時間
- 3〜5日
- 初心者へのやさしさ
- 45/100
- issue の種類
- バグ
- 明瞭さ
- おおむね明確
- 活発さ
- 静か
- 技術スタック
- python, typescript
- 領域
- cli, computer-graphics, frontend
調査の方向性
Start with the CJK atlas-flood reproduction and compare WebGL with term:disablewebgl = true. Read frontend/app/view/term/termwrap.ts, especially setTermRenderer, the WebGL setup around lines 26 and 339–372, and the onContextLoss handler at line 361; inspect the referenced xterm.js atlas behavior and existing lifecycle hooks. Done means the provided flood scripts render stable, correct CJK glyphs with WebGL enabled while the terminal data remains unchanged.
索引モデルが issue の本文から書いたものです。
説明
Bug: Terminal block shows garbled CJK characters after rendering many distinct CJK glyphs (xterm.js WebGL atlas slot misalignment)
Environment
- Wave Terminal version: latest main (commit
9c42d97d) - Platform: macOS (likely Linux/Windows also affected — root cause is in upstream xterm.js)
- Renderer: default WebGL renderer enabled (
term:disablewebglisfalse) - Font: default
Hack(no CJK glyphs in this font) - Window size: medium-to-large terminal block (>80 cols × >20 rows helps trigger)
Summary
When the terminal block receives a high volume of distinct CJK (Chinese / Japanese / Korean / fullwidth) characters — for example, while streaming output from claude-code CLI's long Chinese-language answers — the rendered screen shows garbled glyphs that look like two CJK characters superimposed on top of each other.
The terminal data itself is correct:
- Copy-and-paste of the affected region yields the proper CJK text
- The garbling is purely a screen-rendering artifact of the WebGL renderer
- It self-clears after a moment (when the next redraw overwrites the corrupted cells), or immediately clears when the mouse hovers / selects across the affected region
Reproduction
Repro kit: a self-contained Python script is provided below. Run it inside Wave Terminal (running it in macOS Terminal.app / iTerm2 will not reproduce, since they don't use the same renderer).
Step 1 — Start a fresh Wave Terminal block
Step 2 — Run the atlas flood script
Save the following script as /tmp/wave-cjk-repro/01-atlas-flood.py and run it inside Wave:
#!/usr/bin/env python3
"""Atlas Flood: dump ~21k distinct CJK chars to overflow xterm.js WebGL texture atlas."""
import sys, time, random
CJK_START, CJK_END = 0x4E00, 0x9FFF
COLORS = [31, 32, 33, 34, 35, 36, 91, 92, 93, 94, 95, 96]
def random_cjk_line(width=80):
n = width // 2
return "".join(chr(random.randint(CJK_START, CJK_END)) for _ in range(n))
print("\n[Atlas Flood] 启动 — 即将遍历 21,000+ 不重复 CJK 字符以触发 atlas 驱逐\n")
time.sleep(2)
try:
for r in range(500):
sys.stdout.write("\033[2J\033[H")
sys.stdout.write(f"=== Round {r+1}/500 | CJK Atlas Flood ===\n\n")
for row in range(30):
color = COLORS[row % len(COLORS)]
sys.stdout.write(f"\033[{color}m{random_cjk_line(120)}\033[0m\n")
sys.stdout.flush()
time.sleep(0.02)
except KeyboardInterrupt:
sys.stdout.write("\033[0m\n[stopped]\n")
Run with:
python3 /tmp/wave-cjk-repro/01-atlas-flood.py
The script floods the terminal with ~21,000 distinct CJK Unified Ideographs (U+4E00–U+9FFF) at a rate that exceeds xterm.js' WebGL texture-atlas slot budget, forcing LRU eviction of glyph slots.
Expected behavior: a stable screen of correct CJK characters.
Actual behavior: certain CJK cells render as two halves of different characters stacked together (the visual fingerprint of an atlas-slot LRU misalignment where a wide-char slot was partially overwritten by an adjacent glyph).
Step 3 — Verify fingerprint (to confirm WebGL renderer is the cause)
- While the garbled output is on screen, slowly move the mouse over the affected area — it instantly clears. This is a strong indicator of a rendering-layer (atlas / dirty-region) issue.
- Copy the garbled text — pasting it into a text editor yields the correct CJK characters, proving the underlying buffer data is fine.
- Repro with WebGL disabled — in Wave settings, set
term:disablewebgl = true(or toggle "Disable WebGL" in command palette). Re-run the script. Garbling does not occur. This isolates the bug to the WebGL renderer.
Additional repro scripts
| Script | What it stresses |
|---|---|
02-mixed-width.py |
mixed CJK + ASCII + fullwidth (closer to real LLM chat output) |
03-claude-mimic.py |
full Claude Code emulation: spinner + box-drawing + token streaming + DEC mode 2026 |
04-extreme.sh |
multi-process parallel flood (max probability) |
Root cause (analysis)
frontend/app/view/term/termwrap.ts initializes xterm.js with the WebGL renderer (@xterm/addon-webgl, see lines ~26, 339–372). The WebGL renderer caches glyphs in a fixed-size GPU texture atlas with an LRU eviction policy.
Known weakness in the WebglAddon (upstream xterm.js): when the atlas evicts a wide character slot (CJK / fullwidth — width = 2 cells) and reuses that slot for a new glyph, the slot's left/right halves can be partially filled by the adjacent stale glyph data, because the eviction routine does not always clear both halves atomically.
Result on screen: the new glyph's left half + the old evicted glyph's right half are drawn together, producing the characteristic "two characters superimposed" artifact.
The custom onContextLoss handler in setTermRenderer (termwrap.ts:361) only covers hard GPU context loss, not this soft atlas-corruption case. Hence:
- No automatic recovery → user must hover / scroll / get a new redraw
- Disabling WebGL bypasses the bug entirely
- The artifact "self-heals" when new input lands on those cells
Suggested fix (direction, not a final patch)
Several mitigation strategies exist. The Wave team should pick based on UX tradeoffs:
-
Periodic
clearTextureAtlas()— callthis.webglAddon.clearTextureAtlas()on a low-frequency timer (e.g. every 60–120 s), or after detecting a burst of distinct CJK characters. The first frame after clearing may have a brief hiccup while the atlas refills, but the user-visible impact is a barely-perceptible blink vs. persistent garbling. -
Visibility / DPR-aware refresh — listen for
document.visibilitychangeandwindow.matchMedia('(resolution: ${devicePixelRatio}dppx)')change events; forceterminal.refresh(0, rows-1)on resume. Cheap, fixes related "soft context loss" cases too. -
Conditional mitigation when Claude Code is active — Wave already tracks
claudeCodeActiveAtom(termwrap.ts:144, osc-handlers.ts:122). When true, run strategy #1 with a tighter interval. Least cost in normal usage. -
Upstream patch — the cleanest fix is in
@xterm/addon-webglitself (improved wide-char LRU eviction). Tracking upstream issues / filing a PR there would benefit the broader xterm.js ecosystem.
The most pragmatic combination for Wave is #1 + #2, implemented inside the existing setTermRenderer lifecycle (no API surface change needed).
Related (upstream xterm.js)
This is the same class of bug as several open upstream issues (e.g. xterm.js #4836 "WebGL renderer glitches with CJK characters", #5050 "Texture atlas garbled when many wide chars"). Search upstream for "atlas wide char" for the latest.
- 主要言語
- Go
- スター
- 22.4k
- フォーク
- 1.1k
- 平均マージ
- 12日 6時間
- マージ済み PR(30日)
- 14
環境構築
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートなし
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
wavetermdev/waveterm のほかの issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 84/100
wavetermdev/waveterm#3481 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
wavetermdev/waveterm#3435 ·
メンテナーはふだん 1 日以内に返信
-
難易度 1/5 1時間未満 初心者へのやさしさ 85/100
wavetermdev/waveterm#3432 ·
メンテナーはふだん 1 日以内に返信
-
enhancement triage
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100
wavetermdev/waveterm#3431 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 86/100
wavetermdev/waveterm#3428 ·
メンテナーはふだん 1 日以内に返信
wavetermdev/waveterm の issue をすべて見る
似ている issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 84/100
メンテナーはふだん 2 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
linonetwo/cpa-session-archive#25 ·
メンテナーはふだん 1 日以内に返信
-
area/testing kind/bug
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
メンテナーはふだん 1 日以内に返信
-
agent-butler-finding bug
難易度 1/5 1時間未満 初心者へのやさしさ 94/100
jordansmall/spindrift#4367 ·
メンテナーはふだん 1 日以内に返信
-
bug
難易度 2/5 1時間未満 初心者へのやさしさ 90/100
メンテナーはふだん 1 日以内に返信