From c3b8c8e42cb120be4ce972f984b74c3dccfec18b Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Tue, 21 Apr 2026 10:45:19 -0500 Subject: [PATCH] fix(tui): stabilize model picker viewport height MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Warning row, "↑ N more" / "↓ N more" hints, and the items list were all conditionally rendered, so the picker jumped in size as the selection moved or providers without a warning slid into view. Render every slot unconditionally: warning falls back to a blank line, hints render an empty string when at the edge, and the items grid always emits VISIBLE rows padded with blanks. Height is now constant across providers, model counts, and scroll position. --- ui-tui/src/components/modelPicker.tsx | 39 ++++++++++++++++++++------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/ui-tui/src/components/modelPicker.tsx b/ui-tui/src/components/modelPicker.tsx index 5ee19e407c..395ad4ccae 100644 --- a/ui-tui/src/components/modelPicker.tsx +++ b/ui-tui/src/components/modelPicker.tsx @@ -174,13 +174,14 @@ export function ModelPicker({ gw, onCancel, onSelect, sessionId, t }: ModelPicke Current model: {currentModel || '(unknown)'} - {provider?.warning ? warning: {provider.warning} : null} - {off > 0 && ↑ {off} more} + {provider?.warning ? `warning: ${provider.warning}` : ' '} + {off > 0 ? ` ↑ ${off} more` : ' '} - {items.map((row, i) => { + {Array.from({ length: VISIBLE }, (_, i) => { + const row = items[i] const idx = off + i - return ( + return row ? ( + ) : ( + ) })} - {off + VISIBLE < rows.length && ↓ {rows.length - off - VISIBLE} more} + + {off + VISIBLE < rows.length ? ` ↓ ${rows.length - off - VISIBLE} more` : ' '} + + persist: {persistGlobal ? 'global' : 'session'} · g toggle ↑/↓ select · Enter choose · 1-9,0 quick · Esc cancel @@ -207,13 +213,23 @@ export function ModelPicker({ gw, onCancel, onSelect, sessionId, t }: ModelPicke {names[providerIdx] || '(unknown provider)'} - {!models.length ? no models listed for this provider : null} - {provider?.warning ? warning: {provider.warning} : null} - {off > 0 && ↑ {off} more} + {provider?.warning ? `warning: ${provider.warning}` : ' '} + {off > 0 ? ` ↑ ${off} more` : ' '} - {items.map((row, i) => { + {Array.from({ length: VISIBLE }, (_, i) => { + const row = items[i] const idx = off + i + if (!row) { + return !models.length && i === 0 ? ( + + no models listed for this provider + + ) : ( + + ) + } + return ( ↓ {models.length - off - VISIBLE} more} + + {off + VISIBLE < models.length ? ` ↓ ${models.length - off - VISIBLE} more` : ' '} + + persist: {persistGlobal ? 'global' : 'session'} · g toggle {models.length ? '↑/↓ select · Enter switch · 1-9,0 quick · Esc back' : 'Enter/Esc back'}