Skip to content

TUI consistency: replace emoji status icons with Unicode text symbols #5787

Description

@pomelo-nwu

Background

Qwen Code's TUI already has a well-established, consistent glyph vocabulary built entirely from Unicode text symbols:

  • > user message prefix
  • assistant message prefix
  • / / completed / unselected / in-progress (todos, selections)
  • success / checked
  • / token flow direction
  • ⠋ ⠙ ⠹ … braille spinner frames (ink-spinner)
  • ░ ▒ ▓ █ progress / shade blocks

Every one of these is a narrow (width=1) text symbol that renders predictably across terminals, fonts, and CI logs.

Problem

Scattered emoji are used as status indicators and break this consistency. Unlike the rest of the glyph set, emoji have real technical downsides in a TUI:

  • Column misalignment — emoji render at width=2 (e.g. 💡 = width 2) while every other glyph is width=1, so indented blocks drift depending on which code path rendered them.
  • Inconsistent environment rendering — the current thinking icon already encodes this pain: it branches into three different outputs (💡 / / ``) based on CI / `LANG`, so the same thinking line occupies a different number of columns depending on where it runs.
  • Font fallback risk — cold Unicode-emoji-presentation characters can render as tofu () on terminals without a color emoji font.
  • Visual style mismatch — a single colored emoji among a sea of monochrome text glyphs looks like a foreign object.

A repo-wide scan (packages/cli/src + packages/core/src, excluding tests) turns up 30+ emoji usages. This issue scopes the two highest-traffic, semantically unambiguous ones; the rest are listed as follow-up cleanup.

Scope of this issue (first batch)

1. Thinking block icon — 💡

packages/cli/src/ui/components/messages/ConversationMessages.tsx defines:

const isUtf8 = /utf-?8/i.test(process.env['LANG'] || process.env['LC_ALL'] || '');
export const THINKING_ICON =
  !process.env['CI'] && isUtf8 ? '💡 ' : isUtf8 ? '⟡ ' : '';

This is the single most visible emoji in the TUI (shown on every model reasoning turn) and the only one that already needed a three-way fallback.

Replace with the mathematical therefore sign (U+2234, width=1):

  • It "sits" on a wide base (two dots down, one up) — a stable, upright glyph.
  • Semantically means "therefore / hence", which is exactly what a thinking block is: the reasoning that leads to a conclusion.
  • Belongs to the same text-symbol family as , , , so it no longer reads as a foreign object.

This also lets us delete the entire isUtf8 / CI branch and collapse THINKING_ICON to a single constant — renders the same everywhere.

Affected render sites (same file): collapsed, pending, and expanded thinking titles; plus ThinkingViewer.tsx header (imports THINKING_ICON).

2. Summary success icon —

packages/cli/src/ui/components/messages/SummaryMessage.tsx:

return <Text color={Colors.AccentGreen}></Text>;

ToolMessage already uses in success color for the exact same "operation succeeded" semantics. is a redundant, emoji variant of a glyph we already standardized on. Replace with to match ToolMessage exactly.

Why these two first

  • Highest visibility — thinking shows on (almost) every turn; summary shows whenever /chat summary runs. These are the glyphs users see most.
  • Semantically unambiguous replacement (therefore) and (success) are not stylistic guesses; they map 1:1 to the existing meaning.
  • Zero new glyph families introduced sits next to in the text-symbol family; is already in use. No risk of creating a new island.

Follow-up cleanup (not in this PR)

Emoji also appear in these locations and should be addressed in subsequent PRs to fully converge on text symbols:

  • Footer.tsx🔒 connection indicator
  • views/McpStatus.tsx🟢 🔴 🔄 💡 MCP server status
  • commands/ideCommand.ts🟢 🟡 🔴 IDE connection status
  • hooks/useContextualTips.ts💡 tip copy
  • hooks/useGeminiStream.ts🔎 🚫 💡 loading phrases
  • components/WelcomeBackDialog.tsx👋 🎯 📋 welcome copy
  • core/agents/runtime/agent-statistics.ts📋 🔧 🔁 🔢 🚀 💡 (verify whether these surface in TUI or are internal labels)
  • core/tools/artifact-tool.ts📄
  • core/tools/shell.ts🤖

For status indicators (green/yellow/red dots), the project already has / in the right colors, which directly replace 🟢 🟡 🔴.

Proposed glyph mapping (recap)

Current Replacement Note
💡 thinking therefore sign; drops the 3-way fallback
summary success matches existing ToolMessage success glyph

中文说明

背景

Qwen Code 的 TUI 已经建立了一套完整、统一的纯文本符号体系,全部由 Unicode 文本符号构成:>(用户消息)、(助手消息)、● ○ ◐(todo/选择态)、(成功)、↓ ↑(token 流向)、盲文 spinner(⠋⠙⠹…)、明暗块(░▒▓█)。这些符号宽度都是 1,在各终端、字体、CI 日志里渲染一致。

问题

少数 emoji 作为状态指示器散落其中,破坏了这套一致性。emoji 在 TUI 里有真实的技术缺陷:

  • 列对齐错位:emoji 渲染宽度为 2(如 💡 占 2 列),而其他符号都是 1,导致缩进块在不同代码路径下偏移。
  • 环境表现不一致:现有 thinking icon 已经反映了这个痛点——它根据 CI/LANG 分支成三套输出(💡 //空),同一条 thinking 行在不同环境占不同列宽。
  • 字体回退风险:冷门 emoji 字符在没有彩色 emoji 字体的终端上会显示成豆腐块()。
  • 风格割裂:一个彩色 emoji 夹在一群单色文本符号里,像外来物。

全仓库扫描(packages/cli/src + packages/core/src,排除测试)发现 30+ 处 emoji。本 issue 先聚焦最高频、语义最明确的两处,其余列为后续清理。

本次范围

1. Thinking icon — 💡

用数学符号 (U+2234,宽 1,"所以/因此")替换。理由:形状端坐(底宽顶尖)、语义上"因此"正对应 thinking 的"推出结论的推理"、与 ✦ ✓ ● 同属文本符号族。同时可删除 isUtf8/CI 三分支判断,把 THINKING_ICON 收敛成单常量。

2. Summary 成功 icon —

ToolMessage 成功态已经用 (success 色), 是多余且语义重复的 emoji 变体。直接对齐

为什么先做这两处

  • 曝光最高:thinking 几乎每轮都出现,summary 在 /chat summary 时出现。
  • 替换语义无歧义(因此)、(成功)与现有含义 1:1 对应。
  • 不引入新符号族 紧邻 已在用,不会制造新孤岛。

后续清理(不在本次 PR)

  • Footer.tsx 🔒McpStatus.tsx 🟢🔴🔄💡ideCommand.ts 🟢🟡🔴(状态点,项目已有 ● ◐ 可直接替换)
  • useContextualTips.ts 💡useGeminiStream.ts 🔎🚫💡WelcomeBackDialog.tsx 👋🎯📋(文案类)
  • coreagent-statistics.ts artifact-tool.ts shell.ts 的 emoji(需确认是 TUI 渲染还是内部标签)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions