@@ -1148,32 +1148,24 @@ fn render_progress(state: &mut ProgressState) {
11481148 } )
11491149 . sum ( ) ;
11501150 let completed_stages: usize = state. patches . values ( ) . map ( |p| p. completed_stages ) . sum ( ) ;
1151- let percent = if total_stages > 0 {
1152- ( completed_stages * 100 ) / total_stages
1153- } else {
1154- 0
1155- } ;
11561151 let width = 20 ;
1157- let filled = if total_stages > 0 {
1158- ( completed_stages * width) / total_stages
1159- } else {
1160- 0
1161- } ;
1152+ let ( display_completed_stages, percent, filled) =
1153+ calculate_progress_metrics ( total_stages, completed_stages, width) ;
11621154
11631155 let mut tw = TruncatingWriter :: new ( limit, state. color_choice ) ;
11641156 let _ = tw. write_segment ( "Overall: [" , None , true ) ;
11651157
11661158 let filled_bar = "█" . repeat ( filled) ;
11671159 let _ = tw. write_segment ( & filled_bar, Some ( Color :: Green ) , false ) ;
11681160
1169- let empty_bar = "░" . repeat ( width - filled) ;
1161+ let empty_bar = "░" . repeat ( width. saturating_sub ( filled) ) ;
11701162 let _ = tw. write_segment ( & empty_bar, None , false ) ;
11711163
11721164 let _ = tw. write_segment ( "] " , None , true ) ;
11731165
11741166 let stats = format ! (
11751167 "{}% | {}/{} stages | {} turns" ,
1176- percent, completed_stages , total_stages, state. total_turns
1168+ percent, display_completed_stages , total_stages, state. total_turns
11771169 ) ;
11781170 let _ = tw. write_segment ( & stats, None , false ) ;
11791171
@@ -1185,6 +1177,22 @@ fn render_progress(state: &mut ProgressState) {
11851177 let _ = std:: io:: stderr ( ) . flush ( ) ;
11861178}
11871179
1180+ fn calculate_progress_metrics (
1181+ total_stages : usize ,
1182+ completed_stages : usize ,
1183+ width : usize ,
1184+ ) -> ( usize , usize , usize ) {
1185+ if total_stages == 0 {
1186+ return ( 0 , 0 , 0 ) ;
1187+ }
1188+
1189+ let display_completed_stages = completed_stages. min ( total_stages) ;
1190+ let percent = display_completed_stages. saturating_mul ( 100 ) / total_stages;
1191+ let filled = ( display_completed_stages. saturating_mul ( width) / total_stages) . min ( width) ;
1192+
1193+ ( display_completed_stages, percent, filled)
1194+ }
1195+
11881196#[ allow( clippy:: too_many_arguments) ]
11891197async fn handle_review_command (
11901198 input : String ,
@@ -2281,6 +2289,13 @@ fn identify_subsystems_from_paths(
22812289mod tests {
22822290 use super :: * ;
22832291
2292+ #[ test]
2293+ fn test_progress_metrics_clamp_completed_stages_to_total ( ) {
2294+ assert_eq ! ( calculate_progress_metrics( 1 , 5 , 20 ) , ( 1 , 100 , 20 ) ) ;
2295+ assert_eq ! ( calculate_progress_metrics( 5 , 2 , 20 ) , ( 2 , 40 , 8 ) ) ;
2296+ assert_eq ! ( calculate_progress_metrics( 0 , 5 , 20 ) , ( 0 , 0 , 0 ) ) ;
2297+ }
2298+
22842299 #[ test]
22852300 fn test_cli_parsing ( ) {
22862301 let args = vec ! [ "sashiko" , "--download" , "100" , "--track" , "--no-api" ] ;
0 commit comments