Skip to content

Conversation

@Excellencedev
Copy link

@Excellencedev Excellencedev commented Jan 24, 2026

Description

Adds deeplink support for controlling recording state and a new Raycast extension.

Changes

  • apps/desktop:
    • Added PauseRecording, ResumeRecording, CycleMicrophone, CycleCamera, StartDefaultRecording to deeplink_actions.rs.
    • Implemented helper functions in lib.rs for pausing, resuming, and cycling inputs.
  • apps/raycast-extension:
    • Created a new Raycast extension cap-control with commands to Start, Stop, Pause, Resume, and Switch inputs.

How to test

  1. Run pnpm dev:desktop.
  2. Open cap-desktop://action?value="start_default_recording" to start.
  3. Open cap-desktop://action?value="pause_recording" to pause.
  4. Open cap-desktop://action?value="resume_recording" to resume.
  5. Open cap-desktop://action?value="cycle_microphone" to switch mic.
  6. Install the Raycast extension from apps/raycast-extension and test commands.

/closes #1540
/claim #1540

Greptile Summary

Added deeplink support for controlling recording state (pause, resume, cycle microphone/camera) and created a new Raycast extension to invoke these deeplinks. The Raycast extension provides commands for starting, stopping, pausing, resuming recordings, and switching camera/microphone inputs.

Major Changes:

  • Added 6 new deeplink actions: StartDefaultRecording, PauseRecording, ResumeRecording, SetMicrophone, SetCamera, CycleMicrophone, CycleCamera
  • Implemented helper functions cycle_mic_input() and cycle_camera_input() in lib.rs
  • Created Raycast extension with 6 commands that trigger deeplinks via cap-desktop:// URLs
  • Reused existing recording::pause_recording() and recording::resume_recording() functions from recording.rs

Critical Issues Found:

  • Multiple syntax errors prevent compilation: duplicate match arms in deeplink_actions.rs:190-200, duplicate permission checks and orphaned braces in lib.rs:635-641, duplicate variable assignment in lib.rs:667-671
  • New commands cycle_mic_input and cycle_camera_input not registered in the command collection, making them uncallable
  • Missing permissions checks in CycleMicrophone and CycleCamera deeplink handlers (inconsistent with SetMicrophone and SetCamera)
  • Documentation has conflicting build instructions

Positive Notes:

  • Proper reuse of existing recording functions maintains event emission consistency
  • Permission checks properly implemented for most operations
  • Clean Raycast extension architecture with proper error handling
  • Good separation of concerns between deeplink parsing and execution

Confidence Score: 1/5

  • This PR cannot be merged - it contains multiple critical syntax errors that prevent compilation
  • Score reflects compilation-breaking syntax errors in both deeplink_actions.rs (duplicate match arms) and lib.rs (duplicate permission checks, orphaned braces, duplicate variable assignments). Additionally, new commands are not registered making them uncallable. These are blocking issues that must be fixed before merge.
  • apps/desktop/src-tauri/src/lib.rs requires immediate attention due to multiple syntax errors. apps/desktop/src-tauri/src/deeplink_actions.rs has duplicate match arms causing compilation failure.

Important Files Changed

Filename Overview
apps/desktop/src-tauri/src/deeplink_actions.rs Added new deeplink actions (pause, resume, cycle inputs). Has critical duplicate match arms causing compilation failure.
apps/desktop/src-tauri/src/lib.rs Added cycle_mic_input and cycle_camera_input functions. Has multiple critical syntax errors: duplicate permission checks, orphaned braces, duplicate variable assignments, and missing command registration.
apps/raycast-extension/README.md Documentation for Raycast extension. Has duplicate build step with conflicting commands.

Sequence Diagram

