feat: Add deep link actions for recording control and Raycast extension#1744
feat: Add deep link actions for recording control and Raycast extension#1744BossChaos wants to merge 1 commit intoCapSoftware:mainfrom
Conversation
- Added PauseRecording, ResumeRecording, TogglePauseRecording actions - Added SwitchMicrophone and SwitchCamera actions - Created Raycast extension with 8 commands for full Cap control - Extension uses cap-desktop:// deep link protocol - Commands: start/stop/pause/resume/toggle recording, switch mic/camera, open settings
| @@ -0,0 +1,47 @@ | |||
| import { Action, ActionPanel, Icon, List, showToast, Toast } from "@raycast/api"; | |||
| import { sendDeepLink, getAvailableMicrophones } from "../deeplink"; | |||
There was a problem hiding this comment.
Broken import path — compilation error across all commands
All command files import from "../deeplink", but deeplink.ts lives in the same src/ directory. With moduleResolution: "node" (per tsconfig.json), "../deeplink" resolves to apps/raycast-extension/deeplink.ts — a file that does not exist. The correct relative import is "./deeplink". The same broken path appears in every command file (stop-recording.tsx, start-recording.tsx, pause-recording.tsx, resume-recording.tsx, toggle-pause.tsx, switch-camera.tsx).
| import { sendDeepLink, getAvailableMicrophones } from "../deeplink"; | |
| import { sendDeepLink, getAvailableMicrophones } from "./deeplink"; |
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/raycast-extension/src/switch-microphone.tsx
Line: 2
Comment:
**Broken import path — compilation error across all commands**
All command files import `from "../deeplink"`, but `deeplink.ts` lives in the same `src/` directory. With `moduleResolution: "node"` (per `tsconfig.json`), `"../deeplink"` resolves to `apps/raycast-extension/deeplink.ts` — a file that does not exist. The correct relative import is `"./deeplink"`. The same broken path appears in every command file (`stop-recording.tsx`, `start-recording.tsx`, `pause-recording.tsx`, `resume-recording.tsx`, `toggle-pause.tsx`, `switch-camera.tsx`).
```suggestion
import { sendDeepLink, getAvailableMicrophones } from "./deeplink";
```
How can I resolve this? If you propose a fix, please make it concise.| return ( | ||
| <ActionPanel> | ||
| <Action title="Stop Recording" icon={Icon.Stop} onAction={handleStop} /> | ||
| </ActionPanel> | ||
| ); |
There was a problem hiding this comment.
mode: "view" commands cannot return a bare <ActionPanel>
In Raycast, a command declared with "mode": "view" must return a top-level view component (<Detail>, <List>, <Form>, etc.) as its root — <ActionPanel> is only valid as a child of those views. Returning <ActionPanel> directly will cause Raycast to display a blank or broken screen. The same issue exists in start-recording.tsx, pause-recording.tsx, resume-recording.tsx, and toggle-pause.tsx.
For fire-and-forget commands that don't need a UI, change the "mode" to "no-view" in package.json and call showHUD(...) instead of showToast(...) (toasts require an active view).
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/raycast-extension/src/stop-recording.tsx
Line: 27-31
Comment:
**`mode: "view"` commands cannot return a bare `<ActionPanel>`**
In Raycast, a command declared with `"mode": "view"` must return a top-level view component (`<Detail>`, `<List>`, `<Form>`, etc.) as its root — `<ActionPanel>` is only valid as a child of those views. Returning `<ActionPanel>` directly will cause Raycast to display a blank or broken screen. The same issue exists in `start-recording.tsx`, `pause-recording.tsx`, `resume-recording.tsx`, and `toggle-pause.tsx`.
For fire-and-forget commands that don't need a UI, change the `"mode"` to `"no-view"` in `package.json` and call `showHUD(...)` instead of `showToast(...)` (toasts require an active view).
How can I resolve this? If you propose a fix, please make it concise.| return ( | ||
| <List> | ||
| <List.Section title="Select Microphone"> | ||
| {["Default", "MacBook Pro Microphone", "External Microphone"].map((label) => ( |
There was a problem hiding this comment.
Hardcoded device list —
getAvailableMicrophones imported but unused
The component iterates a hardcoded array ["Default", "MacBook Pro Microphone", "External Microphone"] instead of calling the already-imported getAvailableMicrophones(). The same pattern exists in switch-camera.tsx (hardcoded list; getAvailableCameras isn't even imported). Additionally, both getAvailableMicrophones and getAvailableCameras in deeplink.ts are themselves hardcoded stubs rather than fetching real device info, so the underlying data needs to come from the desktop app via an IPC/query mechanism.
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/raycast-extension/src/switch-microphone.tsx
Line: 8
Comment:
**Hardcoded device list — `getAvailableMicrophones` imported but unused**
The component iterates a hardcoded array `["Default", "MacBook Pro Microphone", "External Microphone"]` instead of calling the already-imported `getAvailableMicrophones()`. The same pattern exists in `switch-camera.tsx` (hardcoded list; `getAvailableCameras` isn't even imported). Additionally, both `getAvailableMicrophones` and `getAvailableCameras` in `deeplink.ts` are themselves hardcoded stubs rather than fetching real device info, so the underlying data needs to come from the desktop app via an IPC/query mechanism.
How can I resolve this? If you propose a fix, please make it concise.|
|
||
| return ( | ||
| <ActionPanel> | ||
| <Action title="Toggle Pause" icon={Icon.SwitchCamera} onAction={handleToggle} /> |
There was a problem hiding this comment.
Wrong icon for toggle-pause action
Icon.SwitchCamera is semantically incorrect for a pause-toggle action. A more appropriate icon would be Icon.Pause, Icon.Play, or Icon.ArrowClockwise.
| <Action title="Toggle Pause" icon={Icon.SwitchCamera} onAction={handleToggle} /> | |
| <Action title="Toggle Pause" icon={Icon.Pause} onAction={handleToggle} /> |
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/raycast-extension/src/toggle-pause.tsx
Line: 29
Comment:
**Wrong icon for toggle-pause action**
`Icon.SwitchCamera` is semantically incorrect for a pause-toggle action. A more appropriate icon would be `Icon.Pause`, `Icon.Play`, or `Icon.ArrowClockwise`.
```suggestion
<Action title="Toggle Pause" icon={Icon.Pause} onAction={handleToggle} />
```
How can I resolve this? If you propose a fix, please make it concise.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Summary
This PR implements the bounty requirements for #1540:
1. Extended Deep Link Support
Added new deep link actions for complete recording control:
PauseRecording- Pause an active recordingResumeRecording- Resume a paused recordingTogglePauseRecording- Toggle pause stateSwitchMicrophone- Switch to a different microphoneSwitchCamera- Switch to a different camera2. Raycast Extension
Created a complete Raycast extension (
apps/raycast-extension/) with 8 commands:The extension uses the
cap-desktop://actiondeep link protocol to communicate with the desktop app.Testing
deeplink_actions.rsFiles Changed
apps/desktop/src-tauri/src/deeplink_actions.rs- Extended enum and execute logicapps/raycast-extension/*- New Raycast extension (11 files)Fixes #1540
Greptile Summary
This PR adds 5 new deep link actions to the Rust backend (
PauseRecording,ResumeRecording,TogglePauseRecording,SwitchMicrophone,SwitchCamera) and introduces a new Raycast extension with 8 commands that communicate via thecap-desktop://actiondeep link protocol. There are three P1 issues that prevent the extension from working end-to-end:DeepLinkActionis missing#[serde(tag = \"action\")]. The extension generates internally-tagged JSON ({\"action\":\"pause_recording\"}), but serde's default external tagging expects\"pause_recording\"— every deep link action will silently fail with aParseFailederror.from \"../deeplink\", butdeeplink.tsis in the samesrc/directory — this is a compilation error.<ActionPanel>but are declared asmode: \"view\"inpackage.json, which will render blank in Raycast.Confidence Score: 2/5
Not safe to merge — three P1 issues collectively prevent the extension from compiling and all deep link actions from executing.
All three P1 issues are on the critical path: broken imports prevent building, wrong serde representation means zero actions reach the Rust handler, and wrong view mode means no Raycast UI renders. None of these are speculative.
apps/desktop/src-tauri/src/deeplink_actions.rs(serde tag),apps/raycast-extension/src/deeplink.ts(import paths, hardcoded stubs),apps/raycast-extension/package.json(command modes), and all 5 fire-and-forget command files.Important Files Changed
#[serde(tag = "action")]means all deep links from the Raycast extension will fail to deserialize.{"action":...}) incompatible with the Rust enum's default serde representation; device-list helpers are hardcoded stubs.<ActionPanel>as root component but is declaredmode: "view"— will render blank in Raycast; same issue affects start/pause/resume/toggle-pause commands.getAvailableMicrophonesbut uses a hardcoded device list; import path"../deeplink"is incorrect (should be"./deeplink")."../deeplink"is incorrect.<ActionPanel>(same view-mode bug); uses semantically wrongIcon.SwitchCameraicon.mode: "view"; the 5 fire-and-forget commands should usemode: "no-view"to match their implementations.Sequence Diagram
sequenceDiagram participant User participant Raycast participant deeplink.ts participant OS participant CapDesktop as Cap Desktop (Tauri) participant Rust as deeplink_actions.rs User->>Raycast: Invoke command (e.g. Pause Recording) Raycast->>deeplink.ts: sendDeepLink("pause_recording") deeplink.ts->>deeplink.ts: JSON.stringify({action: "pause_recording"}) deeplink.ts->>OS: exec(`open "cap-desktop://action?value="`) OS->>CapDesktop: Deep link event CapDesktop->>Rust: DeepLinkAction::try_from(&url) Rust->>Rust: serde_json::from_str(json_value) Note over Rust: Expects "pause_recording" string<br/>Got {"action":"pause_recording"}<br/>ParseFailed error Rust-->>CapDesktop: Err(ParseFailed) CapDesktop-->>User: No action taken (silent failure)Comments Outside Diff (1)
apps/desktop/src-tauri/src/deeplink_actions.rs, line 18-20 (link)#[serde(tag = "action")]causes all deep link actions to failThe
DeepLinkActionenum uses serde's default externally-tagged representation. This means serde expects JSON like"pause_recording"(a bare string for unit variants) or{"switch_microphone":{"mic_label":"..."}}(wrapped struct). However, the Raycast extension (deeplink.ts) generates{"action":"pause_recording"}and{"action":"switch_microphone","mic_label":"..."}— which is the internally-tagged (#[serde(tag = "action")]) format. As-is,serde_json::from_strwill return aParseFailederror for every action sent from the extension.Prompt To Fix With AI
Prompt To Fix All With AI
Reviews (1): Last reviewed commit: "feat: Add deep link actions for recordin..." | Re-trigger Greptile