fix(server): CJK-aware charWidth — fixes box alignment on zh/ja/ko terminals#9
Merged
Conversation
…terminals
Pet cards use box-drawing borders with content padded by stringWidth().
A handful of art characters — `◉` (U+25C9), `—` (U+2014), `✦/✧`, `★`,
`Λ`, `×` — fall in Unicode's "Ambiguous East Asian Width" class. On
CJK-locale terminals (Chinese/Japanese/Korean Windows Terminal, iTerm2
under zh/ja/ko, etc.) those render as 2 columns, but charWidth() was
returning 1 — so padding under-counted and the right `│` drifted past
the top `╮`. Reproduced visually on Win11 with system code page 936.
Detection order:
1. POSIX env vars (LC_ALL / LC_CTYPE / LANG / LANGUAGE) — covers Mac
and Linux.
2. Intl.DateTimeFormat resolved locale — covers Windows where the env
vars are usually unset but the system locale is e.g. "zh-CN".
When the locale resolves to zh/ja/ko, charWidth() additionally treats
General Punctuation, Geometric Shapes, Misc Symbols, Dingbats, Arrows,
and Math Operators as 2 cols. Box-drawing (0x2500–0x257F) is
intentionally excluded because every mainstream terminal special-cases
those to 1-wide for TUI sanity even under a CJK locale — including them
would double the border width.
charWidth/stringWidth/padDisplay now take an optional explicit `cjk`
parameter (defaulting to the auto-detected locale) so tests are
deterministic regardless of where the suite happens to run.
Surfaced via /pet browse on Win11 zh-CN — Owl/Labrador/Lion/etc. cards
visibly broken in user report.
Owner
Author
|
shipped in v0.4.4. CJK alignment now correct on zh/ja/ko terminals. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What broke
On a Chinese-locale Windows 11 box (system code page 936),
/pet browsereturns visibly broken boxes:The art uses several Unicode "Ambiguous East Asian Width" characters —
◉(U+25C9),—(U+2014),✦/✧,★,Λ,×. Per UAX #11 these render as 2 columns on East Asian locale terminals (Chinese/Japanese/Korean Windows Terminal, iTerm2 underzh/ja/ko, …) and 1 column elsewhere.charWidth()was always returning 1, sostringWidth()under-counted andpad()left the right border too far out.Fix
server/utils.tsnow detects the locale and treats the relevant Ambiguous-width ranges as 2 cols when CJK:LC_ALL/LC_CTYPE/LANG/LANGUAGE) — Mac/Linux happy pathIntl.DateTimeFormat().resolvedOptions().locale— Windows fallback (env vars are usually unset there but the system locale is e.g.zh-CN)When CJK is detected, these ranges become 2-wide:
0x2010–0x2027,0x2030–0x205E—0x2150–0x218F0x2190–0x21FF0x2200–0x22FFΛ(technically Greek but kept narrow)0x2580–0x25FF◉0x2600–0x26FF0x2700–0x27BF✦,✧Box-drawing (
0x2500–0x257F) is intentionally excluded — every mainstream terminal special-cases box drawings to 1-wide for TUI sanity even under a CJK locale. Including them here would double the border width.charWidth/stringWidth/padDisplayall gained an optional explicitcjkparameter (defaulting to the auto-detected locale) so tests are deterministic regardless of where the suite happens to run.Test plan
bun test— 305 → 307 pass (added explicit non-CJK + CJK + box-drawing cases)charWidth('◉') === 2,charWidth('─') === 1,IS_CJK_LOCALE === true.animalCard()for Owl now produces a card whose right border lines up with the top.◉/—and existing 12-wide art lines stay 12-wide.npx petsonality@<next>/pet browse on a fresh CJK Windows box.Out of scope (separate PR)
bun run buildon Win11 still fails atbuild:artuntil #7 lands — that's an independent path-resolution bug, not coupled to this fix.🤖 Generated with Claude Code