@@ -289,10 +289,14 @@ impl<'a> MinimalSessionView<'a> {
289289 }
290290
291291 /// Renders a single tool call with status indicator
292- fn render_tool_call ( & self , call : & ToolCallDisplay ) -> Vec < Line < ' static > > {
292+ fn render_tool_call ( & self , call : & ToolCallDisplay , width : u16 ) -> Vec < Line < ' static > > {
293293 use crate :: ui:: consts:: TOOL_SPINNER_FRAMES ;
294294 let mut lines = Vec :: new ( ) ;
295295
296+ // Calculate available width for content (accounting for indentation)
297+ let content_width = ( width as usize ) . saturating_sub ( 6 ) ; // 6 chars for prefix/indent
298+ let line_width = ( width as usize ) . saturating_sub ( 8 ) ; // 8 chars for nested content
299+
296300 // Status indicator with color - animated spinner for Running status
297301 let ( dot, dot_color) = match call. status {
298302 ToolStatus :: Pending => ( "○" . to_string ( ) , self . colors . warning ) ,
@@ -305,9 +309,9 @@ impl<'a> MinimalSessionView<'a> {
305309 ToolStatus :: Failed => ( "●" . to_string ( ) , self . colors . error ) ,
306310 } ;
307311
308- // Line 1: ◐ ToolName summary_args (truncate summary to avoid overflow )
312+ // Line 1: ◐ ToolName summary_args (truncate summary to fit terminal width )
309313 let summary = crate :: views:: tool_call:: format_tool_summary ( & call. name , & call. arguments ) ;
310- let max_summary = 60_usize . saturating_sub ( call. name . len ( ) ) ;
314+ let max_summary = content_width . saturating_sub ( call. name . len ( ) + 2 ) ;
311315 let summary_truncated = if summary. len ( ) > max_summary {
312316 format ! (
313317 "{}..." ,
@@ -335,9 +339,15 @@ impl<'a> MinimalSessionView<'a> {
335339 // Live output lines (for Running status with output)
336340 if call. status == ToolStatus :: Running && !call. live_output . is_empty ( ) {
337341 for output_line in & call. live_output {
338- // Truncate long lines to fit display
339- let truncated = if output_line. len ( ) > 70 {
340- format ! ( "{}..." , & output_line. chars( ) . take( 67 ) . collect:: <String >( ) )
342+ // Truncate long lines to fit terminal width
343+ let truncated = if output_line. len ( ) > line_width {
344+ format ! (
345+ "{}..." ,
346+ & output_line
347+ . chars( )
348+ . take( line_width. saturating_sub( 3 ) )
349+ . collect:: <String >( )
350+ )
341351 } else {
342352 output_line. clone ( )
343353 } ;
@@ -348,17 +358,21 @@ impl<'a> MinimalSessionView<'a> {
348358 }
349359 }
350360
351- // Result summary line (if completed/failed) - truncate to avoid overflow
361+ // Result summary line (if completed/failed) - truncate to fit terminal width
352362 if let Some ( ref result) = call. result {
353363 let result_color = if result. success {
354364 self . colors . text_dim
355365 } else {
356366 self . colors . error
357367 } ;
358- let summary_truncated = if result. summary . len ( ) > 70 {
368+ let summary_truncated = if result. summary . len ( ) > line_width {
359369 format ! (
360370 "{}..." ,
361- & result. summary. chars( ) . take( 67 ) . collect:: <String >( )
371+ & result
372+ . summary
373+ . chars( )
374+ . take( line_width. saturating_sub( 3 ) )
375+ . collect:: <String >( )
362376 )
363377 } else {
364378 result. summary . clone ( )
@@ -368,13 +382,21 @@ impl<'a> MinimalSessionView<'a> {
368382 Span :: styled( summary_truncated, Style :: default ( ) . fg( result_color) ) ,
369383 ] ) ) ;
370384
371- // If error and not collapsed, show full error
385+ // If error and not collapsed, show full error with wrapping
372386 if !result. success && !call. collapsed {
373387 for err_line in result. output . lines ( ) . take ( 5 ) {
374- lines. push ( Line :: from ( vec ! [
375- Span :: raw( " " ) ,
376- Span :: styled( err_line. to_string( ) , Style :: default ( ) . fg( self . colors. error) ) ,
377- ] ) ) ;
388+ // Wrap long error lines
389+ let wrapped = Self :: wrap_text ( err_line, line_width. saturating_sub ( 4 ) ) ;
390+ for ( i, wrapped_line) in wrapped. iter ( ) . enumerate ( ) {
391+ let prefix = if i == 0 { " " } else { " " } ;
392+ lines. push ( Line :: from ( vec ! [
393+ Span :: raw( prefix. to_string( ) ) ,
394+ Span :: styled(
395+ wrapped_line. clone( ) ,
396+ Style :: default ( ) . fg( self . colors. error) ,
397+ ) ,
398+ ] ) ) ;
399+ }
378400 }
379401 }
380402 }
@@ -388,10 +410,18 @@ impl<'a> MinimalSessionView<'a> {
388410 ) ) ) ;
389411 if let Ok ( args_str) = serde_json:: to_string_pretty ( & call. arguments ) {
390412 for arg_line in args_str. lines ( ) . take ( 10 ) {
391- lines. push ( Line :: from ( vec ! [
392- Span :: raw( " " ) ,
393- Span :: styled( arg_line. to_string( ) , Style :: default ( ) . fg( self . colors. text) ) ,
394- ] ) ) ;
413+ // Wrap long argument lines
414+ let wrapped = Self :: wrap_text ( arg_line, line_width. saturating_sub ( 4 ) ) ;
415+ for ( i, wrapped_line) in wrapped. iter ( ) . enumerate ( ) {
416+ let prefix = if i == 0 { " " } else { " " } ;
417+ lines. push ( Line :: from ( vec ! [
418+ Span :: raw( prefix. to_string( ) ) ,
419+ Span :: styled(
420+ wrapped_line. clone( ) ,
421+ Style :: default ( ) . fg( self . colors. text) ,
422+ ) ,
423+ ] ) ) ;
424+ }
395425 }
396426 }
397427 }
@@ -409,10 +439,14 @@ impl<'a> MinimalSessionView<'a> {
409439 /// [in_progress] task2
410440 /// [completed] task3
411441 /// ```
412- fn render_subagent ( & self , task : & SubagentTaskDisplay ) -> Vec < Line < ' static > > {
442+ fn render_subagent ( & self , task : & SubagentTaskDisplay , width : u16 ) -> Vec < Line < ' static > > {
413443 use crate :: app:: SubagentTodoStatus ;
414444 let mut lines = Vec :: new ( ) ;
415445
446+ // Calculate available width for content (accounting for indentation)
447+ let content_width = ( width as usize ) . saturating_sub ( 6 ) ; // 6 chars for prefix/indent
448+ let line_width = ( width as usize ) . saturating_sub ( 8 ) ; // 8 chars for nested content
449+
416450 // Status indicator with color
417451 let ( indicator, indicator_color) = match & task. status {
418452 SubagentDisplayStatus :: Starting
@@ -441,18 +475,19 @@ impl<'a> MinimalSessionView<'a> {
441475 Span :: styled( " ⎿ " , Style :: default ( ) . fg( self . colors. text_muted) ) ,
442476 Span :: styled( "Error: " , Style :: default ( ) . fg( self . colors. error) ) ,
443477 ] ) ) ;
444- // Display error message, truncate if too long
445- for ( i, err_line) in error_msg. lines ( ) . take ( 5 ) . enumerate ( ) {
446- let truncated = if err_line. len ( ) > 70 {
447- format ! ( "{}..." , & err_line. chars( ) . take( 67 ) . collect:: <String >( ) )
448- } else {
449- err_line. to_string ( )
450- } ;
451- let prefix = if i == 0 { " " } else { " " } ;
452- lines. push ( Line :: from ( vec ! [
453- Span :: styled( prefix, Style :: default ( ) . fg( self . colors. text_muted) ) ,
454- Span :: styled( truncated, Style :: default ( ) . fg( self . colors. error) ) ,
455- ] ) ) ;
478+ // Display error message with wrapping
479+ for err_line in error_msg. lines ( ) . take ( 5 ) {
480+ let wrapped = Self :: wrap_text ( err_line, line_width. saturating_sub ( 4 ) ) ;
481+ for ( i, wrapped_line) in wrapped. iter ( ) . enumerate ( ) {
482+ let prefix = if i == 0 { " " } else { " " } ;
483+ lines. push ( Line :: from ( vec ! [
484+ Span :: styled( prefix, Style :: default ( ) . fg( self . colors. text_muted) ) ,
485+ Span :: styled(
486+ wrapped_line. clone( ) ,
487+ Style :: default ( ) . fg( self . colors. error) ,
488+ ) ,
489+ ] ) ) ;
490+ }
456491 }
457492 } else {
458493 // Fallback: no error message provided
@@ -469,9 +504,17 @@ impl<'a> MinimalSessionView<'a> {
469504 SubagentTodoStatus :: InProgress => ( "[in_progress]" , self . colors . accent ) ,
470505 SubagentTodoStatus :: Pending => ( "[pending]" , self . colors . text_muted ) ,
471506 } ;
472- // Truncate long todo content
473- let content = if todo. content . len ( ) > 50 {
474- format ! ( "{}..." , & todo. content. chars( ) . take( 47 ) . collect:: <String >( ) )
507+ // Calculate max content width (accounting for status text)
508+ let max_content = content_width. saturating_sub ( status_text. len ( ) + 1 ) ;
509+ let content = if todo. content . len ( ) > max_content {
510+ format ! (
511+ "{}..." ,
512+ & todo
513+ . content
514+ . chars( )
515+ . take( max_content. saturating_sub( 3 ) )
516+ . collect:: <String >( )
517+ )
475518 } else {
476519 todo. content . clone ( )
477520 } ;
@@ -485,13 +528,17 @@ impl<'a> MinimalSessionView<'a> {
485528 ] ) ) ;
486529 }
487530 } else {
488- // No todos yet - show current activity with ⎿ (truncate if too long)
531+ // No todos yet - show current activity with ⎿ (wrap if too long)
489532 let activity = if task. current_activity . is_empty ( ) {
490533 "Initializing..." . to_string ( )
491- } else if task. current_activity . len ( ) > 70 {
534+ } else if task. current_activity . len ( ) > content_width {
492535 format ! (
493536 "{}..." ,
494- & task. current_activity. chars( ) . take( 67 ) . collect:: <String >( )
537+ & task
538+ . current_activity
539+ . chars( )
540+ . take( content_width. saturating_sub( 3 ) )
541+ . collect:: <String >( )
495542 )
496543 } else {
497544 task. current_activity . clone ( )
@@ -742,7 +789,7 @@ impl<'a> MinimalSessionView<'a> {
742789 . iter ( )
743790 . find ( |c| & c. id == tool_call_id)
744791 {
745- all_lines. extend ( self . render_tool_call ( call) ) ;
792+ all_lines. extend ( self . render_tool_call ( call, width ) ) ;
746793 }
747794 }
748795 }
@@ -763,15 +810,15 @@ impl<'a> MinimalSessionView<'a> {
763810 }
764811
765812 for call in & sorted_calls {
766- all_lines. extend ( self . render_tool_call ( call) ) ;
813+ all_lines. extend ( self . render_tool_call ( call, width ) ) ;
767814 }
768815 } else if let Some ( ref content) = streaming_content {
769816 all_lines. extend ( self . render_streaming_content ( content, width) ) ;
770817 }
771818
772819 // Render active subagents
773820 for task in & self . app_state . active_subagents {
774- all_lines. extend ( self . render_subagent ( task) ) ;
821+ all_lines. extend ( self . render_subagent ( task, width ) ) ;
775822 }
776823
777824 all_lines
0 commit comments