Skip to content

feat(react-grab): compact bracketed copy format#343

Merged
aidenybai merged 15 commits into
mainfrom
feat/compact-copy-format
May 15, 2026
Merged

feat(react-grab): compact bracketed copy format#343
aidenybai merged 15 commits into
mainfrom
feat/compact-copy-format

Conversation

@aidenybai
Copy link
Copy Markdown
Owner

@aidenybai aidenybai commented May 13, 2026

Summary

  • Replace fragile @file:line compact format with richer single-line bracketed references: [<li> "Buy groceries" in TodoItem @/src/App.tsx]
  • Include high-signal identifying attrs (id, data-testid) and a truncated text snippet to differentiate elements in multi-select
  • Only include line numbers for Next.js projects where symbolication is reliable (Vite source maps are too unreliable)
  • Remove copy-html and copy-styles plugins, replace with copy-details plugin for full verbose output
  • Plain DOM elements (no fiber) get an inline HTML preview fallback: [<div id="foo">content</div>]

Test plan

  • Select a todo item → clipboard should be [<li> "Buy groceries" in TodoItem @/src/App.tsx]
  • Select an element with data-testid → attr appears in brackets
  • Select a plain DOM element (no React fiber) → inline HTML preview in brackets
  • Multi-select elements → each on its own line, deduped by content
  • Comment + copy → single \n separator, not \n\n
  • Context menu shows "Copy details" instead of "Copy HTML" / "Copy styles"
  • "Copy details" produces full multi-line verbose output (unchanged)

Made with Cursor


Note

Medium Risk
Changes the default clipboard output format and removes existing copy-related plugins/options/hooks, which may break consumers relying on the previous snippet/HTML/styles copy behaviors or maxContextLines/transformSnippet extension points.

Overview
Updates react-grab’s default copy behavior to generate compact bracketed references (tag + optional id/data-testid + truncated direct text + component name + file path, with line numbers only for Next.js) and dedupes multi-select output to one reference per line.

Replaces the copy-html/copy-styles actions with a new copy-details plugin that copies the full verbose multi-line snippet output, and removes the maxContextLines option plus the transformSnippet hook and related utilities.

Adjusts fallback formatting for non-React elements to use an inline HTML preview, updates Storybook fixtures and e2e expectations to match the new copy output, and deletes the copy-styles e2e suite.

Reviewed by Cursor Bugbot for commit 2e4dd2a. Bugbot is set up for automated code reviews on this repo. Configure here.


Summary by cubic

Switches default copy in react-grab to a compact, single-line bracketed reference like [<button id="save"> "Save" in Button @/src/App.tsx] and adds a copy-details action for the full verbose output. Also removes unused hooks/options and speeds up multi-select by resolving sources in parallel.

  • New Features

    • Compact references include tag, id/data-testid, direct text (30 chars), component, and file; line numbers only for Next.js.
    • Plain DOM falls back to a single-line inline HTML preview; multi-select prints one per line with dedupe.
    • Comment + copy now uses a single newline; context menu shows "Copy details".
  • Refactors

    • Added copy-details plugin; removed copy-html, copy-styles, and the transformSnippet hook; dropped maxContextLines (removed from options and script parsing).
    • Centralized compact builder and inline preview; exported getDirectTextContent and getInlineHTMLPreview; getFallbackContext now delegates to getInlineHTMLPreview.
    • Updated e2e to assert component names; fixed the compact-format regex; clarified disabled-elements behavior (click falls through to the container); restored Primitives docs in README and updated architecture docs.
    • Parallelized source resolution in the compact builder for faster multi-select copies.
    • Sanitized quotes in compact references to avoid malformed output; re-exported disposeBaselineStyles from react-grab/primitives.

Written for commit 2e4dd2a. Summary will update on new commits.

…eferences

Replace the fragile @file:line compact format with a richer single-line
bracketed reference: [<tag> "text" in Component @/file]. Line numbers
are only included for Next.js where symbolication is reliable.

- Rewrite buildCompactContent to include tag name, identifying attrs
  (id, data-testid), truncated text snippet, component name, and file
- Add getInlineHTMLPreview for single-line HTML fallback (plain DOM)
- Remove copy-html, copy-styles plugins; add copy-details plugin
- Remove dead generateVerboseContent code path
- Change comment separator from \n\n to \n

Co-authored-by: Cursor <cursoragent@cursor.com>
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented May 13, 2026

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

Project Deployment Actions Updated (UTC)
react-grab-storybook Ready Ready Preview, Comment May 15, 2026 11:31am
react-grab-website Ready Ready Preview, Comment May 15, 2026 11:31am

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 13, 2026

Open in StackBlitz

npm i https://pkg.pr.new/aidenybai/react-grab/@react-grab/cli@343
npm i https://pkg.pr.new/aidenybai/react-grab/grab@343
npm i https://pkg.pr.new/aidenybai/react-grab/@react-grab/mcp@343
npm i https://pkg.pr.new/aidenybai/react-grab@343

commit: 2e4dd2a

- Prefix unused params in copy-details plugin with _
- Switch from element.textContent to getDirectTextContent to avoid
  noisy concatenated text from container elements
- Increase COMPACT_TEXT_MAX_LENGTH from 20 to 30 so short sentences
  like "This is deeply nested content" aren't truncated mid-word
- Update drag-selection tests to assert component names instead of
  nested text content

Co-authored-by: Cursor <cursoragent@cursor.com>
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 12 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/react-grab/src/core/copy.ts">

<violation number="1" location="packages/react-grab/src/core/copy.ts:80">
P2: The new fallback content path skips `transformSnippet`, so plugin-provided per-element snippet transforms no longer run for default copy operations.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread packages/react-grab/src/core/copy.ts
Comment thread packages/react-grab/e2e/element-context.spec.ts Outdated
Comment thread packages/react-grab/src/core/copy.ts Outdated
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 6 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/react-grab/src/core/copy.ts">

<violation number="1" location="packages/react-grab/src/core/copy.ts:46">
P2: Use `textContent` here instead of direct-only text; nested labels can disappear and distinct selected elements may dedupe into one copied reference.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread packages/react-grab/src/core/copy.ts Outdated
identifyingAttrs += ` ${attrName}="${attrValue}"`;
}
}
const directText = getDirectTextContent(element);
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.

P2: Use textContent here instead of direct-only text; nested labels can disappear and distinct selected elements may dedupe into one copied reference.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/react-grab/src/core/copy.ts, line 46:

<comment>Use `textContent` here instead of direct-only text; nested labels can disappear and distinct selected elements may dedupe into one copied reference.</comment>

<file context>
@@ -42,7 +43,7 @@ const buildCompactContent = async (elements: Element[]): Promise<string | null>
         }
       }
-      const directText = element.textContent?.trim() ?? "";
+      const directText = getDirectTextContent(element);
       const textSnippet = directText
         ? ` "${truncateString(directText, COMPACT_TEXT_MAX_LENGTH)}"`
</file context>

Tests now assert component names (TodoItem, TodoList) instead of
element text content, which the compact format omits for elements
whose text lives in child nodes.

Co-authored-by: Cursor <cursoragent@cursor.com>
…ormSnippet hook

- Simplify element-context test assertions to directly verify compact
  format and component name instead of tautological OR
- Remove transformSnippet from CopyHooks and call site since the
  compact format bypasses per-element snippet transforms

Co-authored-by: Cursor <cursoragent@cursor.com>
The \w+ pattern fails when attrs like data-testid follow the tag name.
Use [\s>] to match either a space (attrs present) or > (no attrs).

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread packages/react-grab/src/core/copy.ts
Comment thread apps/website-v2/next-env.d.ts Outdated
aidenybai and others added 2 commits May 13, 2026 11:16
- Remove maxContextLines from CopyOptions and call site since the
  compact format doesn't use it
- Remove accidentally committed apps/website-v2/next-env.d.ts

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread packages/react-grab/src/core/context.ts
…Preview

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread packages/react-grab/e2e/disabled-elements.spec.ts
const clipboardContent = await reactGrab.getClipboardContent();
expect(clipboardContent).not.toContain("Buy groceries");
expect(clipboardContent).not.toContain("Walk the dog");
await expect.poll(() => reactGrab.getClipboardContent()).toContain("TodoItem");
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Multi-select tests no longer validate specific selected elements

Medium Severity

Several multi-select tests previously verified which specific elements were (and were not) in the clipboard using text content like "Buy groceries" or "Walk the dog", with .not.toContain checks. They now only assert toContain("TodoItem"), which is true for any todo item. The compact format still includes text snippets (e.g., "Buy groceries"), so these checks could be preserved. Tests like "should reset accumulated selection" and "should deselect already-selected element" can no longer detect if the wrong set of elements was copied.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 22c28cb. Configure here.

Co-authored-by: Cursor <cursoragent@cursor.com>
The child has pointer-events: none so the click falls through to the
parent container. The clipboard correctly shows disabled-test-container.

Co-authored-by: Cursor <cursoragent@cursor.com>
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 1 file (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/react-grab/e2e/disabled-elements.spec.ts">

<violation number="1" location="packages/react-grab/e2e/disabled-elements.spec.ts:128">
P2: This assertion checks the wrapper id, but the copied content is built from the selected element itself, so it should still expect `pointer-events-none` (or the copied text).</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.


await reactGrab.page.mouse.click(bounds.x + bounds.width / 2, bounds.y + bounds.height / 2);
await expect.poll(() => reactGrab.getClipboardContent()).toContain("Pointer Events None");
await expect.poll(() => reactGrab.getClipboardContent()).toContain("disabled-test-container");
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.

P2: This assertion checks the wrapper id, but the copied content is built from the selected element itself, so it should still expect pointer-events-none (or the copied text).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/react-grab/e2e/disabled-elements.spec.ts, line 128:

<comment>This assertion checks the wrapper id, but the copied content is built from the selected element itself, so it should still expect `pointer-events-none` (or the copied text).</comment>

<file context>
@@ -125,7 +125,7 @@ test.describe("Disabled Element Selection", () => {
 
     await reactGrab.page.mouse.click(bounds.x + bounds.width / 2, bounds.y + bounds.height / 2);
-    await expect.poll(() => reactGrab.getClipboardContent()).toContain("pointer-events-none");
+    await expect.poll(() => reactGrab.getClipboardContent()).toContain("disabled-test-container");
   });
 
</file context>
Suggested change
await expect.poll(() => reactGrab.getClipboardContent()).toContain("disabled-test-container");
await expect.poll(() => reactGrab.getClipboardContent()).toContain("pointer-events-none");

Tip: Review your code locally with the cubic CLI to iterate faster.

Co-authored-by: Cursor <cursoragent@cursor.com>
Copy link
Copy Markdown

@cursor cursor Bot left a comment

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 2 total unresolved issues (including 1 from previous review).

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 25a14ad. Configure here.

Comment thread packages/react-grab/src/core/copy.ts
Extract formatCompactReference so source resolution runs concurrently
for all elements instead of sequentially in a loop.

Co-authored-by: Cursor <cursoragent@cursor.com>
- Remove transformSnippet from PluginHooks and plugin registry since
  the compact format bypasses per-element snippet transforms
- Remove maxContextLines from Options, plugin registry, script options
  parsing, and init defaults since it's no longer consumed
- Update architecture.md to reference copy-details instead of the
  deleted copy-html and copy-styles plugins

Co-authored-by: Cursor <cursoragent@cursor.com>
@cubic-dev-ai
Copy link
Copy Markdown
Contributor

cubic-dev-ai Bot commented May 15, 2026

You're iterating quickly on this pull request. To help protect your rate limits, cubic has paused automatic reviews on new pushes for now—when you're ready for another review, comment @cubic-dev-ai review.

- Replace double quotes with single quotes in attr values and text
  snippets to prevent malformed bracket output
- Re-export disposeBaselineStyles from primitives so consumers can
  clean up the baseline iframe created by extractElementCss

Co-authored-by: Cursor <cursoragent@cursor.com>
The Primitives section was accidentally removed when copy-styles and
copy-html plugins were deleted. The API itself is unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
@aidenybai aidenybai merged commit f9db926 into main May 15, 2026
15 checks passed
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