diff --git a/packages/cli/src/ui/components/SuggestionsDisplay.test.tsx b/packages/cli/src/ui/components/SuggestionsDisplay.test.tsx index c5b3fc7e709..023d1080907 100644 --- a/packages/cli/src/ui/components/SuggestionsDisplay.test.tsx +++ b/packages/cli/src/ui/components/SuggestionsDisplay.test.tsx @@ -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', () => { @@ -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( { ); 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); + }); + + 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( + , + ); + + 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'); }); }); diff --git a/packages/cli/src/ui/components/SuggestionsDisplay.tsx b/packages/cli/src/ui/components/SuggestionsDisplay.tsx index e999abdf164..1fe417cd821 100644 --- a/packages/cli/src/ui/components/SuggestionsDisplay.tsx +++ b/packages/cli/src/ui/components/SuggestionsDisplay.tsx @@ -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, @@ -148,8 +158,8 @@ export function SuggestionsDisplay({ flexShrink={1} paddingLeft={2} > - - {suggestion.description} + + {normalizeDescription(suggestion.description)} )}