@@ -8,6 +8,7 @@ import { FREEBUFF_MODELS } from '@codebuff/common/constants/freebuff-models'
88import { switchFreebuffModel } from '../hooks/use-freebuff-session'
99import { useFreebuffModelStore } from '../state/freebuff-model-store'
1010import { useFreebuffSessionStore } from '../state/freebuff-session-store'
11+ import { useTerminalDimensions } from '../hooks/use-terminal-dimensions'
1112import { useTheme } from '../hooks/use-theme'
1213
1314import type { KeyEvent } from '@opentui/core'
@@ -27,6 +28,7 @@ import type { KeyEvent } from '@opentui/core'
2728 */
2829export const FreebuffModelSelector : React . FC = ( ) => {
2930 const theme = useTheme ( )
31+ const { terminalWidth } = useTerminalDimensions ( )
3032 const selectedModel = useFreebuffModelStore ( ( s ) => s . selectedModel )
3133 const session = useFreebuffSessionStore ( ( s ) => s . session )
3234 const [ pending , setPending ] = useState < string | null > ( null )
@@ -64,6 +66,27 @@ export const FreebuffModelSelector: React.FC = () => {
6466 [ ] ,
6567 )
6668
69+ // Decide row vs column layout based on whether both buttons actually fit
70+ // side-by-side. Each button's inner text is "● {displayName} · {tagline} {hint}",
71+ // plus 2 cols of border and 2 cols of padding. Buttons are separated by a
72+ // gap of 2. If the total exceeds the terminal width, stack vertically.
73+ const stackVertically = useMemo ( ( ) => {
74+ const BUTTON_CHROME = 4 // 2 border + 2 padding
75+ const GAP = 2
76+ const total = FREEBUFF_MODELS . reduce ( ( sum , model , idx ) => {
77+ const inner =
78+ 2 /* indicator + space */ +
79+ model . displayName . length +
80+ 3 /* " · " */ +
81+ model . tagline . length +
82+ 2 /* " " */ +
83+ hintWidth
84+ return sum + inner + BUTTON_CHROME + ( idx > 0 ? GAP : 0 )
85+ } , 0 )
86+ // Leave a small margin for the surrounding padding on the waiting-room screen.
87+ return total > terminalWidth - 4
88+ } , [ hintWidth , terminalWidth ] )
89+
6790 const pick = useCallback (
6891 ( modelId : string ) => {
6992 if ( pending ) return
@@ -74,18 +97,18 @@ export const FreebuffModelSelector: React.FC = () => {
7497 [ pending , selectedModel ] ,
7598 )
7699
77- // Tab / Shift+Tab and Left/Right arrow keys move the focus highlight only;
78- // Enter or Space commits the switch. Two-step navigation prevents the user
79- // from accidentally giving up their place in line by tabbing past their
80- // queue. Up/Down intentionally do nothing so they don't fight other
81- // vertical UI.
100+ // Tab / Shift+Tab and arrow keys move the focus highlight only; Enter or
101+ // Space commits the switch. Two-step navigation prevents the user from
102+ // accidentally giving up their place in line by tabbing past their queue.
82103 useKeyboard (
83104 useCallback (
84105 ( key : KeyEvent ) => {
85106 if ( pending ) return
86107 const name = key . name ?? ''
87- const isForward = name === 'right' || ( name === 'tab' && ! key . shift )
88- const isBackward = name === 'left' || ( name === 'tab' && key . shift )
108+ const isForward =
109+ name === 'right' || name === 'down' || ( name === 'tab' && ! key . shift )
110+ const isBackward =
111+ name === 'left' || name === 'up' || ( name === 'tab' && key . shift )
89112 const isCommit = name === 'return' || name === 'enter' || name === 'space'
90113 if ( ! isForward && ! isBackward && ! isCommit ) return
91114 if ( isCommit ) {
@@ -121,8 +144,9 @@ export const FreebuffModelSelector: React.FC = () => {
121144 >
122145 < box
123146 style = { {
124- flexDirection : 'row' ,
125- gap : 2 ,
147+ flexDirection : stackVertically ? 'column' : 'row' ,
148+ gap : stackVertically ? 0 : 2 ,
149+ alignItems : 'flex-start' ,
126150 } }
127151 >
128152 { FREEBUFF_MODELS . map ( ( model ) => {
0 commit comments