From 9e7c10ad8861e969a8eb96f04c30c3c993cb882d Mon Sep 17 00:00:00 2001 From: Jerop Kipruto Date: Mon, 2 Feb 2026 12:00:13 -0500 Subject: [PATCH] feat(plan): use `placeholder` for choice question "Other" option (#18101) --- .../src/ui/components/AskUserDialog.test.tsx | 67 +++++++++++++++++++ .../cli/src/ui/components/AskUserDialog.tsx | 2 +- .../__snapshots__/AskUserDialog.test.tsx.snap | 20 ++++++ packages/core/src/confirmation-bus/types.ts | 2 +- packages/core/src/tools/ask-user.test.ts | 18 +++++ packages/core/src/tools/ask-user.ts | 2 +- 6 files changed, 108 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/ui/components/AskUserDialog.test.tsx b/packages/cli/src/ui/components/AskUserDialog.test.tsx index a30fb9b4af..63cf901235 100644 --- a/packages/cli/src/ui/components/AskUserDialog.test.tsx +++ b/packages/cli/src/ui/components/AskUserDialog.test.tsx @@ -1008,4 +1008,71 @@ describe('AskUserDialog', () => { // Should contain the full long question (or at least its parts) expect(lastFrame()).toContain('This is a very long question'); }); + + describe('Choice question placeholder', () => { + it('uses placeholder for "Other" option when provided', async () => { + const questions: Question[] = [ + { + question: 'Select your preferred language:', + header: 'Language', + options: [ + { label: 'TypeScript', description: '' }, + { label: 'JavaScript', description: '' }, + ], + placeholder: 'Type another language...', + multiSelect: false, + }, + ]; + + const { stdin, lastFrame } = renderWithProviders( + , + { width: 80 }, + ); + + // Navigate to the "Other" option + writeKey(stdin, '\x1b[B'); // Down + writeKey(stdin, '\x1b[B'); // Down to Other + + await waitFor(() => { + expect(lastFrame()).toMatchSnapshot(); + }); + }); + + it('uses default placeholder when not provided', async () => { + const questions: Question[] = [ + { + question: 'Select your preferred language:', + header: 'Language', + options: [ + { label: 'TypeScript', description: '' }, + { label: 'JavaScript', description: '' }, + ], + multiSelect: false, + }, + ]; + + const { stdin, lastFrame } = renderWithProviders( + , + { width: 80 }, + ); + + // Navigate to the "Other" option + writeKey(stdin, '\x1b[B'); // Down + writeKey(stdin, '\x1b[B'); // Down to Other + + await waitFor(() => { + expect(lastFrame()).toMatchSnapshot(); + }); + }); + }); }); diff --git a/packages/cli/src/ui/components/AskUserDialog.tsx b/packages/cli/src/ui/components/AskUserDialog.tsx index ba4c14510f..4c45b356fc 100644 --- a/packages/cli/src/ui/components/AskUserDialog.tsx +++ b/packages/cli/src/ui/components/AskUserDialog.tsx @@ -780,7 +780,7 @@ const ChoiceQuestionView: React.FC = ({ // Render inline text input for custom option if (optionItem.type === 'other') { - const placeholder = 'Enter a custom value'; + const placeholder = question.placeholder || 'Enter a custom value'; return ( {showCheck && ( diff --git a/packages/cli/src/ui/components/__snapshots__/AskUserDialog.test.tsx.snap b/packages/cli/src/ui/components/__snapshots__/AskUserDialog.test.tsx.snap index 7f5d630bc1..e33d946d88 100644 --- a/packages/cli/src/ui/components/__snapshots__/AskUserDialog.test.tsx.snap +++ b/packages/cli/src/ui/components/__snapshots__/AskUserDialog.test.tsx.snap @@ -1,5 +1,25 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html +exports[`AskUserDialog > Choice question placeholder > uses default placeholder when not provided 1`] = ` +"Select your preferred language: + + 1. TypeScript + 2. JavaScript +● 3. Enter a custom value + +Enter to submit · Esc to cancel" +`; + +exports[`AskUserDialog > Choice question placeholder > uses placeholder for "Other" option when provided 1`] = ` +"Select your preferred language: + + 1. TypeScript + 2. JavaScript +● 3. Type another language... + +Enter to submit · Esc to cancel" +`; + exports[`AskUserDialog > Scroll Arrows (useAlternateBuffer: false) > shows scroll arrows correctly when useAlternateBuffer is false 1`] = ` "Choose an option diff --git a/packages/core/src/confirmation-bus/types.ts b/packages/core/src/confirmation-bus/types.ts index fcdd600f3c..7debbb85da 100644 --- a/packages/core/src/confirmation-bus/types.ts +++ b/packages/core/src/confirmation-bus/types.ts @@ -148,7 +148,7 @@ export interface Question { options?: QuestionOption[]; /** Allow multiple selections. Only applies when type='choice'. */ multiSelect?: boolean; - /** Placeholder hint text. Only applies when type='text'. */ + /** Placeholder hint text. For type='text', shown in the input field. For type='choice', shown in the "Other" custom input. */ placeholder?: string; } diff --git a/packages/core/src/tools/ask-user.test.ts b/packages/core/src/tools/ask-user.test.ts index da41ff45f2..d747ed1d16 100644 --- a/packages/core/src/tools/ask-user.test.ts +++ b/packages/core/src/tools/ask-user.test.ts @@ -177,6 +177,24 @@ describe('AskUserTool', () => { expect(result).toBeNull(); }); + it('should accept placeholder for choice type', () => { + const result = tool.validateToolParams({ + questions: [ + { + question: 'Which language?', + header: 'Language', + type: QuestionType.CHOICE, + options: [ + { label: 'TypeScript', description: 'Typed JavaScript' }, + { label: 'JavaScript', description: 'Dynamic language' }, + ], + placeholder: 'Type another language...', + }, + ], + }); + expect(result).toBeNull(); + }); + it('should return error if option has empty label', () => { const result = tool.validateToolParams({ questions: [ diff --git a/packages/core/src/tools/ask-user.ts b/packages/core/src/tools/ask-user.ts index c155dec4e9..601d80178b 100644 --- a/packages/core/src/tools/ask-user.ts +++ b/packages/core/src/tools/ask-user.ts @@ -90,7 +90,7 @@ export class AskUserTool extends BaseDeclarativeTool< placeholder: { type: 'string', description: - "Only applies when type='text'. Hint text shown in the input field.", + "Hint text shown in the input field. For type='text', shown in the main input. For type='choice', shown in the 'Other' custom input.", }, }, },