Skip to content

feat: Add Deeplinks for recording controls and Raycast Extension#1824

Closed
pon024587-collab wants to merge 2 commits into
CapSoftware:mainfrom
pon024587-collab:feat/deeplinks-raycast
Closed

feat: Add Deeplinks for recording controls and Raycast Extension#1824
pon024587-collab wants to merge 2 commits into
CapSoftware:mainfrom
pon024587-collab:feat/deeplinks-raycast

Conversation

@pon024587-collab
Copy link
Copy Markdown

@pon024587-collab pon024587-collab commented May 15, 2026

Description

This PR implements extended deep link support for recording controls and a new Raycast extension, fulfilling the requirements for issue #1540.

Added Deep Link Actions:

  • start_default_recording: Starts a recording using the last saved settings.
  • pause_recording: Pauses the active recording.
  • resume_recording: Resumes the paused recording.
  • switch_mic: Switches the microphone input (requires mic_label).
  • switch_camera: Switches the camera input (requires camera_id as DeviceOrModelID).

Raycast Extension:

  • Located in apps/raycast-extension.
  • Commands for Start/Stop/Pause/Resume recording.
  • Commands for switching Microphone and Camera with arguments.

Related Issue

Fixes #1540

Greptile Summary

This PR adds deep link support for five new recording control actions (start_default_recording, pause_recording, resume_recording, switch_mic, switch_camera) to the Rust backend, and introduces a new Raycast extension that triggers those actions via cap:// URLs.

  • The StartDefaultRecording handler has a compile-breaking type error: capture_system_audio is bool in StartRecordingInputs but the new code passes Some(settings.system_audio) (Option<bool>), which will prevent the desktop app from building.
  • The PauseRecording and ResumeRecording deep link handlers skip the RecordingEvent::Paused / RecordingEvent::Resumed emissions that the existing Tauri commands fire, leaving the UI out of sync when these actions are triggered via deep link.
  • The Raycast extension's switch-camera command unconditionally wraps the user input as a DeviceID, making cameras identified by ModelID unreachable via this command.

Confidence Score: 2/5

Not safe to merge: the desktop app will fail to compile due to a type mismatch in the new StartDefaultRecording handler, and the pause/resume deep link paths silently skip UI event emissions.

The StartDefaultRecording handler passes Some(settings.system_audio) (Option<bool>) into a field typed bool, which is a hard compile error that blocks the entire desktop build. Additionally, the pause/resume handlers omit the RecordingEvent emissions that keep the UI in sync, so those actions would silently leave the UI showing stale state even if the compile error were fixed first.

apps/desktop/src-tauri/src/deeplink_actions.rs needs the most attention — fix the type mismatch on line 132 and add the missing event emissions in the PauseRecording/ResumeRecording arms.

Important Files Changed

Filename Overview
apps/desktop/src-tauri/src/deeplink_actions.rs Adds 5 new deep link actions; contains a compile-breaking type mismatch (Option<bool> passed where bool expected) and missing RecordingEvent emissions in the pause/resume handlers.
apps/raycast-extension/package.json New Raycast extension manifest declaring 6 commands with correct metadata and dependencies.
apps/raycast-extension/src/utils.ts Shared utility that opens a cap://action deep link; uses any type, losing type safety for the action payload.
apps/raycast-extension/src/switch-camera.ts Switch-camera command that unconditionally wraps the user input as DeviceID, making ModelID-identified cameras inaccessible.
apps/raycast-extension/src/switch-mic.ts Switch-mic command that passes the user-supplied label directly; correct and consistent with the Rust handler.
apps/raycast-extension/src/start-recording.ts Minimal command that fires the start_default_recording deep link action; straightforward and correct.
apps/raycast-extension/src/stop-recording.ts Minimal command that fires the stop_recording deep link action; straightforward and correct.
apps/raycast-extension/src/pause-recording.ts Minimal command that fires the pause_recording deep link action; correct on the client side.
apps/raycast-extension/src/resume-recording.ts Minimal command that fires the resume_recording deep link action; correct on the client side.
Prompt To Fix All With AI
Fix the following 4 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 4
apps/desktop/src-tauri/src/deeplink_actions.rs:132
**Type mismatch: compile error in `StartDefaultRecording`**

`StartRecordingInputs.capture_system_audio` is typed as `bool` (see `recording.rs` line 515), but this line passes `Some(settings.system_audio)` which is `Option<bool>`. This is a type mismatch that will prevent the project from compiling. The `Some(...)` wrapper must be removed: use `capture_system_audio: settings.system_audio` instead.

### Issue 2 of 4
apps/desktop/src-tauri/src/deeplink_actions.rs:179-193
**Missing `RecordingEvent` emissions after pause/resume**

The existing Tauri commands `pause_recording` and `resume_recording` in `recording.rs` (lines 1516, 1530) emit `RecordingEvent::Paused` and `RecordingEvent::Resumed` after the operation so the UI can update its state. The new deep link handlers call `recording.pause()` / `recording.resume()` but omit those event emissions. As a result, triggering pause or resume via a deep link will leave the UI stuck showing the pre-action recording state (e.g. the pause button will still show as active after pausing).

### Issue 3 of 4
apps/raycast-extension/src/utils.ts:3
Using `any` for the `action` parameter bypasses TypeScript's type checking. Since the action can be either a plain string (for simple actions like `"stop_recording"`) or a structured object (for actions with parameters), a union type provides better safety and documents the API contract.

```suggestion
type CapAction =
  | string
  | { switch_mic: { mic_label: string } }
  | { switch_camera: { camera_id: { DeviceID: string } } };

export async function executeCapAction(action: CapAction) {
```

### Issue 4 of 4
apps/raycast-extension/src/switch-camera.ts:4-8
**`DeviceID` hardcoded; `ModelID` cameras are unreachable**

The Rust enum `DeviceOrModelID` has two variants: `DeviceID` and `ModelID`. The Raycast command always wraps the user-supplied string as `{ DeviceID: ... }`, so any camera that is identified by a model ID (common for virtual or continuity cameras) cannot be switched to via this command. Consider either accepting the variant as a second argument or documenting that only physical device IDs are supported.

Reviews (1): Last reviewed commit: "feat: add deeplink support for recording..." | Re-trigger Greptile

Greptile also left 4 inline comments on this PR.

@superagent-security superagent-security Bot added contributor:verified Contributor passed trust analysis. pr:flagged PR flagged for review by security analysis. labels May 15, 2026
Copy link
Copy Markdown

@superagent-security superagent-security Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Superagent found 1 security concern(s).

