Skip to content

feat: add Warp plugin using Warp URI scheme#467

Open
devin-ai-integration[bot] wants to merge 3 commits into
mainfrom
devin/1781119980-warp-plugin
Open

feat: add Warp plugin using Warp URI scheme#467
devin-ai-integration[bot] wants to merge 3 commits into
mainfrom
devin/1781119980-warp-plugin

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a built-in Warp plugin that hooks into react-grab's plugin system. The "Warp" context-menu/toolbar action copies the grabbed element context to the clipboard, then opens Warp via its URI schemein the grabbed component's source directory by default — so you can paste the context straight into Warp's agent.

Warp's URI scheme has no payload/prompt parameter (only new_window / new_tab / launch / tab_config with an optional path), so the design is copy-then-open: navigation is deferred until the onAfterCopy plugin hook fires, guaranteeing the clipboard is populated before Warp opens.

Usage

import { registerPlugin, warpPlugin } from "react-grab";

registerPlugin(warpPlugin);

Or customize:

import { registerPlugin, createWarpPlugin } from "react-grab";

registerPlugin(
  createWarpPlugin({
    newWindow: true, // open a new window instead of a tab
    path: "/Users/me/project", // override the working directory (defaults to the grabbed file's directory)
    launchConfig: "my-config", // open a saved Launch Configuration instead
    tabConfig: "my-tab", // open a saved Tab Config instead
    usePreview: true, // target Warp Preview (warppreview://)
  }),
);

Source

packages/react-grab/src/core/plugins/warp.ts:

import type { Plugin, WarpPluginOptions } from "../../types.js";
import { buildWarpUri } from "../../utils/build-warp-uri.js";
import { getDirectoryPath } from "../../utils/get-directory-path.js";
import { normalizeFilePath } from "../../utils/normalize-file-path.js";

export const createWarpPlugin = (options: WarpPluginOptions = {}): Plugin => {
  let isAwaitingCopy = false;
  let grabbedDirectoryPath: string | undefined;

  const openWarp = () => {
    window.location.href = buildWarpUri({
      ...options,
      path: options.path ?? grabbedDirectoryPath,
    });
  };

  return {
    name: "warp",
    actions: [
      {
        id: "warp",
        label: "Warp",
        shortcut: "W",
        showInToolbarMenu: true,
        onAction: (context) => {
          grabbedDirectoryPath = context.filePath
            ? getDirectoryPath(normalizeFilePath(context.filePath))
            : undefined;

          if (context.copy) {
            isAwaitingCopy = true;
            context.copy();
          } else {
            openWarp();
          }
        },
      },
    ],
    hooks: {
      onAfterCopy: () => {
        if (!isAwaitingCopy) return;
        isAwaitingCopy = false;
        openWarp();
      },
    },
  };
};

export const warpPlugin = createWarpPlugin();

packages/react-grab/src/utils/build-warp-uri.ts:

import type { WarpPluginOptions } from "../types.js";

export const buildWarpUri = (options: WarpPluginOptions): string => {
  const scheme = options.usePreview ? "warppreview" : "warp";

  if (options.launchConfig) {
    return `${scheme}://launch/${encodeURIComponent(options.launchConfig)}`;
  }

  if (options.tabConfig) {
    const newWindowSuffix = options.newWindow ? "?new_window=true" : "";
    return `${scheme}://tab_config/${encodeURIComponent(options.tabConfig)}${newWindowSuffix}`;
  }

  const action = options.newWindow ? "new_window" : "new_tab";
  const pathSuffix = options.path ? `?path=${encodeURIComponent(options.path)}` : "";
  return `${scheme}://action/${action}${pathSuffix}`;
};

Generated URI examples

Options URI
grab on a component in src/components/Button.tsx warp://action/new_tab?path=src%2Fcomponents
{} (no source info) warp://action/new_tab
{ newWindow: true } warp://action/new_window
{ path: "/Users/me/project" } warp://action/new_tab?path=%2FUsers%2Fme%2Fproject
{ launchConfig: "my config" } warp://launch/my%20config
{ tabConfig: "my_tab", newWindow: true } warp://tab_config/my_tab?new_window=true
{ usePreview: true } warppreview://action/new_tab

Also exports WarpPluginOptions from types.ts, adds a README "Plugins > Warp" section, and a changeset (react-grab patch).

Link to Devin session: https://app.devin.ai/sessions/0510545c8f05421db80929b4a19509a6
Requested by: @aidenybai

