Conversation
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
|
Cursor Agent can help with this pull request. Just |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
@react-grab/cli
grab
@react-grab/ami
@react-grab/amp
@react-grab/claude-code
@react-grab/codex
@react-grab/copilot
@react-grab/cursor
@react-grab/droid
@react-grab/gemini
@react-grab/opencode
react-grab
@react-grab/relay
@react-grab/utils
commit: |
@react-grab/cli
grab
@react-grab/ami
@react-grab/amp
@react-grab/claude-code
@react-grab/codex
@react-grab/copilot
@react-grab/cursor
@react-grab/droid
@react-grab/gemini
@react-grab/opencode
react-grab
@react-grab/relay
@react-grab/utils
commit: |
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
|
This run croaked 😵 The workflow encountered an error before any progress could be reported. Please check the link below for details. |
| typeof value === "object" && value !== null; | ||
|
|
||
| const readString = (value: unknown): string | null => | ||
| typeof value === "string" ? value : null; |
There was a problem hiding this comment.
Duplicate isRecord/readString helpers across three new files
Low Severity
The isRecord and readString helper functions are identically defined in get-solid-source-info.ts, get-svelte-source-info.ts, and get-vue-source-info.ts. Additionally, readNumber is duplicated in the Svelte file. These could be extracted to a shared utility to reduce duplication and ensure consistent fixes.
Additional Locations (2)
There was a problem hiding this comment.
1 issue found across 28 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/utils/get-vue-source-info.ts">
<violation number="1" location="packages/react-grab/src/utils/get-vue-source-info.ts:10">
P2: Fix `isRecord` to allow functions, ensuring Vue functional components do not lose their component names.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| const VUE_PARENT_COMPONENT_PARENT_PROPERTY_NAME = "parent"; | ||
|
|
||
| const isRecord = (value: unknown): value is Record<string, unknown> => | ||
| typeof value === "object" && value !== null; |
There was a problem hiding this comment.
P2: Fix isRecord to allow functions, ensuring Vue functional components do not lose their component names.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/react-grab/src/utils/get-vue-source-info.ts, line 10:
<comment>Fix `isRecord` to allow functions, ensuring Vue functional components do not lose their component names.</comment>
<file context>
@@ -0,0 +1,151 @@
+const VUE_PARENT_COMPONENT_PARENT_PROPERTY_NAME = "parent";
+
+const isRecord = (value: unknown): value is Record<string, unknown> =>
+ typeof value === "object" && value !== null;
+
+const readString = (value: unknown): string | null =>
</file context>
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
| lineNumber: sourceInfo.lineNumber, | ||
| componentName: sourceInfo.componentName, | ||
| }; | ||
| }); |
| ): Promise<string> => | ||
| getResolvedFrameworkStackFrames(element).then((stackFrames) => { | ||
| const { maxLines = 3 } = options; | ||
| if (maxLines < 1) return ""; | ||
| if (stackFrames.length < 1) return ""; |
| ): Record<string, unknown> | null => { | ||
| if (!component) return null; | ||
| const componentType = component.type; | ||
| return isRecord(componentType) ? componentType : null; |
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
| } | ||
|
|
||
| return pluginRegistry.hooks.resolveElementSource(element); | ||
| }; |
There was a problem hiding this comment.
Column number lost for React elements after refactoring
Medium Severity
resolveElementSourceInfo hardcodes columnNumber: null for React stack frame sources. The old code in notifyElementsSelected extracted columnNumber directly from frame.columnNumber, preserving it in the selection notification payload. Now that information is always lost because resolveSourceFromStack doesn't return column numbers and the wrapper doesn't compensate for that.
Additional Locations (1)
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 4 potential issues.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
| const frameworkStack = await resolveFrameworkStack(element); | ||
|
|
||
| if (reactSource) return [reactSource, ...frameworkStack]; | ||
| return frameworkStack; |
There was a problem hiding this comment.
React source double-counted in merged stack context
Medium Severity
resolveElementStack prepends the React source info into the returned array. Callers then format this via formatElementStack and merge it with getReactStackContext output. Because formatReactStackContext omits line/column numbers for non-Next.js projects while formatElementStack always includes them, mergeStackContext deduplication won't match the two representations of the same React source. This causes the React source to consume two of the three allowed maxLines, crowding out the framework-specific frames the feature is designed to surface.
Additional Locations (2)
| frameworkStackContext, | ||
| maxLines, | ||
| ); | ||
| }, |
There was a problem hiding this comment.
Plugin source hooks registered but never invoked
Medium Severity
The frameworkSourcePlugin registers resolveElementSource, resolveElementComponentName, and resolveElementStackContext hooks, and the plugin registry exposes them via callHookResolveAsync. However, all core code paths (core/index.tsx, core/context.ts, agent/manager.ts, primitives.ts) import and call resolveElementSourceInfo, resolveElementComponentName, and resolveElementStack directly from ./source/index.js, completely bypassing the plugin registry hooks. These hooks are dead code that will never run.
Additional Locations (1)
| frameworkStackContext, | ||
| maxLines, | ||
| ); | ||
| }, |
There was a problem hiding this comment.
Triplicated React-framework stack merge logic across files
Low Severity
The same React + framework stack context merge pattern — calling getReactStackContext, resolveElementStack, formatElementStack, then conditionally calling mergeStackContext — is copy-pasted across three locations: frameworkSourcePlugin, the getStackContext public API, and getElementContext. A single shared helper would reduce maintenance burden and the risk of these diverging (the context.ts version already differs slightly in its conditional structure).
Additional Locations (2)
| locationParts.push(String(sourceInfo.columnNumber)); | ||
| } | ||
| return locationParts.join(":"); | ||
| }; |
There was a problem hiding this comment.
Column appended without line yields misleading location
Low Severity
formatSourceLocation independently checks lineNumber and columnNumber for null, so if lineNumber is null but columnNumber is not, the output becomes filePath:columnNumber — which looks identical to filePath:lineNumber and would mislead anyone navigating to that location. A columnNumber without a lineNumber is meaningless and the column part could be guarded on lineNumber being present.
There was a problem hiding this comment.
4 issues found across 20 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/primitives.ts">
<violation number="1" location="packages/react-grab/src/primitives.ts:50">
P2: `stackString` does not merge the full React stack context, resulting in a truncated stack trace for React components.</violation>
</file>
<file name="packages/react-grab/src/core/source/index.ts">
<violation number="1" location="packages/react-grab/src/core/source/index.ts:43">
P1: React source is double-counted in the merged stack context. `resolveElementStack` prepends the React source info into its result, but callers also independently fetch `reactStackContext` via `getReactStackContext`. Since `formatReactStackContext` omits line/column for non-Next.js projects while `formatElementStack` always includes them, `mergeStackContext` deduplication won't match the two representations of the same source. This causes the React frame to consume 2 of the 3 allowed `maxLines`, crowding out the framework-specific frames this feature is meant to surface.
Consider either removing the React source from `resolveElementStack` (so it only returns framework frames), or not fetching `reactStackContext` separately when using the merged flow.</violation>
</file>
<file name="packages/react-grab/src/core/plugins/framework-source.ts">
<violation number="1" location="packages/react-grab/src/core/plugins/framework-source.ts:15">
P2: The plugin hooks `resolveElementSource`, `resolveElementComponentName`, and `resolveElementStackContext` registered by `frameworkSourcePlugin` are dead code. All core code paths (`core/index.tsx`, `core/context.ts`, `agent/manager.ts`, `primitives.ts`) now import and call the resolution functions directly from `./source/index.js`, completely bypassing the plugin registry. These hooks will never be invoked, making the plugin registration misleading.</violation>
</file>
<file name="packages/react-grab/src/utils/format-element-stack.ts">
<violation number="1" location="packages/react-grab/src/utils/format-element-stack.ts:11">
P2: Column is appended without guarding on `lineNumber` being present. If `lineNumber` is `null` but `columnNumber` is not (e.g., `5`), the output becomes `file.tsx:5` — indistinguishable from `file.tsx:<line 5>`, which would mislead anyone navigating to that location. Guard the column append on `lineNumber` being non-null.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| const reactSource = await resolveReactSourceInfo(element); | ||
| const frameworkStack = await resolveFrameworkStack(element); | ||
|
|
||
| if (reactSource) return [reactSource, ...frameworkStack]; |
There was a problem hiding this comment.
P1: React source is double-counted in the merged stack context. resolveElementStack prepends the React source info into its result, but callers also independently fetch reactStackContext via getReactStackContext. Since formatReactStackContext omits line/column for non-Next.js projects while formatElementStack always includes them, mergeStackContext deduplication won't match the two representations of the same source. This causes the React frame to consume 2 of the 3 allowed maxLines, crowding out the framework-specific frames this feature is meant to surface.
Consider either removing the React source from resolveElementStack (so it only returns framework frames), or not fetching reactStackContext separately when using the merged flow.
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/source/index.ts, line 43:
<comment>React source is double-counted in the merged stack context. `resolveElementStack` prepends the React source info into its result, but callers also independently fetch `reactStackContext` via `getReactStackContext`. Since `formatReactStackContext` omits line/column for non-Next.js projects while `formatElementStack` always includes them, `mergeStackContext` deduplication won't match the two representations of the same source. This causes the React frame to consume 2 of the 3 allowed `maxLines`, crowding out the framework-specific frames this feature is meant to surface.
Consider either removing the React source from `resolveElementStack` (so it only returns framework frames), or not fetching `reactStackContext` separately when using the merged flow.</comment>
<file context>
@@ -0,0 +1,67 @@
+ const reactSource = await resolveReactSourceInfo(element);
+ const frameworkStack = await resolveFrameworkStack(element);
+
+ if (reactSource) return [reactSource, ...frameworkStack];
+ return frameworkStack;
+};
</file context>
| const stackString = await getStackContext(element); | ||
| const stack = (await getReactStack(element)) ?? []; | ||
| const elementStack = await resolveElementStack(element); | ||
| const stackString = formatElementStack(elementStack); |
There was a problem hiding this comment.
P2: stackString does not merge the full React stack context, resulting in a truncated stack trace for React components.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/react-grab/src/primitives.ts, line 50:
<comment>`stackString` does not merge the full React stack context, resulting in a truncated stack trace for React components.</comment>
<file context>
@@ -31,23 +32,24 @@ export interface ReactGrabElementContext {
- const stackString = await getStackContext(element);
+ const stack = (await getReactStack(element)) ?? [];
+ const elementStack = await resolveElementStack(element);
+ const stackString = formatElementStack(elementStack);
const htmlPreview = getHTMLPreview(element);
- const componentName = getComponentDisplayName(element);
</file context>
| export const frameworkSourcePlugin: Plugin = { | ||
| name: "framework-source", | ||
| hooks: { | ||
| resolveElementSource: (element) => resolveElementSourceInfo(element), |
There was a problem hiding this comment.
P2: The plugin hooks resolveElementSource, resolveElementComponentName, and resolveElementStackContext registered by frameworkSourcePlugin are dead code. All core code paths (core/index.tsx, core/context.ts, agent/manager.ts, primitives.ts) now import and call the resolution functions directly from ./source/index.js, completely bypassing the plugin registry. These hooks will never be invoked, making the plugin registration misleading.
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/plugins/framework-source.ts, line 15:
<comment>The plugin hooks `resolveElementSource`, `resolveElementComponentName`, and `resolveElementStackContext` registered by `frameworkSourcePlugin` are dead code. All core code paths (`core/index.tsx`, `core/context.ts`, `agent/manager.ts`, `primitives.ts`) now import and call the resolution functions directly from `./source/index.js`, completely bypassing the plugin registry. These hooks will never be invoked, making the plugin registration misleading.</comment>
<file context>
@@ -1,21 +1,39 @@
name: "framework-source",
hooks: {
- resolveElementSource: (element) => getFrameworkSourceInfo(element),
+ resolveElementSource: (element) => resolveElementSourceInfo(element),
resolveElementComponentName: (element) =>
- getFrameworkComponentName(element),
</file context>
| if (sourceInfo.lineNumber !== null) { | ||
| locationParts.push(String(sourceInfo.lineNumber)); | ||
| } | ||
| if (sourceInfo.columnNumber !== null) { |
There was a problem hiding this comment.
P2: Column is appended without guarding on lineNumber being present. If lineNumber is null but columnNumber is not (e.g., 5), the output becomes file.tsx:5 — indistinguishable from file.tsx:<line 5>, which would mislead anyone navigating to that location. Guard the column append on lineNumber being non-null.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/react-grab/src/utils/format-element-stack.ts, line 11:
<comment>Column is appended without guarding on `lineNumber` being present. If `lineNumber` is `null` but `columnNumber` is not (e.g., `5`), the output becomes `file.tsx:5` — indistinguishable from `file.tsx:<line 5>`, which would mislead anyone navigating to that location. Guard the column append on `lineNumber` being non-null.</comment>
<file context>
@@ -0,0 +1,36 @@
+ if (sourceInfo.lineNumber !== null) {
+ locationParts.push(String(sourceInfo.lineNumber));
+ }
+ if (sourceInfo.columnNumber !== null) {
+ locationParts.push(String(sourceInfo.columnNumber));
+ }
</file context>



Add framework-agnostic DOM-to-source resolution support for Solid, Vue, and Svelte to React Grab via a new plugin system, extending its ability to locate source code for non-React elements.
Summary by cubic
Adds framework-agnostic DOM→source mapping to
react-grabvia a new plugin system. In dev, Solid, Vue, and Svelte now resolve file, line, and component info; React and framework stack context lines are merged, deduped, and capped for clearer context.New Features
resolveElementSource,resolveElementComponentName,resolveElementStackContext.framework-sourceplugin:__svelte_metafor file/line/column.data-v-inspector(line/column) or falls back to__vueParentComponent.type.__fileand__name.$$click, etc.), scans loaded Vite modules to inferfile:line:columnnear handler bodies (falls back to generated positions).api.getSource,api.getStackContext, and snippet generation.frameworkSourcePlugin; added e2e coverage and a single multi-framework Vite playground under@react-grab/framework-playground(React, Vue, Solid, Svelte) for manual testing.Migration
vite-plugin-vue-inspectorfor line/column; otherwise file-only via runtime metadata.Written for commit 07b224c. Summary will update on new commits.