From f51b6b8cc15b417fbe38e13e5511adeae4d1589c Mon Sep 17 00:00:00 2001 From: Dhirender Choudhary Date: Sun, 21 Jun 2026 17:27:55 +0530 Subject: [PATCH 1/2] fix(ui): prevent chat from aggressively scrolling to bottom on slash command Resolves #581. Previously, rendering the slash command completions menu outside of the main bordered input altered the height of the live viewport area. This height change triggered Ink.js to recalculate flexbox layouts, forcing the chat history to unexpectedly snap to the bottom in smaller terminal windows. This commit moves the rendering of both command completions and file suggestions inside the bounded layout. The container expands naturally without disrupting the overall window height calculation, keeping the user's scroll position intact. Additionally: - Adds structural layout tests verifying completions render inside the container. - Fixes test timing flakes where simulated stdin input was occasionally ignored. --- source/components/user-input.spec.tsx | 91 ++++++++++++++++++++++++++- source/components/user-input.tsx | 85 ++++++++++++------------- 2 files changed, 133 insertions(+), 43 deletions(-) diff --git a/source/components/user-input.spec.tsx b/source/components/user-input.spec.tsx index 2e57a44f..97047bde 100644 --- a/source/components/user-input.spec.tsx +++ b/source/components/user-input.spec.tsx @@ -3,7 +3,7 @@ import {render} from 'ink-testing-library'; import React from 'react'; import {themes} from '../config/themes'; import {ThemeContext} from '../hooks/useTheme'; -import {UIStateProvider} from '../hooks/useUIState'; +import {UIStateProvider, useUIStateContext} from '../hooks/useUIState'; import UserInput from './user-input'; console.log(`\nuser-input.spec.tsx – ${React.version}`); @@ -393,6 +393,7 @@ test('UserInput does not show ctrl-o hint when onToggleCompactDisplay is not pro unmount(); }); +<<<<<<< HEAD // ============================================================================ // Command Completion Navigation Tests // ============================================================================ @@ -484,3 +485,91 @@ test('completion menu dismissal/reset after selection or escape', async t => { unmount(); }); +======= +test('UserInput renders completions text when typing /', async t => { + const {stdin, lastFrame, unmount} = render( + + + + ); + + await new Promise(resolve => setTimeout(resolve, 50)); + stdin.write('/'); + await new Promise(resolve => setTimeout(resolve, 150)); + + const output = lastFrame()!; + t.truthy(output); + t.regex(output, /Available commands:/); + unmount(); +}); + +test('UserInput renders completions BEFORE the mode indicator (inside the input box)', async t => { + const {stdin, lastFrame, unmount} = render( + + + + ); + + await new Promise(resolve => setTimeout(resolve, 50)); + stdin.write('/'); + await new Promise(resolve => setTimeout(resolve, 150)); + + const output = lastFrame()!; + t.truthy(output); + + const completionsIdx = output.indexOf('Available commands:'); + const modeIdx = output.indexOf('normal mode'); + t.true(completionsIdx > -1, 'Completions text should be present'); + t.true(modeIdx > -1, 'Mode indicator should be present'); + t.true( + completionsIdx < modeIdx, + 'Completions must render before the mode indicator (inside the bordered input box)', + ); + unmount(); +}); + +test('UserInput completions appear on a line above the mode indicator', async t => { + const {stdin, lastFrame, unmount} = render( + + + + ); + + await new Promise(resolve => setTimeout(resolve, 50)); + stdin.write('/'); + await new Promise(resolve => setTimeout(resolve, 150)); + + const output = lastFrame()!; + const lines = output.split('\n'); + + let completionLine = -1; + let modeLine = -1; + for (let i = 0; i < lines.length; i++) { + if (lines[i].includes('Available commands:')) completionLine = i; + if (lines[i].includes('normal mode')) modeLine = i; + } + + t.true(completionLine > -1, 'Should find completions line'); + t.true(modeLine > -1, 'Should find mode indicator line'); + t.true( + completionLine < modeLine, + `Completions (line ${completionLine}) must be above mode indicator (line ${modeLine})`, + ); + unmount(); +}); + +test('UserInput does not show completions when input is empty', t => { + const {lastFrame, unmount} = render( + + + + ); + + const output = lastFrame()!; + t.truthy(output); + t.notRegex(output, /Available commands:/); + unmount(); +}); + + +>>>>>>> 9d48b592 (fix(ui): prevent chat from aggressively scrolling to bottom on slash command) diff --git a/source/components/user-input.tsx b/source/components/user-input.tsx index a291bbb1..b654f8b3 100644 --- a/source/components/user-input.tsx +++ b/source/components/user-input.tsx @@ -651,47 +651,6 @@ export default function UserInput({ )} - {showCompletions && completions.length > 0 && ( - - Available commands: - {completions.map((completion, index) => { - const isSelected = index === selectedCompletionIndex; - return ( - - {isSelected ? '▸ ' : ' '}/{completion.name} - - ); - })} - - )} - {isFileAutocompleteMode && fileCompletions.length > 0 && ( - - - File suggestions (↑/↓ to navigate, Tab to select): - - {fileCompletions.slice(0, 5).map((file, index) => ( - - {index === selectedFileIndex ? '▸ ' : ' '} - {file.path} - - ))} - - )} - Press escape again to clear )} + + {showCompletions && completions.length > 0 && ( + + Available commands: + {completions.map((completion, index) => { + const isSelected = index === selectedCompletionIndex; + return ( + + {isSelected ? '▸ ' : ' '}/{completion.name} + + ); + })} + + )} + {isFileAutocompleteMode && fileCompletions.length > 0 && ( + + + File suggestions (↑/↓ to navigate, Tab to select): + + {fileCompletions.slice(0, 5).map((file, index) => ( + + {index === selectedFileIndex ? '▸ ' : ' '} + {file.path} + + ))} + + )} {attachments.length > 0 && ( @@ -738,7 +740,6 @@ export default function UserInput({ · ctrl-x remove last )} - {/* Development mode indicator - always visible */} Date: Sun, 21 Jun 2026 17:43:34 +0530 Subject: [PATCH 2/2] fix(tests): remove leftover merge conflict markers in spec file --- source/components/user-input.spec.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/components/user-input.spec.tsx b/source/components/user-input.spec.tsx index 97047bde..eafd4a2b 100644 --- a/source/components/user-input.spec.tsx +++ b/source/components/user-input.spec.tsx @@ -393,7 +393,7 @@ test('UserInput does not show ctrl-o hint when onToggleCompactDisplay is not pro unmount(); }); -<<<<<<< HEAD + // ============================================================================ // Command Completion Navigation Tests // ============================================================================ @@ -485,7 +485,7 @@ test('completion menu dismissal/reset after selection or escape', async t => { unmount(); }); -======= + test('UserInput renders completions text when typing /', async t => { const {stdin, lastFrame, unmount} = render( @@ -572,4 +572,4 @@ test('UserInput does not show completions when input is empty', t => { }); ->>>>>>> 9d48b592 (fix(ui): prevent chat from aggressively scrolling to bottom on slash command) +