feat(react-grab): compact bracketed copy format#343
Conversation
…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>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
commit: |
- 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>
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
| identifyingAttrs += ` ${attrName}="${attrValue}"`; | ||
| } | ||
| } | ||
| const directText = getDirectTextContent(element); |
There was a problem hiding this comment.
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>
- 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>
…Preview Co-authored-by: Cursor <cursoragent@cursor.com>
| 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"); |
There was a problem hiding this comment.
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)
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>
There was a problem hiding this comment.
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"); |
There was a problem hiding this comment.
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>
| 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>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 2 total unresolved issues (including 1 from previous review).
❌ 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.
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>
|
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 |
- 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>


Summary
@file:linecompact format with richer single-line bracketed references:[<li> "Buy groceries" in TodoItem @/src/App.tsx]id,data-testid) and a truncated text snippet to differentiate elements in multi-selectcopy-htmlandcopy-stylesplugins, replace withcopy-detailsplugin for full verbose output[<div id="foo">content</div>]Test plan
[<li> "Buy groceries" in TodoItem @/src/App.tsx]data-testid→ attr appears in brackets\nseparator, not\n\nMade 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/transformSnippetextension points.Overview
Updates
react-grab’s default copy behavior to generate compact bracketed references (tag + optionalid/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-stylesactions with a newcopy-detailsplugin that copies the full verbose multi-line snippet output, and removes themaxContextLinesoption plus thetransformSnippethook 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-stylese2e 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-grabto a compact, single-line bracketed reference like[<button id="save"> "Save" in Button @/src/App.tsx]and adds acopy-detailsaction for the full verbose output. Also removes unused hooks/options and speeds up multi-select by resolving sources in parallel.New Features
id/data-testid, direct text (30 chars), component, and file; line numbers only for Next.js.Refactors
copy-detailsplugin; removedcopy-html,copy-styles, and thetransformSnippethook; droppedmaxContextLines(removed from options and script parsing).getDirectTextContentandgetInlineHTMLPreview;getFallbackContextnow delegates togetInlineHTMLPreview.disposeBaselineStylesfromreact-grab/primitives.Written for commit 2e4dd2a. Summary will update on new commits.