Skip to content

Commit c669013

Browse files
alexremoonclaude
andcommitted
feat: major improvements to element picker and code cleanup
- Support picking disabled elements with temporary pointer-events override - Use elementsFromPoint for reliable element detection - Switch from mouseover to mousemove for better tracking - Fade instructions hint when cursor approaches (0.2 opacity) - Move pick icon inside button for better UX - Strip absolute paths to src/ for React component files - Check element's own _debugSource before parent's - Remove 67+ lines of unused code (createPlaceholderImage, getFallbackComponentInfo, etc.) - Remove Vue from description (not implemented) - Simplify devtools.js to single line - Clean up debug console.log statements 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 109a7bf commit c669013

8 files changed

Lines changed: 96 additions & 150 deletions

File tree

chrome-devtools-extension/content.css

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,6 @@
5757
font-size: 12px !important;
5858
z-index: 1000001 !important;
5959
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3) !important;
60-
}
61-
62-
.claude-devtools-instructions .key {
63-
background: rgba(244, 243, 238, 0.2) !important;
64-
color: #C15F3C !important;
65-
padding: 2px 6px !important;
66-
border-radius: 2px !important;
67-
font-size: 11px !important;
68-
margin: 0 2px !important;
69-
border: 1px solid rgba(193, 95, 60, 0.3) !important;
60+
pointer-events: none !important;
61+
transition: opacity 0.2s ease !important;
7062
}

chrome-devtools-extension/content.js

Lines changed: 63 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,36 @@ class ClaudeDevTools {
2929
(document.head || document.documentElement).appendChild(script);
3030
}
3131

32+
injectPickerStyles() {
33+
this.pickerStyle = document.createElement("style");
34+
this.pickerStyle.id = "claude-devtools-picker-style";
35+
this.pickerStyle.textContent = `
36+
*[disabled] {
37+
pointer-events: all !important;
38+
}
39+
button[disabled],
40+
input[disabled],
41+
select[disabled],
42+
textarea[disabled] {
43+
pointer-events: all !important;
44+
}
45+
`;
46+
document.head.appendChild(this.pickerStyle);
47+
}
48+
49+
removePickerStyles() {
50+
if (this.pickerStyle) {
51+
this.pickerStyle.remove();
52+
this.pickerStyle = null;
53+
}
54+
}
55+
3256
startPicking() {
3357
if (this.isPicking) return;
3458

3559
this.isPicking = true;
3660
this.createOverlay();
61+
this.injectPickerStyles();
3762
this.bindEvents();
3863
}
3964

@@ -42,6 +67,7 @@ class ClaudeDevTools {
4267

4368
this.isPicking = false;
4469
this.removeOverlay();
70+
this.removePickerStyles();
4571
this.unbindEvents();
4672
this.clearHighlight();
4773
}
@@ -90,11 +116,7 @@ class ClaudeDevTools {
90116
}
91117

92118
bindEvents() {
93-
document.addEventListener("mouseover", this.handleMouseOver, {
94-
capture: true,
95-
passive: false,
96-
});
97-
document.addEventListener("mouseout", this.handleMouseOut, {
119+
document.addEventListener("mousemove", this.handleMouseMove, {
98120
capture: true,
99121
passive: false,
100122
});
@@ -106,8 +128,6 @@ class ClaudeDevTools {
106128
capture: true,
107129
passive: false,
108130
});
109-
110-
// Only block interactions that interfere with element selection
111131
document.addEventListener("mousedown", this.blockEvent, {
112132
capture: true,
113133
passive: false,
@@ -124,16 +144,10 @@ class ClaudeDevTools {
124144
capture: true,
125145
passive: false,
126146
});
127-
// Don't block wheel - allow scrolling
128-
// Don't block touch events - allow mobile interaction
129147
}
130148

131149
unbindEvents() {
132-
document.removeEventListener("mouseover", this.handleMouseOver, {
133-
capture: true,
134-
passive: false,
135-
});
136-
document.removeEventListener("mouseout", this.handleMouseOut, {
150+
document.removeEventListener("mousemove", this.handleMouseMove, {
137151
capture: true,
138152
passive: false,
139153
});
@@ -145,8 +159,6 @@ class ClaudeDevTools {
145159
capture: true,
146160
passive: false,
147161
});
148-
149-
// Remove blocked event listeners
150162
document.removeEventListener("mousedown", this.blockEvent, {
151163
capture: true,
152164
passive: false,
@@ -165,39 +177,42 @@ class ClaudeDevTools {
165177
});
166178
}
167179

168-
handleMouseOver = async (e) => {
180+
handleMouseMove = async (e) => {
169181
if (!this.isPicking) return;
170182

171-
// Skip our own elements
172-
if (this.isOurElement(e.target)) return;
173-
174183
e.stopPropagation();
175184
e.preventDefault();
176185

177-
await this.highlightElement(e.target);
178-
};
179-
180-
handleMouseOut = (e) => {
181-
if (!this.isPicking) return;
186+
const instructionsRect = this.instructions.getBoundingClientRect();
187+
const isNearInstructions =
188+
e.clientY >= instructionsRect.top - 50 &&
189+
e.clientY <= instructionsRect.bottom + 20 &&
190+
e.clientX >= instructionsRect.left - 50 &&
191+
e.clientX <= instructionsRect.right + 50;
192+
193+
if (isNearInstructions) {
194+
this.instructions.style.opacity = "0.2";
195+
} else {
196+
this.instructions.style.opacity = "1";
197+
}
182198

183-
// Skip our own elements
184-
if (this.isOurElement(e.target)) return;
199+
const element = this.getElementFromPoint(e.clientX, e.clientY);
200+
if (!element) return;
185201

186-
e.stopPropagation();
187-
e.preventDefault();
188-
this.clearHighlight();
202+
if (element !== this.currentElement) {
203+
await this.highlightElement(element);
204+
}
189205
};
190206

191207
handleClick = async (e) => {
192208
if (!this.isPicking) return;
193209

194-
// Skip our own elements
195-
if (this.isOurElement(e.target)) return;
196-
197210
e.stopPropagation();
198211
e.preventDefault();
199212

200-
const element = e.target;
213+
const element = this.getElementFromPoint(e.clientX, e.clientY);
214+
if (!element) return;
215+
201216
await this.selectElement(element);
202217
};
203218

@@ -214,22 +229,23 @@ class ClaudeDevTools {
214229

215230
blockEvent = (e) => {
216231
if (!this.isPicking) return;
217-
if (this.isOurElement(e.target)) return;
218232
e.stopPropagation();
219233
e.preventDefault();
220234
};
221235

222-
isOurElement(element) {
223-
return (
224-
element === this.overlay ||
225-
element === this.instructions ||
226-
element === this.highlighter ||
227-
element === this.label ||
228-
element.closest(".claude-devtools-overlay") ||
229-
element.closest(".claude-devtools-instructions") ||
230-
element.closest(".claude-devtools-highlighter") ||
231-
element.closest(".claude-devtools-label")
232-
);
236+
getElementFromPoint(x, y) {
237+
const elements = document.elementsFromPoint(x, y);
238+
for (const element of elements) {
239+
if (
240+
element !== this.overlay &&
241+
element !== this.instructions &&
242+
element !== this.highlighter &&
243+
element !== this.label
244+
) {
245+
return element;
246+
}
247+
}
248+
return null;
233249
}
234250

235251
async highlightElement(element) {
@@ -324,8 +340,7 @@ class ClaudeDevTools {
324340

325341
setTimeout(() => {
326342
window.removeEventListener("message", handleMessage);
327-
// element.removeAttribute('data-claude-devtools-id');
328-
resolve(this.getFallbackComponentInfo(element));
343+
resolve(null);
329344
}, 500);
330345
});
331346
}
@@ -336,10 +351,6 @@ class ClaudeDevTools {
336351
);
337352
}
338353

339-
getFallbackComponentInfo() {
340-
return null;
341-
}
342-
343354
getElementHTML(element) {
344355
const clone = element.cloneNode(true);
345356
this.removeUnwantedAttributes(clone);
@@ -475,58 +486,6 @@ class ClaudeDevTools {
475486
}
476487
}
477488

478-
async createPlaceholderImage(rect, tagName) {
479-
return new Promise((resolve) => {
480-
// Create a simple canvas-based placeholder that won't cause taint issues
481-
const canvas = document.createElement("canvas");
482-
const ctx = canvas.getContext("2d");
483-
484-
// Limit canvas size to reasonable dimensions
485-
const maxWidth = Math.min(rect.width, 400);
486-
const maxHeight = Math.min(rect.height, 300);
487-
488-
canvas.width = maxWidth;
489-
canvas.height = maxHeight;
490-
491-
// Create a gradient background
492-
const gradient = ctx.createLinearGradient(0, 0, maxWidth, maxHeight);
493-
gradient.addColorStop(0, "#f8f9fa");
494-
gradient.addColorStop(1, "#e9ecef");
495-
496-
ctx.fillStyle = gradient;
497-
ctx.fillRect(0, 0, maxWidth, maxHeight);
498-
499-
// Add border
500-
ctx.strokeStyle = "#dee2e6";
501-
ctx.lineWidth = 2;
502-
ctx.strokeRect(1, 1, maxWidth - 2, maxHeight - 2);
503-
504-
// Add element info text
505-
ctx.fillStyle = "#495057";
506-
ctx.font =
507-
"16px -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif";
508-
ctx.textAlign = "center";
509-
ctx.textBaseline = "middle";
510-
511-
const text = `<${tagName.toLowerCase()}>`;
512-
ctx.fillText(text, maxWidth / 2, maxHeight / 2 - 10);
513-
514-
// Add dimensions text
515-
ctx.font = "12px monospace";
516-
ctx.fillStyle = "#6c757d";
517-
const dimText = `${Math.round(rect.width)}×${Math.round(rect.height)}px`;
518-
ctx.fillText(dimText, maxWidth / 2, maxHeight / 2 + 15);
519-
520-
try {
521-
const dataURL = canvas.toDataURL("image/png");
522-
resolve(dataURL);
523-
} catch (error) {
524-
// If even this fails, return null
525-
console.warn("Failed to create placeholder image:", error);
526-
resolve(null);
527-
}
528-
});
529-
}
530489
}
531490

532491
// Initialize
Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1 @@
1-
// Create the Claude DevTools panel
2-
chrome.devtools.panels.create(
3-
"Claude",
4-
"claude.svg",
5-
"panel.html",
6-
function (panel) {
7-
console.log("Claude DevTools panel created");
8-
9-
// Panel event listeners
10-
panel.onShown.addListener(function (panelWindow) {
11-
console.log("Claude DevTools panel shown");
12-
// Panel is now visible
13-
});
14-
15-
panel.onHidden.addListener(function () {
16-
console.log("Claude DevTools panel hidden");
17-
});
18-
},
19-
);
1+
chrome.devtools.panels.create("Claude", "claude.svg", "panel.html");

chrome-devtools-extension/injected.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,11 @@
3333
let fileName = "";
3434
let lineNumber = "";
3535

36-
if (fiber._debugOwner._debugSource) {
37-
fileName = fiber._debugOwner._debugSource.fileName;
36+
if (fiber._debugSource) {
37+
fileName = this.stripAbsolutePath(fiber._debugSource.fileName);
38+
lineNumber = fiber._debugSource.lineNumber;
39+
} else if (fiber._debugOwner._debugSource) {
40+
fileName = this.stripAbsolutePath(fiber._debugOwner._debugSource.fileName);
3841
lineNumber = fiber._debugOwner._debugSource.lineNumber;
3942
} else if (fiber._debugOwner._debugStack) {
4043
const stack = fiber._debugOwner._debugStack.stack;
@@ -110,6 +113,14 @@
110113
const internals = ["Fragment", "StrictMode", "Profiler", "Suspense"];
111114
return internals.includes(name) || name.startsWith("React.");
112115
}
116+
117+
stripAbsolutePath(path) {
118+
const index = path.indexOf("/src/");
119+
if (index !== -1) {
120+
return path.substring(index + 1);
121+
}
122+
return path;
123+
}
113124
}
114125

115126
const detector = new FrameworkDetector();

chrome-devtools-extension/manifest.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"manifest_version": 3,
33
"name": "Claude DevTools",
4-
"version": "0.0.10",
5-
"description": "Pick elements and send them to Claude with React/Angular/Vue component detection",
4+
"version": "0.0.11",
5+
"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>"],
88
"content_security_policy": {

0 commit comments

Comments
 (0)