Skip to content

Navigation Menu

Sign in
Sign up

fix(tui): ambiguous-width mode + circled-digit display insurance (fixes #3301) - #3554

Open
wubing-wx wants to merge 1 commit into
MoonshotAI:main from
wubing-wx:fix/3302-ambiguous-width
Open

fix(tui): ambiguous-width mode + circled-digit display insurance (fixes #3301) #3554
wubing-wx wants to merge 1 commit into
MoonshotAI:main from
wubing-wx:fix/3302-ambiguous-width

Conversation

@wubing-wx

@wubing-wx wubing-wx commented Sep 5, 2026

Copy link
Copy Markdown

Problem

On Windows Terminal with CJK locales, East Asian Ambiguous characters (1 ★ → α ...) render as 2 cells, but pi-tui's width calculation counts them as 1. Every padded line containing one misaligns — circled digits visibly overlap the following text. Reported in #3301 (dup #3302, closed).

Fix (two layers)

Root fix — configurable ambiguous width. pi-tui's grapheme width now honors an ambiguous-width mode: setAmbiguousWidthMode('wide' | 'narrow'), routed through a mode-aware cellWidth(). Surfaced as ambiguous_width in tui.toml (narrow | wide | auto; auto treats zh/ja/ko locales as wide). Switching modes clears the string width cache — the cache is keyed by string only, so stale widths would otherwise survive a mode change.

Insurance — circled digits display as N. Assistant and thinking text pass through replaceCircledNumbers() before paint: 1-20 (1)-(20) 1.-20. ⓵-⓾ ❶-❿ 0⓿ all render as 1. 2. 3. forms. Display-layer only — the transcript data is untouched, so /copy and logs keep the original glyphs. Even where the width mode and terminal disagree, no ambiguous glyph can misalign the layout anymore.

Tests

  • packages/pi-tui/test/ambiguous-width.test.ts — narrow default, wide mode for 1/★/→/α, padding alignment for the issue's exact probe string
  • apps/kimi-code/test/tui/utils/text-sanitize.test.ts — all five circled/parenthesized digit ranges + zero forms

Both suites green on Windows (zh-CN) against this branch.

...onshotAI#3302)
Root fix — pi-tui's grapheme width now honors a configurable East Asian
Ambiguous width mode: setAmbiguousWidthMode('wide'|'narrow'), surfaced as
ambiguous_width in tui.toml ('auto' defaults CJK locales to wide, matching
what their terminals actually render). Switching modes clears the string
width cache so stale widths are never served. graphemeWidth routes through
the mode-aware cellWidth.
Insurance — assistant and thinking text pass through
replaceCircledNumbers() before display: 1-20 (1)-(20) 1.-20. ⓵-⓾ ❶-❿ 0⓿ all
paint as '1.' '2.' '3.' forms. Display-layer only; transcript data is
untouched, so /copy and logs keep the original glyphs.

changeset-bot Bot commented Sep 5, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: a5e8ec4

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a5e8ec448d

i️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +59 to +61
/** East Asian Ambiguous chars (1 ★ →) cell width: "narrow"=1, "wide"=2,
* "auto"=detect from locale (CJK locales default wide; upstream #3302). */
ambiguous_width: z.enum(['narrow', 'wide', 'auto']).optional(),

@chatgpt-codex-connector chatgpt-codex-connector Bot Sep 5, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Add a changeset for the user-visible TUI fix

This changes the published CLI's rendering behavior and adds a user-facing tui.toml setting, but the commit adds no .changeset entry, so the fix will be absent from the next package version bump and generated changelog. Add an @moonshot-ai/kimi-code changeset describing the visible rendering fix.

AGENTS.md reference: AGENTS.md:L85-L86

Useful? React with 👍 / 👎.

renderLatex: config.render_latex ?? DEFAULT_TUI_CONFIG.renderLatex,
disablePasteBurst: config.disable_paste_burst ?? DEFAULT_TUI_CONFIG.disablePasteBurst,
cacheExpiryHint: config.cache_expiry_hint ?? DEFAULT_TUI_CONFIG.cacheExpiryHint,
ambiguousWidth: config.ambiguous_width ?? DEFAULT_TUI_CONFIG.ambiguousWidth,

@chatgpt-codex-connector chatgpt-codex-connector Bot Sep 5, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve ambiguous_width when rewriting TUI config

When a user manually sets ambiguous_width = "wide" or "narrow" and later changes any preference that calls saveTuiConfig—such as the theme, editor, update setting, or cache hint—the whole file is rewritten without this new field because renderTuiConfig never emits it and currentTuiConfig does not retain it. On the next launch normalization silently restores auto, undoing the user's workaround; serialize and carry the selected value through preference saves.

Useful? React with 👍 / 👎.

updateContent(text: string, opts?: AssistantMarkdownOptions): void {
const displayText = text.trim();
// Display-layer only: circled digits overlap on CJK terminals (#3302).
const displayText = replaceCircledNumbers(text.trim());

@chatgpt-codex-connector chatgpt-codex-connector Bot Sep 5, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Sanitize rendered text without rewriting Markdown targets

Whenever assistant Markdown contains one of these glyphs in non-display source, this transforms it before Markdown parsing. For example, [docs](https://example.com/1) is parsed with an href ending in /1., so the OSC 8 link navigates to the wrong resource, and fenced or inline code containing 1 is likewise displayed inaccurately. Apply the substitution only to rendered prose text nodes rather than to the complete Markdown source.

Useful? React with 👍 / 👎.

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

Reviewers

@chatgpt-codex-connector chatgpt-codex-connector[bot] chatgpt-codex-connector[bot] left review comments

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

1 participant

AltStyle によって変換されたページ (->オリジナル) /