From 38b9afb6b3b9eaabe1e155337402b9f3787ab007 Mon Sep 17 00:00:00 2001 From: whoami Date: Sat, 21 Feb 2026 01:29:13 +0800 Subject: [PATCH 1/5] feat(deeplink): add recording pause/resume/restart and device switch actions --- .../desktop/src-tauri/src/deeplink_actions.rs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/apps/desktop/src-tauri/src/deeplink_actions.rs b/apps/desktop/src-tauri/src/deeplink_actions.rs index fce75b4a84..be5837f50d 100644 --- a/apps/desktop/src-tauri/src/deeplink_actions.rs +++ b/apps/desktop/src-tauri/src/deeplink_actions.rs @@ -26,6 +26,16 @@ pub enum DeepLinkAction { mode: RecordingMode, }, StopRecording, + PauseRecording, + ResumeRecording, + TogglePauseRecording, + RestartRecording, + SwitchMicrophone { + mic_label: Option, + }, + SwitchCamera { + camera: Option, + }, OpenEditor { project_path: PathBuf, }, @@ -146,6 +156,24 @@ impl DeepLinkAction { DeepLinkAction::StopRecording => { crate::recording::stop_recording(app.clone(), app.state()).await } + DeepLinkAction::PauseRecording => { + crate::recording::pause_recording(app.clone(), app.state()).await + } + DeepLinkAction::ResumeRecording => { + crate::recording::resume_recording(app.clone(), app.state()).await + } + DeepLinkAction::TogglePauseRecording => { + crate::recording::toggle_pause_recording(app.clone(), app.state()).await + } + DeepLinkAction::RestartRecording => { + crate::recording::restart_recording(app.clone(), app.state()).await + } + DeepLinkAction::SwitchMicrophone { mic_label } => { + crate::set_mic_input(app.state(), mic_label).await + } + DeepLinkAction::SwitchCamera { camera } => { + crate::set_camera_input(app.clone(), app.state(), camera, None).await + } DeepLinkAction::OpenEditor { project_path } => { crate::open_project_from_path(Path::new(&project_path), app.clone()) } From e02ac717a303bd436b3109e0464ccbe002c4eb57 Mon Sep 17 00:00:00 2001 From: whoami Date: Sat, 21 Feb 2026 01:52:17 +0800 Subject: [PATCH 2/5] fix: return unit for restart deeplink action --- apps/desktop/src-tauri/src/deeplink_actions.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src-tauri/src/deeplink_actions.rs b/apps/desktop/src-tauri/src/deeplink_actions.rs index be5837f50d..f43bf2a0ee 100644 --- a/apps/desktop/src-tauri/src/deeplink_actions.rs +++ b/apps/desktop/src-tauri/src/deeplink_actions.rs @@ -166,7 +166,9 @@ impl DeepLinkAction { crate::recording::toggle_pause_recording(app.clone(), app.state()).await } DeepLinkAction::RestartRecording => { - crate::recording::restart_recording(app.clone(), app.state()).await + crate::recording::restart_recording(app.clone(), app.state()) + .await + .map(|_| ()) } DeepLinkAction::SwitchMicrophone { mic_label } => { crate::set_mic_input(app.state(), mic_label).await From 7bd37b3cc33f86d94c9d695d365fc2c126f87d0a Mon Sep 17 00:00:00 2001 From: whoami Date: Sat, 21 Feb 2026 03:39:50 +0800 Subject: [PATCH 3/5] fix: handle empty mic label in deeplink switch --- apps/desktop/src-tauri/src/deeplink_actions.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src-tauri/src/deeplink_actions.rs b/apps/desktop/src-tauri/src/deeplink_actions.rs index f43bf2a0ee..c5389dd0f3 100644 --- a/apps/desktop/src-tauri/src/deeplink_actions.rs +++ b/apps/desktop/src-tauri/src/deeplink_actions.rs @@ -171,7 +171,11 @@ impl DeepLinkAction { .map(|_| ()) } DeepLinkAction::SwitchMicrophone { mic_label } => { - crate::set_mic_input(app.state(), mic_label).await + crate::set_mic_input( + app.state::>(), + mic_label.filter(|label| !label.trim().is_empty()), + ) + .await } DeepLinkAction::SwitchCamera { camera } => { crate::set_camera_input(app.clone(), app.state(), camera, None).await From cf8787ff8a358317184a7b7df05b0ec954b53940 Mon Sep 17 00:00:00 2001 From: whoami Date: Sat, 21 Feb 2026 04:21:41 +0800 Subject: [PATCH 4/5] fix(deeplink): error when pause/resume/toggle without active recording --- apps/desktop/src-tauri/src/deeplink_actions.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/apps/desktop/src-tauri/src/deeplink_actions.rs b/apps/desktop/src-tauri/src/deeplink_actions.rs index c5389dd0f3..ef72bfe9d9 100644 --- a/apps/desktop/src-tauri/src/deeplink_actions.rs +++ b/apps/desktop/src-tauri/src/deeplink_actions.rs @@ -157,13 +157,25 @@ impl DeepLinkAction { crate::recording::stop_recording(app.clone(), app.state()).await } DeepLinkAction::PauseRecording => { - crate::recording::pause_recording(app.clone(), app.state()).await + let state = app.state::>(); + if state.read().await.current_recording().is_none() { + return Err("Recording not in progress".to_string()); + } + crate::recording::pause_recording(app.clone(), state).await } DeepLinkAction::ResumeRecording => { - crate::recording::resume_recording(app.clone(), app.state()).await + let state = app.state::>(); + if state.read().await.current_recording().is_none() { + return Err("Recording not in progress".to_string()); + } + crate::recording::resume_recording(app.clone(), state).await } DeepLinkAction::TogglePauseRecording => { - crate::recording::toggle_pause_recording(app.clone(), app.state()).await + let state = app.state::>(); + if state.read().await.current_recording().is_none() { + return Err("Recording not in progress".to_string()); + } + crate::recording::toggle_pause_recording(app.clone(), state).await } DeepLinkAction::RestartRecording => { crate::recording::restart_recording(app.clone(), app.state()) From b6264a0c6f85bb4e2b54132558e47206a7a5ba6a Mon Sep 17 00:00:00 2001 From: whoami Date: Sat, 21 Feb 2026 08:51:42 +0800 Subject: [PATCH 5/5] test(deeplink): cover action URL parsing paths --- .../desktop/src-tauri/src/deeplink_actions.rs | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/apps/desktop/src-tauri/src/deeplink_actions.rs b/apps/desktop/src-tauri/src/deeplink_actions.rs index ef72bfe9d9..5087d4cdb2 100644 --- a/apps/desktop/src-tauri/src/deeplink_actions.rs +++ b/apps/desktop/src-tauri/src/deeplink_actions.rs @@ -201,3 +201,46 @@ impl DeepLinkAction { } } } + +#[cfg(test)] +mod tests { + use super::{ActionParseFromUrlError, DeepLinkAction}; + use tauri::Url; + + fn action_url(payload: &str) -> Url { + let mut url = Url::parse("cap-desktop://action").expect("valid action url"); + url.query_pairs_mut().append_pair("value", payload); + url + } + + #[test] + fn parses_pause_recording_action() { + let url = action_url(r#""pause_recording""#); + + let action = DeepLinkAction::try_from(&url).expect("parse pause action"); + assert!(matches!(action, DeepLinkAction::PauseRecording)); + } + + #[test] + fn parses_switch_microphone_action() { + let url = action_url(r#"{"switch_microphone":{"mic_label":"Studio Mic"}}"#); + + let action = DeepLinkAction::try_from(&url).expect("parse switch microphone action"); + match action { + DeepLinkAction::SwitchMicrophone { mic_label } => { + assert_eq!(mic_label.as_deref(), Some("Studio Mic")); + } + other => panic!("unexpected action: {other:?}"), + } + } + + #[test] + fn rejects_non_action_domain() { + let mut url = Url::parse("cap-desktop://signin").expect("valid signin url"); + url.query_pairs_mut() + .append_pair("value", r#""stop_recording""#); + + let error = DeepLinkAction::try_from(&url).expect_err("signin deeplink is not action"); + assert!(matches!(error, ActionParseFromUrlError::NotAction)); + } +}