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
50 changes: 44 additions & 6 deletions packages/cli/src/ui/components/SuggestionsDisplay.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@

import { render } from 'ink-testing-library';
import { describe, it, expect, beforeEach, afterAll } from 'vitest';
import { SuggestionsDisplay } from './SuggestionsDisplay.js';
import {
SuggestionsDisplay,
normalizeDescription,
} from './SuggestionsDisplay.js';
import { setLanguageAsync } from '../../i18n/index.js';

describe('SuggestionsDisplay', () => {
Expand Down Expand Up @@ -38,9 +41,9 @@ describe('SuggestionsDisplay', () => {
expect(lastFrame()).toContain('正在加载建议...');
});

it('wraps long slash command descriptions instead of truncating them', () => {
it('truncates long slash command descriptions to a single line by default', () => {
const description =
'This long command description should wrap across multiple lines and remain fully visible in the menu.';
'This long command description should be truncated to a single line so it cannot fill the entire terminal window.';
const { lastFrame } = render(
<SuggestionsDisplay
suggestions={[
Expand All @@ -60,9 +63,44 @@ describe('SuggestionsDisplay', () => {
);

const output = lastFrame() ?? '';
const normalizedOutput = output.replace(/\s+/g, ' ').trim();

expect(normalizedOutput).toContain(description);
expect(output.split('\n').length).toBeGreaterThan(1);
// The description is cut off with an ellipsis and the full text is gone.
expect(output).toContain('…');
expect(output).not.toContain('entire terminal window');
// A single suggestion with a long description must not blow up vertically.
expect(output.split('\n').length).toBeLessThanOrEqual(2);
Comment on lines +67 to +71
});

it('collapses newlines in multi-line descriptions so a row stays one line', () => {
const description = [
'First line of the skill description.',
'',
'- bullet one',
'- bullet two',
].join('\n');
const { lastFrame } = render(
<SuggestionsDisplay
suggestions={[{ label: 'skill', value: 'skill', description }]}
activeIndex={0}
isLoading={false}
width={120}
scrollOffset={0}
userInput="/sk"
mode="slash"
/>,
);

const output = lastFrame() ?? '';
// The verbatim multi-line layout (with the blank line / bullets stacked)
// must not appear; everything collapses onto the single command row.
expect(output).not.toContain('\n\n');
expect(output).toContain('First line of the skill description.');
expect(output).toContain('- bullet one - bullet two');
});
});

describe('normalizeDescription', () => {
it('collapses all whitespace runs into single spaces and trims', () => {
expect(normalizeDescription(' a\n\nb\t c ')).toBe('a b c');
});
});
14 changes: 12 additions & 2 deletions packages/cli/src/ui/components/SuggestionsDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,16 @@ interface SuggestionsDisplayProps {
export const MAX_SUGGESTIONS_TO_SHOW = 8;
export { MAX_WIDTH };

/**
* Collapse all runs of whitespace (including newlines from multi-line
* SKILL.md/command descriptions) into single spaces so a description renders
* as a single logical line. Without this, frontmatter line breaks are
* preserved verbatim and a single long description can fill the whole terminal.
*/
export function normalizeDescription(description: string): string {
return description.replace(/\s+/g, ' ').trim();
}

export function SuggestionsDisplay({
suggestions,
activeIndex,
Expand Down Expand Up @@ -148,8 +158,8 @@ export function SuggestionsDisplay({
flexShrink={1}
paddingLeft={2}
>
<Text color={textColor} wrap="wrap">
{suggestion.description}
<Text color={textColor} wrap="truncate-end">
{normalizeDescription(suggestion.description)}
</Text>
</Box>
)}
Expand Down
Loading