mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-07-09 17:19:02 +00:00
fix(cli): switch TUI prefix ✦→◆ to fix glyph overflow on some terminals (#5974)
* fix(cli): replace ✦ (U+2726) with ◆ (U+25C6) and add ∵/∴ thinking icons - Replace ✦ with ◆ across all TUI components to fix East Asian Ambiguous width misalignment (string-width reports 1 but terminals render 2 columns). - Use ∵ (because) during thinking streaming, ∴ (therefore) when thinking is complete — matches the mathematical reasoning pair. Co-Authored-By: Qwen Code <noreply@alibaba-inc.com> * fix(cli): reduce STATUS_INDICATOR_WIDTH from 3 to 2 after ◆ replacement ◆ (U+25C6) is a consistent width-1 character across all terminals, so the tool status indicator no longer needs the extra column that was reserved for the ambiguous-width ✦ (U+2726). Co-Authored-By: Qwen Code <noreply@alibaba-inc.com> * fix(cli): catch missed ✦→◆ references in tests, docs, and scenarios * fix(cli): shorten tmux spinner frames from 3 to 2 chars to match STATUS_INDICATOR_WIDTH=2 TMUX_SPINNER_FRAMES changed from ['. ', '.. ', '...'] to ['· ', '··'] to prevent 1-column overflow in tmux when STATUS_INDICATOR_WIDTH was reduced from 3 to 2 after the ◆ replacement. * revert(cli): keep narrow '.' tmux spinner frames instead of ambiguous '·' '.' (U+002E) is Narrow (always 1 col), giving a guaranteed fixed-width tmux spinner. '·' (U+00B7) is East Asian Ambiguous, so on ambiguous-width=2 terminals the frames become 3/4 cols and the spinner jitters — the opposite of the "fixed-width frames" the surrounding comment promises. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen Code <noreply@alibaba-inc.com> Co-authored-by: pomelo.lcw <pomelo.lcw@alibaba-inc.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
parent
c48dc32b57
commit
e92fcbab46
12 changed files with 19 additions and 18 deletions
|
|
@ -99,7 +99,7 @@ Everything saved is plain markdown — you can open, edit, or delete any file at
|
|||
|
||||
Qwen periodically goes through its saved memories to remove duplicates and clean up outdated entries. This runs automatically in the background once a day after enough sessions have accumulated. You can trigger it manually with `/dream` if you want it to run now.
|
||||
|
||||
While cleanup is running, **✦ dreaming** appears in the corner of the screen. Your session continues normally.
|
||||
Your session continues normally while cleanup runs in the background.
|
||||
|
||||
### Turning it on or off
|
||||
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ function makeEnv(): NodeJS.ProcessEnv {
|
|||
const afterPrompt = finalScreen.slice(
|
||||
finalScreen.lastIndexOf('Cron: PONG7742'),
|
||||
);
|
||||
expect(afterPrompt).toContain('✦');
|
||||
expect(afterPrompt).toContain('◆');
|
||||
});
|
||||
|
||||
it('user input takes priority over cron', { timeout: 180_000 }, async () => {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import type { ScenarioConfig } from '../scenario-runner.js';
|
|||
* - Info message prefix (● filled circle)
|
||||
* - Error message prefix (✕)
|
||||
* - User message prefix (>)
|
||||
* - Assistant message prefix (✦)
|
||||
* - Assistant message prefix (◆)
|
||||
*/
|
||||
export default {
|
||||
name: 'message-components',
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ export const Footer: React.FC = () => {
|
|||
});
|
||||
}
|
||||
// Dream tasks now surface via the BackgroundTasksPill (e.g. "1 dream")
|
||||
// alongside the other background-task kinds. The previous `✦ dreaming`
|
||||
// alongside the other background-task kinds. The previous `◆ dreaming`
|
||||
// right-column indicator was removed to avoid two simultaneous signals
|
||||
// for the same underlying state.
|
||||
if (promptTokenCount > 0 && contextWindowSize && !hideContextIndicator) {
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ describe('<HistoryItemDisplay />', () => {
|
|||
|
||||
const output = lastFrame() ?? '';
|
||||
expect(output.startsWith('\n')).toBe(true);
|
||||
expect(output).toContain('✦ Hello');
|
||||
expect(output).toContain('◆ Hello');
|
||||
});
|
||||
|
||||
it('renders tool summaries without a leading spacer row', () => {
|
||||
|
|
|
|||
|
|
@ -48,9 +48,9 @@ export function VoiceIndicator({
|
|||
<Text color="gray">{' ' + t('listening…')}</Text>
|
||||
</>
|
||||
) : status === 'refining' ? (
|
||||
<Text color="yellow">{'✦ ' + t('refining…')}</Text>
|
||||
<Text color="yellow">{'◆ ' + t('refining…')}</Text>
|
||||
) : (
|
||||
<Text color="yellow">{'✦ ' + t('transcribing…')}</Text>
|
||||
<Text color="yellow">{'◆ ' + t('transcribing…')}</Text>
|
||||
)}
|
||||
</Box>
|
||||
{interimText ? (
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
exports[`<HistoryItemDisplay /> > should render a full gemini item when using availableTerminalHeightGemini 1`] = `
|
||||
"
|
||||
✦ Example code block:
|
||||
◆ Example code block:
|
||||
1 Line 1
|
||||
2 Line 2
|
||||
3 Line 3
|
||||
|
|
@ -111,7 +111,7 @@ exports[`<HistoryItemDisplay /> > should render a full gemini_content item when
|
|||
|
||||
exports[`<HistoryItemDisplay /> > should render a truncated gemini item 1`] = `
|
||||
"
|
||||
✦ Example code block:
|
||||
◆ Example code block:
|
||||
... first 41 lines hidden ...
|
||||
42 Line 42
|
||||
43 Line 43
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ describe('<CompressionMessage />', () => {
|
|||
const { lastFrame } = render(<CompressionMessage {...props} />);
|
||||
const output = lastFrame();
|
||||
|
||||
expect(output).toContain('✦');
|
||||
expect(output).toContain('◆');
|
||||
expect(output).toContain(
|
||||
'Chat history compressed from 100 to 50 tokens.',
|
||||
);
|
||||
|
|
@ -67,7 +67,7 @@ describe('<CompressionMessage />', () => {
|
|||
const { lastFrame } = render(<CompressionMessage {...props} />);
|
||||
const output = lastFrame();
|
||||
|
||||
expect(output).toContain('✦');
|
||||
expect(output).toContain('◆');
|
||||
expect(output).toContain(
|
||||
`compressed from ${original} to ${newTokens} tokens`,
|
||||
);
|
||||
|
|
@ -89,7 +89,7 @@ describe('<CompressionMessage />', () => {
|
|||
const { lastFrame } = render(<CompressionMessage {...props} />);
|
||||
const output = lastFrame();
|
||||
|
||||
expect(output).toContain('✦');
|
||||
expect(output).toContain('◆');
|
||||
expect(output).toContain(
|
||||
'Compression was not beneficial for this history size.',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ export function CompressionMessage({
|
|||
{isPending ? (
|
||||
<Spinner type="dots" />
|
||||
) : (
|
||||
<Text color={theme.text.accent}>✦</Text>
|
||||
<Text color={theme.text.accent}>◆</Text>
|
||||
)}
|
||||
</Box>
|
||||
<Box>
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import { wrapToVisualLines } from '../../utils/textUtils.js';
|
|||
import { formatDuration } from '../../utils/displayUtils.js';
|
||||
|
||||
export const THINKING_ICON = '∴ ';
|
||||
export const THINKING_ICON_PENDING = '∵ ';
|
||||
|
||||
export const toggleKeyHint =
|
||||
process.platform === 'darwin' ? 'option+t' : 'alt+t';
|
||||
|
|
@ -233,7 +234,7 @@ export const AssistantMessage: React.FC<AssistantMessageProps> = ({
|
|||
}) => (
|
||||
<PrefixedMarkdownMessage
|
||||
text={text}
|
||||
prefix="✦"
|
||||
prefix="◆"
|
||||
prefixColor={theme.text.accent}
|
||||
ariaLabel={SCREEN_READER_MODEL_PREFIX}
|
||||
isPending={isPending}
|
||||
|
|
@ -257,7 +258,7 @@ export const AssistantMessageContent: React.FC<
|
|||
isPending={isPending}
|
||||
availableTerminalHeight={availableTerminalHeight}
|
||||
contentWidth={contentWidth}
|
||||
basePrefix="✦"
|
||||
basePrefix="◆"
|
||||
sourceCopyIndexOffsets={sourceCopyIndexOffsets}
|
||||
/>
|
||||
);
|
||||
|
|
@ -321,7 +322,7 @@ export const ThinkMessage: React.FC<ThinkMessageProps> = ({
|
|||
return (
|
||||
<Box flexDirection="column">
|
||||
<Text dimColor italic>
|
||||
{THINKING_ICON}
|
||||
{THINKING_ICON_PENDING}
|
||||
{t('Thinking')}…{durationSuffix}
|
||||
</Text>
|
||||
<Box paddingLeft={2}>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import {
|
|||
} from '../../constants.js';
|
||||
import { theme } from '../../semantic-colors.js';
|
||||
|
||||
export const STATUS_INDICATOR_WIDTH = 3;
|
||||
export const STATUS_INDICATOR_WIDTH = 2;
|
||||
|
||||
type ToolStatusIndicatorProps = {
|
||||
status: ToolCallStatus;
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import { useTranslation } from 'react-i18next'
|
|||
|
||||
// Fallback text icons for badges without iconDataUrl
|
||||
// Using simple characters since SVG rendering may not work in all contexts
|
||||
const SKILL_ICON_TEXT = '✦'
|
||||
const SKILL_ICON_TEXT = '◆'
|
||||
const SOURCE_ICON_TEXT = '⊕'
|
||||
const CONTEXT_ICON_TEXT = '⚙'
|
||||
const COMMAND_ICON_TEXT = '/'
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue