Skip to content

feat: show React component names in inspector tree, fix JSXMemberExpression & React 19 support#506

Closed
Akshay090 wants to merge 3 commits into
zh-lx:mainfrom
Akshay090:feat/jsx-member-expr-and-fiber-tree-simplified
Closed

feat: show React component names in inspector tree, fix JSXMemberExpression & React 19 support#506
Akshay090 wants to merge 3 commits into
zh-lx:mainfrom
Akshay090:feat/jsx-member-expr-and-fiber-tree-simplified

Conversation

@Akshay090
Copy link
Copy Markdown

@Akshay090 Akshay090 commented Mar 26, 2026

Summary

This PR improves the "Click node to locate" panel by showing actual React component names instead of just DOM tags, adds support for JSXMemberExpression in data-insp-path, and restores React 19 compatibility for source file resolution.

Changes

1. JSXMemberExpression support (packages/core/src/server/transform/transform-jsx.ts)

The Babel transform that injects data-insp-path previously only handled JSXIdentifier (simple tags like <div>, <Button>). JSXMemberExpression elements like <Typography.H2>, <Page.Content>, <Icons.Close /> were silently dropped — nodeName resolved to an empty string and the element was skipped.

Added getJSXElementName() that recursively handles JSXIdentifier, JSXMemberExpression, and JSXNamespacedName. Includes 5 new test cases.

2. React 19 jsx-dev-runtime patch (packages/vite/src/index.ts)

