feat(cli): unify /chat and /resume command UX (#20256)

This commit is contained in:
Dmitry Lyalin
2026-03-08 18:50:51 -04:00
committed by GitHub
parent d012929a28
commit d41735d6a9
18 changed files with 619 additions and 90 deletions

View File

@@ -990,6 +990,12 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
}
if (isEnterKey && buffer.text.startsWith('/')) {
if (suggestion.submitValue) {
setExpandedSuggestionIndex(-1);
handleSubmit(suggestion.submitValue.trim());
return true;
}
const { isArgumentCompletion, leafCommand } =
completion.slashCompletionRange;

View File

@@ -127,4 +127,44 @@ describe('SuggestionsDisplay', () => {
await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
it('renders command section separators for slash mode', async () => {
const groupedSuggestions = [
{
label: 'list',
value: 'list',
description: 'Browse auto-saved chats',
sectionTitle: 'auto',
},
{
label: 'list',
value: 'list',
description: 'List checkpoints',
sectionTitle: 'checkpoints',
},
{
label: 'save',
value: 'save',
description: 'Save checkpoint',
sectionTitle: 'checkpoints',
},
];
const { lastFrame, waitUntilReady } = render(
<SuggestionsDisplay
suggestions={groupedSuggestions}
activeIndex={0}
isLoading={false}
width={100}
scrollOffset={0}
userInput="/resume"
mode="slash"
/>,
);
await waitUntilReady();
const frame = lastFrame();
expect(frame).toContain('-- auto --');
expect(frame).toContain('-- checkpoints --');
});
});

View File

@@ -14,9 +14,12 @@ import { sanitizeForDisplay } from '../utils/textUtils.js';
export interface Suggestion {
label: string;
value: string;
insertValue?: string;
description?: string;
matchedIndex?: number;
commandKind?: CommandKind;
sectionTitle?: string;
submitValue?: string;
}
interface SuggestionsDisplayProps {
suggestions: Suggestion[];
@@ -86,6 +89,12 @@ export function SuggestionsDisplay({
const isExpanded = originalIndex === expandedIndex;
const textColor = isActive ? theme.ui.focus : theme.text.secondary;
const isLong = suggestion.value.length >= MAX_WIDTH;
const previousSectionTitle =
suggestions[originalIndex - 1]?.sectionTitle;
const shouldRenderSectionHeader =
mode === 'slash' &&
!!suggestion.sectionTitle &&
suggestion.sectionTitle !== previousSectionTitle;
const labelElement = (
<ExpandableText
label={suggestion.value}
@@ -99,37 +108,48 @@ export function SuggestionsDisplay({
return (
<Box
key={`${suggestion.value}-${originalIndex}`}
flexDirection="row"
backgroundColor={isActive ? theme.background.focus : undefined}
flexDirection="column"
>
<Box
{...(mode === 'slash'
? { width: commandColumnWidth, flexShrink: 0 as const }
: { flexShrink: 1 as const })}
>
<Box>
{labelElement}
{suggestion.commandKind &&
COMMAND_KIND_SUFFIX[suggestion.commandKind] && (
<Text color={textColor}>
{COMMAND_KIND_SUFFIX[suggestion.commandKind]}
</Text>
)}
</Box>
</Box>
{shouldRenderSectionHeader && (
<Text color={theme.text.secondary}>
-- {suggestion.sectionTitle} --
</Text>
)}
{suggestion.description && (
<Box flexGrow={1} paddingLeft={3}>
<Text color={textColor} wrap="truncate">
{sanitizeForDisplay(suggestion.description, 100)}
</Text>
<Box
flexDirection="row"
backgroundColor={isActive ? theme.background.focus : undefined}
>
<Box
{...(mode === 'slash'
? { width: commandColumnWidth, flexShrink: 0 as const }
: { flexShrink: 1 as const })}
>
<Box>
{labelElement}
{suggestion.commandKind &&
COMMAND_KIND_SUFFIX[suggestion.commandKind] && (
<Text color={textColor}>
{COMMAND_KIND_SUFFIX[suggestion.commandKind]}
</Text>
)}
</Box>
</Box>
)}
{isActive && isLong && (
<Box width={3} flexShrink={0}>
<Text color={Colors.Gray}>{isExpanded ? ' ← ' : ' → '}</Text>
</Box>
)}
{suggestion.description && (
<Box flexGrow={1} paddingLeft={3}>
<Text color={textColor} wrap="truncate">
{sanitizeForDisplay(suggestion.description, 100)}
</Text>
</Box>
)}
{isActive && isLong && (
<Box width={3} flexShrink={0}>
<Text color={Colors.Gray}>{isExpanded ? ' ← ' : ' → '}</Text>
</Box>
)}
</Box>
</Box>
);
})}