Skip to content

Commit 0cca09e

Browse files
alexremoonclaude
andcommitted
bump: version 0.0.13 - improved component detection and event handling
- Fixed click event leakage by enabling pointer-events on overlay - Added recursive component hierarchy walking for Angular - Refactored React component detection to skip internals - Collected all parent component sources for better debugging - Improved props sanitization across frameworks 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent f1dc7cc commit 0cca09e

6 files changed

Lines changed: 163 additions & 173 deletions

File tree

chrome-devtools-extension/content.css

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@
4141
height: 100vh !important;
4242
background: rgba(42, 38, 33, 0.4) !important;
4343
z-index: 999998 !important;
44-
pointer-events: none !important;
44+
pointer-events: auto !important;
45+
cursor: crosshair !important;
4546
}
4647

4748
.claude-devtools-instructions {

chrome-devtools-extension/content.js

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
class ClaudeDevTools {
22
constructor() {
33
this.isPicking = false;
4+
this.isSelecting = false;
45
this.currentElement = null;
56
this.overlay = null;
67
this.instructions = null;
@@ -152,7 +153,7 @@ class ClaudeDevTools {
152153
});
153154
}
154155

155-
handleMouseMove = async (e) => {
156+
handleMouseMove = (e) => {
156157
if (!this.isPicking) return;
157158

158159
e.stopPropagation();
@@ -175,35 +176,38 @@ class ClaudeDevTools {
175176
if (!element) return;
176177

177178
if (element !== this.currentElement) {
178-
await this.highlightElement(element);
179+
this.highlightElement(element);
179180
}
180181
};
181182

182-
handleClick = async (e) => {
183-
if (!this.isPicking) return;
183+
handleClick = (e) => {
184+
if (!this.isPicking && !this.isSelecting) return;
184185

185186
e.stopPropagation();
186187
e.preventDefault();
187188

189+
if (!this.isPicking) return; // Already selecting
190+
188191
const element = this.getElementFromPoint(e.clientX, e.clientY);
189192
if (!element) return;
190193

191-
await this.selectElement(element);
194+
this.selectElement(element);
192195
};
193196

194197
handleKeyDown = (e) => {
195-
if (!this.isPicking) return;
198+
if (!this.isPicking && !this.isSelecting) return;
196199

197200
if (e.key === "Escape") {
198201
e.stopPropagation();
199202
e.preventDefault();
200203
this.stopPicking();
204+
this.isSelecting = false;
201205
return;
202206
}
203207
};
204208

205209
blockEvent = (e) => {
206-
if (!this.isPicking) return;
210+
if (!this.isPicking && !this.isSelecting) return;
207211
e.stopPropagation();
208212
e.preventDefault();
209213
};
@@ -263,7 +267,7 @@ class ClaudeDevTools {
263267
}
264268

265269
async selectElement(element) {
266-
this.stopPicking();
270+
this.isSelecting = true;
267271

268272
// Ensure element has an ID before getting component info
269273
let elementId = element.getAttribute("data-claude-devtools-id");
@@ -280,7 +284,10 @@ class ClaudeDevTools {
280284
screenshot: await this.captureElementScreenshot(element),
281285
elementId: elementId,
282286
};
287+
288+
this.stopPicking();
283289
chrome.storage.local.set({ selectedElement: elementData });
290+
this.isSelecting = false;
284291
}
285292

286293
getComponentInfo(element) {

chrome-devtools-extension/injected.js

Lines changed: 107 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,26 @@
22
"use strict";
33

44
class FrameworkDetector {
5-
getComponentInfo(element) {
6-
const reactInfo = this.getReactInfo(element);
7-
if (reactInfo) return { ...reactInfo, framework: "React" };
5+
detectFramework() {
6+
if (window.__REACT_DEVTOOLS_GLOBAL_HOOK__) {
7+
return "React";
8+
}
9+
if (window.ng) {
10+
return "Angular";
11+
}
12+
return null;
13+
}
814

9-
const angularInfo = this.getAngularInfo(element);
10-
if (angularInfo) return { ...angularInfo, framework: "Angular" };
15+
getComponentInfo(element) {
16+
const framework = this.detectFramework();
17+
18+
if (framework === "React") {
19+
const reactInfo = this.getReactInfo(element);
20+
if (reactInfo) return { ...reactInfo, framework: "React" };
21+
} else if (framework === "Angular") {
22+
const angularInfo = this.getAngularInfo(element);
23+
if (angularInfo) return { ...angularInfo, framework: "Angular" };
24+
}
1125

1226
return null;
1327
}
@@ -19,99 +33,106 @@
1933

2034
if (!fiberKey) return null;
2135

22-
const fiber = element[fiberKey];
23-
if (!fiber?._debugOwner?.elementType) return null;
24-
25-
const componentType = fiber._debugOwner.elementType;
26-
const name =
27-
componentType.name ||
28-
componentType.displayName ||
29-
fiber._debugOwner.type?.name;
30-
31-
if (!name || this.isReactInternal(name)) return null;
32-
33-
let fileName = "";
34-
let lineNumber = "";
35-
36-
if (fiber._debugSource) {
37-
fileName = fiber._debugSource.fileName;
38-
lineNumber = fiber._debugSource.lineNumber;
39-
} else if (fiber._debugOwner._debugSource) {
40-
fileName = fiber._debugOwner._debugSource.fileName;
41-
lineNumber = fiber._debugOwner._debugSource.lineNumber;
42-
} else if (fiber._debugOwner._debugStack) {
43-
const stack = fiber._debugOwner._debugStack.stack;
44-
if (stack) {
45-
const lines = stack.split("\n");
46-
for (const line of lines) {
47-
let match = line.match(/at\s+\w+\s+\(([^)]+):(\d+):\d+\)/);
48-
if (!match) {
49-
match = line.match(/at\s+(.+):(\d+):(\d+)$/);
50-
}
51-
if (
52-
match &&
53-
(match[1].includes(".jsx") || match[1].includes(".tsx"))
54-
) {
55-
const fullPath = match[1];
56-
fileName = fullPath.split("/").pop();
57-
lineNumber = match[2];
58-
break;
59-
}
36+
let fiber = element[fiberKey];
37+
if (!fiber) return null;
38+
39+
while (fiber) {
40+
const name = fiber.type?.name || fiber.type?.displayName;
41+
if (name && !this.isReactInternal(name)) {
42+
break;
43+
}
44+
fiber = fiber._debugOwner;
45+
if (!fiber) return null;
46+
}
47+
48+
const name = fiber.type?.name || fiber.type?.displayName;
49+
if (!name) return null;
50+
51+
const sources = [];
52+
const functionsToLocate = [];
53+
let currentFiber = fiber;
54+
let finalName = name;
55+
let finalProps = fiber.memoizedProps;
56+
57+
while (currentFiber) {
58+
if (currentFiber._debugSource) {
59+
if (sources.length === 0) {
60+
finalName =
61+
currentFiber.type?.name ||
62+
currentFiber.type?.displayName ||
63+
finalName;
64+
finalProps = currentFiber.memoizedProps || finalProps;
6065
}
66+
sources.push({
67+
fileName: currentFiber._debugSource.fileName,
68+
lineNumber: currentFiber._debugSource.lineNumber,
69+
});
70+
} else if (
71+
currentFiber.type &&
72+
typeof currentFiber.type === "function"
73+
) {
74+
functionsToLocate.push(currentFiber.type);
6175
}
62-
} else {
63-
fileName = "not available";
76+
77+
currentFiber = currentFiber._debugOwner;
6478
}
6579

80+
const files = sources.map((s) => `${s.fileName}:${s.lineNumber}`);
81+
6682
return {
67-
name,
68-
props: fiber._debugOwner.memoizedProps,
69-
file:
70-
fileName && lineNumber
71-
? `${fileName}:${lineNumber}`
72-
: fileName,
73-
needsSourceDetection: !fileName || !lineNumber,
83+
name: finalName,
84+
props: finalProps,
85+
files: files.length > 0 ? files : null,
86+
functionsToLocate: functionsToLocate,
87+
needsSourceDetection: sources.length === 0,
7488
elementId: element.getAttribute("data-claude-devtools-id"),
7589
};
7690
}
7791

7892
getAngularInfo(element) {
79-
if (window.ng?.getOwningComponent) {
80-
const component = window.ng.getOwningComponent(element);
81-
if (component) {
82-
const props = {};
83-
84-
for (const key in component) {
85-
if (
86-
component.hasOwnProperty(key) &&
87-
!key.startsWith("_") &&
88-
!key.startsWith("ng")
89-
) {
90-
const value = component[key];
91-
if (typeof value !== "function" && typeof value !== "undefined") {
92-
props[key] = value;
93-
}
94-
}
95-
}
93+
if (!window.ng?.getOwningComponent) return null;
94+
95+
let currentComponent = window.ng.getOwningComponent(element);
96+
if (!currentComponent) return null;
9697

97-
return {
98-
name: component.constructor.name.startsWith("_")
99-
? component.constructor.name.substring(1)
100-
: component.constructor.name,
101-
props: Object.keys(props).length > 0 ? props : null,
102-
file: "not available",
103-
needsSourceDetection: true,
104-
elementId: element.getAttribute("data-claude-devtools-id"),
105-
};
98+
const functionsToLocate = [];
99+
let finalName = null;
100+
let finalProps = null;
101+
102+
while (currentComponent) {
103+
if (!finalName) {
104+
finalName = currentComponent.constructor.name.startsWith("_")
105+
? currentComponent.constructor.name.substring(1)
106+
: currentComponent.constructor.name;
107+
finalProps = currentComponent;
108+
}
109+
110+
functionsToLocate.push(currentComponent.constructor);
111+
112+
try {
113+
currentComponent = window.ng.getOwningComponent(currentComponent);
114+
} catch {
115+
break;
106116
}
107117
}
108118

109-
return null;
119+
return {
120+
name: finalName,
121+
props: finalProps,
122+
files: null,
123+
functionsToLocate: functionsToLocate,
124+
needsSourceDetection: true,
125+
elementId: element.getAttribute("data-claude-devtools-id"),
126+
};
110127
}
111128

112129
isReactInternal(name) {
113130
const internals = ["Fragment", "StrictMode", "Profiler", "Suspense"];
114-
return internals.includes(name) || name.startsWith("React.");
131+
return (
132+
internals.includes(name) ||
133+
name.startsWith("React.") ||
134+
name.startsWith("Primitive.")
135+
);
115136
}
116137
}
117138

@@ -134,11 +155,16 @@
134155
);
135156
const componentInfo = element ? detector.getComponentInfo(element) : null;
136157

158+
if (componentInfo?.functionsToLocate) {
159+
window.__claudeDevToolsFunctionsToLocate =
160+
componentInfo.functionsToLocate;
161+
}
162+
137163
const sanitizedInfo = componentInfo
138164
? {
139165
name: componentInfo.name,
140166
framework: componentInfo.framework,
141-
file: componentInfo.file,
167+
files: componentInfo.files,
142168
props: sanitizeProps(componentInfo.props),
143169
needsSourceDetection: componentInfo.needsSourceDetection,
144170
}

chrome-devtools-extension/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"manifest_version": 3,
33
"name": "Claude DevTools",
4-
"version": "0.0.12",
4+
"version": "0.0.13",
55
"description": "Pick elements and send them to Claude with React/Angular component detection",
66
"permissions": ["activeTab", "storage", "tabs", "debugger", "scripting"],
77
"host_permissions": ["<all_urls>"],

0 commit comments

Comments
 (0)