app.state(),
crate::recording::StartRecordingInputs {
capture_target: target,
capture_system_audio: Some(settings.system_audio),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0 Type mismatch: compile error in StartDefaultRecording

StartRecordingInputs.capture_system_audio is typed as bool (see recording.rs line 515), but this line passes Some(settings.system_audio) which is Option<bool>. This is a type mismatch that will prevent the project from compiling. The Some(...) wrapper must be removed: use capture_system_audio: settings.system_audio instead.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src-tauri/src/deeplink_actions.rs
Line: 132

Comment:
**Type mismatch: compile error in `StartDefaultRecording`**

`StartRecordingInputs.capture_system_audio` is typed as `bool` (see `recording.rs` line 515), but this line passes `Some(settings.system_audio)` which is `Option<bool>`. This is a type mismatch that will prevent the project from compiling. The `Some(...)` wrapper must be removed: use `capture_system_audio: settings.system_audio` instead.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +179 to +193
DeepLinkAction::PauseRecording => {
let state = app.state::<ArcLock<App>>();
let mut app_state = state.write().await;
if let Some(recording) = app_state.current_recording_mut() {
recording.pause().await.map_err(|e| e.to_string())?;
}
Ok(())
}
DeepLinkAction::ResumeRecording => {
let state = app.state::<ArcLock<App>>();
let mut app_state = state.write().await;
if let Some(recording) = app_state.current_recording_mut() {
recording.resume().await.map_err(|e| e.to_string())?;
}
Ok(())
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Missing RecordingEvent emissions after pause/resume

The existing Tauri commands pause_recording and resume_recording in recording.rs (lines 1516, 1530) emit RecordingEvent::Paused and RecordingEvent::Resumed after the operation so the UI can update its state. The new deep link handlers call recording.pause() / recording.resume() but omit those event emissions. As a result, triggering pause or resume via a deep link will leave the UI stuck showing the pre-action recording state (e.g. the pause button will still show as active after pausing).

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src-tauri/src/deeplink_actions.rs
Line: 179-193

Comment:
**Missing `RecordingEvent` emissions after pause/resume**

The existing Tauri commands `pause_recording` and `resume_recording` in `recording.rs` (lines 1516, 1530) emit `RecordingEvent::Paused` and `RecordingEvent::Resumed` after the operation so the UI can update its state. The new deep link handlers call `recording.pause()` / `recording.resume()` but omit those event emissions. As a result, triggering pause or resume via a deep link will leave the UI stuck showing the pre-action recording state (e.g. the pause button will still show as active after pausing).

How can I resolve this? If you propose a fix, please make it concise.

Comment thread apps/raycast-extension/src/utils.ts Outdated
@@ -0,0 +1,7 @@
import { open } from "@raycast/api";

export async function executeCapAction(action: any) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Using any for the action parameter bypasses TypeScript's type checking. Since the action can be either a plain string (for simple actions like "stop_recording") or a structured object (for actions with parameters), a union type provides better safety and documents the API contract.

Suggested change
export async function executeCapAction(action: any) {
type CapAction =
| string
| { switch_mic: { mic_label: string } }
| { switch_camera: { camera_id: { DeviceID: string } } };
export async function executeCapAction(action: CapAction) {
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/raycast-extension/src/utils.ts
Line: 3

Comment:
Using `any` for the `action` parameter bypasses TypeScript's type checking. Since the action can be either a plain string (for simple actions like `"stop_recording"`) or a structured object (for actions with parameters), a union type provides better safety and documents the API contract.

```suggestion
type CapAction =
  | string
  | { switch_mic: { mic_label: string } }
  | { switch_camera: { camera_id: { DeviceID: string } } };

export async function executeCapAction(action: CapAction) {
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +4 to +8
await executeCapAction({
switch_camera: {
camera_id: { DeviceID: props.arguments.camera_id },
},
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 DeviceID hardcoded; ModelID cameras are unreachable

The Rust enum DeviceOrModelID has two variants: DeviceID and ModelID. The Raycast command always wraps the user-supplied string as { DeviceID: ... }, so any camera that is identified by a model ID (common for virtual or continuity cameras) cannot be switched to via this command. Consider either accepting the variant as a second argument or documenting that only physical device IDs are supported.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/raycast-extension/src/switch-camera.ts
Line: 4-8

Comment:
**`DeviceID` hardcoded; `ModelID` cameras are unreachable**

The Rust enum `DeviceOrModelID` has two variants: `DeviceID` and `ModelID`. The Raycast command always wraps the user-supplied string as `{ DeviceID: ... }`, so any camera that is identified by a model ID (common for virtual or continuity cameras) cannot be switched to via this command. Consider either accepting the variant as a second argument or documenting that only physical device IDs are supported.

How can I resolve this? If you propose a fix, please make it concise.

@superagent-security superagent-security Bot added pr:verified PR passed security analysis. and removed pr:flagged PR flagged for review by security analysis. labels May 15, 2026
@pon024587-collab pon024587-collab deleted the feat/deeplinks-raycast branch May 16, 2026 14:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor:verified Contributor passed trust analysis. pr:verified PR passed security analysis.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bounty: Deeplinks support + Raycast Extension

2 participants