From 34f24daa8d8aa51a1edb0402bbe8e3a5e10de5fb Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Tue, 21 Apr 2026 14:19:05 -0500 Subject: [PATCH] fix(tui): stabilize slash-completion dropdown height MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The completion popup (e.g. typing `/model`) grew from 8 rows at compIdx=0 up to 16 rows at compIdx≥8 — the slice end was `compIdx + 8` so every arrow-down added another rendered row until the window filled. Reported during TUI v2 retest: "as i scroll and more options appear, for some reason more options appear and it expands the height". Fixed viewport (`COMPLETION_WINDOW = 16`) centered on compIdx, clamped so it never slides past the array bounds. Renders exactly `min(WINDOW, completions.length)` rows every frame. --- ui-tui/src/components/appOverlays.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/ui-tui/src/components/appOverlays.tsx b/ui-tui/src/components/appOverlays.tsx index 844996af3f..0d08c58972 100644 --- a/ui-tui/src/components/appOverlays.tsx +++ b/ui-tui/src/components/appOverlays.tsx @@ -13,6 +13,8 @@ import { ApprovalPrompt, ClarifyPrompt, ConfirmPrompt } from './prompts.js' import { SessionPicker } from './sessionPicker.js' import { SkillsHub } from './skillsHub.js' +const COMPLETION_WINDOW = 16 + export function PromptZone({ cols, onApprovalChoice, @@ -106,7 +108,12 @@ export function FloatingOverlays({ return null } - const start = Math.max(0, compIdx - 8) + // Fixed viewport centered on compIdx — previously the slice end was + // compIdx + 8 so the dropdown grew from 8 rows to 16 as the user scrolled + // down, bouncing the height on every keystroke. + const viewportSize = Math.min(COMPLETION_WINDOW, completions.length) + + const start = Math.max(0, Math.min(compIdx - Math.floor(COMPLETION_WINDOW / 2), completions.length - viewportSize)) return ( @@ -168,7 +175,7 @@ export function FloatingOverlays({ {!!completions.length && ( - {completions.slice(start, compIdx + 8).map((item, i) => { + {completions.slice(start, start + viewportSize).map((item, i) => { const active = start + i === compIdx return (