React 19 removed _debugSource from fiber nodes (facebook/react#32574), which broke source file resolution for tools that rely on it.

The Vite plugin now intercepts jsx-dev-runtime.js during dev and patches _debugInfo to carry the source parameter (fileName, lineNumber, columnNumber) that jsxDEV() still receives but React 19 no longer stores. Supports React 19.0–19.1 and 19.2+ (different internal signatures).

Adapted from vite-plugin-react-click-to-component by @ArnaudBarre.

3. Vite pre-bundling compatibility (packages/vite/src/index.ts)

Vite pre-bundles dependencies into chunk files (e.g. chunk-XXXXX.js), so the jsx-dev-runtime code can end up in a chunk where the filename no longer contains "jsx-dev-runtime". This caused the React 19 patch to silently not apply in real-world Vite projects.

Fixed with two layers:

  • optimizeDeps.exclude: Excludes react/jsx-dev-runtime from Vite's dependency pre-bundling so the transform hook can intercept it by filename
  • Content-based fallback: For edge cases where jsx-dev-runtime still ends up in a chunk (e.g. re-exported by another dep), detects the _debugInfo property definition in code content for files under node_modules

4. Fiber-based component tree (packages/core/src/client/index.ts)

The right-click context menu ("Click node to locate") now walks React's _debugOwner chain to build the component tree, showing actual component names like <Dashboard>, <UserProfile>, <Card> instead of just <div>, <span>, <input>.

  • Uses React DevTools global hook or __reactFiber to get fibers from DOM elements
  • Walks _debugOwner for the component hierarchy (naturally skips framework internals)
  • Reads _debugSource (React <19) or _debugInfo (React 19, via our patch) for file paths
  • Filters out components from node_modules or with unresolvable names
  • Resolves HOC-wrapped component names (React.memo, React.forwardRef, .WrappedComponent)
  • Falls back to the original DOM-based tree for non-React apps or when fibers aren't available

Before / After

Before — tree shows mostly <div> tags:

image

After — tree shows actual React component hierarchy:

image

Testing

  • Tested with React 18 (demos/vite-react) and React 19 (local demo)
  • Tested with Vite 7 (pre-bundled chunk detection verified)
  • All existing tests pass + 5 new tests for JSXMemberExpression
  • Non-React apps fall back to existing DOM-based behavior (no regression)

…ression

## What changed

1. **JSXMemberExpression support** (`transform-jsx.ts`)
   - `<Typography.H2>`, `<Page.Content>`, `<Icons.Close />` etc. now correctly
     appear in `data-insp-path`. Previously only simple identifiers like `<div>`
     were captured; member expressions were silently dropped.

2. **React 19 jsx-dev-runtime patch** (`packages/vite`)
   - React 19 removed `_debugSource` from fibers (facebook/react#32574).
     The Vite plugin now patches `jsx-dev-runtime.js` at dev time to inject
     the `source` parameter into `_debugInfo`, restoring file/line/column
     info for the client to read. Supports React 19.0–19.2+.
   - Adapted from vite-plugin-react-click-to-component by ArnaudBarre.

3. **Fiber-based component tree in context menu** (`packages/core/client`)
   - The right-click "Click node to locate" panel now walks React's
     `_debugOwner` chain to show actual component names (e.g. `<Dashboard>`,
     `<UserProfile>`) instead of only DOM elements (`<div>`, `<span>`).
   - Components from `node_modules` or with unresolvable names are filtered out.
   - Falls back to the original DOM-based tree for non-React apps.

## Before / After

**Before:** tree shows mostly `<div>` tags with only a few component names that
happened to have `data-insp-path`.

**After:** tree shows the actual React component hierarchy with proper names,
making it far easier to navigate to the right source file.

Made-with: Cursor
@qodo-free-for-open-source-projects
Copy link
Copy Markdown

Review Summary by Qodo

Show React component names in inspector and fix JSXMemberExpression support

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Add React fiber-based component tree to inspector context menu
  - Shows actual component names instead of just DOM tags
  - Walks React's _debugOwner chain for component hierarchy
  - Filters out node_modules and unresolvable components
• Support JSXMemberExpression in data-insp-path injection
  - Handles <Typography.H2>, <Page.Content>, <Icons.Close /> patterns
  - Recursively resolves member expressions and namespaced names
• Patch React 19 jsx-dev-runtime to restore source file information
  - Injects source parameter into _debugInfo for fiber source resolution
  - Supports React 19.0–19.1 and 19.2+ with different signatures
• Fallback to DOM-based tree for non-React applications
Diagram
flowchart LR
  A["JSX Transform"] -->|"Handle member expressions"| B["getJSXElementName()"]
  B -->|"Inject data-insp-path"| C["DOM Elements"]
  D["React 19 jsx-dev-runtime"] -->|"Patch source info"| E["_debugInfo"]
  E -->|"Provide source data"| F["Client Inspector"]
  C -->|"Extract fibers"| G["getReactFiber()"]
  G -->|"Walk _debugOwner chain"| H["getLayersFromFiber()"]
  H -->|"Build component tree"| I["generateFiberNodeTree()"]
  I -->|"Display in context menu"| J["Inspector Panel"]
  F -->|"Fallback if no fibers"| K["DOM-based tree"]
Loading

Grey Divider

File Changes

1. packages/core/src/client/index.ts ✨ Enhancement +166/-1

Add fiber-based React component tree extraction

• Added React fiber helper functions to extract component names and source information
• Implemented getReactFiber() to access React fibers via DevTools hook or __reactFiber property
• Added getFiberComponentName() and resolveInnerComponentName() to resolve HOC-wrapped component
 names
• Implemented getLayersFromFiber() to walk _debugOwner chain and build component hierarchy
• Added generateFiberNodeTree() method to create tree structure from fiber layers
• Modified generateNodeTree() to try fiber-based tree first, fallback to DOM-based tree

packages/core/src/client/index.ts


2. packages/core/src/server/transform/transform-jsx.ts ✨ Enhancement +17/-1

Support JSXMemberExpression in element name resolution

• Added getJSXElementName() function to recursively handle JSXIdentifier, JSXMemberExpression, and
 JSXNamespacedName
• Supports nested member expressions like Typography.H2, Page.Content, A.B.C
• Handles namespaced names like namespace:name
• Updated JSX traversal to use new function instead of simple .name property access

packages/core/src/server/transform/transform-jsx.ts


3. packages/vite/src/index.ts 🐞 Bug fix +40/-0

Patch React 19 jsx-dev-runtime for source info restoration

• Added patchReact19JsxDevRuntime() function to patch jsx-dev-runtime at dev time
• Injects source parameter into _debugInfo property for React 19 compatibility
• Handles React 19.0–19.1 and 19.2+ with different internal function signatures
• Integrated patch into transform hook to intercept jsx-dev-runtime.js files

packages/vite/src/index.ts


View more (2)
4. packages/core/types/client/index.d.ts 📝 Documentation +1/-0

Add fiber tree generation method declaration

• Added generateFiberNodeTree() method signature to TypeScript declarations
• Returns TreeNode | null to indicate success or fallback to DOM-based tree

packages/core/types/client/index.d.ts


5. test/core/server/transform/transform-jsx.test.ts 🧪 Tests +37/-0

Add JSXMemberExpression test coverage

• Added 5 new test cases for JSXMemberExpression support
• Tests simple member expressions (Typography.H2), nested patterns (Page.Content), self-closing
 tags (Icons.Close)
• Tests deeply nested expressions (A.B.C) and escape tag filtering
• Verifies that member expressions are correctly injected into data-insp-path

test/core/server/transform/transform-jsx.test.ts


Grey Divider

Qodo Logo

@qodo-free-for-open-source-projects
Copy link
Copy Markdown

qodo-free-for-open-source-projects Bot commented Mar 26, 2026

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📎 Requirement gaps (0) 📐 Spec deviations (0)

Grey Divider


Action required

1. Namespaced JSX breaks parsing 🐞 Bug ✓ Correctness
Description
transformJsx() now injects data-insp-path names containing : for JSXNamespacedName, but the
client parser splits on : and assumes the last 3 segments are [line, column, name]. This
mis-parses line/column (NaN) and breaks locate/copy for namespaced JSX elements (e.g.
svg:xlink).
Code

packages/core/src/server/transform/transform-jsx.ts[R23-25]

+  if (nameNode.type === 'JSXNamespacedName') {
+    return `${nameNode.namespace?.name || ''}:${nameNode.name?.name || ''}`;
+  }
Evidence
The server-side JSX transform encodes the node name into a colon-delimited data-insp-path value.
The new JSXNamespacedName case returns a colon-containing string (namespace:name), which
collides with the delimiter scheme expected by the client-side getSourceInfo() parser that uses
split(':') and fixed-from-end indexing.

packages/core/src/server/transform/transform-jsx.ts[13-27]
packages/core/src/server/transform/transform-jsx.ts[62-69]
packages/core/src/client/index.ts[577-593]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`JSXNamespacedName` now produces a `nodeName` containing `:` (e.g. `svg:xlink`). This value is appended into `data-insp-path` using `:` as the delimiter, but the client splits `data-insp-path` on `:` and assumes the last 3 segments are `[line, column, name]`. That assumption breaks as soon as `name` itself contains `:`.

## Issue Context
- `transformJsx()` injects `data-insp-path="${filePath}:${line}:${column}:${nodeName}"`.
- `getSourceInfo()` parses with `paths.split(':')` and fixed indexing from the end.

## Fix Focus Areas
- packages/core/src/server/transform/transform-jsx.ts[13-27]
- packages/core/src/server/transform/transform-jsx.ts[62-69]
- packages/core/src/client/index.ts[577-593]

## Implementation guidance
Choose one:
1) **Change encoding**: represent namespaced JSX without `:` (e.g. `namespace.name`), so `data-insp-path` remains safely colon-delimited.
2) **Change decoding**: update `getSourceInfo()` to parse from the right by locating the last two numeric segments (line/column) and treat everything after them as `name` (joined with `:`), and everything before them as `path` (joined with `:`). Add a unit/integration test covering a namespaced JSX tag to prevent regressions.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Non-HTMLElement fiber target 🐞 Bug ⛯ Reliability
Description
generateNodeTree() passes nodePath[0] (from e.composedPath()) into the fiber-tree builder
assuming it is an HTMLElement, and getLayersFromFiber() can fall back to storing that target
as a layer element. If that value isn’t an HTMLElement, later calls like
renderCover(node.element) can throw when getBoundingClientRect() is invoked.
Code