sequenceDiagram
    participant Raycast
    participant DeeplinkURL
    participant DeeplinkHandler
    participant App
    participant Recording
    participant MicFeed
    participant Camera

    Note over Raycast,Camera: Start Recording Flow
    Raycast->>DeeplinkURL: cap-desktop://action?value="start_default_recording"
    DeeplinkURL->>DeeplinkHandler: Parse action
    DeeplinkHandler->>DeeplinkHandler: Check screen recording permission
    DeeplinkHandler->>App: list_displays()
    App-->>DeeplinkHandler: displays[]
    DeeplinkHandler->>Recording: start_recording(display, mode=Instant)
    Recording-->>Raycast: Recording started

    Note over Raycast,Camera: Pause/Resume Flow
    Raycast->>DeeplinkURL: cap-desktop://action?value="pause_recording"
    DeeplinkURL->>DeeplinkHandler: Parse action
    DeeplinkHandler->>Recording: pause_recording()
    Recording->>Recording: Emit RecordingEvent::Paused
    Recording-->>Raycast: Recording paused
    
    Raycast->>DeeplinkURL: cap-desktop://action?value="resume_recording"
    DeeplinkURL->>DeeplinkHandler: Parse action
    DeeplinkHandler->>Recording: resume_recording()
    Recording->>Recording: Emit RecordingEvent::Resumed
    Recording-->>Raycast: Recording resumed

    Note over Raycast,Camera: Cycle Microphone Flow
    Raycast->>DeeplinkURL: cap-desktop://action?value="cycle_microphone"
    DeeplinkURL->>DeeplinkHandler: Parse action
    DeeplinkHandler->>App: cycle_mic_input()
    App->>App: Check microphone permission
    App->>MicFeed: list()
    MicFeed-->>App: mic_list[]
    App->>App: Find current + select next
    App->>App: set_mic_input(next_label)
    App-->>Raycast: Microphone switched

    Note over Raycast,Camera: Cycle Camera Flow
    Raycast->>DeeplinkURL: cap-desktop://action?value="cycle_camera"
    DeeplinkURL->>DeeplinkHandler: Parse action
    DeeplinkHandler->>App: cycle_camera_input()
    App->>App: Check camera permission
    App->>Camera: list_cameras()
    Camera-->>App: camera_list[]
    App->>App: Find current + select next
    App->>App: set_camera_input(next_id)
    App-->>Raycast: Camera switched

    Note over Raycast,Camera: Stop Recording Flow
    Raycast->>DeeplinkURL: cap-desktop://action?value="stop_recording"
    DeeplinkURL->>DeeplinkHandler: Parse action
    DeeplinkHandler->>Recording: stop_recording()
    Recording-->>Raycast: Recording stopped
Loading

@Excellencedev
Copy link
Author

@richiemcilroy Please review

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

12 files reviewed, 3 comments

Edit Code Review Agent Settings | Greptile

Excellencedev and others added 4 commits January 24, 2026 11:51
Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com>
Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com>
Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com>
Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com>
@Excellencedev
Copy link
Author

@greptileai please rereview and update description

Excellencedev and others added 3 commits January 24, 2026 11:57
Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com>
Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com>
Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com>
Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

Additional Comments (1)

  1. apps/raycast-extension/src/utils.ts, line 1-30 (link)

    syntax: Duplicate imports (lines 1 and 3) and duplicate function implementation (lines 7-17 and 18-30). Remove the first set.

12 files reviewed, 3 comments

Edit Code Review Agent Settings | Greptile

Excellencedev and others added 2 commits January 24, 2026 11:58
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
@Excellencedev
Copy link
Author

@greptileai please rereview and update description

Excellencedev and others added 2 commits January 24, 2026 12:06
Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com>
Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com>
Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

12 files reviewed, 3 comments

Edit Code Review Agent Settings | Greptile

Excellencedev and others added 4 commits January 24, 2026 12:07
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com>
Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com>
Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com>
@Excellencedev
Copy link
Author

@greptileai please rereview and update description

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

12 files reviewed, 7 comments

Edit Code Review Agent Settings | Greptile

Excellencedev and others added 4 commits January 24, 2026 12:49
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
@Excellencedev
Copy link
Author

https://github.com/greptileai please rereview and update description

Excellencedev and others added 2 commits January 24, 2026 12:58
Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com>
Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com>
@Excellencedev
Copy link
Author

@greptileai rereview and update description

