Skip to content

feat: deeplink controls + Raycast extension (#1540)#1905

Closed
nitin-rachabathuni wants to merge 3 commits into
CapSoftware:mainfrom
nitin-rachabathuni:bounty/issue-1540
Closed

feat: deeplink controls + Raycast extension (#1540)#1905
nitin-rachabathuni wants to merge 3 commits into
CapSoftware:mainfrom
nitin-rachabathuni:bounty/issue-1540

Conversation

@nitin-rachabathuni

@nitin-rachabathuni nitin-rachabathuni commented Jun 9, 2026

Copy link
Copy Markdown

Closes #1540

Summary

  • Fixed cap-desktop://action?... parsing by using host_str() instead of domain() (custom schemes return None for domain)
  • Added path-based deeplinks for recording control: record/start, record/stop, record/pause, record/resume, record/toggle-pause
  • Added device switching deeplinks: device/microphone?label=... and device/camera?model_id|device_id|label|off
  • Pauses active recording before mic/camera switches (matches in-app UI behavior)
  • Extracted start_recording_from_saved_settings() for reuse from deeplinks and hotkeys
  • Added Raycast extension at apps/raycast/ with Cap Control and Switch Device commands
  • Documented all routes in apps/desktop/src-tauri/DEEPLINKS.md
  • Added unit tests for deeplink parsing

Testing

  • npx tsc --noEmit in apps/raycast/ passes
  • Rust unit tests in deeplink_actions (run: cargo test deeplink_actions)
  • Manual macOS smoke test: open "cap-desktop://record/pause" with Cap running

Demo

Demo video to follow after macOS manual verification.

/claim #1540

Greptile Summary

This PR adds path-based deeplinks (record/*, device/*) for controlling Cap recordings and switching devices from external tools, fixes the original action host parsing bug (using host_str() instead of domain()), and ships a new Raycast extension that drives those deeplinks via system_profiler device enumeration.

  • Rust (deeplink_actions.rs): replaces the broken TryFrom impl with a clean parse_deeplink function, adds PauseRecording, ResumeRecording, TogglePauseRecording, SetMicrophone, SetCamera variants, and a pause_for_input_change helper; unit tests cover all new routes.
  • Refactor (lib.rs): extracts start_recording_from_saved_settings() and reuses it from both the RequestStartRecording event handler and the new StartSavedRecording deeplink variant.
  • Raycast extension (apps/raycast/): two commands — Cap Control (static action list) and Switch Device (dynamic system_profiler enumeration) — that open or copy deeplinks.

Confidence Score: 3/5

Two correctness bugs need fixing before merge: recordings can be left permanently paused when a device-switch deeplink targets an unknown device, and the Raycast Switch Device command can throw a runtime error by calling showToast after it has been dismissed.

The deeplink parsing refactor and Raycast extension are well-structured and include good test coverage, but both issues are in newly exercised code paths. A user sending cap-desktop://device/camera?label=UnknownCam will silently pause their recording with no recovery path. The showToast issue in switch-device.tsx can surface as an uncaught Raycast runtime error in normal use.

apps/desktop/src-tauri/src/deeplink_actions.rs (SetCamera/SetMicrophone execute handlers) and apps/raycast/src/switch-device.tsx (error handling in useEffect)

Important Files Changed

Filename Overview
apps/desktop/src-tauri/src/deeplink_actions.rs Core deeplink parsing refactor: fixes host_str() bug, adds record/device routes, and unit tests — but the SetCamera/SetMicrophone handlers pause first, then validate, leaving the recording paused if the device switch fails.
apps/desktop/src-tauri/src/lib.rs Extracts start_recording_from_saved_settings() for reuse; refactor preserves original behavior and is straightforward.
apps/raycast/src/switch-device.tsx Switch Device Raycast command; showToast is called outside the !cancelled guard, risking a Raycast runtime error when the command is dismissed mid-load.
apps/raycast/src/lib/devices.ts Heuristic system_profiler parser for microphones and cameras; logic is macOS-specific and best-effort but appropriate for the use case.
apps/raycast/src/lib/deeplinks.ts Static deeplink action registry and openCapDeeplink helper; clean and correct.
apps/raycast/src/cap-control.tsx Cap Control Raycast command; simple list over static actions, no issues.
apps/raycast/package.json New Raycast extension package manifest; dependencies look appropriate.
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
apps/desktop/src-tauri/src/deeplink_actions.rs:372-383
**Recording left paused on failed device switch**

`pause_for_input_change` runs before the device selector is validated. If `resolve_camera_selector` or `set_mic_input` returns an error, the `?` short-circuits and the recording is left permanently paused — the user has no way to tell why, and no resume is triggered. To fix, resolve and validate the camera/mic label before calling `pause_for_input_change`, then pause only once the switch is confirmed possible.

### Issue 2 of 2
apps/raycast/src/switch-device.tsx:21-33
`showToast` is outside the `!cancelled` guard, so if the component unmounts while `loadDeviceItems` is still in flight and that call rejects, Raycast will receive an API call from a dismissed command, which throws a runtime error.

```suggestion
			} catch (loadError) {
				const message =
					loadError instanceof Error ? loadError.message : String(loadError);
				if (!cancelled) {
					setItems([]);
					setError(message);
					await showToast({
						style: Toast.Style.Failure,
						title: "Failed to enumerate devices",
						message,
					});
				}
			} finally {
```

Reviews (1): Last reviewed commit: "feat: add deeplink controls and Raycast ..." | Re-trigger Greptile

Greptile also left 2 inline comments on this PR.

Extend cap-desktop deeplinks with path-based recording and device routes,
fix action-host parsing for custom URL schemes, and add a Raycast extension
with Cap Control and Switch Device commands.
@superagent-security

Copy link
Copy Markdown

Superagent didn't find any vulnerabilities or security issues in this PR.

Comment thread apps/desktop/src-tauri/src/deeplink_actions.rs
Comment thread apps/raycast/src/switch-device.tsx
@nitin-rachabathuni

nitin-rachabathuni commented Jun 9, 2026

Copy link
Copy Markdown
Author

Validation update

Automated checks

  • ✅ Raycast extension: npx tsc --noEmit passes
  • ✅ Superagent security scan: clean
  • ✅ 8 Rust unit tests added in deeplink_actions.rs (parsing + camera resolution)

Manual validation script

Added scripts/demo-bounty-1540.sh on the PR branch — re-run anytime:

./scripts/demo-bounty-1540.sh

Notes

  • Legacy cap-desktop://action?value=... deeplinks work on the stock Cap app (demo opens Settings).
  • New path-based routes (cap-desktop://record/pause, etc.) require Cap built from this PR — full E2E needs pnpm dev:desktop after sidecar build.
  • Rust cargo test deeplink_actions blocked locally by ffmpeg-sys-next bindgen on this macOS 26 env; CI should cover compile + tests.

Happy to iterate on any Greptile/maintainer feedback.

Validate microphone and camera targets before pausing the active recording,
and guard Raycast showToast when the Switch Device command is dismissed.
@nitin-rachabathuni

Copy link
Copy Markdown
Author

Addressed both Greptile P1 comments in 22a5613:

  1. deeplink_actions.rsSetMicrophone / SetCamera now resolve and validate the target device before calling pause_for_input_change, so a failed switch no longer leaves the recording paused.
  2. switch-device.tsxshowToast moved inside the !cancelled guard to avoid Raycast runtime errors when the command is dismissed mid-load.

Review threads marked resolved.

@nitin-rachabathuni

Copy link
Copy Markdown
Author

Overnight status (no new code changes needed):

  • Both Greptile P1 threads remain resolved (22a5613fb)
  • Security checks: Contributor trust, Socket, Superagent — all green
  • Vercel FAILURE is fork authorization (vercel.com/git/authorize) — expected for external contributors; not a code/build regression from this PR

PR is mergeable from GitHub’s perspective; awaiting maintainer review in a crowded field (#1540).

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

2 participants