packages/core/src/client/index.ts[R965-971]

  generateNodeTree = (nodePath: HTMLElement[]): TreeNode => {
-    let root: TreeNode;
+    // Try fiber-based tree first (shows React component names)
+    const target = nodePath[0];
+    if (target) {
+      const fiberTree = this.generateFiberNodeTree(target);
+      if (fiberTree) return fiberTree;
+    }
Evidence
The context menu handler uses e.composedPath() (not filtered) and the new code uses the first
entry as the fiber target. getLayersFromFiber() records element: domNode || target, so a
non-HTMLElement target can propagate into the tree. The hover logic calls
renderCover(node.element), and renderCover() assumes an HTMLElement and calls
getBoundingClientRect().

packages/core/src/client/index.ts[952-972]
packages/core/src/client/index.ts[176-199]
packages/core/src/client/index.ts[507-516]
packages/core/src/client/index.ts[1175-1189]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The fiber-tree path assumes the first `composedPath()` entry is an `HTMLElement`. In reality, `composedPath()` can include non-HTMLElement entries, and `getLayersFromFiber()` can store `target` as `layer.element` via `domNode || target`. Later `renderCover()` calls `getBoundingClientRect()` on `node.element` and can throw if it’s not an `HTMLElement`.

## Issue Context
- `handleContextMenu()` obtains `nodePath = e.composedPath()` and passes it into `generateNodeTree()`.
- `generateNodeTree()` uses `const target = nodePath[0]` for fiber tree.
- `getLayersFromFiber()` sets `element: domNode || target`.
- `handleMouseEnterNode()` calls `this.renderCover(node.element)`.

## Fix Focus Areas
- packages/core/src/client/index.ts[952-972]
- packages/core/src/client/index.ts[176-199]
- packages/core/src/client/index.ts[507-516]
- packages/core/src/client/index.ts[1175-1189]

## Implementation guidance
- In `generateNodeTree()`, pick the first actual `HTMLElement` from the composed path:
 - e.g. `const target = nodePath.find((n): n is HTMLElement => n instanceof HTMLElement);`
- Alternatively (or additionally), make `getLayersFromFiber()` accept `EventTarget | Node` but *only* push layers where `element` is an `HTMLElement`:
 - Use `domNode ?? (target instanceof HTMLElement ? target : null)` and skip the layer if null.
- As defense-in-depth, consider guarding `renderCover()` so it no-ops if the argument is not an `HTMLElement`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment on lines +23 to +25
if (nameNode.type === 'JSXNamespacedName') {
return `${nameNode.namespace?.name || ''}:${nameNode.name?.name || ''}`;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. Namespaced jsx breaks parsing 🐞 Bug ✓ Correctness

transformJsx() now injects data-insp-path names containing : for JSXNamespacedName, but the
client parser splits on : and assumes the last 3 segments are [line, column, name]. This
mis-parses line/column (NaN) and breaks locate/copy for namespaced JSX elements (e.g.
svg:xlink).
Agent Prompt
## Issue description
`JSXNamespacedName` now produces a `nodeName` containing `:` (e.g. `svg:xlink`). This value is appended into `data-insp-path` using `:` as the delimiter, but the client splits `data-insp-path` on `:` and assumes the last 3 segments are `[line, column, name]`. That assumption breaks as soon as `name` itself contains `:`.

## Issue Context
- `transformJsx()` injects `data-insp-path="${filePath}:${line}:${column}:${nodeName}"`.
- `getSourceInfo()` parses with `paths.split(':')` and fixed indexing from the end.

## Fix Focus Areas
- packages/core/src/server/transform/transform-jsx.ts[13-27]
- packages/core/src/server/transform/transform-jsx.ts[62-69]
- packages/core/src/client/index.ts[577-593]

## Implementation guidance
Choose one:
1) **Change encoding**: represent namespaced JSX without `:` (e.g. `namespace.name`), so `data-insp-path` remains safely colon-delimited.
2) **Change decoding**: update `getSourceInfo()` to parse from the right by locating the last two numeric segments (line/column) and treat everything after them as `name` (joined with `:`), and everything before them as `path` (joined with `:`). Add a unit/integration test covering a namespaced JSX tag to prevent regressions.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@Akshay090 Akshay090 marked this pull request as draft March 26, 2026 08:52
Vite pre-bundles dependencies into chunk files (e.g. chunk-XXXXX.js),
so the jsx-dev-runtime code ends up in a chunk without "jsx-dev-runtime"
in the filename. Add content-based fallback detection for these chunks
by checking for "_debugInfo" and "value: null" in the code content.

Made-with: Cursor
@Akshay090 Akshay090 force-pushed the feat/jsx-member-expr-and-fiber-tree-simplified branch from c319f16 to 560f030 Compare March 26, 2026 08:53
…nsp-path parsing

data-insp-path uses colon as its delimiter (filePath:line:column:tagName).
JSXNamespacedName produces names like `svg:xlink` which would break the
client-side parser. Use dot separator instead (e.g. `svg.xlink`).

Added a test to verify the separator is dot, not colon.

Made-with: Cursor
@Akshay090
Copy link
Copy Markdown
Author

Addressed the JSXNamespacedName parsing issue raised by Qodo bot — changed the separator from : to . (e.g. svg:xlinksvg.xlink) so it doesn't break data-insp-path colon-delimited parsing. Added a test to verify. See 3d952bf.

@Akshay090 Akshay090 marked this pull request as ready for review March 26, 2026 09:24
@qodo-free-for-open-source-projects
Copy link
Copy Markdown

Review Summary by Qodo

Show React component names in inspector and fix JSXMemberExpression support

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Show actual React component names in inspector tree via fiber walking
• Add JSXMemberExpression support for complex component tags
• Patch React 19 jsx-dev-runtime to restore source info on fibers
• Exclude jsx-dev-runtime from Vite pre-bundling with fallback detection
Diagram
flowchart LR
  A["JSX Transform"] -->|"Handle JSXMemberExpression"| B["data-insp-path injection"]
  C["React Fiber"] -->|"Walk _debugOwner chain"| D["Component hierarchy"]
  E["React 19 jsx-dev-runtime"] -->|"Patch _debugInfo"| F["Source info restored"]
  G["Vite Pre-bundling"] -->|"Exclude + Content fallback"| H["Reliable patch application"]
  B --> I["Inspector Tree"]
  D --> I
  F --> I
  H --> I
Loading

Grey Divider

File Changes

1. packages/core/src/client/index.ts ✨ Enhancement +166/-1

Add fiber-based component tree extraction for inspector

• Added React fiber helper functions to extract component names and source info
• Implemented getLayersFromFiber() to walk _debugOwner chain for component hierarchy
• Added generateFiberNodeTree() method to build tree from fiber layers
• Modified generateNodeTree() to try fiber-based tree first, fallback to DOM-based
• Includes HOC unwrapping logic for React.memo, React.forwardRef, and .WrappedComponent

packages/core/src/client/index.ts


2. packages/core/src/server/transform/transform-jsx.ts ✨ Enhancement +19/-1

Support JSXMemberExpression and JSXNamespacedName in transforms

• Added getJSXElementName() function to recursively handle JSXIdentifier, JSXMemberExpression, and
 JSXNamespacedName
• Supports nested member expressions like Typography.H2, Page.Content, A.B.C
• Uses dot separator for namespaced names to avoid breaking data-insp-path parsing
• Updated JSX traversal to use new function instead of simple .name access

packages/core/src/server/transform/transform-jsx.ts


3. packages/vite/src/index.ts ✨ Enhancement +63/-0

Patch React 19 jsx-dev-runtime and handle Vite pre-bundling

• Added patchReact19JsxDevRuntime() function to inject source into _debugInfo
• Supports React 19.0-19.1 and 19.2+ with different internal signatures
• Added config() hook to exclude react/jsx-dev-runtime from Vite pre-bundling
• Added content-based fallback detection for jsx-dev-runtime in pre-bundled chunks
• Integrated patch into transform() hook with dual-layer detection strategy

packages/vite/src/index.ts


View more (3)
4. packages/core/types/client/index.d.ts 📝 Documentation +1/-0

Update type definitions for fiber tree generation

• Added generateFiberNodeTree() method signature to TypeScript declarations

packages/core/types/client/index.d.ts


5. packages/vite/types/index.d.ts 📝 Documentation +5/-0

Update Vite plugin type definitions

• Added config() method return type with optimizeDeps.exclude configuration

packages/vite/types/index.d.ts


6. test/core/server/transform/transform-jsx.test.ts 🧪 Tests +47/-0

Add tests for JSXMemberExpression and JSXNamespacedName

• Added 5 new test cases for JSXMemberExpression support
• Added 1 test case for JSXNamespacedName with dot separator
• Tests cover simple member expressions, self-closing tags, nested expressions, and escape tag
 filtering

test/core/server/transform/transform-jsx.test.ts


Grey Divider

Qodo Logo

@qodo-free-for-open-source-projects
Copy link
Copy Markdown

qodo-free-for-open-source-projects Bot commented Mar 26, 2026

Code Review by Qodo

🐞 Bugs (4) 📘 Rule violations (0) 📎 Requirement gaps (0) 📐 Spec deviations (0)

Grey Divider


Action required

1. Unsafe React 19 runtime patch 🐞 Bug ✓ Correctness ⭐ New
Description
patchReact19JsxDevRuntime() uses a whitespace-sensitive substring to detect the React 19.0–19.1
ReactElement signature, and then unconditionally applies global argument-threading replacements; if
the signature check fails on transformed/minified code, the replacements can inject a duplicate
source argument and break jsx-dev-runtime execution.
Code

packages/vite/src/index.ts[R20-47]

+function patchReact19JsxDevRuntime(code: string): string | undefined {
+  if (code.includes('_source')) return undefined; // React <19, already has source
+
+  const defineIndex = code.indexOf('"_debugInfo"');
+  if (defineIndex === -1) return undefined;
+  const valueIndex = code.indexOf('value: null', defineIndex);
+  if (valueIndex === -1) return undefined;
+
+  let patched =
+    code.slice(0, valueIndex) + 'value: source' + code.slice(valueIndex + 11);
+
+  // React 19.0-19.1: source is already a param of ReactElement
+  if (patched.includes('function ReactElement(type, key, self, source,')) {
+    return patched;
+  }
+
+  // React 19.2+: source needs to be threaded through jsxDEV → jsxDEVImpl → ReactElement
+  patched = patched.replace(
+    /maybeKey,\s*isStaticChildren/g,
+    'maybeKey, isStaticChildren, source',
+  );
+  patched = patched.replace(
+    /(\w+)?,\s*debugStack,\s*debugTask/g,
+    (m: string, previousArg: string) => {
+      if (previousArg === 'source') return m;
+      return m.replace('debugTask', 'debugTask, source');
+    },
+  );
Evidence
The version split is controlled by `patched.includes('function ReactElement(type, key, self,
source,')`, which can fail if whitespace or formatting differs. When it fails, the code runs global
replace(/maybeKey,\s*isStaticChildren/g, ...) without checking whether source is already
present, and the plugin intentionally applies this patch not only to the original jsx-dev-runtime
file but also to rewritten/pre-bundled node_modules chunks via the content-based fallback.

packages/vite/src/index.ts[16-49]
packages/vite/src/index.ts[143-159]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The React 19 patch relies on a brittle, formatting-dependent string match to decide whether to early-return for 19.0–19.1. If that check misses, the subsequent global regex rewrites can inject `source` even when it is already threaded, producing duplicated arguments and potentially breaking runtime JSX behavior.

### Issue Context
This plugin also patches pre-bundled/re-written `node_modules` chunks via a content-based fallback, where formatting is less predictable than the original source.

### Fix Focus Areas
- packages/vite/src/index.ts[16-49]
- packages/vite/src/index.ts[143-159]

### Suggested change
- Replace the whitespace-sensitive `includes('function ReactElement(type, key, self, source,')` with a regex tolerant to whitespace/minification, e.g. `function\s+ReactElement\(type\s*,\s*key\s*,\s*self\s*,\s*source\s*,`.
- Make the argument-threading replacements idempotent:
 - Before replacing `maybeKey, isStaticChildren`, first check for an existing `maybeKey, isStaticChildren, source` (with flexible whitespace) and skip/avoid double insertion.
 - Consider adding a second structural guard (e.g., presence of `jsxDEV` / `ReactElement` markers together) so the fallback patch cannot accidentally mutate unrelated `node_modules` chunks.
- Optionally detect already-patched code (`value: source`) and return `undefined` early to avoid re-processing.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Namespaced JSX breaks parsing 🐞 Bug ✓ Correctness
Description
transformJsx() now injects data-insp-path names containing : for JSXNamespacedName, but the
client parser splits on : and assumes the last 3 segments are [line, column, name]. This
mis-parses line/column (NaN) and breaks locate/copy for namespaced JSX elements (e.g.
svg:xlink).
Code

packages/core/src/server/transform/transform-jsx.ts[R23-25]

+  if (nameNode.type === 'JSXNamespacedName') {
+    return `${nameNode.namespace?.name || ''}:${nameNode.name?.name || ''}`;
+  }
Evidence
The server-side JSX transform encodes the node name into a colon-delimited data-insp-path value.
The new JSXNamespacedName case returns a colon-containing string (namespace:name), which
collides with the delimiter scheme expected by the client-side getSourceInfo() parser that uses
split(':') and fixed-from-end indexing.

packages/core/src/server/transform/transform-jsx.ts[13-27]
packages/core/src/server/transform/transform-jsx.ts[62-69]
packages/core/src/client/index.ts[577-593]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`JSXNamespacedName` now produces a `nodeName` containing `:` (e.g. `svg:xlink`). This value is appended into `data-insp-path` using `:` as the delimiter, but the client splits `data-insp-path` on `:` and assumes the last 3 segments are `[line, column, name]`. That assumption breaks as soon as `name` itself contains `:`.
## Issue Context
- `transformJsx()` injects `data-insp-path="${filePath}:${line}:${column}:${nodeName}"`.
- `getSourceInfo()` parses with `paths.split(':')` and fixed indexing from the end.
## Fix Focus Areas
- packages/core/src/server/transform/transform-jsx.ts[13-27]
- packages/core/src/server/transform/transform-jsx.ts[62-69]
- packages/core/src/client/index.ts[577-593]
## Implementation guidance
Choose one:
1) **Change encoding**: represent namespaced JSX without `:` (e.g. `namespace.name`), so `data-insp-path` remains safely colon-delimited.
2) **Change decoding**: update `getSourceInfo()` to parse from the right by locating the last two numeric segments (line/column) and treat everything after them as `name` (joined with `:`), and everything before them as `path` (joined with `:`). Add a unit/integration test covering a namespaced JSX tag to prevent regressions.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. DevTools hook null crash 🐞 Bug ⛯ Reliability ⭐ New
Description
getReactFiber() assumes __REACT_DEVTOOLS_GLOBAL_HOOK__.renderers exists and has .values(); if the
hook exists but renderers is missing/not a Map, calling renderers.values() throws before the inner
try/catch and breaks the context-menu tree generation.
Code

packages/core/src/client/index.ts[R99-109]

+  if ('__REACT_DEVTOOLS_GLOBAL_HOOK__' in window) {
+    const { renderers } = (window as any).__REACT_DEVTOOLS_GLOBAL_HOOK__;
+    for (const renderer of renderers.values()) {
+      try {
+        const fiber = renderer.findFiberByHostInstance(element);
+        if (fiber) return fiber;
+      } catch {
+        // React may be mid-render
+      }
+    }
+  }
Evidence
The code destructures renderers and immediately iterates renderers.values() without checking
renderers is defined/iterable, and this path runs for every context-menu action before the DOM-based
fallback is attempted.

packages/core/src/client/index.ts[98-114]
packages/core/src/client/index.ts[965-974]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`getReactFiber()` can throw when `window.__REACT_DEVTOOLS_GLOBAL_HOOK__` exists but `renderers` is missing or not a Map/iterable, because the code calls `renderers.values()` without validating it.

### Issue Context
This exception happens before the inner `try/catch` (which only wraps `findFiberByHostInstance`), so it can prevent building the inspector tree.

### Fix Focus Areas
- packages/core/src/client/index.ts[98-114]

### Suggested change
- Read the hook into a local `hook` variable.
- Validate `hook?.renderers` is present and that `typeof renderers.values === 'function'` (or that it’s iterable) before looping.
- If not valid, skip DevTools-hook traversal and fall back to the `__reactFiber` key scan.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Non-HTMLElement fiber target 🐞 Bug ⛯ Reliability
Description
generateNodeTree() passes nodePath[0] (from e.composedPath()) into the fiber-tree builder
assuming it is an HTMLElement, and getLayersFromFiber() can fall back to storing that target
as a layer element. If that value isn’t an HTMLElement, later calls like
renderCover(node.element) can throw when getBoundingClientRect() is invoked.
Code

packages/core/src/client/index.ts[R965-971]

 generateNodeTree = (nodePath: HTMLElement[]): TreeNode => {
-    let root: TreeNode;
+    // Try fiber-based tree first (shows React component names)
+    const target = nodePath[0];
+    if (target) {
+      const fiberTree = this.generateFiberNodeTree(target);
+      if (fiberTree) return fiberTree;
+    }
Evidence
The context menu handler uses e.composedPath() (not filtered) and the new code uses the first
entry as the fiber target. getLayersFromFiber() records element: domNode || target, so a
non-HTMLElement target can propagate into the tree. The hover logic calls
renderCover(node.element), and renderCover() assumes an HTMLElement and calls
getBoundingClientRect().

packages/core/src/client/index.ts[952-972]
packages/core/src/client/index.ts[176-199]
packages/core/src/client/index.ts[507-516]
packages/core/src/client/index.ts[1175-1189]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The fiber-tree path assumes the first `composedPath()` entry is an `HTMLElement`. In reality, `composedPath()` can include non-HTMLElement entries, and `getLayersFromFiber()` can store `target` as `layer.element` via `domNode || target`. Later `renderCover()` calls `getBoundingClientRect()` on `node.element` and can throw if it’s not an `HTMLElement`.
## Issue Context
- `handleContextMenu()` obtains `nodePath = e.composedPath()` and passes it into `generateNodeTree()`.
- `generateNodeTree()` uses `const target = nodePath[0]` for fiber tree.
- `getLayersFromFiber()` sets `element: domNode || target`.
- `handleMouseEnterNode()` calls `this.renderCover(node.element)`.
## Fix Focus Areas
- packages/core/src/client/index.ts[952-972]
- packages/core/src/client/index.ts[176-199]
- packages/core/src/client/index.ts[507-516]
- packages/core/src/client/index.ts[1175-1189]
## Implementation guidance
- In `generateNodeTree()`, pick the first actual `HTMLElement` from the composed path:
- e.g. `const target = nodePath.find((n): n is HTMLElement => n instanceof HTMLElement);`
- Alternatively (or additionally), make `getLayersFromFiber()` accept `EventTarget | Node` but *only* push layers where `element` is an `HTMLElement`:
- Use `domNode ?? (target instanceof HTMLElement ? target : null)` and skip the layer if null.
- As defense-in-depth, consider guarding `renderCover()` so it no-ops if the argument is not an `HTMLElement`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment on lines +20 to +47
function patchReact19JsxDevRuntime(code: string): string | undefined {
if (code.includes('_source')) return undefined; // React <19, already has source

const defineIndex = code.indexOf('"_debugInfo"');
if (defineIndex === -1) return undefined;
const valueIndex = code.indexOf('value: null', defineIndex);
if (valueIndex === -1) return undefined;

let patched =
code.slice(0, valueIndex) + 'value: source' + code.slice(valueIndex + 11);

// React 19.0-19.1: source is already a param of ReactElement
if (patched.includes('function ReactElement(type, key, self, source,')) {
return patched;
}

// React 19.2+: source needs to be threaded through jsxDEV → jsxDEVImpl → ReactElement
patched = patched.replace(
/maybeKey,\s*isStaticChildren/g,
'maybeKey, isStaticChildren, source',
);
patched = patched.replace(
/(\w+)?,\s*debugStack,\s*debugTask/g,
(m: string, previousArg: string) => {
if (previousArg === 'source') return m;
return m.replace('debugTask', 'debugTask, source');
},
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. Unsafe react 19 runtime patch 🐞 Bug ✓ Correctness

patchReact19JsxDevRuntime() uses a whitespace-sensitive substring to detect the React 19.0–19.1
ReactElement signature, and then unconditionally applies global argument-threading replacements; if
the signature check fails on transformed/minified code, the replacements can inject a duplicate
source argument and break jsx-dev-runtime execution.
Agent Prompt
### Issue description
The React 19 patch relies on a brittle, formatting-dependent string match to decide whether to early-return for 19.0–19.1. If that check misses, the subsequent global regex rewrites can inject `source` even when it is already threaded, producing duplicated arguments and potentially breaking runtime JSX behavior.

### Issue Context
This plugin also patches pre-bundled/re-written `node_modules` chunks via a content-based fallback, where formatting is less predictable than the original source.

### Fix Focus Areas
- packages/vite/src/index.ts[16-49]
- packages/vite/src/index.ts[143-159]

### Suggested change
- Replace the whitespace-sensitive `includes('function ReactElement(type, key, self, source,')` with a regex tolerant to whitespace/minification, e.g. `function\s+ReactElement\(type\s*,\s*key\s*,\s*self\s*,\s*source\s*,`.
- Make the argument-threading replacements idempotent:
  - Before replacing `maybeKey, isStaticChildren`, first check for an existing `maybeKey, isStaticChildren, source` (with flexible whitespace) and skip/avoid double insertion.
  - Consider adding a second structural guard (e.g., presence of `jsxDEV` / `ReactElement` markers together) so the fallback patch cannot accidentally mutate unrelated `node_modules` chunks.
- Optionally detect already-patched code (`value: source`) and return `undefined` early to avoid re-processing.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@async3619
Copy link
Copy Markdown

@zh-lx please can you review this? It's literally pain in the bum

@zh-lx
Copy link
Copy Markdown
Owner

zh-lx commented Apr 1, 2026

@Akshay090 Thanks for you PR. I'll check it today.

@zh-lx
Copy link
Copy Markdown
Owner

zh-lx commented Apr 2, 2026

Thanks for your PR.

The primary reason why React component names are not currently displayed is that the dd attribute injected into components in React does not automatically inherit to the root node of the component.

Therefore, as long as this issue is resolved, not only can component names be displayed in the right-click component tree, but also when hovering over the component root node with the left mouse button, component names can be displayed and the location of the component call can be pinpointed.

Therefore, I implemented a simpler method in #513.

@zh-lx
Copy link
Copy Markdown
Owner

zh-lx commented Apr 7, 2026

resolved by #513

@zh-lx zh-lx closed this Apr 7, 2026
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.

3 participants