Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fix-working-tips-swarm.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code": patch
---

Fix working tips getting squeezed against the agent swarm progress bar.
9 changes: 8 additions & 1 deletion apps/kimi-code/src/tui/components/chrome/moon-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ export class MoonLoader extends Text {
private colorFn?: (s: string) => string;
private label: string;
private displayText = '';
// Inline text used when the spinner is embedded into another line (e.g. the
// agent-swarm progress status line). It intentionally excludes the tip: the
// tip is only rendered when the loader sits on its own row in the activity
// pane, otherwise it would get squeezed against whatever follows the inline
// spinner (like the swarm progress bar).
private inlineText = '';
private tip: string = '';
private availableWidth = 0;

Expand Down Expand Up @@ -75,13 +81,14 @@ export class MoonLoader extends Text {
}

renderInline(): string {
return this.displayText;
return this.inlineText;
}

private updateDisplay(): void {
const frame = this.frames[this.currentFrame]!;
const coloredFrame = this.colorFn ? this.colorFn(frame) : frame;
const baseText = this.label ? `${coloredFrame} ${this.label}` : coloredFrame;
this.inlineText = baseText;
Comment thread
liruifengv marked this conversation as resolved.
let text = baseText;
if (this.tip) {
const withTip = baseText + currentTheme.fg('textDim', this.tip);
Expand Down
42 changes: 42 additions & 0 deletions apps/kimi-code/test/tui/components/chrome/moon-loader.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import type { TUI } from '@earendil-works/pi-tui';
import { afterEach, describe, expect, it } from 'vitest';

import { MoonLoader } from '#/tui/components/chrome/moon-loader';

// MoonLoader starts a real setInterval in its constructor, so every loader
// created in these tests must be stopped to avoid leaving live timers behind.
const loaders: MoonLoader[] = [];

function createLoader(): MoonLoader {
const ui = { requestRender() {} } as unknown as TUI;
const loader = new MoonLoader(ui, 'moon');
loaders.push(loader);
return loader;
}

afterEach(() => {
for (const loader of loaders) loader.stop();
loaders.length = 0;
});

describe('MoonLoader', () => {
it('keeps the tip out of renderInline so it does not squeeze against the swarm progress bar', () => {
const loader = createLoader();
loader.setTip(' · Tip: ctrl+s: steer mid-turn');
loader.setAvailableWidth(80);

const inline = loader.renderInline();
expect(inline).not.toContain('Tip');
expect(inline).not.toContain('steer');
expect(inline.trim().length).toBeGreaterThan(0);
});

it('still shows the tip on its own row when width allows', () => {
const loader = createLoader();
loader.setTip(' · Tip: ctrl+s: steer mid-turn');
loader.setAvailableWidth(80);

const row = loader.render(80).join('\n');
expect(row).toContain('Tip: ctrl+s: steer mid-turn');
});
});
Loading