Skip to content

feat: add deeplink support and Raycast extension (#1540)#1728

Open
bcornish1797 wants to merge 2 commits intoCapSoftware:mainfrom
bcornish1797:feat/1540-deeplinks-raycast-bcornish
Open

feat: add deeplink support and Raycast extension (#1540)#1728
bcornish1797 wants to merge 2 commits intoCapSoftware:mainfrom
bcornish1797:feat/1540-deeplinks-raycast-bcornish

Conversation

@bcornish1797
Copy link
Copy Markdown

@bcornish1797 bcornish1797 commented Apr 10, 2026

Summary

  • add path-based cap-desktop:// deeplinks for start/stop/pause/resume/toggle/restart plus microphone, camera, and settings actions
  • keep backward compatibility with the existing cap-desktop://action?value=... JSON deeplinks
  • avoid hardcoded device names in the Raycast extension and keep camera payloads aligned with Rust DeviceOrModelID handling
  • add desktop deeplink docs and a short demo video at apps/raycast/demo/cap-raycast-demo.mp4

/claim #1540

Validation

  • corepack pnpm --dir apps/raycast run typecheck

Notes

  • apps/raycast/demo/cap-raycast-demo.mp4 is included in this branch as a lightweight walkthrough video
  • I could not run the Rust-side cargo tests in this environment because Rust tooling is unavailable here
  • ray lint still hits Raycast package validation / standalone lint wiring issues in this monorepo context, but the extension source typechecks cleanly

Greptile Summary

This PR adds path-based cap-desktop:// deeplinks (e.g. record/start, record/stop, device/microphone, etc.) to the Tauri desktop app alongside a new Raycast extension that invokes them. The Rust side adds new DeepLinkAction variants with unit tests covering the key parse paths, and backward-compat with the existing JSON-payload format is preserved.

Confidence Score: 5/5

Safe to merge; all remaining findings are minor style/doc suggestions with no correctness impact.

The Rust parsing logic is sound, backward compatibility with legacy JSON deeplinks is preserved, unit tests cover the main parse paths, and the Raycast extension correctly maps form inputs to URL params. The two open findings are a dead code guard and a missing documentation entry — neither affects runtime behavior.

apps/raycast/src/deeplink.ts (dead guard), apps/desktop/src-tauri/DEEPLINKS.md (undocumented mic_off param)

Important Files Changed

Filename Overview
apps/desktop/src-tauri/src/deeplink_actions.rs Core deeplink parsing & dispatch — adds new path-based variants with good validation; backward compat preserved; unit tests cover major cases.
apps/raycast/src/deeplink.ts URL building and dispatch helper — contains a trivially-true safety guard that is dead code.
apps/raycast/src/start-recording.tsx Start-recording form with camera/mic/mode controls; validation and param construction are correct.
apps/raycast/src/switch-microphone.tsx Microphone-switch form; empty submission explicitly disables the mic (documented via description text).
apps/raycast/src/switch-camera.tsx Camera-switch form with device-ID / model-ID / off options; correct alignment with Rust DeviceOrModelID handling.
apps/desktop/src-tauri/DEEPLINKS.md Deeplink docs — missing documentation for the mic_off parameter that exists in the Rust parser for record/start.
apps/raycast/package.json Raycast extension manifest and deps; all nine commands correctly registered with matching file names.

Sequence Diagram

sequenceDiagram
    participant R as Raycast Extension
    participant OS as macOS open(1)
    participant T as Tauri deeplink handler
    participant P as path_action() parser
    participant A as DeepLinkAction::execute()

    R->>OS: open cap-desktop://record/start?...
    OS->>T: cap-desktop:// URL registered
    T->>P: try_from(&url)
    P-->>T: DeepLinkAction::StartRecordingPath{...}
    T->>A: action.execute(&app_handle)
    A-->>T: Ok(())

    R->>OS: open cap-desktop://device/microphone?label=...
    OS->>T: cap-desktop:// URL registered
    T->>P: try_from(&url)
    P-->>T: DeepLinkAction::SwitchMicrophone{...}
    T->>A: action.execute(&app_handle)
    A-->>T: Ok(())

    R->>OS: open cap-desktop://action?value={...json...}
    OS->>T: legacy JSON deeplink
    T->>P: try_from(&url) serde_json path
    P-->>T: DeepLinkAction (legacy)
    T->>A: action.execute(&app_handle)
    A-->>T: Ok(())
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: apps/raycast/src/deeplink.ts
Line: 41-43

Comment:
**Dead safety guard**

`buildCapUrl` always constructs the URL with the `cap-desktop://` prefix via the template literal, so `url.startsWith("cap-desktop://")` is unconditionally `true` and the branch can never be taken. The guard can be removed without changing behavior.

```suggestion
	const url = buildCapUrl(path, params);

	try {
```

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

---

This is a comment left during a code review.
Path: apps/desktop/src-tauri/DEEPLINKS.md
Line: 15-22

Comment:
**`mic_off` parameter not documented**

The Rust parser for `record/start` calls `parse_microphone_from_query(&query, "mic_label", "mic_off")`, meaning `mic_off=true` is a valid way to explicitly disable the microphone when starting a recording. This parameter is never mentioned in the docs — users relying on the docs will not know it exists.

Consider adding a bullet like:
```
- `mic_off=true` to disable microphone input (cannot be combined with `mic_label`)
```

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

Reviews (1): Last reviewed commit: "feat: add Cap deeplinks and Raycast exte..." | Re-trigger Greptile

Greptile also left 2 inline comments on this PR.

(2/5) Greptile learns from your feedback when you react with thumbs up/down!

Comment on lines +41 to +43
if (!url.startsWith("cap-desktop://")) {
throw new Error("Cap deeplink must use cap-desktop://");
}
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 Dead safety guard

buildCapUrl always constructs the URL with the cap-desktop:// prefix via the template literal, so url.startsWith("cap-desktop://") is unconditionally true and the branch can never be taken. The guard can be removed without changing behavior.

Suggested change
if (!url.startsWith("cap-desktop://")) {
throw new Error("Cap deeplink must use cap-desktop://");
}
const url = buildCapUrl(path, params);
try {
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/raycast/src/deeplink.ts
Line: 41-43

Comment:
**Dead safety guard**

`buildCapUrl` always constructs the URL with the `cap-desktop://` prefix via the template literal, so `url.startsWith("cap-desktop://")` is unconditionally `true` and the branch can never be taken. The guard can be removed without changing behavior.

```suggestion
	const url = buildCapUrl(path, params);

	try {
```

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

Comment on lines +15 to +22
- `capture_type`: `screen` or `window` (required)
- `target`: screen/window name exactly as shown in Cap (required)
- `capture_system_audio`: `true` / `false` (optional)
- `mic_label`: microphone label exactly as shown in Cap (optional)
- omitting `mic_label`, `device_id`, `model_id`, and `off` keeps the current Cap inputs unchanged
- camera:
- `device_id=<id>` or `model_id=<VID:PID>`
- `off=true` to disable camera
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 mic_off parameter not documented

The Rust parser for record/start calls parse_microphone_from_query(&query, "mic_label", "mic_off"), meaning mic_off=true is a valid way to explicitly disable the microphone when starting a recording. This parameter is never mentioned in the docs — users relying on the docs will not know it exists.

Consider adding a bullet like:

- `mic_off=true` to disable microphone input (cannot be combined with `mic_label`)
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src-tauri/DEEPLINKS.md
Line: 15-22

Comment:
**`mic_off` parameter not documented**

The Rust parser for `record/start` calls `parse_microphone_from_query(&query, "mic_label", "mic_off")`, meaning `mic_off=true` is a valid way to explicitly disable the microphone when starting a recording. This parameter is never mentioned in the docs — users relying on the docs will not know it exists.

Consider adding a bullet like:
```
- `mic_off=true` to disable microphone input (cannot be combined with `mic_label`)
```

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

@bcornish1797
Copy link
Copy Markdown
Author

Hi, it looks like the CI workflow is waiting for approval to run (first-time contributor from a fork). Could you please approve the workflow run? Thanks!

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.

1 participant