-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Bounty #1540: Deep Links Support + Raycast Extension #1655
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -15,6 +15,7 @@ import "./styles/theme.css"; | |||||||||||||||
| import { CapErrorBoundary } from "./components/CapErrorBoundary"; | ||||||||||||||||
| import { generalSettingsStore } from "./store"; | ||||||||||||||||
| import { initAnonymousUser } from "./utils/analytics"; | ||||||||||||||||
| import { initRecordingControlDeepLinks } from "./utils/recording-deeplinks"; | ||||||||||||||||
| import { type AppTheme, commands } from "./utils/tauri"; | ||||||||||||||||
| import titlebar from "./utils/titlebar-state"; | ||||||||||||||||
|
|
||||||||||||||||
|
|
@@ -102,6 +103,10 @@ function Inner() { | |||||||||||||||
|
|
||||||||||||||||
| onMount(() => { | ||||||||||||||||
| initAnonymousUser(); | ||||||||||||||||
| // Initialize deep link listener for recording controls | ||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since you already export |
||||||||||||||||
| initRecordingControlDeepLinks().catch((err) => | ||||||||||||||||
| console.error("[App] Failed to init deep links:", err), | ||||||||||||||||
|
Comment on lines
105
to
+108
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Small thing: this repo avoids code comments, so I'd drop the inline
Suggested change
|
||||||||||||||||
| ); | ||||||||||||||||
| }); | ||||||||||||||||
|
|
||||||||||||||||
| return ( | ||||||||||||||||
|
|
||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,216 @@ | ||||||||||||||||||||||||||||||||
| import { onOpenUrl } from "@tauri-apps/plugin-deep-link"; | ||||||||||||||||||||||||||||||||
| import { commands } from "./tauri"; | ||||||||||||||||||||||||||||||||
| import type { ScreenCaptureTarget, RecordingMode } from "./tauri"; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| type DeepLinkCommand = | ||||||||||||||||||||||||||||||||
| | { action: "record"; subaction: "start"; params: StartParams } | ||||||||||||||||||||||||||||||||
| | { action: "record"; subaction: "stop"; params: Record<string, string> } | ||||||||||||||||||||||||||||||||
| | { action: "record"; subaction: "pause"; params: Record<string, string> } | ||||||||||||||||||||||||||||||||
| | { action: "record"; subaction: "resume"; params: Record<string, string> } | ||||||||||||||||||||||||||||||||
| | { action: "record"; subaction: "toggle"; params: Record<string, string> } | ||||||||||||||||||||||||||||||||
| | { action: "devices"; subaction: "mic"; params: { name: string } } | ||||||||||||||||||||||||||||||||
| | { action: "devices"; subaction: "camera"; params: { id: string } }; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| interface StartParams { | ||||||||||||||||||||||||||||||||
| target?: string; | ||||||||||||||||||||||||||||||||
| displayId?: string; | ||||||||||||||||||||||||||||||||
| windowId?: string; | ||||||||||||||||||||||||||||||||
| bounds?: string; | ||||||||||||||||||||||||||||||||
| mode?: RecordingMode; | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| let stopListening: (() => void) | undefined; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||
| * Initialize deep link listener for recording controls | ||||||||||||||||||||||||||||||||
| * Routes: | ||||||||||||||||||||||||||||||||
| * - cap-desktop://record/start?target=display&displayId=1 | ||||||||||||||||||||||||||||||||
| * - cap-desktop://record/start?target=window&windowId=abc | ||||||||||||||||||||||||||||||||
| * - cap-desktop://record/start?target=area&displayId=1&bounds={"x":0,"y":0,"width":1920,"height":1080} | ||||||||||||||||||||||||||||||||
| * - cap-desktop://record/stop | ||||||||||||||||||||||||||||||||
| * - cap-desktop://record/pause | ||||||||||||||||||||||||||||||||
| * - cap-desktop://record/resume | ||||||||||||||||||||||||||||||||
| * - cap-desktop://record/toggle | ||||||||||||||||||||||||||||||||
| * - cap-desktop://devices/mic?name=Built-in Microphone | ||||||||||||||||||||||||||||||||
| * - cap-desktop://devices/camera?id=faceTimeHD | ||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||
| export async function initRecordingControlDeepLinks() { | ||||||||||||||||||||||||||||||||
|
Comment on lines
+24
to
+37
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Repo convention here is no inline code comments; consider moving the route examples to a doc/README and keeping the code comment-free.
Suggested change
|
||||||||||||||||||||||||||||||||
| if (stopListening) { | ||||||||||||||||||||||||||||||||
| console.log("[DeepLink] Recording controls already initialized"); | ||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| console.log("[DeepLink] Initializing recording control deep links..."); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| stopListening = await onOpenUrl(async (urls) => { | ||||||||||||||||||||||||||||||||
| for (const urlString of urls) { | ||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||
| console.log(`[DeepLink] Received: ${urlString}`); | ||||||||||||||||||||||||||||||||
| const url = new URL(urlString); | ||||||||||||||||||||||||||||||||
| const command = parseDeepLinkUrl(url); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| if (command) { | ||||||||||||||||||||||||||||||||
| await executeDeepLinkCommand(command); | ||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||
| console.warn(`[DeepLink] Unknown command: ${url.pathname}`); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||
| console.error(`[DeepLink] Error processing ${urlString}:`, error); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||
|
Comment on lines
+45
to
+61
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Deep link listener registered in every webview window
When a deep link such as Consider restricting initialization to the main application window only, for example by checking the current window label before calling import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
onMount(() => {
initAnonymousUser();
const win = getCurrentWebviewWindow();
if (win.label === "main") {
initRecordingControlDeepLinks().catch((err) =>
console.error("[App] Failed to init deep links:", err),
);
}
});Prompt To Fix With AIThis is a comment left during a code review.
Path: apps/desktop/src/utils/recording-deeplinks.ts
Line: 45-61
Comment:
**Deep link listener registered in every webview window**
`App.tsx`/`Inner` is the root component for all Cap windows (main window, camera overlay, editor, etc.), each of which runs in its own independent JavaScript runtime. Because `initRecordingControlDeepLinks` is called unconditionally in `onMount`, every open window will register its own `onOpenUrl` handler via the Tauri deep-link plugin.
When a deep link such as `cap-desktop://record/stop` arrives, the plugin emits the event to all registered listeners simultaneously. That means every currently-open window will independently call `commands.stopRecording()`, `commands.pauseRecording()`, etc. The first call will succeed; subsequent calls from other windows will fail (no recording in progress) and produce error-level console noise at minimum, with unpredictable side effects depending on how the backend handles concurrent calls.
Consider restricting initialization to the main application window only, for example by checking the current window label before calling `initRecordingControlDeepLinks`:
```ts
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
onMount(() => {
initAnonymousUser();
const win = getCurrentWebviewWindow();
if (win.label === "main") {
initRecordingControlDeepLinks().catch((err) =>
console.error("[App] Failed to init deep links:", err),
);
}
});
```
How can I resolve this? If you propose a fix, please make it concise. |
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| console.log("[DeepLink] Recording control deep links initialized"); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| export async function disposeRecordingControlDeepLinks() { | ||||||||||||||||||||||||||||||||
| if (stopListening) { | ||||||||||||||||||||||||||||||||
| stopListening(); | ||||||||||||||||||||||||||||||||
| stopListening = undefined; | ||||||||||||||||||||||||||||||||
| console.log("[DeepLink] Recording control deep links disposed"); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| function parseDeepLinkUrl(url: URL): DeepLinkCommand | null { | ||||||||||||||||||||||||||||||||
| const pathParts = url.pathname.split("/").filter(Boolean); | ||||||||||||||||||||||||||||||||
| const params = Object.fromEntries(url.searchParams); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| if (pathParts[0] !== "record" && pathParts[0] !== "devices") { | ||||||||||||||||||||||||||||||||
| return null; | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| const [action, subaction] = pathParts; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
Comment on lines
+75
to
+83
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||
| switch (action) { | ||||||||||||||||||||||||||||||||
| case "record": | ||||||||||||||||||||||||||||||||
| if (subaction === "start") { | ||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||
| action: "record", | ||||||||||||||||||||||||||||||||
| subaction: "start", | ||||||||||||||||||||||||||||||||
| params: { | ||||||||||||||||||||||||||||||||
| target: params.target, | ||||||||||||||||||||||||||||||||
| displayId: params.displayId, | ||||||||||||||||||||||||||||||||
| windowId: params.windowId, | ||||||||||||||||||||||||||||||||
| bounds: params.bounds, | ||||||||||||||||||||||||||||||||
| mode: (params.mode as RecordingMode) || "studio", | ||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| if (["stop", "pause", "resume", "toggle"].includes(subaction)) { | ||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||
| action: "record", | ||||||||||||||||||||||||||||||||
| subaction: subaction as "stop" | "pause" | "resume" | "toggle", | ||||||||||||||||||||||||||||||||
| params, | ||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| case "devices": | ||||||||||||||||||||||||||||||||
| if (subaction === "mic" && params.name) { | ||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||
| action: "devices", | ||||||||||||||||||||||||||||||||
| subaction: "mic", | ||||||||||||||||||||||||||||||||
| params: { name: params.name }, | ||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| if (subaction === "camera" && params.id) { | ||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||
| action: "devices", | ||||||||||||||||||||||||||||||||
| subaction: "camera", | ||||||||||||||||||||||||||||||||
| params: { id: params.id }, | ||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| return null; | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| async function executeDeepLinkCommand(command: DeepLinkCommand) { | ||||||||||||||||||||||||||||||||
| console.log(`[DeepLink] Executing: ${command.action}/${command.subaction}`); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| switch (command.action) { | ||||||||||||||||||||||||||||||||
| case "record": | ||||||||||||||||||||||||||||||||
| await executeRecordingCommand(command); | ||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||
| case "devices": | ||||||||||||||||||||||||||||||||
| await executeDeviceCommand(command); | ||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| async function executeRecordingCommand(command: Extract<DeepLinkCommand, { action: "record" }>) { | ||||||||||||||||||||||||||||||||
| switch (command.subaction) { | ||||||||||||||||||||||||||||||||
| case "start": { | ||||||||||||||||||||||||||||||||
| const { target, displayId, windowId, bounds, mode } = command.params; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| let captureTarget: ScreenCaptureTarget | null = null; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| if (target === "display" && displayId) { | ||||||||||||||||||||||||||||||||
| captureTarget = { variant: "display", id: displayId }; | ||||||||||||||||||||||||||||||||
| } else if (target === "window" && windowId) { | ||||||||||||||||||||||||||||||||
| captureTarget = { variant: "window", id: windowId }; | ||||||||||||||||||||||||||||||||
| } else if (target === "area" && displayId && bounds) { | ||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||
| const boundsObj = JSON.parse(bounds); | ||||||||||||||||||||||||||||||||
| captureTarget = { | ||||||||||||||||||||||||||||||||
| variant: "area", | ||||||||||||||||||||||||||||||||
| screen: displayId, | ||||||||||||||||||||||||||||||||
| bounds: boundsObj, | ||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||
| } catch (e) { | ||||||||||||||||||||||||||||||||
| console.error("[DeepLink] Invalid bounds JSON:", e); | ||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||
|
Comment on lines
+153
to
+163
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No structural validation on parsed
Add a basic shape guard before constructing the try {
const boundsObj = JSON.parse(bounds);
if (
typeof boundsObj?.x !== "number" ||
typeof boundsObj?.y !== "number" ||
typeof boundsObj?.width !== "number" ||
typeof boundsObj?.height !== "number"
) {
console.error("[DeepLink] bounds object has unexpected shape:", boundsObj);
return;
}
captureTarget = {
variant: "area",
screen: displayId,
bounds: boundsObj,
};
} catch (e) {Prompt To Fix With AIThis is a comment left during a code review.
Path: apps/desktop/src/utils/recording-deeplinks.ts
Line: 153-163
Comment:
**No structural validation on parsed `bounds` object**
`JSON.parse(bounds)` produces an `any` value that is passed directly as `captureTarget.bounds` with no validation that it actually contains the expected `{ x, y, width, height }` fields (or that those fields are numbers). A malformed or adversarially crafted deep link like `cap-desktop://record/start?target=area&displayId=1&bounds={"x":"evil"}` will pass through to the backend without any client-side rejection.
Add a basic shape guard before constructing the `captureTarget`:
```ts
try {
const boundsObj = JSON.parse(bounds);
if (
typeof boundsObj?.x !== "number" ||
typeof boundsObj?.y !== "number" ||
typeof boundsObj?.width !== "number" ||
typeof boundsObj?.height !== "number"
) {
console.error("[DeepLink] bounds object has unexpected shape:", boundsObj);
return;
}
captureTarget = {
variant: "area",
screen: displayId,
bounds: boundsObj,
};
} catch (e) {
```
How can I resolve this? If you propose a fix, please make it concise. |
||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| } else if (target === "cameraOnly") { | ||||||||||||||||||||||||||||||||
| captureTarget = { variant: "cameraOnly" }; | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| if (captureTarget) { | ||||||||||||||||||||||||||||||||
| const result = await commands.startRecording({ | ||||||||||||||||||||||||||||||||
| capture_target: captureTarget, | ||||||||||||||||||||||||||||||||
| capture_system_audio: true, | ||||||||||||||||||||||||||||||||
| mode: mode || "studio", | ||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||
|
Comment on lines
+170
to
+174
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The value should be read from the application settings store (the same source that the normal recording flow consults) rather than being hardcoded.
Suggested change
For example, the existing recording flow reads this preference via the settings store — the same approach should be used here so the deep-link path stays in sync with user configuration. Prompt To Fix With AIThis is a comment left during a code review.
Path: apps/desktop/src/utils/recording-deeplinks.ts
Line: 170-174
Comment:
**`capture_system_audio` hardcoded to `true`**
`capture_system_audio: true` is unconditionally forced on for all deep-link-initiated recordings, regardless of the user's configured preference in settings. A user who has intentionally disabled system audio capture will find that preference silently overridden whenever recording is started via a deep link (e.g. through the Raycast extension).
The value should be read from the application settings store (the same source that the normal recording flow consults) rather than being hardcoded.
```suggestion
const result = await commands.startRecording({
capture_target: captureTarget,
capture_system_audio: true, // TODO: read from user settings
mode: mode || "studio",
});
```
For example, the existing recording flow reads this preference via the settings store — the same approach should be used here so the deep-link path stays in sync with user configuration.
How can I resolve this? If you propose a fix, please make it concise. |
||||||||||||||||||||||||||||||||
| console.log("[DeepLink] Start recording result:", result); | ||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||
| console.warn("[DeepLink] No valid target specified for start recording"); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| case "stop": | ||||||||||||||||||||||||||||||||
| await commands.stopRecording(); | ||||||||||||||||||||||||||||||||
| console.log("[DeepLink] Recording stopped"); | ||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| case "pause": | ||||||||||||||||||||||||||||||||
| await commands.pauseRecording(); | ||||||||||||||||||||||||||||||||
| console.log("[DeepLink] Recording paused"); | ||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| case "resume": | ||||||||||||||||||||||||||||||||
| await commands.resumeRecording(); | ||||||||||||||||||||||||||||||||
| console.log("[DeepLink] Recording resumed"); | ||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| case "toggle": | ||||||||||||||||||||||||||||||||
| await commands.togglePauseRecording(); | ||||||||||||||||||||||||||||||||
| console.log("[DeepLink] Recording pause/resume toggled"); | ||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| async function executeDeviceCommand(command: Extract<DeepLinkCommand, { action: "devices" }>) { | ||||||||||||||||||||||||||||||||
| switch (command.subaction) { | ||||||||||||||||||||||||||||||||
| case "mic": | ||||||||||||||||||||||||||||||||
| await commands.setMicInput(command.params.name); | ||||||||||||||||||||||||||||||||
| console.log(`[DeepLink] Microphone set to: ${command.params.name}`); | ||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| case "camera": | ||||||||||||||||||||||||||||||||
| await commands.setCameraInput({ DeviceID: command.params.id }, false); | ||||||||||||||||||||||||||||||||
| console.log(`[DeepLink] Camera set to: ${command.params.id}`); | ||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
New Rust enum variants are unreachable through URL parsing
The
executeimplementations forPauseRecording,ResumeRecording,TogglePause,SwitchMic, andSwitchCameraare well-written, but they can never be reached from the existingTryFrom<&Url>parser (lines 88–114). That parser only returnsOk(…)forfile://URLs on macOS; for all other schemes (includingcap-desktop://) both match arms returnErr(…), so every non-file deep link is unconditionally dropped beforeexecuteis ever called.The actual deep-link handling for these actions is implemented exclusively in the TypeScript layer (
recording-deeplinks.ts), which calls the Tauri commands directly. The Rust additions are therefore dead code in the current form.Either fix the
TryFrom<&Url>implementation to parsecap-desktop://record/pauseetc. and route them to these variants, or remove the Rust additions and keep the logic solely in the TypeScript handler to avoid confusion.Prompt To Fix With AI