.map(|_| ())
}
DeepLinkAction::StartDefaultRecording => {
let permissions = crate::permissions::do_permissions_check(false);
Copy link

Choose a reason for hiding this comment

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

Minor security/UX thought: StartDefaultRecording is easy to trigger from outside the app (websites can open custom schemes). Might be worth gating recording-control deeplinks behind an opt-in setting or a lightweight confirmation when the trigger isn’t clearly user-initiated (e.g. Raycast).

#[specta::specta]
#[instrument(skip(state))]
pub async fn cycle_mic_input(state: MutableState<'_, App>) -> Result<(), String> {
if !permissions::do_permissions_check(false)
Copy link

Choose a reason for hiding this comment

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

If this is used via deeplink/Raycast, returning Ok(()) on denied permissions makes failures silent. I'd return an error here so callers/logs can distinguish no-op vs success.

Suggested change
if !permissions::do_permissions_check(false)
if !permissions::do_permissions_check(false).microphone.permitted() {
return Err("Microphone permission denied".to_string());
}

None => camera_list.first(),
};

let next_id = next_camera.map(|c| DeviceOrModelID::DeviceID(c.device_id().to_string()));
Copy link

Choose a reason for hiding this comment

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

Minor: using DeviceOrModelID::from_info here preserves ModelID when available (which is usually the more stable identifier).

Suggested change
let next_id = next_camera.map(|c| DeviceOrModelID::DeviceID(c.device_id().to_string()));
let next_id = next_camera.map(DeviceOrModelID::from_info);

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

12 files reviewed, 4 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +591 to +594
#[tauri::command]
#[specta::specta]
#[instrument(skip(state))]
pub async fn cycle_mic_input(state: MutableState<'_, App>) -> Result<(), String> {
Copy link
Contributor

Choose a reason for hiding this comment

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

logic: cycle_mic_input not registered in tauri_specta::collect_commands! around line 2673. Add it after set_camera_input on line 2675 so it's callable from deeplinks.

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

Comment:
**logic:** `cycle_mic_input` not registered in `tauri_specta::collect_commands!` around line 2673. Add it after `set_camera_input` on line 2675 so it's callable from deeplinks.

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

Comment on lines +628 to +631
#[tauri::command]
#[specta::specta]
#[instrument(skip(app_handle, state))]
pub async fn cycle_camera_input(
Copy link
Contributor

Choose a reason for hiding this comment

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

logic: cycle_camera_input not registered in tauri_specta::collect_commands! around line 2673. Add it after cycle_mic_input so it's callable from deeplinks.

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

Comment:
**logic:** `cycle_camera_input` not registered in `tauri_specta::collect_commands!` around line 2673. Add it after `cycle_mic_input` so it's callable from deeplinks.

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

Excellencedev and others added 4 commits January 24, 2026 13:04
Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com>
#[tauri::command]
#[specta::specta]
#[instrument(skip(state))]
pub async fn cycle_mic_input(state: MutableState<'_, App>) -> Result<(), String> {
Copy link

Choose a reason for hiding this comment

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

Since this is annotated as a #[tauri::command] + #[specta::specta], make sure it's also included in the tauri_specta::collect_commands![] list; otherwise it won't be exposed to the frontend/typegen.

Excellencedev and others added 2 commits January 24, 2026 13:26
Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com>
Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com>
Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com>
@Excellencedev
Copy link
Author

@greptileai review

"Productivity",
"Media"
],
"license": "MIT",
Copy link

Choose a reason for hiding this comment

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

This repo isn't MIT-licensed overall (see root LICENSE), so MIT here is probably misleading. I'd drop the field unless you intend to license this extension differently.

Suggested change
"license": "MIT",
"categories": [
"Productivity",
"Media"
],
"commands": [

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

Additional Comments (1)

  1. apps/desktop/src-tauri/src/lib.rs, line 2680-2682 (link)

    logic: cycle_mic_input and cycle_camera_input not registered in command collection. Add them after set_camera_input so they're callable from deeplinks.

12 files reviewed, 7 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +190 to +200
DeepLinkAction::PauseRecording => {
crate::recording::pause_recording(app.clone(), app.state()).await
}
DeepLinkAction::ResumeRecording => {
crate::recording::resume_recording(app.clone(), app.state()).await
}
crate::recording::pause_recording(app.clone(), app.state()).await
}
DeepLinkAction::ResumeRecording => {
crate::recording::resume_recording(app.clone(), app.state()).await
}
Copy link
Contributor

Choose a reason for hiding this comment

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

syntax: duplicate match arms cause compilation error. Lines 196-200 repeat the PauseRecording and ResumeRecording cases.

Suggested change
DeepLinkAction::PauseRecording => {
crate::recording::pause_recording(app.clone(), app.state()).await
}
DeepLinkAction::ResumeRecording => {
crate::recording::resume_recording(app.clone(), app.state()).await
}
crate::recording::pause_recording(app.clone(), app.state()).await
}
DeepLinkAction::ResumeRecording => {
crate::recording::resume_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::SetMicrophone { label } => {
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src-tauri/src/deeplink_actions.rs
Line: 190:200

Comment:
**syntax:** duplicate match arms cause compilation error. Lines 196-200 repeat the `PauseRecording` and `ResumeRecording` cases.

```suggestion
            DeepLinkAction::PauseRecording => {
                crate::recording::pause_recording(app.clone(), app.state()).await
            }
            DeepLinkAction::ResumeRecording => {
                crate::recording::resume_recording(app.clone(), app.state()).await
            }
            DeepLinkAction::SetMicrophone { label } => {
```

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

Comment on lines +635 to +641
if !permissions::do_permissions_check(false).camera.permitted() {
return Err("Camera permission denied".to_string());
}
if !permissions::do_permissions_check(false).camera.permitted() {
return Err("Camera permission denied".to_string());
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

syntax: duplicate permission check and orphaned closing brace cause compilation error. Lines 638-641 repeat the check and have an extra }.

Suggested change
if !permissions::do_permissions_check(false).camera.permitted() {
return Err("Camera permission denied".to_string());
}
if !permissions::do_permissions_check(false).camera.permitted() {
return Err("Camera permission denied".to_string());
}
}
if !permissions::do_permissions_check(false).camera.permitted() {
return Err("Camera permission denied".to_string());
}
let current_id = {
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src-tauri/src/lib.rs
Line: 635:641

Comment:
**syntax:** duplicate permission check and orphaned closing brace cause compilation error. Lines 638-641 repeat the check and have an extra `}`.

```suggestion
    if !permissions::do_permissions_check(false).camera.permitted() {
        return Err("Camera permission denied".to_string());
    }

    let current_id = {
```

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

Comment on lines +667 to +671
let next_id = next_camera.map(|c| DeviceOrModelID::DeviceID(c.device_id().to_string()));
let next_id = next_camera.map(|c| match c.model_id() {
Some(model_id) => DeviceOrModelID::ModelID(model_id.to_string()),
None => DeviceOrModelID::DeviceID(c.device_id().to_string()),
});
Copy link
Contributor

Choose a reason for hiding this comment

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

syntax: next_id is computed twice with different logic - the second assignment overwrites the first. The second one (lines 668-671) is the correct implementation as it prefers ModelID.

Suggested change
let next_id = next_camera.map(|c| DeviceOrModelID::DeviceID(c.device_id().to_string()));
let next_id = next_camera.map(|c| match c.model_id() {
Some(model_id) => DeviceOrModelID::ModelID(model_id.to_string()),
None => DeviceOrModelID::DeviceID(c.device_id().to_string()),
});
let next_id = next_camera.map(|c| match c.model_id() {
Some(model_id) => DeviceOrModelID::ModelID(model_id.to_string()),
None => DeviceOrModelID::DeviceID(c.device_id().to_string()),
});
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src-tauri/src/lib.rs
Line: 667:671

Comment:
**syntax:** `next_id` is computed twice with different logic - the second assignment overwrites the first. The second one (lines 668-671) is the correct implementation as it prefers ModelID.

```suggestion
    let next_id = next_camera.map(|c| match c.model_id() {
        Some(model_id) => DeviceOrModelID::ModelID(model_id.to_string()),
        None => DeviceOrModelID::DeviceID(c.device_id().to_string()),
    });
```

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

Comment on lines +18 to +19
3. `pnpm build`
3. `npm run build`
Copy link
Contributor

Choose a reason for hiding this comment

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

syntax: duplicate build step with conflicting commands. Line 18 says pnpm build but line 19 says npm run build.

Suggested change
3. `pnpm build`
3. `npm run build`
3. `npm run build`
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/raycast-extension/README.md
Line: 18:19

Comment:
**syntax:** duplicate build step with conflicting commands. Line 18 says `pnpm build` but line 19 says `npm run build`.

```suggestion
3. `npm run build`
```

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

Comment on lines +219 to +222
DeepLinkAction::CycleMicrophone => {
let state = app.state::<ArcLock<App>>();
crate::cycle_mic_input(state).await
}
Copy link
Contributor

Choose a reason for hiding this comment

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

style: missing permissions check before cycling microphone. Other microphone operations (like SetMicrophone on line 202) check permissions first.

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

Comment:
**style:** missing permissions check before cycling microphone. Other microphone operations (like `SetMicrophone` on line 202) check permissions first.

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

Comment on lines +223 to +226
DeepLinkAction::CycleCamera => {
let state = app.state::<ArcLock<App>>();
crate::cycle_camera_input(app.clone(), state).await
}
Copy link
Contributor

Choose a reason for hiding this comment

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

style: missing permissions check before cycling camera. Other camera operations (like SetCamera on line 211) check permissions first.

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

Comment:
**style:** missing permissions check before cycling camera. Other camera operations (like `SetCamera` on line 211) check permissions first.

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bounty: Deeplinks support + Raycast Extension

1 participant