diff --git a/.changeset/tui-unicode-ellipsis.md b/.changeset/tui-unicode-ellipsis.md
new file mode 100644
index 00000000000..f7507d5db6e
--- /dev/null
+++ b/.changeset/tui-unicode-ellipsis.md
@@ -0,0 +1,6 @@
+---
+"@moonshot-ai/kimi-code": patch
+"kimi-code": patch
+---
+
+Use the Unicode ellipsis "…" in user-facing TUI and VS Code webview text.
diff --git a/apps/kimi-code/src/cli/sub/login-flow.ts b/apps/kimi-code/src/cli/sub/login-flow.ts
index 498f83421fd..d10864a428d 100644
--- a/apps/kimi-code/src/cli/sub/login-flow.ts
+++ b/apps/kimi-code/src/cli/sub/login-flow.ts
@@ -53,7 +53,7 @@ export async function runLoginFlow(options: { region?: KimiRegion } = {}): Promi
data.expiresIn !== null && data.expiresIn !== undefined
? `Code expires in ${data.expiresIn}s.`
: undefined,
- 'Waiting for authorization to complete...',
+ 'Waiting for authorization to complete…',
'',
]
.filter((line): line is string => line !== undefined)
diff --git a/apps/kimi-code/src/tui/commands/plugins.ts b/apps/kimi-code/src/tui/commands/plugins.ts
index 80c8931a8aa..6ee5b731f94 100644
--- a/apps/kimi-code/src/tui/commands/plugins.ts
+++ b/apps/kimi-code/src/tui/commands/plugins.ts
@@ -634,7 +634,7 @@ async function installFromPanel(
if (official) {
panel.setInstalling(truncateForStatus(label));
} else {
- host.showStatus(`Installing or updating ${label} from marketplace...`);
+ host.showStatus(`Installing or updating ${label} from marketplace…`);
}
host.state.ui.requestRender();
try {
diff --git a/apps/kimi-code/src/tui/components/dialogs/compaction.ts b/apps/kimi-code/src/tui/components/dialogs/compaction.ts
index 9ade9350c78..52d8bb8ffda 100644
--- a/apps/kimi-code/src/tui/components/dialogs/compaction.ts
+++ b/apps/kimi-code/src/tui/components/dialogs/compaction.ts
@@ -165,7 +165,7 @@ export class CompactionComponent extends Container {
return `${bullet}${label}`;
}
const bullet = this.blinkOn ? currentTheme.fg('text', STATUS_BULLET) : ' ';
- const label = currentTheme.boldFg('primary', 'Compacting context...');
+ const label = currentTheme.boldFg('primary', 'Compacting context…');
const tip = this.tip ? currentTheme.fg('textDim', ` · Tip: ${this.tip}`) : '';
return `${bullet}${label}${tip}`;
}
diff --git a/apps/kimi-code/src/tui/components/dialogs/question-dialog.ts b/apps/kimi-code/src/tui/components/dialogs/question-dialog.ts
index 68187349c13..e1d5a41bc9e 100644
--- a/apps/kimi-code/src/tui/components/dialogs/question-dialog.ts
+++ b/apps/kimi-code/src/tui/components/dialogs/question-dialog.ts
@@ -469,7 +469,7 @@ export class QuestionDialogComponent extends Container implements Focusable {
appendWrapped(lines, ' ', ' ', bodyLine, renderWidth, dim);
}
if (bodyLines.length > visibleBodyLines.length) {
- lines.push(dim(` ... ${String(bodyLines.length - visibleBodyLines.length)} more lines`));
+ lines.push(dim(` … ${String(bodyLines.length - visibleBodyLines.length)} more lines`));
}
}
diff --git a/apps/kimi-code/src/tui/components/dialogs/session-picker.ts b/apps/kimi-code/src/tui/components/dialogs/session-picker.ts
index 75c86687ffc..0fc77224f40 100644
--- a/apps/kimi-code/src/tui/components/dialogs/session-picker.ts
+++ b/apps/kimi-code/src/tui/components/dialogs/session-picker.ts
@@ -265,7 +265,7 @@ export class SessionPickerComponent extends Container implements Focusable {
if (this.loading) {
lines.push(currentTheme.boldFg('primary', truncateToWidth(title, width, ELLIPSIS)));
lines.push(
- currentTheme.fg('textMuted', truncateToWidth('Loading sessions...', width, ELLIPSIS)),
+ currentTheme.fg('textMuted', truncateToWidth('Loading sessions…', width, ELLIPSIS)),
);
lines.push(currentTheme.fg('primary', '─'.repeat(width)));
return lines;
diff --git a/apps/kimi-code/src/tui/components/messages/agent-swarm-progress.ts b/apps/kimi-code/src/tui/components/messages/agent-swarm-progress.ts
index 41fab4a6c1f..c9d3909f2f1 100644
--- a/apps/kimi-code/src/tui/components/messages/agent-swarm-progress.ts
+++ b/apps/kimi-code/src/tui/components/messages/agent-swarm-progress.ts
@@ -33,15 +33,15 @@ const AGENT_SWARM_LEFT_INDENT = ' ';
const AGENT_SWARM_RIGHT_GAP = 1;
const AGENT_SWARM_NON_GRID_LINES = 6;
const COMPACT_TERMINAL_MARK_WIDTH = 1;
-const ORCHESTRATING_LABEL = 'Orchestrating...';
-const PROMPTING_LABEL = 'Prompting...';
-const WORKING_LABEL = 'Working...';
+const ORCHESTRATING_LABEL = 'Orchestrating…';
+const PROMPTING_LABEL = 'Prompting…';
+const WORKING_LABEL = 'Working…';
const COMPLETED_LABEL = 'Completed.';
const FAILED_LABEL = 'Failed.';
const ABORTED_LABEL = 'Aborted.';
const CANCELLED_LABEL = 'Cancelled.';
-const QUEUED_LABEL = 'Queued...';
-const SUSPENDED_LABEL = 'Rate limited...';
+const QUEUED_LABEL = 'Queued…';
+const SUSPENDED_LABEL = 'Rate limited…';
const RESUMED_ITEM_LABEL = '(resumed)';
const CANCELLED_LABEL_DARKEN_FACTOR = 0.72;
const AGENT_SWARM_TITLE_ACCENT_BIAS = 1.3;
diff --git a/apps/kimi-code/src/tui/components/messages/shell-run.ts b/apps/kimi-code/src/tui/components/messages/shell-run.ts
index 747bc3860a1..88c02c19e5f 100644
--- a/apps/kimi-code/src/tui/components/messages/shell-run.ts
+++ b/apps/kimi-code/src/tui/components/messages/shell-run.ts
@@ -17,7 +17,7 @@ const TIMER_INTERVAL_MS = 1000;
const MAX_COMBINED_CHARS = 256 * 1024;
const KEEP_COMBINED_CHARS = 64 * 1024;
-const TRUNCATED_RUNNING_NOTICE = '... (output truncated)';
+const TRUNCATED_RUNNING_NOTICE = '… (output truncated)';
/**
* Live view for a user-initiated `!` shell command. Two phases:
diff --git a/apps/kimi-code/src/tui/components/messages/thinking.ts b/apps/kimi-code/src/tui/components/messages/thinking.ts
index 23a038c7071..e1208b55fe4 100644
--- a/apps/kimi-code/src/tui/components/messages/thinking.ts
+++ b/apps/kimi-code/src/tui/components/messages/thinking.ts
@@ -111,7 +111,7 @@ export class ThinkingComponent implements Component {
);
rendered = [
'',
- spinner + currentTheme.fg('textDim', 'thinking...'),
+ spinner + currentTheme.fg('textDim', 'thinking…'),
...visibleLines.map((line) => MESSAGE_INDENT + line),
];
} else {
@@ -127,7 +127,7 @@ export class ThinkingComponent implements Component {
// Leading blank + first PREVIEW_LINES content lines + hint line.
const truncated = lines.slice(0, 1 + THINKING_PREVIEW_LINES);
const remaining = contentLines.length - THINKING_PREVIEW_LINES;
- const hint = `... (${String(remaining)} more lines, ctrl+o to expand)`;
+ const hint = `… (${String(remaining)} more lines, ctrl+o to expand)`;
const indentWidth = Math.min(MESSAGE_INDENT.length, Math.max(0, width));
const hintWidth = Math.max(0, width - indentWidth);
truncated.push(
diff --git a/apps/kimi-code/src/tui/components/messages/tool-call.ts b/apps/kimi-code/src/tui/components/messages/tool-call.ts
index 4ca5541bbbc..449b2c9abf7 100644
--- a/apps/kimi-code/src/tui/components/messages/tool-call.ts
+++ b/apps/kimi-code/src/tui/components/messages/tool-call.ts
@@ -381,7 +381,7 @@ function truncateArgValue(key: string, value: string): string {
// still tell which file is being touched.
return '…' + value.slice(value.length - (MAX_ARG_LENGTH - 1));
}
- return value.slice(0, MAX_ARG_LENGTH - 3) + '...';
+ return value.slice(0, MAX_ARG_LENGTH - 1) + '…';
}
function makeWorkspaceRelativePath(filePath: string, workspaceDir: string | undefined): string {
@@ -1403,7 +1403,7 @@ export class ToolCallComponent extends Container {
const existingOutput = options?.replace === true ? '' : (activity?.output ?? '');
let output = existingOutput + text;
if (output.length > MAX_LIVE_OUTPUT_CHARS) {
- output = `[...truncated]\n${output.slice(output.length - MAX_LIVE_OUTPUT_CHARS)}`;
+ output = `[…truncated]\n${output.slice(output.length - MAX_LIVE_OUTPUT_CHARS)}`;
}
this.upsertSubToolActivity(id, name, args, activity?.phase ?? 'ongoing', output);
this.rebuildContent();
@@ -1654,7 +1654,7 @@ export class ToolCallComponent extends Container {
const suffix = this.hiddenSubCallCount > 1 ? 's' : '';
this.addChild(
new Text(
- currentTheme.italic(currentTheme.dim(` ${String(this.hiddenSubCallCount)} more tool call${suffix} ...`)),
+ currentTheme.italic(currentTheme.dim(` ${String(this.hiddenSubCallCount)} more tool call${suffix} …`)),
0,
0,
),
@@ -2024,7 +2024,7 @@ export class ToolCallComponent extends Container {
this.addChild(
new Text(
currentTheme.dim(
- `... (${String(remaining)} more lines, ${String(allLines.length)} total, ctrl+o to expand)`,
+ `… (${String(remaining)} more lines, ${String(allLines.length)} total, ctrl+o to expand)`,
),
2,
0,
diff --git a/apps/kimi-code/src/tui/components/messages/tool-renderers/truncated.ts b/apps/kimi-code/src/tui/components/messages/tool-renderers/truncated.ts
index dc1066bece0..f9b4c7183db 100644
--- a/apps/kimi-code/src/tui/components/messages/tool-renderers/truncated.ts
+++ b/apps/kimi-code/src/tui/components/messages/tool-renderers/truncated.ts
@@ -87,15 +87,15 @@ export class TruncatedOutputComponent implements Component {
if (this.tail) {
const shown = contentLines.slice(contentLines.length - this.maxLines);
return [
- this.renderHint(width, `... (${String(remaining)} earlier lines)`),
+ this.renderHint(width, `… (${String(remaining)} earlier lines)`),
...shown,
];
}
const shown = contentLines.slice(0, this.maxLines);
const hint = this.expandHint
- ? `... (${String(remaining)} more lines, ctrl+o to expand)`
- : `... (${String(remaining)} more lines)`;
+ ? `… (${String(remaining)} more lines, ctrl+o to expand)`
+ : `… (${String(remaining)} more lines)`;
return [...shown, this.renderHint(width, hint)];
}
}
diff --git a/apps/kimi-code/src/tui/components/panes/btw-panel.ts b/apps/kimi-code/src/tui/components/panes/btw-panel.ts
index 3dbb88508c0..1982b5d1bd8 100644
--- a/apps/kimi-code/src/tui/components/panes/btw-panel.ts
+++ b/apps/kimi-code/src/tui/components/panes/btw-panel.ts
@@ -145,7 +145,7 @@ export class BtwPanelComponent implements Component {
lines.push(...this.renderTurn(turn, width));
}
if (this.turns.length === 0) {
- lines.push(chalk.hex(currentTheme.palette.textDim)('Ready for a side question...'));
+ lines.push(chalk.hex(currentTheme.palette.textDim)('Ready for a side question…'));
}
lines.push(...this.renderTransientNotices(width));
return this.fitBodyLines(lines);
@@ -213,7 +213,7 @@ export class BtwPanelComponent implements Component {
: thinkingLines;
lines.push(...visibleThinking);
} else if (turn.error === undefined) {
- lines.push(chalk.hex(currentTheme.palette.textDim)('Waiting for answer...'));
+ lines.push(chalk.hex(currentTheme.palette.textDim)('Waiting for answer…'));
}
if (turn.error !== undefined) {
const error = chalk.hex(currentTheme.palette.error)(turn.error);
diff --git a/apps/kimi-code/src/tui/kimi-tui.ts b/apps/kimi-code/src/tui/kimi-tui.ts
index c82c6e7d3d3..da68c171c7a 100644
--- a/apps/kimi-code/src/tui/kimi-tui.ts
+++ b/apps/kimi-code/src/tui/kimi-tui.ts
@@ -3340,7 +3340,7 @@ export class KimiTUI {
break;
}
case 'composing': {
- const spinner = this.ensureActivitySpinner('braille', 'working...', (s) =>
+ const spinner = this.ensureActivitySpinner('braille', 'working…', (s) =>
currentTheme.fg('primary', s),
);
this.syncAgentSwarmActivitySpinner(undefined);
diff --git a/apps/kimi-code/src/tui/utils/background-agent-status.ts b/apps/kimi-code/src/tui/utils/background-agent-status.ts
index aa740fc6c6b..c2322e6335c 100644
--- a/apps/kimi-code/src/tui/utils/background-agent-status.ts
+++ b/apps/kimi-code/src/tui/utils/background-agent-status.ts
@@ -11,7 +11,7 @@ function normalizeBackgroundField(value: string | undefined): string | undefined
const collapsed = value.trim().replaceAll(/\s+/g, ' ');
if (collapsed.length === 0) return undefined;
if (collapsed.length <= MAX_BACKGROUND_FIELD_LENGTH) return collapsed;
- return `${collapsed.slice(0, MAX_BACKGROUND_FIELD_LENGTH - 3)}...`;
+ return `${collapsed.slice(0, MAX_BACKGROUND_FIELD_LENGTH - 1)}…`;
}
export function formatBackgroundAgentTranscript(
diff --git a/apps/kimi-code/src/tui/utils/background-task-status.ts b/apps/kimi-code/src/tui/utils/background-task-status.ts
index 10f579ad9f3..b9e1764965c 100644
--- a/apps/kimi-code/src/tui/utils/background-task-status.ts
+++ b/apps/kimi-code/src/tui/utils/background-task-status.ts
@@ -20,7 +20,7 @@ function truncate(value: string | undefined): string | undefined {
const collapsed = value.trim().replaceAll(/\s+/g, ' ');
if (collapsed.length === 0) return undefined;
if (collapsed.length <= MAX_DETAIL_LENGTH) return collapsed;
- return `${collapsed.slice(0, MAX_DETAIL_LENGTH - 3)}...`;
+ return `${collapsed.slice(0, MAX_DETAIL_LENGTH - 1)}…`;
}
export type BackgroundTaskTranscriptPhase = 'started' | 'updated' | 'terminal';
diff --git a/apps/kimi-code/test/tui/activity-pane.test.ts b/apps/kimi-code/test/tui/activity-pane.test.ts
index 3d8b2ed3823..1501d65e44c 100644
--- a/apps/kimi-code/test/tui/activity-pane.test.ts
+++ b/apps/kimi-code/test/tui/activity-pane.test.ts
@@ -179,7 +179,7 @@ describe('updateActivityPane terminal progress', () => {
expect(setProgress).toHaveBeenLastCalledWith(true);
expect(state.activitySpinner).not.toBeNull();
expect(state.activityContainer.children).toHaveLength(0);
- expect(strip(progress.render(80).join('\n'))).toContain('🌑 Working...');
+ expect(strip(progress.render(80).join('\n'))).toContain('🌑 Working…');
state.activitySpinner?.instance.stop();
driver.sessionEventHandler.clearAgentSwarmProgress();
@@ -209,8 +209,8 @@ describe('updateActivityPane terminal progress', () => {
expect(state.activitySpinner).not.toBeNull();
expect(state.activityContainer.children).toHaveLength(1);
const output = strip(progress.render(80).join('\n'));
- expect(output).toContain(' Working...');
- expect(output).not.toContain('🌑 Working...');
+ expect(output).toContain(' Working…');
+ expect(output).not.toContain('🌑 Working…');
state.activitySpinner?.instance.stop();
driver.sessionEventHandler.clearAgentSwarmProgress();
diff --git a/apps/kimi-code/test/tui/components/dialogs/compaction.test.ts b/apps/kimi-code/test/tui/components/dialogs/compaction.test.ts
index 4f415bc322d..eb213cfa2a0 100644
--- a/apps/kimi-code/test/tui/components/dialogs/compaction.test.ts
+++ b/apps/kimi-code/test/tui/components/dialogs/compaction.test.ts
@@ -20,7 +20,7 @@ describe('CompactionComponent', () => {
const lines = component.render(120).map(strip);
const text = lines.join('\n');
- expect(text).toContain('Compacting context...');
+ expect(text).toContain('Compacting context…');
expect(text).toContain(' keep the recent files only');
} finally {
component.dispose();
@@ -34,7 +34,7 @@ describe('CompactionComponent', () => {
const lines = component.render(120).map(strip);
const text = lines.join('\n');
- expect(text).toContain('Compacting context... · Tip: ctrl+s: steer mid-turn');
+ expect(text).toContain('Compacting context… · Tip: ctrl+s: steer mid-turn');
} finally {
component.dispose();
}
@@ -65,7 +65,7 @@ describe('CompactionComponent', () => {
const text = lines.join('\n');
expect(text).toContain('Compaction cancelled');
- expect(text).not.toContain('Compacting context...');
+ expect(text).not.toContain('Compacting context…');
} finally {
component.dispose();
}
@@ -157,7 +157,7 @@ describe('CompactionComponent', () => {
try {
const headerOf = (): string => {
- const line = component.render(120).find((l) => strip(l).includes('Compacting context...'));
+ const line = component.render(120).find((l) => strip(l).includes('Compacting context…'));
if (line === undefined) throw new Error('header line not found');
return line;
};
diff --git a/apps/kimi-code/test/tui/components/messages/agent-swarm-progress.test.ts b/apps/kimi-code/test/tui/components/messages/agent-swarm-progress.test.ts
index bb460ce0e6e..63d0219ab0a 100644
--- a/apps/kimi-code/test/tui/components/messages/agent-swarm-progress.test.ts
+++ b/apps/kimi-code/test/tui/components/messages/agent-swarm-progress.test.ts
@@ -163,7 +163,7 @@ describe('AgentSwarmProgressComponent', () => {
expect(output).toContain('Agent Swarm');
expect(output).toContain('Review changed files');
- expect(output).toContain('Orchestrating...');
+ expect(output).toContain('Orchestrating…');
expect(output).not.toContain('01');
});
@@ -248,8 +248,8 @@ describe('AgentSwarmProgressComponent', () => {
const output = renderText(component);
- expect(output).toContain('001 Queued...');
- expect(output).toContain('002 Queued...');
+ expect(output).toContain('001 Queued…');
+ expect(output).toContain('002 Queued…');
expect(output).not.toContain('001 [');
expect(output).not.toContain('002 [');
expect(output).not.toContain('agents=2');
@@ -261,11 +261,11 @@ describe('AgentSwarmProgressComponent', () => {
registerSubagents(component, 3);
const lines = renderLines(component, 97);
- const queuedLine = lines.find((line) => line.includes('001 Queued...'));
+ const queuedLine = lines.find((line) => line.includes('001 Queued…'));
expect(queuedLine).toBeDefined();
- expect(queuedLine).toContain('002 Queued...');
- expect(queuedLine).toContain('003 Queued...');
+ expect(queuedLine).toContain('002 Queued…');
+ expect(queuedLine).toContain('003 Queued…');
});
it('omits subagent text when the compact grid is needed to fit available height', () => {
@@ -363,7 +363,7 @@ describe('AgentSwarmProgressComponent', () => {
let output = renderText(component);
expect(output).toContain('001 [');
expect(output).toContain('Running');
- expect(output).toContain('002 Queued...');
+ expect(output).toContain('002 Queued…');
expect(output).not.toContain('002 [');
component.markCompleted('agent-1');
@@ -412,8 +412,8 @@ describe('AgentSwarmProgressComponent', () => {
});
let output = renderText(component);
- expect(output).toContain('Rate limited...');
- expect(output).not.toContain('Queued...');
+ expect(output).toContain('Rate limited…');
+ expect(output).not.toContain('Queued…');
expect(output).not.toContain('Provider rate limit');
expect(output).not.toContain('Failed');
@@ -421,7 +421,7 @@ describe('AgentSwarmProgressComponent', () => {
output = renderText(component);
expect(output).toContain('Running');
- expect(output).not.toContain('Rate limited...');
+ expect(output).not.toContain('Rate limited…');
});
it('renders rate-limited subagents as cancelled when cancelled', () => {
@@ -440,7 +440,7 @@ describe('AgentSwarmProgressComponent', () => {
expect(cellLine).toBeDefined();
expect(cellLine).toContain('⊘ Cancelled.');
- expect(cellLine).not.toContain('Rate limited...');
+ expect(cellLine).not.toContain('Rate limited…');
});
it('renders failure details from AgentSwarm result output', () => {
@@ -605,7 +605,7 @@ describe('AgentSwarmProgressComponent', () => {
});
const promptLine = renderLines(prompting, 80)
- .find((line) => line.includes('Prompting...'));
+ .find((line) => line.includes('Prompting…'));
expect(promptLine).toBeDefined();
const working = createComponent();
@@ -613,15 +613,15 @@ describe('AgentSwarmProgressComponent', () => {
startSubagents(working, 1);
const workingLine = renderLines(working, 80)
- .find((line) => line.includes('Working...'));
+ .find((line) => line.includes('Working…'));
expect(workingLine).toBeDefined();
const promptTextIndex = promptLine?.indexOf('Review the changed') ?? -1;
const progressBarIndex = workingLine?.indexOf('━') ?? -1;
expect(promptTextIndex).toBeGreaterThan(0);
expect(progressBarIndex).toBeGreaterThan(0);
- expect(promptTextIndex).toBe(visibleWidth(' Prompting... '));
- expect(progressBarIndex).toBe(visibleWidth(' Working... '));
+ expect(promptTextIndex).toBe(visibleWidth(' Prompting… '));
+ expect(progressBarIndex).toBe(visibleWidth(' Working… '));
});
it('renders the activity spinner before the total status line', () => {
@@ -632,10 +632,10 @@ describe('AgentSwarmProgressComponent', () => {
component.setActivitySpinnerText(() => '🌗');
const statusLine = renderLines(component, 80)
- .find((line) => line.includes('Working...'));
+ .find((line) => line.includes('Working…'));
expect(statusLine).toBeDefined();
- expect(statusLine?.startsWith(' 🌗 Working...')).toBe(true);
+ expect(statusLine?.startsWith(' 🌗 Working…')).toBe(true);
});
it('keeps a two-cell placeholder after the AgentSwarm tool call ends', () => {
@@ -648,10 +648,10 @@ describe('AgentSwarmProgressComponent', () => {
component.setActivitySpinnerText(() => '🌘');
const statusLine = renderLines(component, 80)
- .find((line) => line.includes('Working...'));
+ .find((line) => line.includes('Working…'));
expect(statusLine).toBeDefined();
- expect(statusLine?.startsWith(' Working...')).toBe(true);
+ expect(statusLine?.startsWith(' Working…')).toBe(true);
expect(statusLine).not.toContain('🌗');
expect(statusLine).not.toContain('🌘');
});
@@ -694,7 +694,7 @@ describe('AgentSwarmProgressComponent', () => {
});
const promptLine = renderLines(prompting, 50)
- .find((line) => line.includes('Prompting...'));
+ .find((line) => line.includes('Prompting…'));
expect(promptLine).toBeDefined();
expect(visibleWidth(promptLine ?? '')).toBeLessThan(50);
@@ -818,7 +818,7 @@ describe('AgentSwarmProgressComponent', () => {
registerSubagents(component, 1);
let output = renderText(component);
- expect(output).toContain('001 Queued...');
+ expect(output).toContain('001 Queued…');
expect(output).not.toContain('001 [');
expect(output).not.toContain('002');
@@ -827,15 +827,15 @@ describe('AgentSwarmProgressComponent', () => {
description: `${DEFAULT_DESCRIPTION} #2 (coder)`,
});
output = renderText(component);
- expect(output).toContain('001 Queued...');
- expect(output).toContain('002 Queued...');
+ expect(output).toContain('001 Queued…');
+ expect(output).toContain('002 Queued…');
expect(output).not.toContain('001 [');
expect(output).not.toContain('002 [');
component.markInputComplete();
output = renderText(component);
- expect(output).toContain('001 Queued...');
- expect(output).toContain('002 Queued...');
+ expect(output).toContain('001 Queued…');
+ expect(output).toContain('002 Queued…');
expect(output).not.toContain('001 [');
});
diff --git a/apps/kimi-code/test/tui/components/messages/shell-execution.test.ts b/apps/kimi-code/test/tui/components/messages/shell-execution.test.ts
index bb501c05b0b..132737c8655 100644
--- a/apps/kimi-code/test/tui/components/messages/shell-execution.test.ts
+++ b/apps/kimi-code/test/tui/components/messages/shell-execution.test.ts
@@ -35,7 +35,7 @@ describe('ShellExecutionComponent', () => {
expect(collapsedOutput).toContain('line1');
expect(collapsedOutput).toContain('line3');
expect(collapsedOutput).not.toContain('line4');
- expect(collapsedOutput).toContain('... (2 more lines, ctrl+o to expand)');
+ expect(collapsedOutput).toContain('… (2 more lines, ctrl+o to expand)');
const expanded = new ShellExecutionComponent({
result: {
@@ -76,7 +76,7 @@ describe('ShellExecutionComponent', () => {
const output = component.render(100).map(strip).join('\n');
expect(output).toContain('hello');
- expect(output).not.toContain('... (2 more lines');
+ expect(output).not.toContain('… (2 more lines');
});
it('preserves internal empty lines while trimming only trailing ones', () => {
@@ -91,7 +91,7 @@ describe('ShellExecutionComponent', () => {
const output = component.render(100).map(strip).join('\n');
expect(output).toContain('a');
expect(output).toContain('b');
- expect(output).not.toContain('... (2 more lines');
+ expect(output).not.toContain('… (2 more lines');
});
it('truncates long single-line output by wrapped visual lines', () => {
@@ -106,7 +106,7 @@ describe('ShellExecutionComponent', () => {
const out = strip(component.render(20).join('\n'));
expect(out).toContain('x');
expect(out).not.toContain('x'.repeat(500));
- expect(out).toContain('... (');
+ expect(out).toContain('… (');
});
describe('shellExecutionResultRenderer', () => {
diff --git a/apps/kimi-code/test/tui/components/messages/shell-run.test.ts b/apps/kimi-code/test/tui/components/messages/shell-run.test.ts
index be89c6f034e..96a4bc110dc 100644
--- a/apps/kimi-code/test/tui/components/messages/shell-run.test.ts
+++ b/apps/kimi-code/test/tui/components/messages/shell-run.test.ts
@@ -90,7 +90,7 @@ describe('ShellRunComponent finished collapse', () => {
const c = create();
c.finish(rows(30), '', false);
const rendered = stripTheme(c.render(80).join('\n'));
- expect(rendered).toContain('... (20 more lines, ctrl+o to expand)');
+ expect(rendered).toContain('… (20 more lines, ctrl+o to expand)');
expect(rendered).toContain('row-01');
expect(rendered).toContain('row-10');
expect(rendered).not.toContain('row-11');
@@ -116,7 +116,7 @@ describe('ShellRunComponent finished collapse', () => {
c.setExpanded(false);
const collapsed = stripTheme(c.render(80).join('\n'));
- expect(collapsed).toContain('... (20 more lines, ctrl+o to expand)');
+ expect(collapsed).toContain('… (20 more lines, ctrl+o to expand)');
expect(collapsed).not.toContain('row-11');
});
@@ -153,7 +153,7 @@ describe('ShellRunComponent finished collapse', () => {
c.append('x'.repeat(300 * 1024));
c.setExpanded(true);
const rendered = stripTheme(c.render(80).join('\n'));
- expect(rendered).toContain('... (output truncated)');
+ expect(rendered).toContain('… (output truncated)');
});
it('keeps the backgrounded view when toggled', () => {
@@ -168,7 +168,7 @@ describe('ShellRunComponent finished collapse', () => {
const c = create();
c.finish(rows(30), 'boom', true);
const collapsed = stripTheme(c.render(80).join('\n'));
- expect(collapsed).toContain('... (21 more lines, ctrl+o to expand)');
+ expect(collapsed).toContain('… (21 more lines, ctrl+o to expand)');
c.setExpanded(true);
const expanded = stripTheme(c.render(80).join('\n'));
diff --git a/apps/kimi-code/test/tui/components/messages/thinking.test.ts b/apps/kimi-code/test/tui/components/messages/thinking.test.ts
index e615d7f5cd1..7e04dbd1f7e 100644
--- a/apps/kimi-code/test/tui/components/messages/thinking.test.ts
+++ b/apps/kimi-code/test/tui/components/messages/thinking.test.ts
@@ -15,8 +15,8 @@ describe('ThinkingComponent', () => {
const component = new ThinkingComponent('working it out', true, 'live');
const out = strip(component.render(80).join('\n'));
- expect(out).toContain('⠋ thinking...');
- expect(out).not.toContain(' ⠋ thinking...');
+ expect(out).toContain('⠋ thinking…');
+ expect(out).not.toContain(' ⠋ thinking…');
expect(out).not.toContain(`${STATUS_BULLET}⠋`);
expect(out).toContain(' working it out');
});
@@ -40,11 +40,11 @@ describe('ThinkingComponent', () => {
requestRender,
} as unknown as TUI);
- expect(strip(component.render(80).join('\n'))).toContain('⠋ thinking...');
+ expect(strip(component.render(80).join('\n'))).toContain('⠋ thinking…');
vi.advanceTimersByTime(80);
expect(requestRender).toHaveBeenCalled();
- expect(strip(component.render(80).join('\n'))).toContain('⠙ thinking...');
+ expect(strip(component.render(80).join('\n'))).toContain('⠙ thinking…');
component.finalize();
requestRender.mockClear();
@@ -63,7 +63,7 @@ describe('ThinkingComponent', () => {
expect(out).toContain('line2');
expect(out).not.toContain('line3');
expect(out).not.toContain('line4');
- expect(out).toContain('... (5 more lines, ctrl+o to expand)');
+ expect(out).toContain('… (5 more lines, ctrl+o to expand)');
});
it('expands and collapses after finalization', () => {
diff --git a/apps/kimi-code/test/tui/components/messages/tool-call.test.ts b/apps/kimi-code/test/tui/components/messages/tool-call.test.ts
index 367e62a56c9..b86141e496f 100644
--- a/apps/kimi-code/test/tui/components/messages/tool-call.test.ts
+++ b/apps/kimi-code/test/tui/components/messages/tool-call.test.ts
@@ -162,7 +162,7 @@ describe('ToolCallComponent', () => {
expect(collapsed).toContain('line2');
expect(collapsed).toContain('line3');
expect(collapsed).not.toContain('line4');
- expect(collapsed).toContain('... (2 more lines, ctrl+o to expand)');
+ expect(collapsed).toContain('… (2 more lines, ctrl+o to expand)');
component.setExpanded(true);
diff --git a/apps/kimi-code/test/tui/components/messages/tool-renderers/registry.test.ts b/apps/kimi-code/test/tui/components/messages/tool-renderers/registry.test.ts
index cff95e628e4..1c4d3b27329 100644
--- a/apps/kimi-code/test/tui/components/messages/tool-renderers/registry.test.ts
+++ b/apps/kimi-code/test/tui/components/messages/tool-renderers/registry.test.ts
@@ -65,14 +65,14 @@ describe('tool-result registry', () => {
expect(out).toContain('b');
expect(out).toContain('c');
expect(out).not.toContain('\nd');
- expect(out).toContain('... (2 more lines, ctrl+o to expand)');
+ expect(out).toContain('… (2 more lines, ctrl+o to expand)');
});
it('uses truncated renderer for Bash to preserve raw output UX', () => {
const renderer = pickResultRenderer('Bash');
const out = strip(joinRender(renderer(call('Bash'), result('one\ntwo\nthree\nfour'), ctx)));
expect(out).toContain('one');
- expect(out).toContain('... (1 more lines, ctrl+o to expand)');
+ expect(out).toContain('… (1 more lines, ctrl+o to expand)');
});
it('Read renders no body when collapsed (header chip carries the count)', () => {
@@ -248,7 +248,7 @@ describe('tool-result registry', () => {
const out = strip(joinRender(renderer(call('SomethingUnknown'), result(longLine), ctx), 20));
expect(out).toContain('x');
expect(out).not.toContain(longLine);
- expect(out).toContain('... (');
+ expect(out).toContain('… (');
});
const waitForCompletedOutput = [
diff --git a/apps/kimi-code/test/tui/components/messages/tool-renderers/truncated.test.ts b/apps/kimi-code/test/tui/components/messages/tool-renderers/truncated.test.ts
index 4c90c039258..3f6471f46c5 100644
--- a/apps/kimi-code/test/tui/components/messages/tool-renderers/truncated.test.ts
+++ b/apps/kimi-code/test/tui/components/messages/tool-renderers/truncated.test.ts
@@ -20,7 +20,7 @@ describe('TruncatedOutputComponent', () => {
const lines = strip(component.render(80).join('\n')).split('\n');
expect(lines[0]?.startsWith(' a')).toBe(true);
expect(lines[1]?.startsWith(' b')).toBe(true);
- expect(lines[2]).toBe(' ... (3 more lines, ctrl+o to expand)');
+ expect(lines[2]).toBe(' … (3 more lines, ctrl+o to expand)');
});
it('defaults to a two-space indent for both content and hint', () => {
@@ -32,7 +32,7 @@ describe('TruncatedOutputComponent', () => {
const lines = strip(component.render(80).join('\n')).split('\n');
expect(lines[0]?.startsWith(' x')).toBe(true);
- expect(lines[1]).toBe(' ... (2 more lines, ctrl+o to expand)');
+ expect(lines[1]).toBe(' … (2 more lines, ctrl+o to expand)');
});
it('omits the ctrl+o promise when expandHint is false', () => {
@@ -45,7 +45,7 @@ describe('TruncatedOutputComponent', () => {
});
const lines = strip(component.render(80).join('\n')).split('\n');
- expect(lines[2]).toBe(' ... (2 more lines)');
+ expect(lines[2]).toBe(' … (2 more lines)');
});
it('renders all lines without a hint when expanded', () => {
diff --git a/apps/kimi-code/test/tui/kimi-tui-message-flow.test.ts b/apps/kimi-code/test/tui/kimi-tui-message-flow.test.ts
index 9774c535fbf..1368610d484 100644
--- a/apps/kimi-code/test/tui/kimi-tui-message-flow.test.ts
+++ b/apps/kimi-code/test/tui/kimi-tui-message-flow.test.ts
@@ -4308,7 +4308,7 @@ command = "vim"
driver.handleUserInput('seq 30');
await vi.waitFor(() => {
const transcript = stripSgr(driver.state.transcriptContainer.render(120).join('\n'));
- expect(transcript).toContain('... (20 more lines, ctrl+o to expand)');
+ expect(transcript).toContain('… (20 more lines, ctrl+o to expand)');
});
let transcript = stripSgr(driver.state.transcriptContainer.render(120).join('\n'));
@@ -4322,7 +4322,7 @@ command = "vim"
driver.state.editor.onToggleToolExpand?.();
transcript = stripSgr(driver.state.transcriptContainer.render(120).join('\n'));
- expect(transcript).toContain('... (20 more lines, ctrl+o to expand)');
+ expect(transcript).toContain('… (20 more lines, ctrl+o to expand)');
expect(transcript).not.toContain('row-11');
});
@@ -4844,7 +4844,7 @@ command = "vim"
expect(session.startBtw).toHaveBeenCalledWith();
});
expect(session.prompt).not.toHaveBeenCalled();
- expect(stripSgr(renderBtwPanel(driver))).toContain('Ready for a side question...');
+ expect(stripSgr(renderBtwPanel(driver))).toContain('Ready for a side question…');
driver.handleUserInput('What are you working on right now?');
@@ -4885,7 +4885,7 @@ command = "vim"
await vi.waitFor(() => {
expect(session.startBtw).toHaveBeenCalledWith();
});
- expect(stripSgr(renderBtwPanel(driver))).toContain('Ready for a side question...');
+ expect(stripSgr(renderBtwPanel(driver))).toContain('Ready for a side question…');
driver.handleUserInput('check /skill:review');
@@ -5179,7 +5179,7 @@ command = "vim"
const lines = getMountedBtwPanel(driver).render(80).map(stripSgr);
expect(lines).toHaveLength(3);
expect(lines.join('\n')).toContain('Q: side question');
- expect(lines.join('\n')).toContain('Waiting for answer...');
+ expect(lines.join('\n')).toContain('Waiting for answer…');
});
it('keeps /btw panel height stable when final output is shorter than thinking', async () => {
@@ -6152,7 +6152,7 @@ command = "vim"
transcript = stripSgr(renderTranscript(driver));
expect(transcript).toContain('001 [');
- expect(transcript).toContain('Queued...');
+ expect(transcript).toContain('Queued…');
expect(transcript).not.toContain('Provider rate limit');
expect(transcript).not.toContain('Failed');
@@ -6191,7 +6191,7 @@ command = "vim"
expect(transcript).toContain('001 [');
expect(transcript).toContain('Reviewing src/a.ts');
expect(transcript).not.toContain('Completed');
- expect(transcript).toContain('002 Queued...');
+ expect(transcript).toContain('002 Queued…');
expect(transcript).not.toContain('002 [');
driver.sessionEventHandler.handleEvent(
@@ -6513,7 +6513,7 @@ command = "vim"
const renderSwarm = (): string =>
stripSgr(swarmProgress.render(transcriptWidth).join('\n'));
- expect(renderSwarm()).toContain('001 Queued...');
+ expect(renderSwarm()).toContain('001 Queued…');
driver.sessionEventHandler.handleEvent(
{
@@ -6539,7 +6539,7 @@ command = "vim"
.reduce((sum, child) => sum + child.render(transcriptWidth).length, 0);
expect(rowsAfterSwarmInTranscript).toBeGreaterThan(0);
- expect(renderSwarm()).toContain('001 Queued...');
+ expect(renderSwarm()).toContain('001 Queued…');
const transcript = stripSgr(
driver.state.transcriptContainer.render(terminalColumns).join('\n'),
);
@@ -6612,7 +6612,7 @@ command = "vim"
let transcript = stripSgr(renderTranscript(driver));
expect(transcript).toContain('Agent Swarm');
- expect(transcript).toContain('Orchestrating...');
+ expect(transcript).toContain('Orchestrating…');
expect(transcript).not.toContain('01');
driver.sessionEventHandler.handleEvent(
@@ -6649,7 +6649,7 @@ command = "vim"
);
transcript = stripSgr(renderTranscript(driver));
- expect(transcript).toContain('001 Queued...');
+ expect(transcript).toContain('001 Queued…');
expect(transcript).not.toContain('001 [');
expect(transcript).toContain('002 src/b');
@@ -6671,8 +6671,8 @@ command = "vim"
);
transcript = stripSgr(renderTranscript(driver));
- expect(transcript).toContain('001 Queued...');
- expect(transcript).toContain('002 Queued...');
+ expect(transcript).toContain('001 Queued…');
+ expect(transcript).toContain('002 Queued…');
expect(transcript).not.toContain('001 [');
expect(transcript).not.toContain('002 [');
});
diff --git a/apps/kimi-code/test/tui/message-replay.test.ts b/apps/kimi-code/test/tui/message-replay.test.ts
index 80b4e2be6e4..57ca82ea8d8 100644
--- a/apps/kimi-code/test/tui/message-replay.test.ts
+++ b/apps/kimi-code/test/tui/message-replay.test.ts
@@ -402,7 +402,7 @@ describe('KimiTUI resume message replay', () => {
]);
const transcript = stripAnsi(driver.state.transcriptContainer.render(140).join('\n'));
- expect(transcript).toContain('... (20 more lines, ctrl+o to expand)');
+ expect(transcript).toContain('… (20 more lines, ctrl+o to expand)');
expect(transcript).toContain('row-01');
expect(transcript).not.toContain('row-11');
});
diff --git a/apps/vscode/webview-ui/src/components/ActionMenu.tsx b/apps/vscode/webview-ui/src/components/ActionMenu.tsx
index f6ee7a2c9e5..0e4b79b4def 100644
--- a/apps/vscode/webview-ui/src/components/ActionMenu.tsx
+++ b/apps/vscode/webview-ui/src/components/ActionMenu.tsx
@@ -140,7 +140,7 @@ export function ActionMenu({ className, onAuthAction }: ActionMenuProps) {
danger={isLoggedIn}
>
{loading ?
A browser window should open automatically. Complete the sign-in process there.