@@ -746,28 +746,35 @@ export class AICodeUI {
746746 const lines : string [ ] = [ ] ;
747747 const { width } = this . viewport . getTerminalSize ( ) ;
748748
749+ // Chrome = 3 lines (separator + separator + status bar)
750+ const chromeLines = 3 ;
751+ // Input can grow up to (viewportHeight - chromeLines - 1) to leave at least 1 line for content
752+ const maxInputLines = Math . max ( 1 , this . viewportHeight - chromeLines - 1 ) ;
753+ const actualInputLines = Math . min ( this . lineEditor . lines . length , maxInputLines ) ;
754+
755+ // Calculate available space for content (welcome box or conversation)
756+ const availableContentLines = this . viewportHeight - chromeLines - actualInputLines ;
757+
749758 // Show welcome box if no messages, otherwise show conversation
750759 if ( this . messages . length === 0 && ! this . isStreaming ) {
751760 const welcomeLines = this . renderWelcomeBox ( width ) ;
752- lines . push ( ...welcomeLines ) ;
761+ lines . push ( ...welcomeLines . slice ( 0 , availableContentLines ) ) ;
753762 } else {
754- // Render conversation history
755- const conversationLines = this . renderConversation ( width ) ;
763+ // Render conversation history with dynamic max lines
764+ const conversationLines = this . renderConversation ( width , availableContentLines ) ;
756765 lines . push ( ...conversationLines ) ;
757766 }
758767
759- // Fill remaining space - input area grows freely, conversation shrinks
760- const inputLineCount = this . lineEditor . lines . length ;
761- const reservedLines = 3 + inputLineCount ; // separator + input lines + status
762- while ( lines . length < this . viewportHeight - reservedLines ) {
768+ // Fill remaining space to push input to bottom
769+ while ( lines . length < availableContentLines ) {
763770 lines . push ( '' ) ;
764771 }
765772
766773 // Full-width separator before input
767774 lines . push ( dim ( BOX . horizontal . repeat ( width ) ) ) ;
768775
769- // Input prompt
770- const inputLines = this . renderInputLines ( width ) ;
776+ // Input prompt (with windowing if needed)
777+ const inputLines = this . renderInputLines ( width , actualInputLines ) ;
771778 lines . push ( ...inputLines ) ;
772779
773780 // Full-width separator after input
@@ -842,9 +849,8 @@ export class AICodeUI {
842849 /**
843850 * Render conversation history
844851 */
845- private renderConversation ( width : number ) : string [ ] {
852+ private renderConversation ( width : number , maxLines : number ) : string [ ] {
846853 const lines : string [ ] = [ ] ;
847- const maxLines = this . viewportHeight - 5 ; // Reserve space for input area
848854
849855 // Collect all message lines
850856 const allLines : string [ ] = [ ] ;
@@ -918,14 +924,31 @@ export class AICodeUI {
918924 }
919925
920926 /**
921- * Render the input lines (all lines shown, area grows freely )
927+ * Render the input lines (with windowing when lines exceed maxVisibleLines )
922928 */
923- private renderInputLines ( width : number ) : string [ ] {
929+ private renderInputLines ( width : number , maxVisibleLines : number ) : string [ ] {
924930 const lines : string [ ] = [ ] ;
925931 const inputText = this . getInput ( ) ;
926932 const hasContent = inputText . length > 0 ;
933+ const totalLines = this . lineEditor . lines . length ;
934+ const cursorLine = this . lineEditor . lineIndex ;
935+
936+ // Calculate which lines to show (keep cursor line visible)
937+ let startLine = 0 ;
938+ if ( totalLines > maxVisibleLines ) {
939+ // Keep cursor visible with some context
940+ if ( cursorLine < Math . floor ( maxVisibleLines / 2 ) ) {
941+ startLine = 0 ;
942+ } else if ( cursorLine > totalLines - Math . ceil ( maxVisibleLines / 2 ) ) {
943+ startLine = totalLines - maxVisibleLines ;
944+ } else {
945+ startLine = cursorLine - Math . floor ( maxVisibleLines / 2 ) ;
946+ }
947+ }
948+ const endLine = Math . min ( startLine + maxVisibleLines , totalLines ) ;
927949
928- this . lineEditor . lines . forEach ( ( line , idx ) => {
950+ for ( let idx = startLine ; idx < endLine ; idx ++ ) {
951+ const line = this . lineEditor . lines [ idx ] ;
929952 const prefix = idx === 0 ? cyan ( '> ' ) : ' ' ;
930953 let lineContent : string ;
931954
@@ -941,19 +964,22 @@ export class AICodeUI {
941964 lineContent = line ;
942965 }
943966
944- // Add send hint on last line if there's content
945- if ( idx === this . lineEditor . lines . length - 1 && hasContent ) {
946- const hint = dim ( ' ↵ send' ) ;
967+ // Add info on last visible line if there's content
968+ if ( idx === endLine - 1 && hasContent ) {
969+ // Show line count if scrolling, otherwise show send hint
970+ const info = totalLines > maxVisibleLines
971+ ? dim ( ` ln ${ cursorLine + 1 } /${ totalLines } ` )
972+ : dim ( ' ↵ send' ) ;
947973 const lineWidth = displayWidth ( prefix + lineContent ) ;
948- const hintWidth = displayWidth ( hint ) ;
949- if ( lineWidth + hintWidth < width - 2 ) {
950- const padding = width - lineWidth - hintWidth - 2 ;
951- lineContent = lineContent + ' ' . repeat ( Math . max ( 0 , padding ) ) + hint ;
974+ const infoWidth = displayWidth ( info ) ;
975+ if ( lineWidth + infoWidth < width - 2 ) {
976+ const padding = width - lineWidth - infoWidth - 2 ;
977+ lineContent = lineContent + ' ' . repeat ( Math . max ( 0 , padding ) ) + info ;
952978 }
953979 }
954980
955981 lines . push ( prefix + lineContent ) ;
956- } ) ;
982+ }
957983
958984 return lines ;
959985 }
0 commit comments