From 5056287c91de33a1dd94b47feabb8f9a1724867b Mon Sep 17 00:00:00 2001 From: XU Ke Date: Thu, 8 Oct 2026 15:56:36 +0800 Subject: [PATCH 1/4] fix(macos): focus the last-used workspace on Dock activation --- src-tauri/src/commands/windows.rs | 3 +- src-tauri/src/commands/workspace_windows.rs | 72 +++++++++++++++++++++ src-tauri/src/lib.rs | 12 +--- 3 files changed, 75 insertions(+), 12 deletions(-) diff --git a/src-tauri/src/commands/windows.rs b/src-tauri/src/commands/windows.rs index fdef162eae..736e0ae1c2 100644 --- a/src-tauri/src/commands/windows.rs +++ b/src-tauri/src/commands/windows.rs @@ -892,7 +892,7 @@ pub async fn open_import_sessions_window( /// (macOS returns early; Windows first syncs the flag from `IsIconic`, so the /// diff it applies is empty), and `show` preserves the maximized flag — a /// tray-hidden maximized workspace comes back maximized. -fn show_and_focus_window(app: &AppHandle, label: &str) { +pub(crate) fn show_and_focus_window(app: &AppHandle, label: &str) { let Some(window) = app.get_webview_window(label) else { return; }; @@ -2341,7 +2341,6 @@ fn wait_for_macos_fullscreen_space_release(window: &tauri::WebviewWindow) { /// foreground. Used by: /// * single-instance plugin (second launch) /// * tray icon left-click and "Show Workspace" menu item -/// * macOS dock-icon reopen #[cfg(feature = "tauri-runtime")] pub fn show_main_window(app: &AppHandle) { show_and_focus_window(app, "main"); diff --git a/src-tauri/src/commands/workspace_windows.rs b/src-tauri/src/commands/workspace_windows.rs index 06c42c9a85..268bfcd5d6 100644 --- a/src-tauri/src/commands/workspace_windows.rs +++ b/src-tauri/src/commands/workspace_windows.rs @@ -268,6 +268,23 @@ impl SessionState { } } + /// Activation follows workspace focus, not auxiliary windows or the + /// always-built but tray-hidden local window. None means restoration is + /// still building the only workspaces; showing local now would reopen it. + fn activation_target( + &self, + exists: impl Fn(WorkspaceWindow) -> bool, + ) -> Option { + self.open + .iter() + .rev() + .copied() + .find(|window| exists(*window)) + .or_else(|| { + (!self.restoring || self.pending.is_empty()).then_some(WorkspaceWindow::Local) + }) + } + /// The list to store, least recently focused first. fn snapshot(&self) -> Vec { self.pending.iter().chain(&self.open).copied().collect() @@ -406,6 +423,21 @@ pub fn on_window_event(window: &tauri::Window, event: &tauri::WindowEvent) { } } +/// Bring the most recently used workspace forward on Dock activation. A +/// hidden/minimized remote still counts as open. Only fall back to local when +/// no workspace remains, never while a remote-only launch is restoring. +pub fn activate_workspace(app: &AppHandle) { + let target = match app.try_state::() { + Some(session) => session + .lock() + .activation_target(|window| app.get_webview_window(&window.label()).is_some()), + None => Some(WorkspaceWindow::Local), + }; + if let Some(target) = target { + crate::commands::windows::show_and_focus_window(app, &target.label()); + } +} + /// Write the final list, and stop recording: the windows about to go away are /// torn down by the quit, not closed by the user. /// @@ -672,6 +704,46 @@ mod tests { } } + #[test] + fn activation_prefers_the_most_recent_existing_workspace() { + let mut state = SessionState::default(); + state.mark_open(LOCAL); + state.mark_open(remote(1)); + state.mark_open(remote(2)); + assert_eq!(state.activation_target(|_| true), Some(remote(2))); + assert_eq!( + state.activation_target(|window| window != remote(2)), + Some(remote(1)) + ); + state.mark_open(LOCAL); + assert_eq!(state.activation_target(|_| true), Some(LOCAL)); + } + + #[test] + fn activation_does_not_reopen_a_closed_local_workspace() { + let mut state = SessionState::default(); + state.mark_open(LOCAL); + state.mark_open(remote(1)); + state.mark_closed(LOCAL); + // main still physically exists, hidden to tray; it must stay hidden. + assert_eq!(state.activation_target(|_| true), Some(remote(1))); + state.mark_closed(remote(1)); + assert_eq!(state.activation_target(|_| true), Some(LOCAL)); + } + + #[test] + fn activation_waits_for_a_remote_only_restore() { + let mut state = SessionState { + pending: vec![remote(1)], + restoring: true, + ..SessionState::default() + }; + assert_eq!(state.activation_target(|window| window == LOCAL), None); + state.mark_open(remote(1)); + assert_eq!(state.activation_target(|_| true), Some(remote(1))); + assert_eq!(state.snapshot(), vec![remote(1)]); + } + #[test] fn stored_list_round_trips() { let windows = [remote(4), LOCAL, remote(2)]; diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 06d40ab5fa..626bfa0b1a 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1375,7 +1375,7 @@ mod tauri_app { // `list_open_folder_details` / `list_opened_tabs` inside it. It // starts hidden only when it was hidden to the tray at quit and // other workspace windows are coming back in its place; the - // tray, the dock and a second launch still bring it up. + // tray and explicit local-workspace actions still bring it up. if app.get_webview_window("main").is_none() { let url = tauri::WebviewUrl::App(workspace_path.into()); let builder = tauri::WebviewWindowBuilder::new(app, "main", url) @@ -2197,15 +2197,7 @@ mod tauri_app { tauri::RunEvent::ExitRequested { .. } | tauri::RunEvent::Exit => shut_down(app), #[cfg(target_os = "macos")] tauri::RunEvent::Reopen { .. } => { - // Dock-icon click: bring the workspace forward - // unconditionally. `has_visible_windows` is true - // whenever any aux window (pet, settings, commit…) - // is alive, so gating on it would suppress recovery - // even though `main` itself is hidden. - // `show_main_window` is idempotent — already-visible - // windows just get re-focused, which is what dock - // activation should do anyway. - windows::show_main_window(app); + workspace_windows::activate_workspace(app); } _ => {} }); From 76f4d9da79bbfa6f1f0c5f5f804c79898ba34c36 Mon Sep 17 00:00:00 2001 From: xintaofei Date: Fri, 9 Oct 2026 08:33:40 +0800 Subject: [PATCH 2/4] docs(macos): say what still brings up a tray-hidden local workspace The setup comment claimed only the tray and explicit local-workspace actions bring a hidden `main` back, but a second launch and a `codeg://` link still do, and the Dock does once no other workspace is open or on its way back. The shared show-and-focus helper still routed Dock activation through `show_main_window`. The Reopen arm also lost the reason it ignores `has_visible_windows`: any auxiliary window makes it true while every workspace is hidden or minimized. Co-Authored-By: Claude Opus 5.5 --- src-tauri/src/commands/windows.rs | 13 +++++++------ src-tauri/src/lib.rs | 8 +++++++- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src-tauri/src/commands/windows.rs b/src-tauri/src/commands/windows.rs index 736e0ae1c2..7ead4b99d6 100644 --- a/src-tauri/src/commands/windows.rs +++ b/src-tauri/src/commands/windows.rs @@ -886,12 +886,13 @@ pub async fn open_import_sessions_window( /// focus alone then left the app with nothing on screen once settings was /// closed too. /// -/// Single source of truth for the sequence: the tray / dock / single-instance -/// path (`show_main_window`) and the auxiliary-window owner restores must not -/// drift apart again. `unminimize` is inert when the window isn't minimized -/// (macOS returns early; Windows first syncs the flag from `IsIconic`, so the -/// diff it applies is empty), and `show` preserves the maximized flag — a -/// tray-hidden maximized workspace comes back maximized. +/// Single source of truth for the sequence: the tray / single-instance path +/// (`show_main_window`), Dock activation +/// (`workspace_windows::activate_workspace`) and the auxiliary-window owner +/// restores must not drift apart again. `unminimize` is inert when the window +/// isn't minimized (macOS returns early; Windows first syncs the flag from +/// `IsIconic`, so the diff it applies is empty), and `show` preserves the +/// maximized flag — a tray-hidden maximized workspace comes back maximized. pub(crate) fn show_and_focus_window(app: &AppHandle, label: &str) { let Some(window) = app.get_webview_window(label) else { return; diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 626bfa0b1a..8555b18927 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1375,7 +1375,9 @@ mod tauri_app { // `list_open_folder_details` / `list_opened_tabs` inside it. It // starts hidden only when it was hidden to the tray at quit and // other workspace windows are coming back in its place; the - // tray and explicit local-workspace actions still bring it up. + // tray, a second launch and a `codeg://` link still bring it + // up, and so does the Dock once no other workspace is open or + // on its way back. if app.get_webview_window("main").is_none() { let url = tauri::WebviewUrl::App(workspace_path.into()); let builder = tauri::WebviewWindowBuilder::new(app, "main", url) @@ -2197,6 +2199,10 @@ mod tauri_app { tauri::RunEvent::ExitRequested { .. } | tauri::RunEvent::Exit => shut_down(app), #[cfg(target_os = "macos")] tauri::RunEvent::Reopen { .. } => { + // Every Dock click, whatever `has_visible_windows` says: + // it is true whenever an auxiliary window (pet, settings, + // commit…) is up, even with every workspace hidden or + // minimized. workspace_windows::activate_workspace(app); } _ => {} From d56abf5129e62a55331fd3765217a7fa702e95b6 Mon Sep 17 00:00:00 2001 From: xintaofei Date: Fri, 9 Oct 2026 08:33:41 +0800 Subject: [PATCH 3/4] test(workspace): pin the local fallback once nothing is left to restore Dock activation waits while a remote-only restore still has windows pending, and falls back to local once none are. No test covered the second half: narrowing the condition to `!restoring` passed every activation test. Closing the restored window before the restore finishes now has to reach the local workspace. Co-Authored-By: Claude Opus 5.5 --- src-tauri/src/commands/workspace_windows.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src-tauri/src/commands/workspace_windows.rs b/src-tauri/src/commands/workspace_windows.rs index 268bfcd5d6..9a388208a5 100644 --- a/src-tauri/src/commands/workspace_windows.rs +++ b/src-tauri/src/commands/workspace_windows.rs @@ -742,6 +742,10 @@ mod tests { state.mark_open(remote(1)); assert_eq!(state.activation_target(|_| true), Some(remote(1))); assert_eq!(state.snapshot(), vec![remote(1)]); + // Still restoring, but nothing is left pending to wait for: with the + // restored window closed again, local is the only workspace left. + state.mark_closed(remote(1)); + assert_eq!(state.activation_target(|_| true), Some(LOCAL)); } #[test] From 64d2a72a7f28604ef62d4dc15b515b85617195d3 Mon Sep 17 00:00:00 2001 From: xintaofei Date: Fri, 9 Oct 2026 08:42:50 +0800 Subject: [PATCH 4/4] docs(workspace): narrow when Dock activation waits on a restore `activate_workspace` said local is never shown while a remote-only launch is restoring. It is, once nothing is left pending; only windows still to be brought back make it wait. Co-Authored-By: Claude Opus 5.5 --- src-tauri/src/commands/workspace_windows.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src-tauri/src/commands/workspace_windows.rs b/src-tauri/src/commands/workspace_windows.rs index 9a388208a5..0dbae18c3a 100644 --- a/src-tauri/src/commands/workspace_windows.rs +++ b/src-tauri/src/commands/workspace_windows.rs @@ -425,7 +425,8 @@ pub fn on_window_event(window: &tauri::Window, event: &tauri::WindowEvent) { /// Bring the most recently used workspace forward on Dock activation. A /// hidden/minimized remote still counts as open. Only fall back to local when -/// no workspace remains, never while a remote-only launch is restoring. +/// no workspace remains, never while a remote-only launch still has windows +/// to bring back. pub fn activate_workspace(app: &AppHandle) { let target = match app.try_state::() { Some(session) => session