fix: Bounty: Deeplinks support + Raycast Extension#1691
fix: Bounty: Deeplinks support + Raycast Extension#1691SyedHannanMehdi wants to merge 43 commits intoCapSoftware:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Adds initial Raycast extension scaffolding intended to control the Cap desktop app via cap:// deeplinks, and introduces new desktop-side deeplink parsing/dispatch utilities.
Changes:
- Added a new Raycast extension package with commands and helper utilities to open
cap://deeplinks. - Added desktop-side deeplink parsing/dispatch modules and a renderer hook intended to consume deeplink actions.
- Added start/stop recording Raycast commands wired to deeplink triggers.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| apps/raycast-extension/tsconfig.json | Adds TypeScript config for the new Raycast extension. |
| apps/raycast-extension/package.json | Declares Raycast extension metadata, commands, deps, and scripts. |
| apps/raycast-extension/src/utils/deeplink.ts | Adds helpers to build/open cap:// URLs from Raycast. |
| apps/raycast-extension/src/start-recording.tsx | Raycast command to trigger cap://record/start. |
| apps/raycast-extension/src/stop-recording.tsx | Raycast command to trigger cap://record/stop. |
| apps/desktop/src/utils/deeplinks.ts | Introduces deeplink parsing + renderer dispatch helpers (currently Electron-based). |
| apps/desktop/src/main/deeplink-handler.ts | Introduces main-process deeplink handler (currently Electron-based). |
| apps/desktop/src/hooks/useDeeplinks.ts | Introduces a deeplink consumer hook (currently React/Electron-oriented). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| }, | ||
| { | ||
| "name": "pause-recording", | ||
| "title": "Pause Recording", | ||
| "subtitle": "Cap", | ||
| "description": "Pause the current Cap screen recording.", | ||
| "mode": "no-view" | ||
| }, | ||
| { | ||
| "name": "resume-recording", | ||
| "title": "Resume Recording", | ||
| "subtitle": "Cap", | ||
| "description": "Resume a paused Cap screen recording.", | ||
| "mode": "no-view" | ||
| }, | ||
| { | ||
| "name": "toggle-recording", | ||
| "title": "Toggle Recording", | ||
| "subtitle": "Cap", | ||
| "description": "Start recording if idle, or stop if already recording.", | ||
| "mode": "no-view" | ||
| }, | ||
| { | ||
| "name": "toggle-pause", | ||
| "title": "Toggle Pause / Resume", | ||
| "subtitle": "Cap", | ||
| "description": "Pause recording if active, or resume if paused.", | ||
| "mode": "no-view" | ||
| }, | ||
| { | ||
| "name": "restart-recording", | ||
| "title": "Restart Recording", | ||
| "subtitle": "Cap", | ||
| "description": "Discard the current recording and start a fresh one.", | ||
| "mode": "no-view" | ||
| }, | ||
| { | ||
| "name": "switch-camera", | ||
| "title": "Switch Camera", | ||
| "subtitle": "Cap", | ||
| "description": "Choose a camera input for Cap.", | ||
| "mode": "view" | ||
| }, | ||
| { | ||
| "name": "switch-microphone", | ||
| "title": "Switch Microphone", | ||
| "subtitle": "Cap", | ||
| "description": "Choose a microphone input for Cap.", | ||
| "mode": "view" | ||
| }, | ||
| { | ||
| "name": "open-settings", | ||
| "title": "Open Cap Settings", | ||
| "subtitle": "Cap", | ||
| "description": "Open the Cap settings window.", | ||
| "mode": "no-view" |
There was a problem hiding this comment.
The manifest declares multiple commands (pause/resume/toggle/restart/switch-*) but only start-recording.tsx and stop-recording.tsx exist in src/. Raycast will fail to build/load the extension for missing command entrypoints. Either add the missing command files or remove the undeclared commands from the manifest until implemented.
| }, | |
| { | |
| "name": "pause-recording", | |
| "title": "Pause Recording", | |
| "subtitle": "Cap", | |
| "description": "Pause the current Cap screen recording.", | |
| "mode": "no-view" | |
| }, | |
| { | |
| "name": "resume-recording", | |
| "title": "Resume Recording", | |
| "subtitle": "Cap", | |
| "description": "Resume a paused Cap screen recording.", | |
| "mode": "no-view" | |
| }, | |
| { | |
| "name": "toggle-recording", | |
| "title": "Toggle Recording", | |
| "subtitle": "Cap", | |
| "description": "Start recording if idle, or stop if already recording.", | |
| "mode": "no-view" | |
| }, | |
| { | |
| "name": "toggle-pause", | |
| "title": "Toggle Pause / Resume", | |
| "subtitle": "Cap", | |
| "description": "Pause recording if active, or resume if paused.", | |
| "mode": "no-view" | |
| }, | |
| { | |
| "name": "restart-recording", | |
| "title": "Restart Recording", | |
| "subtitle": "Cap", | |
| "description": "Discard the current recording and start a fresh one.", | |
| "mode": "no-view" | |
| }, | |
| { | |
| "name": "switch-camera", | |
| "title": "Switch Camera", | |
| "subtitle": "Cap", | |
| "description": "Choose a camera input for Cap.", | |
| "mode": "view" | |
| }, | |
| { | |
| "name": "switch-microphone", | |
| "title": "Switch Microphone", | |
| "subtitle": "Cap", | |
| "description": "Choose a microphone input for Cap.", | |
| "mode": "view" | |
| }, | |
| { | |
| "name": "open-settings", | |
| "title": "Open Cap Settings", | |
| "subtitle": "Cap", | |
| "description": "Open the Cap settings window.", | |
| "mode": "no-view" |
| import { app, BrowserWindow } from "electron"; | ||
| import { | ||
| dispatchDeeplinkToRenderer, | ||
| parseDeeplink, | ||
| registerDeeplinkIpcHandlers, | ||
| } from "../utils/deeplinks"; |
There was a problem hiding this comment.
This desktop app is a Tauri/Solid app, but this file imports Electron APIs (app, BrowserWindow) and relies on Electron lifecycle events (open-url, second-instance). This won’t compile/run in the current desktop architecture. Rework deeplink handling using @tauri-apps/plugin-deep-link (like utils/auth.ts does) and route actions to existing commands.* APIs instead of Electron IPC/BrowserWindow.
apps/desktop/src/utils/deeplinks.ts
Outdated
| import { BrowserWindow, ipcMain } from "electron"; | ||
|
|
There was a problem hiding this comment.
electron (BrowserWindow, ipcMain) is imported here, but the desktop app is Tauri-based and doesn’t have Electron in its dependency graph. This module will fail to typecheck/bundle. Consider making parseDeeplink a pure helper (no Electron imports) and handling dispatch via Tauri deep-link events + commands calls in the renderer.
| import { useEffect } from "react"; | ||
|
|
||
| // ── Type shim for the Electron contextBridge API exposed by preload ────────── | ||
| declare global { | ||
| interface Window { | ||
| // Cap exposes ipcRenderer helpers via contextBridge in preload.ts | ||
| // The exact shape depends on your preload – adapt as needed. | ||
| ipcRenderer?: { | ||
| on: ( | ||
| channel: string, | ||
| listener: (event: unknown, ...args: unknown[]) => void | ||
| ) => void; | ||
| off: ( | ||
| channel: string, | ||
| listener: (event: unknown, ...args: unknown[]) => void | ||
| ) => void; | ||
| }; | ||
| } | ||
| } | ||
|
|
||
| export interface DeeplinkPayload { |
There was a problem hiding this comment.
The desktop renderer uses SolidJS (solid-js), but this hook imports React’s useEffect and assumes an Electron-style window.ipcRenderer. This will break the build (React isn’t a dependency here) and the IPC surface doesn’t exist in Tauri. Rewrite this as a Solid hook (e.g., onMount/onCleanup) that listens via onOpenUrl from @tauri-apps/plugin-deep-link and dispatches to commands.* / stores.
| import { useEffect } from "react"; | |
| // ── Type shim for the Electron contextBridge API exposed by preload ────────── | |
| declare global { | |
| interface Window { | |
| // Cap exposes ipcRenderer helpers via contextBridge in preload.ts | |
| // The exact shape depends on your preload – adapt as needed. | |
| ipcRenderer?: { | |
| on: ( | |
| channel: string, | |
| listener: (event: unknown, ...args: unknown[]) => void | |
| ) => void; | |
| off: ( | |
| channel: string, | |
| listener: (event: unknown, ...args: unknown[]) => void | |
| ) => void; | |
| }; | |
| } | |
| } | |
| export interface DeeplinkPayload { | |
| import { onMount, onCleanup } from "solid-js"; | |
| import { onOpenUrl } from "@tauri-apps/plugin-deep-link"; | |
| export interface DeeplinkPayload { | |
| action: string; | |
| params: Record<string, string>; | |
| } | |
| type DeeplinkListener = (payload: DeeplinkPayload) => void; | |
| const listeners = new Set<DeeplinkListener>(); | |
| // Single global deep-link subscription so we don't double-register | |
| let _globalSubscribed = false; | |
| let _unlisten: (() => void) | null = null; | |
| function ensureGlobalSubscription() { | |
| if (_globalSubscribed) return; | |
| _globalSubscribed = true; | |
| // Register a Tauri deep-link listener and fan out parsed payloads | |
| void onOpenUrl((urls) => { | |
| for (const url of urls) { | |
| try { | |
| const parsed = new URL(url); | |
| const action = parsed.pathname.replace(/^\/+/, ""); | |
| const params: Record<string, string> = {}; | |
| parsed.searchParams.forEach((value, key) => { | |
| params[key] = value; | |
| }); | |
| const payload: DeeplinkPayload = { action, params }; | |
| listeners.forEach((fn) => fn(payload)); | |
| } catch (error) { | |
| // eslint-disable-next-line no-console | |
| console.error("Failed to handle deep link URL", url, error); | |
| } | |
| } | |
| }) | |
| .then((unlisten) => { | |
| _unlisten = unlisten; | |
| }) | |
| .catch((error) => { | |
| // eslint-disable-next-line no-console | |
| console.error("Failed to register deep link listener", error); | |
| }); | |
| } |
| @@ -0,0 +1,103 @@ | |||
| { | |||
| "$schema": "https://www.raycast.com/schemas/extension.json", | |||
| "name": "cap", | |||
There was a problem hiding this comment.
This package.json sets name to cap, which collides with the repo root package name (cap). In a pnpm workspace, duplicate package names can break installs and tooling resolution. Use a unique scoped name (e.g. @cap/raycast-extension) and add the usual workspace fields (private: true, version) to match the monorepo conventions.
| "name": "cap", | |
| "name": "@cap/raycast-extension", | |
| "version": "0.0.0", | |
| "private": true, |
apps/desktop/src/utils/deeplinks.ts
Outdated
| /** | ||
| * Cap Deeplinks Handler | ||
| * | ||
| * Supported deeplinks (cap://): | ||
| * cap://record/start – start a new recording | ||
| * cap://record/stop – stop the current recording | ||
| * cap://record/pause – pause the current recording | ||
| * cap://record/resume – resume a paused recording | ||
| * cap://record/restart – restart (discard) the current recording | ||
| * cap://record/toggle – toggle start/stop | ||
| * cap://record/toggle-pause – toggle pause/resume | ||
| * cap://settings/camera?deviceId=<id> – switch camera input | ||
| * cap://settings/microphone?deviceId=<id> – switch microphone input | ||
| * cap://settings/open – open settings window | ||
| * cap://window/open – open/focus the main window | ||
| * | ||
| * All actions respond with a JSON body when invoked from an HTTP-style | ||
| * scheme callback, but they primarily operate by emitting IPC events to the | ||
| * renderer process. | ||
| */ | ||
|
|
||
| import { BrowserWindow, ipcMain } from "electron"; |
There was a problem hiding this comment.
Wrong runtime framework — Electron APIs in a Tauri app
This file imports directly from electron (BrowserWindow, ipcMain), but the Cap desktop app is built on Tauri, not Electron. electron is not listed as a dependency in apps/desktop/package.json, so this file will fail to compile and the desktop app will not build.
The project already uses @tauri-apps/plugin-deep-link for URL scheme handling — see apps/desktop/src/utils/auth.ts, which calls onOpenUrl from @tauri-apps/plugin-deep-link. The entire deeplink implementation needs to be rewritten using:
onOpenUrlfrom@tauri-apps/plugin-deep-linkto receivecap://URLs in the frontendemit/listenfrom@tauri-apps/api/eventfor cross-component communicationinvokefrom@tauri-apps/api/corefor calling backend commands
BrowserWindow and ipcMain do not exist in Tauri.
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/utils/deeplinks.ts
Line: 1-22
Comment:
**Wrong runtime framework — Electron APIs in a Tauri app**
This file imports directly from `electron` (`BrowserWindow`, `ipcMain`), but the Cap desktop app is built on **Tauri**, not Electron. `electron` is not listed as a dependency in `apps/desktop/package.json`, so this file will fail to compile and the desktop app will not build.
The project already uses `@tauri-apps/plugin-deep-link` for URL scheme handling — see `apps/desktop/src/utils/auth.ts`, which calls `onOpenUrl` from `@tauri-apps/plugin-deep-link`. The entire deeplink implementation needs to be rewritten using:
- `onOpenUrl` from `@tauri-apps/plugin-deep-link` to receive `cap://` URLs in the frontend
- `emit` / `listen` from `@tauri-apps/api/event` for cross-component communication
- `invoke` from `@tauri-apps/api/core` for calling backend commands
`BrowserWindow` and `ipcMain` do not exist in Tauri.
How can I resolve this? If you propose a fix, please make it concise.| /** | ||
| * Cap – Main-process deeplink handler | ||
| * | ||
| * Wire this up inside your existing `app.on("open-url", ...)` / | ||
| * `app.on("second-instance", ...)` handlers, or call | ||
| * `registerDeeplinkHandler()` once during app startup. | ||
| * | ||
| * The handler parses cap:// URLs and dispatches them to the renderer via IPC. | ||
| * The renderer is responsible for the actual recording-state machine. | ||
| */ | ||
|
|
||
| import { app, BrowserWindow } from "electron"; | ||
| import { | ||
| dispatchDeeplinkToRenderer, | ||
| parseDeeplink, | ||
| registerDeeplinkIpcHandlers, | ||
| } from "../utils/deeplinks"; |
There was a problem hiding this comment.
Electron main-process APIs in a Tauri application
This file imports app and BrowserWindow from electron and registers app.on("open-url", ...) / app.on("second-instance", ...) lifecycle hooks — all Electron-specific concepts. Cap's desktop app is a Tauri application; electron is not installed as a dependency.
Additionally, registerDeeplinkHandler() is never called from any existing entry point in the codebase, so even if the import were resolvable, no deeplinks would ever be handled.
The correct Tauri approach is to use the existing @tauri-apps/plugin-deep-link (already installed at ^2.4.1) and register the cap:// scheme in tauri.conf.json / Cargo.toml accordingly.
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/main/deeplink-handler.ts
Line: 1-17
Comment:
**Electron main-process APIs in a Tauri application**
This file imports `app` and `BrowserWindow` from `electron` and registers `app.on("open-url", ...)` / `app.on("second-instance", ...)` lifecycle hooks — all Electron-specific concepts. Cap's desktop app is a Tauri application; `electron` is not installed as a dependency.
Additionally, `registerDeeplinkHandler()` is never called from any existing entry point in the codebase, so even if the import were resolvable, no deeplinks would ever be handled.
The correct Tauri approach is to use the existing `@tauri-apps/plugin-deep-link` (already installed at `^2.4.1`) and register the `cap://` scheme in `tauri.conf.json` / `Cargo.toml` accordingly.
How can I resolve this? If you propose a fix, please make it concise.| import { useEffect } from "react"; | ||
|
|
||
| // ── Type shim for the Electron contextBridge API exposed by preload ────────── | ||
| declare global { | ||
| interface Window { | ||
| // Cap exposes ipcRenderer helpers via contextBridge in preload.ts | ||
| // The exact shape depends on your preload – adapt as needed. | ||
| ipcRenderer?: { | ||
| on: ( | ||
| channel: string, | ||
| listener: (event: unknown, ...args: unknown[]) => void | ||
| ) => void; | ||
| off: ( | ||
| channel: string, | ||
| listener: (event: unknown, ...args: unknown[]) => void | ||
| ) => void; | ||
| }; | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
React hook in a SolidJS application
useEffect is imported from react, but the Cap desktop app uses SolidJS — react is not a dependency of apps/desktop. The import will fail at build time.
The SolidJS equivalent is createEffect / onCleanup from solid-js. The global IPC pattern should also be replaced with Tauri's onOpenUrl from @tauri-apps/plugin-deep-link, which is already used in apps/desktop/src/utils/auth.ts.
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/hooks/useDeeplinks.ts
Line: 16-35
Comment:
**React hook in a SolidJS application**
`useEffect` is imported from `react`, but the Cap desktop app uses **SolidJS** — `react` is not a dependency of `apps/desktop`. The import will fail at build time.
The SolidJS equivalent is `createEffect` / `onCleanup` from `solid-js`. The global IPC pattern should also be replaced with Tauri's `onOpenUrl` from `@tauri-apps/plugin-deep-link`, which is already used in `apps/desktop/src/utils/auth.ts`.
How can I resolve this? If you propose a fix, please make it concise.| { | ||
| "name": "pause-recording", | ||
| "title": "Pause Recording", | ||
| "subtitle": "Cap", | ||
| "description": "Pause the current Cap screen recording.", | ||
| "mode": "no-view" | ||
| }, | ||
| { | ||
| "name": "resume-recording", | ||
| "title": "Resume Recording", | ||
| "subtitle": "Cap", | ||
| "description": "Resume a paused Cap screen recording.", | ||
| "mode": "no-view" | ||
| }, | ||
| { | ||
| "name": "toggle-recording", | ||
| "title": "Toggle Recording", | ||
| "subtitle": "Cap", | ||
| "description": "Start recording if idle, or stop if already recording.", | ||
| "mode": "no-view" | ||
| }, | ||
| { | ||
| "name": "toggle-pause", | ||
| "title": "Toggle Pause / Resume", | ||
| "subtitle": "Cap", | ||
| "description": "Pause recording if active, or resume if paused.", | ||
| "mode": "no-view" | ||
| }, | ||
| { | ||
| "name": "restart-recording", | ||
| "title": "Restart Recording", | ||
| "subtitle": "Cap", | ||
| "description": "Discard the current recording and start a fresh one.", | ||
| "mode": "no-view" | ||
| }, | ||
| { | ||
| "name": "switch-camera", | ||
| "title": "Switch Camera", | ||
| "subtitle": "Cap", | ||
| "description": "Choose a camera input for Cap.", | ||
| "mode": "view" | ||
| }, | ||
| { | ||
| "name": "switch-microphone", | ||
| "title": "Switch Microphone", | ||
| "subtitle": "Cap", | ||
| "description": "Choose a microphone input for Cap.", | ||
| "mode": "view" | ||
| }, | ||
| { | ||
| "name": "open-settings", | ||
| "title": "Open Cap Settings", | ||
| "subtitle": "Cap", | ||
| "description": "Open the Cap settings window.", | ||
| "mode": "no-view" | ||
| } |
There was a problem hiding this comment.
8 out of 10 declared commands have no source file
package.json declares 10 Raycast commands, but only 2 have corresponding source files in the PR (start-recording.tsx and stop-recording.tsx). The following 8 commands are declared but their implementation files are entirely missing:
pause-recording→src/pause-recording.tsxresume-recording→src/resume-recording.tsxtoggle-recording→src/toggle-recording.tsxtoggle-pause→src/toggle-pause.tsxrestart-recording→src/restart-recording.tsxswitch-camera→src/switch-camera.tsxswitch-microphone→src/switch-microphone.tsxopen-settings→src/open-settings.tsx
Raycast requires each command listed in package.json to have a matching entry-point file. Running ray build will fail for all of these missing commands.
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/raycast-extension/package.json
Line: 28-83
Comment:
**8 out of 10 declared commands have no source file**
`package.json` declares 10 Raycast commands, but only 2 have corresponding source files in the PR (`start-recording.tsx` and `stop-recording.tsx`). The following 8 commands are declared but their implementation files are entirely missing:
- `pause-recording` → `src/pause-recording.tsx`
- `resume-recording` → `src/resume-recording.tsx`
- `toggle-recording` → `src/toggle-recording.tsx`
- `toggle-pause` → `src/toggle-pause.tsx`
- `restart-recording` → `src/restart-recording.tsx`
- `switch-camera` → `src/switch-camera.tsx`
- `switch-microphone` → `src/switch-microphone.tsx`
- `open-settings` → `src/open-settings.tsx`
Raycast requires each command listed in `package.json` to have a matching entry-point file. Running `ray build` will fail for all of these missing commands.
How can I resolve this? If you propose a fix, please make it concise.| export function useDeeplinks( | ||
| handlers: Partial<Record<string, (payload: DeeplinkPayload) => void>> | ||
| ): void { | ||
| useEffect(() => { | ||
| const unsubscribe = onDeeplinkAction((payload) => { | ||
| const handler = handlers[payload.action]; | ||
| if (handler) { | ||
| handler(payload); | ||
| } else { | ||
| console.warn("[useDeeplinks] No handler registered for:", payload.action); | ||
| } | ||
| }); | ||
| return unsubscribe; | ||
| // eslint-disable-next-line react-hooks/exhaustive-deps | ||
| }, []); | ||
| } |
There was a problem hiding this comment.
Stale closure over
handlers due to empty dependency array
The useEffect captures the handlers object at mount time with an empty dependency array ([]). Any state variables referenced inside handler callbacks — such as isRecording or isPaused in the JSDoc example — will be permanently stale after the first re-render.
The handlers object needs to be either stabilised with a ref/signal so the callback always reads the current value, or included in the dependency array.
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/hooks/useDeeplinks.ts
Line: 97-112
Comment:
**Stale closure over `handlers` due to empty dependency array**
The `useEffect` captures the `handlers` object at mount time with an empty dependency array (`[]`). Any state variables referenced inside handler callbacks — such as `isRecording` or `isPaused` in the JSDoc example — will be permanently stale after the first re-render.
The handlers object needs to be either stabilised with a ref/signal so the callback always reads the current value, or included in the dependency array.
How can I resolve this? If you propose a fix, please make it concise.| // Single global IPC subscription so we don't double-register | ||
| let _globalSubscribed = false; | ||
| function ensureGlobalSubscription() { | ||
| if (_globalSubscribed) return; | ||
| if (!window.ipcRenderer) return; | ||
| _globalSubscribed = true; | ||
|
|
||
| window.ipcRenderer.on( | ||
| "deeplink-action", | ||
| (_event: unknown, payload: DeeplinkPayload) => { | ||
| listeners.forEach((fn) => fn(payload)); | ||
| } | ||
| ); | ||
| } |
There was a problem hiding this comment.
Global IPC subscription is never cleaned up
ensureGlobalSubscription() calls window.ipcRenderer.on(...) and sets _globalSubscribed = true, but the corresponding window.ipcRenderer.off(...) is never called. If the module is reloaded (e.g., hot-module replacement in development) _globalSubscribed resets to false and a second listener is added without removing the first — this will cause every deeplink to fire twice per reload cycle.
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/hooks/useDeeplinks.ts
Line: 45-58
Comment:
**Global IPC subscription is never cleaned up**
`ensureGlobalSubscription()` calls `window.ipcRenderer.on(...)` and sets `_globalSubscribed = true`, but the corresponding `window.ipcRenderer.off(...)` is never called. If the module is reloaded (e.g., hot-module replacement in development) `_globalSubscribed` resets to `false` and a second listener is added without removing the first — this will cause every deeplink to fire twice per reload cycle.
How can I resolve this? If you propose a fix, please make it concise.| "title": "Cap", | ||
| "description": "Control Cap screen recorder directly from Raycast — start, stop, pause, resume recordings and switch inputs without leaving your keyboard.", | ||
| "icon": "cap-icon.png", | ||
| "author": "cap", |
There was a problem hiding this comment.
The "icon": "cap-icon.png" field references an image that is not included in this PR. Publishing or building the extension without this file will produce a validation error from the Raycast CLI.
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/raycast-extension/package.json
Line: 7
Comment:
**Missing `cap-icon.png` asset**
The `"icon": "cap-icon.png"` field references an image that is not included in this PR. Publishing or building the extension without this file will produce a validation error from the Raycast CLI.
How can I resolve this? If you propose a fix, please make it concise.…nstead of Electron APIs
…-deep-link, remove React/Electron IPC
…ep-link (remove Electron)
…s/plugin-deep-link
Automated fix for #1540 — implemented by CashClaw agent.
Closes #1540
Greptile Summary
This PR attempts to add deeplink support (
cap://URL scheme) for the Cap desktop app and a new Raycast extension to trigger recordings via those deeplinks. Unfortunately, the desktop-side implementation is fundamentally incompatible with the existing codebase and will not compile or function.Critical problems:
apps/desktop/src/utils/deeplinks.tsandapps/desktop/src/main/deeplink-handler.tsboth import fromelectron(BrowserWindow,ipcMain,app). Cap's desktop app is built on Tauri —electronis not a dependency and is never referenced anywhere else in the project. Both files will fail to compile.apps/desktop/src/hooks/useDeeplinks.tsimportsuseEffectfrom React, but the desktop app uses SolidJS. React is not available in the renderer.@tauri-apps/plugin-deep-link(^2.4.1) is already installed and already used inauth.tsviaonOpenUrl. The PR bypasses it entirely.registerDeeplinkHandler()is never called from any entry point in the codebase.Raycast extension problems:
package.jsonbut only 2 have source files. The remaining 8 are missing entirely —ray buildwill fail.cap-icon.pngasset is not included.What works:
apps/raycast-extension/src/utils/deeplink.tsand the two implemented command files are correctly structured using@raycast/api.Confidence Score: 1/5
Not safe to merge — desktop files will not compile due to wrong framework imports; Raycast extension is also incomplete.
Three desktop files import from
electronandreact, neither of which are dependencies of this Tauri/SolidJS app — the project will not build. Eight of ten declared Raycast commands are missing their source files, breaking the extension build too.apps/desktop/src/utils/deeplinks.ts,apps/desktop/src/main/deeplink-handler.ts, andapps/desktop/src/hooks/useDeeplinks.tsmust all be completely rewritten using@tauri-apps/plugin-deep-link,@tauri-apps/api/event, and SolidJS primitives. The Raycast extension needs 8 missing command files.Important Files Changed
BrowserWindowandipcMainfromelectron— a package that does not exist in this Tauri application. Will not compile.electron— non-existent in a Tauri app; also never called from any app entry point.useEffectfrom React in a SolidJS app; useswindow.ipcRenderer(Electron concept) and has a stale-closure bug due to the empty dependency array.cap-icon.pngasset; extension will not build.buildDeeplink/triggerDeeplinkutilities using@raycast/apicorrectly; logic is sound as a standalone Raycast helper.triggerDeeplink("record/start")— correct pattern, but depends on the desktop side actually handling the deeplink.triggerDeeplink("record/stop")— correct pattern, but depends on the desktop side actually handling the deeplink.@raycast/tsconfig; no issues.Sequence Diagram
sequenceDiagram participant Raycast participant OS participant TauriRuntime as Tauri Runtime participant Frontend as SolidJS Frontend Note over Raycast,Frontend: Intended flow Raycast->>OS: open("cap://record/start") OS->>TauriRuntime: deliver cap:// URL via deep-link plugin TauriRuntime->>Frontend: onOpenUrl callback fires Frontend->>Frontend: parse action and dispatch to recording store Note over TauriRuntime,Frontend: Actual flow (broken) TauriRuntime--xTauriRuntime: registerDeeplinkHandler() never called Note right of TauriRuntime: deeplink-handler.ts imports electron (not installed) Note right of Frontend: deeplinks.ts imports electron ipcMain (not installed) Note right of Frontend: useDeeplinks.ts imports React useEffect (wrong framework)Prompt To Fix All With AI
Reviews (1): Last reviewed commit: "feat(raycast): stop-recording command" | Re-trigger Greptile
(2/5) Greptile learns from your feedback when you react with thumbs up/down!