From 82d333e3507b3877dff6ad013f62418ad74dc6ab Mon Sep 17 00:00:00 2001 From: CRT Date: Fri, 2 Oct 2026 00:52:24 +0700 Subject: [PATCH] macOS: the red close button sends the window to the Dock and the app keeps running (Dock click restores, Cmd+Q quits); a pane shown after being parked is shown before it is sized and told to re-lay out (X drew an empty timeline) Co-Authored-By: Claude Opus 5.5 --- crates/waybetter-host/src/app.rs | 29 ++++++++++++++++++++++++++--- crates/waybetter-host/src/host.rs | 14 +++++++++++++- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/crates/waybetter-host/src/app.rs b/crates/waybetter-host/src/app.rs index 08b8b6f..03568e5 100644 --- a/crates/waybetter-host/src/app.rs +++ b/crates/waybetter-host/src/app.rs @@ -1514,14 +1514,37 @@ impl App { }) .build(context) .unwrap_or_else(|error| panic!("Failed to launch {}: {error}", config.title)) - .run(|_, event| log_run_event(&event)); + .run(|app, event| on_run_event(app, &event)); } } -/// Why the app is going away, for `--diag` ([`crate::diag`]): an exit request with its code (None is a +/// Run-loop events: the macOS close-hides rule, and why the app is going away for `--diag` ([`crate::diag`]): an exit request with its code (None is a /// polite quit from outside: Cmd+Q, the Dock, or another app such as an auto-quitter closing the last /// window), window close/destroy events and the final exit. Nothing is written without diagnostics. -fn log_run_event(event: &tauri::RunEvent) { +fn on_run_event(app: &AppHandle, event: &tauri::RunEvent) { + // macOS: the red close button sends the main window to the Dock (minimized, not hidden: an auto-quitter + // such as Swift Quit terminates an app whose window list goes empty, but not one with a minimized window) + // and the app keeps running; a Dock click shows it + // again; Cmd+Q still quits (an exit request, not a window close). + #[cfg(target_os = "macos")] + match event { + tauri::RunEvent::WindowEvent { label, event: tauri::WindowEvent::CloseRequested { api, .. }, .. } if label == policy::MAIN_LABEL => { + api.prevent_close(); + if let Some(window) = app.get_window(policy::MAIN_LABEL) { + let _ = window.minimize(); + } + } + tauri::RunEvent::Reopen { .. } => { + if let Some(window) = app.get_window(policy::MAIN_LABEL) { + let _ = window.unminimize(); + let _ = window.show(); + let _ = window.set_focus(); + } + } + _ => {} + } + #[cfg(not(target_os = "macos"))] + let _ = app; match event { tauri::RunEvent::ExitRequested { code, .. } => crate::diag::log("host", &format!("exit requested code={code:?}")), tauri::RunEvent::Exit => crate::diag::log("host", "exit"), diff --git a/crates/waybetter-host/src/host.rs b/crates/waybetter-host/src/host.rs index b52768c..3d47725 100644 --- a/crates/waybetter-host/src/host.rs +++ b/crates/waybetter-host/src/host.rs @@ -919,11 +919,23 @@ pub fn place_tab(webview: &Webview, rect: crate::layout::Rect, vi let was_visible = placed.was_visible(webview.label()); (placed.place(webview.label(), rect, visible), was_visible) }; + // macOS: a pane that comes back from hidden (parked at 1x1) and is resized while still hidden keeps + // the page's old viewport (X drew an empty timeline until the next real resize). Show it first, then + // size it, then tell the page its window changed. + #[cfg(target_os = "macos")] + let reveal = changed.visible && visible; + #[cfg(not(target_os = "macos"))] + let reveal = false; + if reveal { + set_tab_visible(webview, visible, parked, was_visible); + } if changed.bounds { let (x, y, width, height) = rect; place_child(webview, x, y, width, height); } - if changed.visible { + if reveal { + let _ = webview.eval("window.dispatchEvent(new Event('resize'));"); + } else if changed.visible { set_tab_visible(webview, visible, parked, was_visible); } else { set_memory_target(webview, visible, parked);