From a135d035dff8dc2e957a4eb086ed5fa66698b532 Mon Sep 17 00:00:00 2001 From: rgdevment Date: Tue, 6 Oct 2026 01:45:40 -0300 Subject: [PATCH 1/2] feat(gui): opening the app while it runs shows how to call the panel instead of the panel itself --- app/src-tauri/src/lib.rs | 4 ++-- app/src-tauri/src/welcome.rs | 19 ++++++++++++++++++- app/src-tauri/src/welcome_test.rs | 8 ++++++++ app/src/locales.ts | 2 ++ app/src/tests/tour.test.tsx | 17 +++++++++++++++++ app/src/ui/Welcome.tsx | 24 +++++++++++++++--------- 6 files changed, 62 insertions(+), 12 deletions(-) diff --git a/app/src-tauri/src/lib.rs b/app/src-tauri/src/lib.rs index 3298e8f1..7075083f 100644 --- a/app/src-tauri/src/lib.rs +++ b/app/src-tauri/src/lib.rs @@ -15,7 +15,7 @@ pub fn run() { note::catch_panics(); let app = tauri::Builder::default() .plugin(tauri_plugin_single_instance::init(|app, _argv, _cwd| { - panel::show(app); + welcome::reopened(app); })) .plugin(tauri_plugin_opener::init()) .plugin(tauri_plugin_shell::init()) @@ -79,7 +79,7 @@ pub fn run() { } => api.prevent_exit(), tauri::RunEvent::Exit => panel::quit(app), #[cfg(target_os = "macos")] - tauri::RunEvent::Reopen { .. } => panel::show(app), + tauri::RunEvent::Reopen { .. } => welcome::reopened(app), _ => {} }); } diff --git a/app/src-tauri/src/welcome.rs b/app/src-tauri/src/welcome.rs index 5bedcd1f..a63832c4 100644 --- a/app/src-tauri/src/welcome.rs +++ b/app/src-tauri/src/welcome.rs @@ -11,6 +11,7 @@ pub const LABEL: &str = "welcome"; pub enum Greeting { Tour { former: bool }, News { versions: Vec }, + Keys, Nothing, } @@ -137,7 +138,7 @@ pub fn open(app: &AppHandle) { Ok(window) => { let handle = app.clone(); window.on_window_event(move |event| { - if matches!(event, tauri::WindowEvent::Destroyed) { + if matches!(event, tauri::WindowEvent::Destroyed) && !only_keys(&handle) { mark(&handle); } }); @@ -147,6 +148,22 @@ pub fn open(app: &AppHandle) { } } +fn only_keys(app: &AppHandle) -> bool { + app.try_state::() + .and_then(|state| state.0.lock().ok().map(|held| held.clone())) + .is_some_and(|held| held == Some(Greeting::Keys)) +} + +pub fn reopened(app: &AppHandle) { + if app.get_webview_window(LABEL).is_none() + && let Some(state) = app.try_state::() + && let Ok(mut held) = state.0.lock() + { + *held = Some(Greeting::Keys); + } + open(app); +} + #[tauri::command] pub fn greeting(state: tauri::State<'_, Now>) -> Greeting { state diff --git a/app/src-tauri/src/welcome_test.rs b/app/src-tauri/src/welcome_test.rs index 08227209..894d2dcc 100644 --- a/app/src-tauri/src/welcome_test.rs +++ b/app/src-tauri/src/welcome_test.rs @@ -138,3 +138,11 @@ fn a_build_made_by_hand_never_marks_a_published_version_as_seen() { "the real release still tells what it brings after a build made by hand" ); } + +#[test] +fn opening_it_again_asks_the_window_for_the_shortcut_alone() { + assert_eq!( + serde_json::to_value(Greeting::Keys).expect("it serializes"), + serde_json::json!({ "kind": "keys" }) + ); +} diff --git a/app/src/locales.ts b/app/src/locales.ts index 122df475..a76f020b 100644 --- a/app/src/locales.ts +++ b/app/src/locales.ts @@ -202,6 +202,7 @@ const ES = { welcomeTryAgain: "Probar de nuevo", welcomeStep: "Paso {one}", welcomeKeysOver: "1 · El atajo", + welcomeKeysAloneOver: "El atajo", welcomeKeysTitle: "Pruébalo ahora", welcomeKeysWhy: "Desde cualquier app, esta combinación abre tu historial.", welcomeKeysWaiting: "Esperando que lo pruebes…", @@ -479,6 +480,7 @@ const EN: Record = { welcomeTryAgain: "Try again", welcomeStep: "Step {one}", welcomeKeysOver: "1 · The shortcut", + welcomeKeysAloneOver: "The shortcut", welcomeKeysTitle: "Try it now", welcomeKeysWhy: "From any app, this combination opens your history.", welcomeKeysWaiting: "Waiting for you to try it…", diff --git a/app/src/tests/tour.test.tsx b/app/src/tests/tour.test.tsx index b21965a2..40661e90 100644 --- a/app/src/tests/tour.test.tsx +++ b/app/src/tests/tour.test.tsx @@ -230,3 +230,20 @@ describe("las novedades tras una actualización", () => { expect(theWindow.close).toHaveBeenCalled(); }); }); + +describe("abrir la app cuando ya está corriendo", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("enseña solo el atajo, sin el recorrido, y se cierra con un botón", async () => { + asked.greeting = { kind: "keys" }; + asked.locale = "en"; + render(); + + expect(await screen.findByText("The shortcut")).toBeInTheDocument(); + expect(screen.queryByText("1 · The shortcut")).toBeNull(); + await press("Close"); + expect(theWindow.close).toHaveBeenCalled(); + }); +}); diff --git a/app/src/ui/Welcome.tsx b/app/src/ui/Welcome.tsx index 023ff281..62c7b9ff 100644 --- a/app/src/ui/Welcome.tsx +++ b/app/src/ui/Welcome.tsx @@ -13,6 +13,7 @@ const RELEASES = "https://github.com/rgdevment/CopyPaste/releases"; export type Greeting = | { kind: "tour"; former: boolean } | { kind: "news"; versions: string[] } + | { kind: "keys" } | { kind: "nothing" }; type Step = "former" | "hello" | "trust" | "keys" | "where" | "use"; @@ -92,6 +93,9 @@ export default function Welcome() { )} {kept && greeting?.kind === "news" && } + {kept && greeting?.kind === "keys" && ( + + )} ); } @@ -244,14 +248,18 @@ function TryIt({ mac, dots, next, + alone = false, }: { kept: Kept; change: (what: Partial) => Promise; mac: boolean; dots: ReactNode; next: () => void; + alone?: boolean; }) { const { keys, spare, recheck } = useKeys(kept.shortcut); + const over = alone ? t("welcomeKeysAloneOver") : t("welcomeKeysOver"); + const skip = alone ? t("chromeClose") : t("welcomeSkipStep"); const [tried, setTried] = useState(false); useEffect(() => { @@ -266,9 +274,9 @@ function TryIt({ if (taken) { return ( - } right={null}> + } right={null}> 0 ? "welcomeKeysTakenWhy" : "welcomeKeysTakenAlone", shown)} /> @@ -297,16 +305,14 @@ function TryIt({ tried ? ( setTried(false)} /> ) : ( - + ) } - right={tried ? : null} + right={ + tried ? : null + } > - +
{caps(kept.shortcut, mac).map((one, index) => ( From 71efa87c9c53823cdc845e19f25899f60fc20a70 Mon Sep 17 00:00:00 2001 From: rgdevment Date: Tue, 6 Oct 2026 01:52:20 -0300 Subject: [PATCH 2/2] fix(gui): reopening on macOS leaves visible windows alone, and the welcome state has one writer --- app/src-tauri/src/lib.rs | 5 ++++- app/src-tauri/src/welcome.rs | 27 ++++++++++++++++----------- app/src-tauri/src/welcome_test.rs | 12 +++++++++++- 3 files changed, 31 insertions(+), 13 deletions(-) diff --git a/app/src-tauri/src/lib.rs b/app/src-tauri/src/lib.rs index 7075083f..fb92b2ca 100644 --- a/app/src-tauri/src/lib.rs +++ b/app/src-tauri/src/lib.rs @@ -79,7 +79,10 @@ pub fn run() { } => api.prevent_exit(), tauri::RunEvent::Exit => panel::quit(app), #[cfg(target_os = "macos")] - tauri::RunEvent::Reopen { .. } => welcome::reopened(app), + tauri::RunEvent::Reopen { + has_visible_windows: false, + .. + } => welcome::reopened(app), _ => {} }); } diff --git a/app/src-tauri/src/welcome.rs b/app/src-tauri/src/welcome.rs index a63832c4..bc10acf7 100644 --- a/app/src-tauri/src/welcome.rs +++ b/app/src-tauri/src/welcome.rs @@ -148,18 +148,27 @@ pub fn open(app: &AppHandle) { } } +pub fn remembers(greeting: Option<&Greeting>) -> bool { + !matches!(greeting, Some(Greeting::Keys)) +} + fn only_keys(app: &AppHandle) -> bool { app.try_state::() - .and_then(|state| state.0.lock().ok().map(|held| held.clone())) - .is_some_and(|held| held == Some(Greeting::Keys)) + .and_then(|state| state.0.lock().ok().map(|held| !remembers(held.as_ref()))) + .unwrap_or(false) } -pub fn reopened(app: &AppHandle) { - if app.get_webview_window(LABEL).is_none() - && let Some(state) = app.try_state::() +fn hold(app: &AppHandle, greeting: Greeting) { + if let Some(state) = app.try_state::() && let Ok(mut held) = state.0.lock() { - *held = Some(Greeting::Keys); + *held = Some(greeting); + } +} + +pub fn reopened(app: &AppHandle) { + if app.get_webview_window(LABEL).is_none() { + hold(app, Greeting::Keys); } open(app); } @@ -177,11 +186,7 @@ pub fn greeting(state: tauri::State<'_, Now>) -> Greeting { #[tauri::command] pub async fn tour(app: AppHandle) { let wanted = Greeting::Tour { former: false }; - if let Some(state) = app.try_state::() - && let Ok(mut held) = state.0.lock() - { - *held = Some(wanted.clone()); - } + hold(&app, wanted.clone()); if app.get_webview_window(LABEL).is_some() { let _ = app.emit_to(LABEL, "greeting", wanted); } diff --git a/app/src-tauri/src/welcome_test.rs b/app/src-tauri/src/welcome_test.rs index 894d2dcc..27309442 100644 --- a/app/src-tauri/src/welcome_test.rs +++ b/app/src-tauri/src/welcome_test.rs @@ -1,4 +1,4 @@ -use super::{Greeting, behind, decide, told}; +use super::{Greeting, behind, decide, remembers, told}; fn said(all: &[&str]) -> Vec { all.iter().map(|one| (*one).to_owned()).collect() @@ -146,3 +146,13 @@ fn opening_it_again_asks_the_window_for_the_shortcut_alone() { serde_json::json!({ "kind": "keys" }) ); } + +#[test] +fn closing_the_shortcut_alone_leaves_the_welcome_as_it_was() { + assert!(!remembers(Some(&Greeting::Keys))); + assert!(remembers(Some(&Greeting::Tour { former: false }))); + assert!(remembers(Some(&Greeting::News { + versions: vec!["3.0.0".to_owned()] + }))); + assert!(remembers(None)); +}