From 12450444f9096c4327736d29ad10bf5a4069a827 Mon Sep 17 00:00:00 2001 From: jamiepine <32987599+jamiepine@users.noreply.github.com> Date: Sat, 3 Oct 2026 20:31:55 +0000 Subject: [PATCH] fix(tauri): only restore a pill that was visible before the paste; keep tao's focusable ivar on the re-classed panel Review follow-ups: a paste completing after the frontend already hid the pill must not re-show it (nothing would hide it again until the next dictation), so the hide/show cycle now applies only when the window was visible. The live object re-classed by object_setClass is tao's TaoWindow, which declares a 'focusable' ivar read by Window::set_focusable and its key/main-window overrides; VoiceboxPillPanel declares the same ivar so those lookups cannot panic after the swap. --- tauri/src-tauri/src/main.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tauri/src-tauri/src/main.rs b/tauri/src-tauri/src/main.rs index c0686b31..a9f447a3 100644 --- a/tauri/src-tauri/src/main.rs +++ b/tauri/src-tauri/src/main.rs @@ -98,6 +98,12 @@ fn pill_panel_class() -> &'static objc::runtime::Class { let superclass = class!(NSPanel); let mut decl = ClassDecl::new("VoiceboxPillPanel", superclass).expect("register VoiceboxPillPanel"); + // The object being re-classed is tao's `TaoWindow`, not a bare + // NSWindow: tao declares a `focusable` ivar on it and reads it from + // `Window::set_focusable` and its own key/main-window overrides. + // Declare the same ivar here so those lookups keep resolving after + // the swap instead of panicking on a missing ivar. + decl.add_ivar::("focusable"); unsafe { decl.add_method( sel!(canBecomeKeyWindow), @@ -1381,8 +1387,14 @@ async fn paste_final_text( // it holds key focus Spotlight-style — the keystroke would land in the // pill instead of the target app. Hidden it can't swallow keys; restored // immediately after so the webview never suspends between dictations. + // Only a pill that was actually on screen gets restored: a paste that + // lands after the frontend's `dictate:hide` already ran must not bring + // the window back, since nothing would hide it again until the next + // dictation. #[cfg(target_os = "macos")] - let pill = app.get_webview_window(DICTATE_WINDOW_LABEL); + let pill = app + .get_webview_window(DICTATE_WINDOW_LABEL) + .filter(|w| w.is_visible().unwrap_or(false)); #[cfg(target_os = "macos")] if let Some(ref w) = pill { let _ = w.hide();