feat: Add markdown rendering to ask_user tool (#18211)

This commit is contained in:
Jack Wotherspoon
2026-02-03 16:04:38 -05:00
committed by GitHub
parent 3e954930f1
commit 69c0585ab2
3 changed files with 279 additions and 101 deletions

View File

@@ -10,6 +10,7 @@ import { renderWithProviders } from '../../test-utils/render.js';
import { waitFor } from '../../test-utils/async.js';
import { AskUserDialog } from './AskUserDialog.js';
import { QuestionType, type Question } from '@google/gemini-cli-core';
import chalk from 'chalk';
import { UIStateContext, type UIState } from '../contexts/UIStateContext.js';
// Helper to write to stdin with proper act() wrapping
@@ -941,6 +942,125 @@ describe('AskUserDialog', () => {
});
});
describe('Markdown rendering', () => {
it('auto-bolds plain single-line questions', async () => {
const questions: Question[] = [
{
question: 'Which option do you prefer?',
header: 'Test',
options: [{ label: 'Yes', description: '' }],
multiSelect: false,
},
];
const { lastFrame } = renderWithProviders(
<AskUserDialog
questions={questions}
onSubmit={vi.fn()}
onCancel={vi.fn()}
width={120}
availableHeight={40}
/>,
{ width: 120 },
);
await waitFor(() => {
const frame = lastFrame();
// Plain text should be rendered as bold
expect(frame).toContain(chalk.bold('Which option do you prefer?'));
});
});
it('does not auto-bold questions that already have markdown', async () => {
const questions: Question[] = [
{
question: 'Is **this** working?',
header: 'Test',
options: [{ label: 'Yes', description: '' }],
multiSelect: false,
},
];
const { lastFrame } = renderWithProviders(
<AskUserDialog
questions={questions}
onSubmit={vi.fn()}
onCancel={vi.fn()}
width={120}
availableHeight={40}
/>,
{ width: 120 },
);
await waitFor(() => {
const frame = lastFrame();
// Should NOT have double-bold (the whole question bolded AND "this" bolded)
// "Is " should not be bold, only "this" should be bold
expect(frame).toContain('Is ');
expect(frame).toContain(chalk.bold('this'));
expect(frame).not.toContain('**this**');
});
});
it('renders bold markdown in question', async () => {
const questions: Question[] = [
{
question: 'Is **this** working?',
header: 'Test',
options: [{ label: 'Yes', description: '' }],
multiSelect: false,
},
];
const { lastFrame } = renderWithProviders(
<AskUserDialog
questions={questions}
onSubmit={vi.fn()}
onCancel={vi.fn()}
width={120}
availableHeight={40}
/>,
{ width: 120 },
);
await waitFor(() => {
const frame = lastFrame();
// Check for chalk.bold('this') - asterisks should be gone, text should be bold
expect(frame).toContain(chalk.bold('this'));
expect(frame).not.toContain('**this**');
});
});
it('renders inline code markdown in question', async () => {
const questions: Question[] = [
{
question: 'Run `npm start`?',
header: 'Test',
options: [{ label: 'Yes', description: '' }],
multiSelect: false,
},
];
const { lastFrame } = renderWithProviders(
<AskUserDialog
questions={questions}
onSubmit={vi.fn()}
onCancel={vi.fn()}
width={120}
availableHeight={40}
/>,
{ width: 120 },
);
await waitFor(() => {
const frame = lastFrame();
// Backticks should be removed
expect(frame).toContain('npm start');
expect(frame).not.toContain('`npm start`');
});
});
});
it('uses availableTerminalHeight from UIStateContext if availableHeight prop is missing', () => {
const questions: Question[] = [
{

View File

@@ -27,10 +27,60 @@ import { useTextBuffer } from './shared/text-buffer.js';
import { getCachedStringWidth } from '../utils/textUtils.js';
import { useTabbedNavigation } from '../hooks/useTabbedNavigation.js';
import { DialogFooter } from './shared/DialogFooter.js';
import { MarkdownDisplay } from '../utils/MarkdownDisplay.js';
import { RenderInline } from '../utils/InlineMarkdownRenderer.js';
import { MaxSizedBox } from './shared/MaxSizedBox.js';
import { UIStateContext } from '../contexts/UIStateContext.js';
import { useAlternateBuffer } from '../hooks/useAlternateBuffer.js';
/** Padding for dialog content to prevent text from touching edges. */
const DIALOG_PADDING = 4;
/**
* Checks if text is a single line without markdown identifiers.
*/
function isPlainSingleLine(text: string): boolean {
// Must be a single line (no newlines)
if (text.includes('\n') || text.includes('\r')) {
return false;
}
// Check for common markdown identifiers
const markdownPatterns = [
/^#{1,6}\s/, // Headers
/^[`~]{3,}/, // Code fences
/^[-*+]\s/, // Unordered lists
/^\d+\.\s/, // Ordered lists
/^[-*_]{3,}$/, // Horizontal rules
/\|/, // Tables
/\*\*|__/, // Bold
/(?<!\*)\*(?!\*)/, // Italic (single asterisk not part of bold)
/(?<!_)_(?!_)/, // Italic (single underscore not part of bold)
/`[^`]+`/, // Inline code
/\[.*?\]\(.*?\)/, // Links
/!\[/, // Images
];
for (const pattern of markdownPatterns) {
if (pattern.test(text)) {
return false;
}
}
return true;
}
/**
* Auto-bolds plain single-line text by wrapping in **.
* Returns the text unchanged if it already contains markdown.
*/
function autoBoldIfPlain(text: string): string {
if (isPlainSingleLine(text)) {
return `**${text}**`;
}
return text;
}
interface AskUserDialogState {
answers: { [key: string]: string };
isEditingCustomOption: boolean;
@@ -303,9 +353,11 @@ const TextQuestionView: React.FC<TextQuestionViewProps> = ({
maxWidth={availableWidth}
overflowDirection="bottom"
>
<Text bold color={theme.text.primary}>
{question.question}
</Text>
<MarkdownDisplay
text={autoBoldIfPlain(question.question)}
terminalWidth={availableWidth - DIALOG_PADDING}
isPending={false}
/>
</MaxSizedBox>
</Box>
@@ -734,7 +786,7 @@ const ChoiceQuestionView: React.FC<ChoiceQuestionViewProps> = ({
: undefined;
const questionHeight =
listHeight && !isAlternateBuffer
? Math.min(15, Math.max(1, listHeight - 4))
? Math.min(15, Math.max(1, listHeight - DIALOG_PADDING))
: undefined;
const maxItemsToShow =
listHeight && questionHeight
@@ -750,15 +802,18 @@ const ChoiceQuestionView: React.FC<ChoiceQuestionViewProps> = ({
maxWidth={availableWidth}
overflowDirection="bottom"
>
<Text bold color={theme.text.primary}>
{question.question}
<Box flexDirection="column">
<MarkdownDisplay
text={autoBoldIfPlain(question.question)}
terminalWidth={availableWidth - DIALOG_PADDING}
isPending={false}
/>
{question.multiSelect && (
<Text color={theme.text.secondary} italic>
{' '}
(Select all that apply)
</Text>
)}
</Text>
</Box>
</MaxSizedBox>
</Box>
@@ -833,7 +888,10 @@ const ChoiceQuestionView: React.FC<ChoiceQuestionViewProps> = ({
{optionItem.description && (
<Text color={theme.text.secondary} wrap="wrap">
{' '}
{optionItem.description}
<RenderInline
text={optionItem.description}
defaultColor={theme.text.secondary}
/>
</Text>
)}
</Box>

View File

@@ -1,21 +1,21 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`ExitPlanModeDialog > useAlternateBuffer: false > bubbles up Ctrl+C when feedback is empty while editing 1`] = `
"## Overview
"Overview
Add user authentication to the CLI application.
## Implementation Steps
Implementation Steps
1. Create \`src/auth/AuthService.ts\` with login/logout methods
2. Add session storage in \`src/storage/SessionStore.ts\`
3. Update \`src/commands/index.ts\` to check auth status
4. Add tests in \`src/auth/__tests__/\`
1. Create src/auth/AuthService.ts with login/logout methods
2. Add session storage in src/storage/SessionStore.ts
3. Update src/commands/index.ts to check auth status
4. Add tests in src/auth/__tests__/
## Files to Modify
Files to Modify
- \`src/index.ts\` - Add auth middleware
- \`src/config.ts\` - Add auth configuration options
- src/index.ts - Add auth middleware
- src/config.ts - Add auth configuration options
1. Yes, automatically accept edits
Approves plan and allows tools to run automatically
@@ -27,21 +27,21 @@ Enter to submit · Esc to cancel"
`;
exports[`ExitPlanModeDialog > useAlternateBuffer: false > calls onFeedback when feedback is typed and submitted 1`] = `
"## Overview
"Overview
Add user authentication to the CLI application.
## Implementation Steps
Implementation Steps
1. Create \`src/auth/AuthService.ts\` with login/logout methods
2. Add session storage in \`src/storage/SessionStore.ts\`
3. Update \`src/commands/index.ts\` to check auth status
4. Add tests in \`src/auth/__tests__/\`
1. Create src/auth/AuthService.ts with login/logout methods
2. Add session storage in src/storage/SessionStore.ts
3. Update src/commands/index.ts to check auth status
4. Add tests in src/auth/__tests__/
## Files to Modify
Files to Modify
- \`src/index.ts\` - Add auth middleware
- \`src/config.ts\` - Add auth configuration options
- src/index.ts - Add auth middleware
- src/config.ts - Add auth configuration options
1. Yes, automatically accept edits
Approves plan and allows tools to run automatically
@@ -55,20 +55,20 @@ Enter to submit · Esc to cancel"
exports[`ExitPlanModeDialog > useAlternateBuffer: false > displays error state when file read fails 1`] = `" Error reading plan: File not found"`;
exports[`ExitPlanModeDialog > useAlternateBuffer: false > handles long plan content appropriately 1`] = `
"## Overview
"Overview
Implement a comprehensive authentication system with multiple providers.
## Implementation Steps
Implementation Steps
1. Create \`src/auth/AuthService.ts\` with login/logout methods
2. Add session storage in \`src/storage/SessionStore.ts\`
3. Update \`src/commands/index.ts\` to check auth status
4. Add OAuth2 provider support in \`src/auth/providers/OAuth2Provider.ts\`
5. Add SAML provider support in \`src/auth/providers/SAMLProvider.ts\`
6. Add LDAP provider support in \`src/auth/providers/LDAPProvider.ts\`
7. Create token refresh mechanism in \`src/auth/TokenManager.ts\`
8. Add multi-factor authentication in \`src/auth/MFAService.ts\`
1. Create src/auth/AuthService.ts with login/logout methods
2. Add session storage in src/storage/SessionStore.ts
3. Update src/commands/index.ts to check auth status
4. Add OAuth2 provider support in src/auth/providers/OAuth2Provider.ts
5. Add SAML provider support in src/auth/providers/SAMLProvider.ts
6. Add LDAP provider support in src/auth/providers/LDAPProvider.ts
7. Create token refresh mechanism in src/auth/TokenManager.ts
8. Add multi-factor authentication in src/auth/MFAService.ts
... last 22 lines hidden ...
● 1. Yes, automatically accept edits
@@ -81,21 +81,21 @@ Enter to select · ↑/↓ to navigate · Esc to cancel"
`;
exports[`ExitPlanModeDialog > useAlternateBuffer: false > renders correctly with plan content 1`] = `
"## Overview
"Overview
Add user authentication to the CLI application.
## Implementation Steps
Implementation Steps
1. Create \`src/auth/AuthService.ts\` with login/logout methods
2. Add session storage in \`src/storage/SessionStore.ts\`
3. Update \`src/commands/index.ts\` to check auth status
4. Add tests in \`src/auth/__tests__/\`
1. Create src/auth/AuthService.ts with login/logout methods
2. Add session storage in src/storage/SessionStore.ts
3. Update src/commands/index.ts to check auth status
4. Add tests in src/auth/__tests__/
## Files to Modify
Files to Modify
- \`src/index.ts\` - Add auth middleware
- \`src/config.ts\` - Add auth configuration options
- src/index.ts - Add auth middleware
- src/config.ts - Add auth configuration options
● 1. Yes, automatically accept edits
Approves plan and allows tools to run automatically
@@ -107,21 +107,21 @@ Enter to select · ↑/↓ to navigate · Esc to cancel"
`;
exports[`ExitPlanModeDialog > useAlternateBuffer: true > bubbles up Ctrl+C when feedback is empty while editing 1`] = `
"## Overview
"Overview
Add user authentication to the CLI application.
## Implementation Steps
Implementation Steps
1. Create \`src/auth/AuthService.ts\` with login/logout methods
2. Add session storage in \`src/storage/SessionStore.ts\`
3. Update \`src/commands/index.ts\` to check auth status
4. Add tests in \`src/auth/__tests__/\`
1. Create src/auth/AuthService.ts with login/logout methods
2. Add session storage in src/storage/SessionStore.ts
3. Update src/commands/index.ts to check auth status
4. Add tests in src/auth/__tests__/
## Files to Modify
Files to Modify
- \`src/index.ts\` - Add auth middleware
- \`src/config.ts\` - Add auth configuration options
- src/index.ts - Add auth middleware
- src/config.ts - Add auth configuration options
1. Yes, automatically accept edits
Approves plan and allows tools to run automatically
@@ -133,21 +133,21 @@ Enter to submit · Esc to cancel"
`;
exports[`ExitPlanModeDialog > useAlternateBuffer: true > calls onFeedback when feedback is typed and submitted 1`] = `
"## Overview
"Overview
Add user authentication to the CLI application.
## Implementation Steps
Implementation Steps
1. Create \`src/auth/AuthService.ts\` with login/logout methods
2. Add session storage in \`src/storage/SessionStore.ts\`
3. Update \`src/commands/index.ts\` to check auth status
4. Add tests in \`src/auth/__tests__/\`
1. Create src/auth/AuthService.ts with login/logout methods
2. Add session storage in src/storage/SessionStore.ts
3. Update src/commands/index.ts to check auth status
4. Add tests in src/auth/__tests__/
## Files to Modify
Files to Modify
- \`src/index.ts\` - Add auth middleware
- \`src/config.ts\` - Add auth configuration options
- src/index.ts - Add auth middleware
- src/config.ts - Add auth configuration options
1. Yes, automatically accept edits
Approves plan and allows tools to run automatically
@@ -161,42 +161,42 @@ Enter to submit · Esc to cancel"
exports[`ExitPlanModeDialog > useAlternateBuffer: true > displays error state when file read fails 1`] = `" Error reading plan: File not found"`;
exports[`ExitPlanModeDialog > useAlternateBuffer: true > handles long plan content appropriately 1`] = `
"## Overview
"Overview
Implement a comprehensive authentication system with multiple providers.
## Implementation Steps
Implementation Steps
1. Create \`src/auth/AuthService.ts\` with login/logout methods
2. Add session storage in \`src/storage/SessionStore.ts\`
3. Update \`src/commands/index.ts\` to check auth status
4. Add OAuth2 provider support in \`src/auth/providers/OAuth2Provider.ts\`
5. Add SAML provider support in \`src/auth/providers/SAMLProvider.ts\`
6. Add LDAP provider support in \`src/auth/providers/LDAPProvider.ts\`
7. Create token refresh mechanism in \`src/auth/TokenManager.ts\`
8. Add multi-factor authentication in \`src/auth/MFAService.ts\`
9. Implement session timeout handling in \`src/auth/SessionManager.ts\`
10. Add audit logging for auth events in \`src/auth/AuditLogger.ts\`
11. Create user profile management in \`src/auth/UserProfile.ts\`
12. Add role-based access control in \`src/auth/RBACService.ts\`
13. Implement password policy enforcement in \`src/auth/PasswordPolicy.ts\`
14. Add brute force protection in \`src/auth/BruteForceGuard.ts\`
15. Create secure cookie handling in \`src/auth/CookieManager.ts\`
1. Create src/auth/AuthService.ts with login/logout methods
2. Add session storage in src/storage/SessionStore.ts
3. Update src/commands/index.ts to check auth status
4. Add OAuth2 provider support in src/auth/providers/OAuth2Provider.ts
5. Add SAML provider support in src/auth/providers/SAMLProvider.ts
6. Add LDAP provider support in src/auth/providers/LDAPProvider.ts
7. Create token refresh mechanism in src/auth/TokenManager.ts
8. Add multi-factor authentication in src/auth/MFAService.ts
9. Implement session timeout handling in src/auth/SessionManager.ts
10. Add audit logging for auth events in src/auth/AuditLogger.ts
11. Create user profile management in src/auth/UserProfile.ts
12. Add role-based access control in src/auth/RBACService.ts
13. Implement password policy enforcement in src/auth/PasswordPolicy.ts
14. Add brute force protection in src/auth/BruteForceGuard.ts
15. Create secure cookie handling in src/auth/CookieManager.ts
## Files to Modify
Files to Modify
- \`src/index.ts\` - Add auth middleware
- \`src/config.ts\` - Add auth configuration options
- \`src/routes/api.ts\` - Add auth endpoints
- \`src/middleware/cors.ts\` - Update CORS for auth headers
- \`src/utils/crypto.ts\` - Add encryption utilities
- src/index.ts - Add auth middleware
- src/config.ts - Add auth configuration options
- src/routes/api.ts - Add auth endpoints
- src/middleware/cors.ts - Update CORS for auth headers
- src/utils/crypto.ts - Add encryption utilities
## Testing Strategy
Testing Strategy
- Unit tests for each auth provider
- Integration tests for full auth flows
- Security penetration testing
- Load testing for session management
- Unit tests for each auth provider
- Integration tests for full auth flows
- Security penetration testing
- Load testing for session management
● 1. Yes, automatically accept edits
Approves plan and allows tools to run automatically
@@ -208,21 +208,21 @@ Enter to select · ↑/↓ to navigate · Esc to cancel"
`;
exports[`ExitPlanModeDialog > useAlternateBuffer: true > renders correctly with plan content 1`] = `
"## Overview
"Overview
Add user authentication to the CLI application.
## Implementation Steps
Implementation Steps
1. Create \`src/auth/AuthService.ts\` with login/logout methods
2. Add session storage in \`src/storage/SessionStore.ts\`
3. Update \`src/commands/index.ts\` to check auth status
4. Add tests in \`src/auth/__tests__/\`
1. Create src/auth/AuthService.ts with login/logout methods
2. Add session storage in src/storage/SessionStore.ts
3. Update src/commands/index.ts to check auth status
4. Add tests in src/auth/__tests__/
## Files to Modify
Files to Modify
- \`src/index.ts\` - Add auth middleware
- \`src/config.ts\` - Add auth configuration options
- src/index.ts - Add auth middleware
- src/config.ts - Add auth configuration options
● 1. Yes, automatically accept edits
Approves plan and allows tools to run automatically