Co-Authored-By: Aiden Bai <aiden.bai05@gmail.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@vercel

vercel Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
react-grab-storybook Ready Ready Preview, Comment Jun 10, 2026 7:54pm
react-grab-website Ready Ready Preview, Comment Jun 10, 2026 7:54pm

@pkg-pr-new

pkg-pr-new Bot commented Jun 10, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@react-grab/cli@467
npm i https://pkg.pr.new/grab@467
npm i https://pkg.pr.new/react-grab@467

commit: d9d158e

Comment thread packages/react-grab/src/core/plugins/warp.ts Outdated
Co-Authored-By: Aiden Bai <aiden.bai05@gmail.com>
if (!isAwaitingCopy) return;
isAwaitingCopy = false;
openWarp();
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Opens Warp after failed copy

Medium Severity

The Warp plugin’s onAfterCopy hook opens Warp whenever isAwaitingCopy is set, but it ignores the success argument from runCopyFlow. A failed or empty copy still triggers onAfterCopy with success false, so Warp can launch with nothing useful on the clipboard.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 2baee95. Configure here.

if (!isAwaitingCopy) return;
isAwaitingCopy = false;
openWarp();
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale flag opens Warp later

Medium Severity

isAwaitingCopy is set when the Warp action calls context.copy(), but it is only cleared inside onAfterCopy. Some copy paths never run runCopyFlow, so that hook never fires and the flag stays true until an unrelated later copy completes and unexpectedly opens Warp.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 2baee95. Configure here.

@@ -0,0 +1,39 @@
import type { Plugin, WarpPluginOptions } from "../../types.js";

@vercel vercel Bot Jun 10, 2026

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.

The warp plugin uses a shared boolean flag for all copy operations, causing race conditions and missing success validation

Fix on Vercel

Co-Authored-By: Aiden Bai <aiden.bai05@gmail.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 3 total unresolved issues (including 2 from previous reviews).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit d9d158e. Configure here.

@@ -0,0 +1,4 @@
export const getDirectoryPath = (filePath: string): string => {
const lastSlashIndex = filePath.lastIndexOf("/");
return lastSlashIndex > 0 ? filePath.slice(0, lastSlashIndex) : filePath;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Directory path extraction incorrect

Low Severity

getDirectoryPath only strips the final segment when lastIndexOf("/") is greater than zero. Root-level absolute paths like /App.tsx keep the full file path, and paths with no slash return the filename, so the default Warp path query can point at a file instead of its directory.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit d9d158e. Configure here.

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

Test results

Tested end-to-end on the apps/e2e-app dev server with warpPlugin registered (temporary local edit, not committed). Warp isn't installable headlessly on Linux, so I registered a fake x-scheme-handler/warp protocol handler that logs every received URI — giving a concrete signal for the warp:// navigation.

Caveat worth a maintainer judgment call: the default path comes from the bundler-reported source path, which in a Vite dev app is server-relative (/src/App.tsxpath=/src), not an absolute filesystem path — so Warp would literally open /src. It's the same filePath the Open action uses (resolved server-side there). Users can override with createWarpPlugin({ path }).

  • ✅ "Warp" action (Ctrl+W) appears in the grab context menu alongside Copy/Style/Comment/Open
  • ✅ Clicking Warp copies first ("Copied" label), then navigates; handler logged exactly warp://action/new_tab?path=%2Fsrc (directory of grabbed file /src/App.tsx)
  • ✅ Clipboard contained the grab context: [<span>Walk the dog</span> in TodoItem (at /src/App.tsx) in TodoList in App]
  • ✅ All 6 buildWarpUri variants exact-matched expected URIs
  • ✅ CI: all 15 checks green

Primary flow

Warp in context menu Protocol prompt after copy
Warp action in menu Open Warp prompt + Copied label

Logged URI: warp://action/new_tab?path=%2Fsrc

Clipboard evidence

Clipboard content pasted into input

URI builder variants
PASS {} -> warp://action/new_tab
PASS {"newWindow":true} -> warp://action/new_window
PASS {"path":"/Users/me/project"} -> warp://action/new_tab?path=%2FUsers%2Fme%2Fproject
PASS {"launchConfig":"my config"} -> warp://launch/my%20config
PASS {"tabConfig":"my_tab","newWindow":true} -> warp://tab_config/my_tab?new_window=true
PASS {"usePreview":true} -> warppreview://action/new_tab

Devin session

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant