Skip to content
Merged
Show file tree
Hide file tree
Changes from 52 commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
fa992f7
Skip package sourcemap frames for source resolution
cursoragent Jun 2, 2026
fd6e488
Limit react-grab unit test scope
cursoragent Jun 2, 2026
0030f5c
Address package frame quality findings
cursoragent Jun 3, 2026
4d8048b
Address review feedback on package stack frames
cursoragent Jun 3, 2026
ea15420
Fix package frame dedupe typecheck
cursoragent Jun 3, 2026
a8373db
Ignore shadcn ui source frames
cursoragent Jun 3, 2026
190cd69
Add configurable source frame policy
cursoragent Jun 3, 2026
b916094
Reset source ignore regex state
cursoragent Jun 3, 2026
67f2294
Skip ignored app frames in stack output
cursoragent Jun 3, 2026
8189395
Preserve leading dedupe for ignored frames
cursoragent Jun 3, 2026
6652b88
Reduce source frame classification overhead
cursoragent Jun 3, 2026
bc020d8
Preserve package labels for bundled frames
cursoragent Jun 3, 2026
84aaeea
Pass source policy options explicitly
cursoragent Jun 3, 2026
8f07296
Apply source policy to stack context API
cursoragent Jun 3, 2026
0a0836b
Cache default fiber source resolution
cursoragent Jun 3, 2026
a014313
Merge remote-tracking branch 'origin/main' into cursor/skip-package-s…
cursoragent Jun 3, 2026
7e90512
Simplify source policy boundaries
cursoragent Jun 4, 2026
d3f9f9c
Rank cached fiber source as fallback
cursoragent Jun 4, 2026
12f0d89
Extract pure source selection and add unit tests
aidenybai Jun 5, 2026
281a4c7
Document source-frame classification heuristics
aidenybai Jun 5, 2026
926fbcd
Consolidate package-name parsing and extract stack frame formatter
aidenybai Jun 5, 2026
5c67617
Classify fiber source from its raw path
aidenybai Jun 5, 2026
e18b4f6
Surface ignored fallback source in copied snippet
aidenybai Jun 5, 2026
cf79167
Keep UI-component frames as named context
aidenybai Jun 5, 2026
e70714d
fix
aidenybai Jun 5, 2026
2924723
Move unit tests into tests/ folder
aidenybai Jun 5, 2026
7076ea7
Remove unnecessary import/export aliasing
aidenybai Jun 6, 2026
56e5080
Improve HTML preview text extraction
cursoragent Jun 7, 2026
3ee3f97
Stabilize code block preview regression
cursoragent Jun 7, 2026
14c9799
Cover nested link HTML previews
cursoragent Jun 7, 2026
a039a96
Format link preview regression
cursoragent Jun 7, 2026
3d929e7
Add selector hints for low-signal traces
cursoragent Jun 7, 2026
92afd4d
Prefer link attributes for selector hints
cursoragent Jun 7, 2026
4f309bb
Expect compact href selector hint
cursoragent Jun 7, 2026
e1b635d
Clean up preview context formatting boundaries
cursoragent Jun 7, 2026
80a3a08
Format trace context result type
cursoragent Jun 7, 2026
015d4e6
Respect hidden roots in preview text
cursoragent Jun 7, 2026
e648c55
Keep preview text formatting compact
cursoragent Jun 7, 2026
73fea85
Include mixed inline preview text
cursoragent Jun 7, 2026
17c3cff
Simplify selector hint trace heuristic
cursoragent Jun 7, 2026
b2addcf
Format simplified trace heuristic
cursoragent Jun 7, 2026
39e0b55
Minimize preview text policy changes
cursoragent Jun 7, 2026
c2d16f3
Fold in #461: Improve HTML previews for syntax-highlighted code blocks
aidenybai Jun 7, 2026
d1b5fe0
Update formatStackContext tests for TraceContextResult return
aidenybai Jun 7, 2026
990bbe7
Unify source-trust check through classifySourcePath
aidenybai Jun 7, 2026
18967aa
Remove unreachable getFallbackContext
aidenybai Jun 7, 2026
2f8c801
Move preview text tag sets to constants
aidenybai Jun 7, 2026
7b046e2
fix
aidenybai Jun 7, 2026
b5d59cd
Dig past low-signal frames for a real app source
aidenybai Jun 7, 2026
ce4a72c
Trim comments that restate code
aidenybai Jun 7, 2026
34c89d4
Raise trace context hard cap to 20 frames
aidenybai Jun 7, 2026
b3ca773
Retry fiber-source resolution after a null result
aidenybai Jun 7, 2026
c20d702
Remove configurable source.ignorePaths option
aidenybai Jun 7, 2026
ec3fa39
Clarify hardMaxLines uses max not min
aidenybai Jun 7, 2026
2fe43bf
Include stack metadata in custom grab payload
cursoragent Jun 9, 2026
67f4f22
Fix nullable custom payload entries
cursoragent Jun 9, 2026
a34d457
Clarify source path fields
cursoragent Jun 9, 2026
5a9c268
Clarify file path and file name comment
cursoragent Jun 9, 2026
74536dd
Avoid selector hints after app source
cursoragent Jun 9, 2026
d42fdea
Keep custom grab entries aligned
cursoragent Jun 9, 2026
f8e25f0
Allow custom copy payload without entries
cursoragent Jun 9, 2026
73cea2f
Simplify source-frame PR and split context.ts into focused modules
aidenybai Jun 10, 2026
e14a25e
Deduplicate copy payload types and clarify names
aidenybai Jun 10, 2026
fb29e48
Replace ignored-path handling with line budget extension
aidenybai Jun 12, 2026
830693d
Rename source classification kind to source with shorter values
aidenybai Jun 12, 2026
80a506b
Rename source classification field to origin
aidenybai Jun 12, 2026
a149248
Keep per-entry metadata when multi-element copy content is transformed
aidenybai Jun 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ React Grab turns a browser selection into source context your agent can use:
The copied context includes the selected element and its component stack with source locations:

```txt
[<a class="ml-auto inline-block text-sm" href="#">Forgot your password?</a> in LoginForm (at components/login-form.tsx:46:19)]
<a class="ml-auto inline-block text-sm" href="#">Forgot your password?</a>
in LoginForm (at components/login-form.tsx:46:19)
```

## Manual Installation
Expand Down
3 changes: 2 additions & 1 deletion packages/grab/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ React Grab turns a browser selection into source context your agent can use:
The copied context includes the selected element and its component stack with source locations:

```txt
[<a class="ml-auto inline-block text-sm" href="#">Forgot your password?</a> in LoginForm (at components/login-form.tsx:46:19)]
<a class="ml-auto inline-block text-sm" href="#">Forgot your password?</a>
in LoginForm (at components/login-form.tsx:46:19)
```

## Manual Installation
Expand Down
3 changes: 2 additions & 1 deletion packages/react-grab/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ React Grab turns a browser selection into source context your agent can use:
The copied context includes the selected element and its component stack with source locations:

```txt
[<a class="ml-auto inline-block text-sm" href="#">Forgot your password?</a> in LoginForm (at components/login-form.tsx:46:19)]
<a class="ml-auto inline-block text-sm" href="#">Forgot your password?</a>
in LoginForm (at components/login-form.tsx:46:19)
```

## Manual Installation
Expand Down
136 changes: 136 additions & 0 deletions packages/react-grab/e2e/element-context.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,5 +139,141 @@ test.describe("Element Context Fallback", () => {
expect(clipboard).toContain("long-dom-element");
expect(clipboard.length).toBeLessThanOrEqual(510);
});

test("should include descendant text for syntax highlighted code blocks", async ({
reactGrab,
}) => {
await reactGrab.page.evaluate(() => {
const wrapper = document.createElement("div");
Object.assign(wrapper.style, {
position: "fixed",
top: "200px",
left: "200px",
width: "600px",
height: "160px",
zIndex: "999",
});

const codeBlock = document.createElement("pre");
codeBlock.className = "shiki shiki-themes github-light github-dark";
codeBlock.tabIndex = 0;
codeBlock.innerHTML = `
<code>
<span class="line"><span>git</span><span> add</span><span> .github/workflows/react-doctor.yml</span></span>
<span class="line"><span>git</span><span> commit</span><span> -m</span><span> "Add React Doctor to CI"</span></span>
<span class="line"><span>git</span><span> push</span></span>
</code>
`;

wrapper.appendChild(codeBlock);
document.body.appendChild(wrapper);
});

const didCopy = await reactGrab.copyElementViaApi("pre.shiki");
expect(didCopy).toBe(true);

const clipboard = await reactGrab.getClipboardContent();
expect(clipboard).toContain("<pre");
expect(clipboard).toContain("git add .github/workflows/react-doctor.yml");
expect(clipboard).toContain('git commit -m "Add React Doctor to CI"');
expect(clipboard).toContain("</pre>");
});

test("should include descendant text for nested link labels", async ({ reactGrab }) => {
await reactGrab.page.evaluate(() => {
const wrapper = document.createElement("div");
Object.assign(wrapper.style, {
position: "fixed",
top: "200px",
left: "200px",
width: "320px",
height: "80px",
zIndex: "999",
});

const link = document.createElement("a");
link.href = "/docs/ci-and-prs/github-actions-setup";
link.className = "flex h-8 w-full items-center gap-2 rounded-md px-2";
link.innerHTML = `
<span aria-hidden="true">#</span>
<span><span>GitHub Actions setup</span></span>
`;

wrapper.appendChild(link);
document.body.appendChild(wrapper);
});

const didCopy = await reactGrab.copyElementViaApi(
"a[href='/docs/ci-and-prs/github-actions-setup']",
);
expect(didCopy).toBe(true);

const clipboard = await reactGrab.getClipboardContent();
expect(clipboard).toContain('<a href="/docs/ci-and-prs/github-actions-setup"');
expect(clipboard).toContain("GitHub Actions setup");
expect(clipboard).not.toContain("# GitHub Actions setup");
expect(clipboard).toContain('selector: [href="/docs/ci-and-prs/github-actions-setup"]');
expect(clipboard).toContain("</a>");
});

test("should skip preview text for hidden selected roots", async ({ reactGrab }) => {
await reactGrab.page.evaluate(() => {
const wrapper = document.createElement("div");
Object.assign(wrapper.style, {
position: "fixed",
top: "200px",
left: "200px",
width: "200px",
height: "80px",
zIndex: "999",
});

const hiddenLabel = document.createElement("span");
hiddenLabel.setAttribute("aria-hidden", "true");
hiddenLabel.setAttribute("data-testid", "decorative-hidden-label");
hiddenLabel.textContent = "Decorative Hidden Label";

wrapper.appendChild(hiddenLabel);
document.body.appendChild(wrapper);
});

const didCopy = await reactGrab.copyElementViaApi("[data-testid='decorative-hidden-label']");
expect(didCopy).toBe(true);

const clipboard = await reactGrab.getClipboardContent();
expect(clipboard).toContain('aria-hidden="true"');
expect(clipboard).not.toContain("Decorative Hidden Label");
});

test("should include nested text for mixed inline content", async ({ reactGrab }) => {
await reactGrab.page.evaluate(() => {
const wrapper = document.createElement("div");
Object.assign(wrapper.style, {
position: "fixed",
top: "200px",
left: "200px",
width: "260px",
height: "80px",
zIndex: "999",
});

const link = document.createElement("a");
link.href = "/docs/mixed-content";
link.textContent = "Read ";
const emphasizedText = document.createElement("em");
emphasizedText.textContent = "the docs";
link.appendChild(emphasizedText);

wrapper.appendChild(link);
document.body.appendChild(wrapper);
});

const didCopy = await reactGrab.copyElementViaApi("a[href='/docs/mixed-content']");
expect(didCopy).toBe(true);

const clipboard = await reactGrab.getClipboardContent();
expect(clipboard).toContain("Read the docs");
expect(clipboard).not.toContain("<em ...>");
});
});
});
2 changes: 1 addition & 1 deletion packages/react-grab/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
"build": "NODE_ENV=production vp pack",
"build:profiling": "pnpm run prebuild && NODE_ENV=profiling REACT_GRAB_NO_MINIFY=true REACT_GRAB_SOURCEMAP=true vp pack",
"dev": "concurrently \"pnpm:css:watch\" \"vp pack --watch\"",
"test": "playwright test",
"test": "vp test run tests && playwright test",
"test:perf": "playwright test --grep @perf --reporter=list",
"test:perf:baseline": "PERF_LABEL=baseline playwright test --grep @perf --reporter=list",
"test:expect": "bun e2e/react-grab.expect.ts",
Expand Down
4 changes: 2 additions & 2 deletions packages/react-grab/src/components/renderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
FROZEN_GLOW_EDGE_PX,
Z_INDEX_OVERLAY_CANVAS,
} from "../constants.js";
import { openFile } from "../utils/open-file.js";
import { requestOpenFile } from "../utils/open-file.js";
import { isElementConnected } from "../utils/is-element-connected.js";
import { OverlayCanvas } from "./overlay-canvas.js";
import { SelectionLabel } from "./selection-label/index.js";
Expand Down Expand Up @@ -104,7 +104,7 @@ export const ReactGrabRenderer: Component<ReactGrabRendererProps> = (props) => {
onCancelDismiss={props.onCancelDismiss}
onOpen={() => {
if (props.selectionFilePath) {
openFile(props.selectionFilePath, props.selectionLineNumber);
requestOpenFile(props.selectionFilePath, props.selectionLineNumber);
}
}}
isContextMenuOpen={props.contextMenuPosition !== null}
Expand Down
5 changes: 5 additions & 0 deletions packages/react-grab/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export const INPUT_FOCUS_ACTIVATION_DELAY_MS = 400;
export const INPUT_TEXT_SELECTION_ACTIVATION_DELAY_MS = 600;
export const DEFAULT_KEY_HOLD_DURATION_MS = 100;
export const DEFAULT_MAX_CONTEXT_LINES = 3;
// Hard cap when digging past low-signal frames for a trusted app source.
Comment thread
aidenybai marked this conversation as resolved.
Outdated
export const MAX_TRACE_CONTEXT_LINES = 20;
export const SYMBOLICATION_TIMEOUT_MS = 5000;
export const MIN_HOLD_FOR_ACTIVATION_AFTER_COPY_MS = 200;
export const FINDER_TIMEOUT_MS = 200;
Expand Down Expand Up @@ -105,6 +107,9 @@ export const PREVIEW_IDENTIFYING_ATTRS = new Set([
"open",
]);

export const PREVIEW_DESCENDANT_TEXT_TAGS = new Set(["a", "code", "pre"]);
export const PREVIEW_SKIPPED_TEXT_TAGS = new Set(["script", "style", "template", "noscript"]);

export const MODIFIER_KEYS: readonly string[] = ["Meta", "Control", "Shift", "Alt"];

export const ARROW_KEYS = new Set(["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"]);
Expand Down
Loading
Loading