@@ -526,6 +526,7 @@ pub async fn stop_recording(
526526 } ;
527527
528528 // === AUDIO VALIDATION - Check quality before transcription ===
529+ // Important: Keep pill window visible during validation for feedback
529530 let validation_start = Instant :: now ( ) ;
530531 log_start ( "AUDIO_VALIDATION" ) ;
531532 log_with_context ( log:: Level :: Debug , "Validating audio" , & [
@@ -560,26 +561,27 @@ pub async fn stop_recording(
560561 log:: warn!( "Failed to remove silent audio file: {}" , e) ;
561562 }
562563
563- // Emit event to show user feedback
564+ // Emit to pill window for immediate user feedback
564565 let _ = emit_to_window (
565566 & app,
566- "main" ,
567- "no-speech-detected" ,
568- serde_json:: json!( {
569- "title" : "No Speech Detected" ,
570- "message" : "The recording appears to be silent. Please check your microphone and speak clearly." ,
571- "severity" : "warning" ,
572- "actions" : [ "retry" , "settings" ]
573- } ) ,
567+ "pill" ,
568+ "transcription-empty" ,
569+ "No speech detected"
574570 ) ;
575571
576- // Hide pill window
577- if let Err ( e) = crate :: commands:: window:: hide_pill_widget ( app. clone ( ) ) . await {
578- log:: error!( "Failed to hide pill window: {}" , e) ;
579- }
580-
581- // Transition back to Idle
582- update_recording_state ( & app, RecordingState :: Idle , None ) ;
572+ // Wait for feedback to show before hiding pill
573+ let app_for_hide = app. clone ( ) ;
574+ tokio:: spawn ( async move {
575+ tokio:: time:: sleep ( std:: time:: Duration :: from_millis ( 2000 ) ) . await ;
576+
577+ // Hide pill window
578+ if let Err ( e) = crate :: commands:: window:: hide_pill_widget ( app_for_hide. clone ( ) ) . await {
579+ log:: error!( "Failed to hide pill window: {}" , e) ;
580+ }
581+
582+ // Transition back to Idle
583+ update_recording_state ( & app_for_hide, RecordingState :: Idle , None ) ;
584+ } ) ;
583585
584586 return Ok ( "" . to_string ( ) ) ; // Don't proceed to transcription
585587 }
@@ -591,26 +593,27 @@ pub async fn stop_recording(
591593 log:: warn!( "Failed to remove quiet audio file: {}" , e) ;
592594 }
593595
594- // Emit event with specific guidance
596+ // Emit to pill window for immediate user feedback
595597 let _ = emit_to_window (
596598 & app,
597- "main" ,
598- "no-speech-detected" ,
599- serde_json:: json!( {
600- "title" : "Audio Too Quiet" ,
601- "message" : suggestion,
602- "severity" : "warning" ,
603- "actions" : [ "retry" , "settings" ]
604- } ) ,
599+ "pill" ,
600+ "transcription-empty" ,
601+ "Audio too quiet - please speak louder"
605602 ) ;
606603
607- // Hide pill window
608- if let Err ( e) = crate :: commands:: window:: hide_pill_widget ( app. clone ( ) ) . await {
609- log:: error!( "Failed to hide pill window: {}" , e) ;
610- }
611-
612- // Transition back to Idle
613- update_recording_state ( & app, RecordingState :: Idle , None ) ;
604+ // Wait for feedback to show before hiding pill
605+ let app_for_hide = app. clone ( ) ;
606+ tokio:: spawn ( async move {
607+ tokio:: time:: sleep ( std:: time:: Duration :: from_millis ( 2000 ) ) . await ;
608+
609+ // Hide pill window
610+ if let Err ( e) = crate :: commands:: window:: hide_pill_widget ( app_for_hide. clone ( ) ) . await {
611+ log:: error!( "Failed to hide pill window: {}" , e) ;
612+ }
613+
614+ // Transition back to Idle
615+ update_recording_state ( & app_for_hide, RecordingState :: Idle , None ) ;
616+ } ) ;
614617
615618 return Ok ( "" . to_string ( ) ) ; // Don't proceed to transcription
616619 }
@@ -622,26 +625,27 @@ pub async fn stop_recording(
622625 log:: warn!( "Failed to remove short audio file: {}" , e) ;
623626 }
624627
625- // Emit event
628+ // Emit to pill window for immediate user feedback
626629 let _ = emit_to_window (
627630 & app,
628- "main" ,
629- "no-speech-detected" ,
630- serde_json:: json!( {
631- "title" : "Recording Too Short" ,
632- "message" : format!( "Recording was only {:.1}s. Please hold the recording button longer and speak clearly." , duration) ,
633- "severity" : "warning" ,
634- "actions" : [ "retry" ]
635- } ) ,
631+ "pill" ,
632+ "transcription-empty" ,
633+ format ! ( "Recording too short ({:.1}s)" , duration)
636634 ) ;
637635
638- // Hide pill window
639- if let Err ( e) = crate :: commands:: window:: hide_pill_widget ( app. clone ( ) ) . await {
640- log:: error!( "Failed to hide pill window: {}" , e) ;
641- }
642-
643- // Transition back to Idle
644- update_recording_state ( & app, RecordingState :: Idle , None ) ;
636+ // Wait for feedback to show before hiding pill
637+ let app_for_hide = app. clone ( ) ;
638+ tokio:: spawn ( async move {
639+ tokio:: time:: sleep ( std:: time:: Duration :: from_millis ( 2000 ) ) . await ;
640+
641+ // Hide pill window
642+ if let Err ( e) = crate :: commands:: window:: hide_pill_widget ( app_for_hide. clone ( ) ) . await {
643+ log:: error!( "Failed to hide pill window: {}" , e) ;
644+ }
645+
646+ // Transition back to Idle
647+ update_recording_state ( & app_for_hide, RecordingState :: Idle , None ) ;
648+ } ) ;
645649
646650 return Ok ( "" . to_string ( ) ) ; // Don't proceed to transcription
647651 }
@@ -956,38 +960,6 @@ pub async fn stop_recording(
956960
957961 log:: debug!( "Transcription successful, {} chars" , text. len( ) ) ;
958962
959- // Check if transcription is empty or only whitespace
960- if text. trim ( ) . is_empty ( ) {
961- log:: info!( "Transcription is empty, no speech detected" ) ;
962-
963- // Emit event to pill for user feedback
964- let _ = emit_to_window (
965- & app_for_task,
966- "pill" ,
967- "transcription-empty" ,
968- "No speech detected" ,
969- ) ;
970-
971- // Wait a bit for feedback to show
972- let app_for_empty = app_for_task. clone ( ) ;
973- tokio:: spawn ( async move {
974- tokio:: time:: sleep ( std:: time:: Duration :: from_millis ( 2000 ) ) . await ;
975-
976- // Hide pill window
977- let app_state = app_for_empty. state :: < AppState > ( ) ;
978- if let Some ( window_manager) = app_state. get_window_manager ( ) {
979- if let Err ( e) = window_manager. hide_pill_window ( ) . await {
980- log:: error!( "Failed to hide pill window: {}" , e) ;
981- }
982- }
983-
984- // Transition to idle state
985- update_recording_state ( & app_for_empty, RecordingState :: Idle , None ) ;
986- } ) ;
987-
988- return ;
989- }
990-
991963 // Check if AI enhancement is enabled BEFORE spawning task
992964 let ai_enabled = match app_for_task. store ( "settings" ) {
993965 Ok ( store) => store
0 commit comments