@@ -199,14 +199,8 @@ fn reset_scanner<A: Adapter>(app: &AppHandle, message: &str) {
199199 c. data . lock ( ) . unwrap_or_else ( |p| p. into_inner ( ) ) . message =
200200 "Input cleanup will be retried before scanning resumes." . into ( ) ;
201201 }
202- let _ = render_tiles ( & [ ] ) ;
203202 app. state :: < switch_runtime:: Controller > ( ) . stop ( ) ;
204- HOST . with ( |host| {
205- if let Some ( host) = host. borrow_mut ( ) . as_mut ( ) {
206- host. hide ( ) ;
207- }
208- } ) ;
209- hide_prompt ( ) ;
203+ hide_scan_visuals ( ) ;
210204 let token = c
211205 . data
212206 . lock ( )
@@ -423,13 +417,7 @@ fn dispatch<A: Adapter>(
423417 if !c. enabled . load ( Ordering :: SeqCst ) || !input_active ( app, input_generation, remote) {
424418 return Err ( "Scan action was cancelled." . into ( ) ) ;
425419 }
426- render_tiles ( & [ ] ) ?;
427- hide_prompt ( ) ;
428- HOST . with ( |slot| {
429- if let Some ( host) = slot. borrow_mut ( ) . as_mut ( ) {
430- host. hide ( ) ;
431- }
432- } ) ;
420+ hide_scan_visuals ( ) ;
433421 if let Err ( error) = A :: activate ( app, request) {
434422 A :: cleanup ( app) ?;
435423 let mut d = c. data . lock ( ) . unwrap_or_else ( |p| p. into_inner ( ) ) ;
@@ -464,6 +452,37 @@ fn render_countdown(countdown: Option<&crate::scanning::Countdown>) -> Result<()
464452 Ok ( ( ) )
465453 } )
466454}
455+
456+ // Rendering can fail after a native window is shown but before its cache is
457+ // committed. Cleanup must never rely on that cache to decide whether to hide.
458+ fn clear_cached_visual < H , C : Default > ( state : & mut ( H , C ) , hide : impl FnOnce ( & mut H ) ) {
459+ hide ( & mut state. 0 ) ;
460+ state. 1 = C :: default ( ) ;
461+ }
462+
463+ fn hide_scan_visuals ( ) {
464+ for slot in [ & HOST , & PROMPT , & LABEL ] {
465+ slot. with ( |host| {
466+ if let Some ( host) = host. borrow_mut ( ) . as_mut ( ) {
467+ host. hide ( ) ;
468+ }
469+ } ) ;
470+ }
471+ TILES . with ( |slot| {
472+ clear_cached_visual ( & mut slot. borrow_mut ( ) , |hosts| {
473+ for host in hosts {
474+ host. hide ( ) ;
475+ }
476+ } )
477+ } ) ;
478+ COUNTDOWN . with ( |slot| {
479+ clear_cached_visual ( & mut slot. borrow_mut ( ) , |host| {
480+ if let Some ( host) = host {
481+ host. hide ( ) ;
482+ }
483+ } )
484+ } ) ;
485+ }
467486fn render_tiles ( tiles : & [ crate :: scanning:: FrameTile ] ) -> Result < ( ) , String > {
468487 TILES . with ( |slot| {
469488 let mut slot = slot. borrow_mut ( ) ;
@@ -492,6 +511,13 @@ fn render<A: Adapter>(
492511 let mut d = c. data . lock ( ) . unwrap_or_else ( |p| p. into_inner ( ) ) ;
493512 let cursor = app. state :: < crate :: overlay:: CursorOverlay > ( ) ;
494513 let active = d. engine . as_ref ( ) . is_some_and ( Session :: active) || prompt. is_some ( ) ;
514+ if !active {
515+ hide_scan_visuals ( ) ;
516+ if let Some ( ( token, _) ) = d. cursor_suppression . take ( ) {
517+ cursor. release_scan ( token) ;
518+ }
519+ return Ok ( ( ) ) ;
520+ }
495521 if active {
496522 let ( token, started) = match d. cursor_suppression {
497523 Some ( lease) => lease,
@@ -524,12 +550,6 @@ fn render<A: Adapter>(
524550 render_countdown ( frame. countdown . as_ref ( ) ) ?;
525551 render_label ( frame. label_for_prompt ( prompt. is_some ( ) ) , & frame. tiles ) ?;
526552 show_prompt ( app, prompt) ?;
527- if !active {
528- // Native scanning windows have all been hidden before the cursor can return.
529- if let Some ( ( token, _) ) = d. cursor_suppression . take ( ) {
530- cursor. release_scan ( token) ;
531- }
532- }
533553 Ok ( ( ) )
534554}
535555fn tick < A : Adapter > ( app : & AppHandle ) {
@@ -889,6 +909,21 @@ pub fn restart_point_on_display(app: &AppHandle, next: bool) -> Result<(), Strin
889909
890910#[ cfg( test) ]
891911mod ownership_tests {
912+ #[ test]
913+ fn failed_partial_presentations_are_hidden_even_with_empty_caches ( ) {
914+ let mut tiles = ( vec ! [ true , true , false ] , Vec :: < u8 > :: new ( ) ) ;
915+ super :: clear_cached_visual ( & mut tiles, |hosts| hosts. fill ( false ) ) ;
916+ assert ! ( tiles. 0 . iter( ) . all( |visible| !visible) ) ;
917+ assert ! ( tiles. 1 . is_empty( ) ) ;
918+ let mut countdown = ( Some ( true ) , None :: < u8 > ) ;
919+ super :: clear_cached_visual ( & mut countdown, |host| {
920+ if let Some ( visible) = host {
921+ * visible = false ;
922+ }
923+ } ) ;
924+ assert_eq ! ( countdown, ( Some ( false ) , None ) ) ;
925+ }
926+
892927 #[ test]
893928 fn stopped_remote_input_cannot_fall_back_to_a_matching_local_generation ( ) {
894929 assert ! ( !super :: source_is_current( true , false , true ) ) ;
0 commit comments