Skip to content

Commit 90604fc

Browse files
alexremoonclaude
andcommitted
feat: add project root detection and version compatibility checks
- Add /project-root endpoint to host server - Add version compatibility with minimal (0.0.3) and recommended (0.0.12) versions - Display red status for unsupported versions, yellow for outdated versions - Compute relative paths from project root for all frameworks - Remove React /src/ path hack in favor of project root relative paths - Move version requirements to config.json to avoid Chrome manifest warnings - Apply relative path conversion in UI display and Claude prompts 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 6d365ff commit 90604fc

7 files changed

Lines changed: 96 additions & 34 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"host_version_requirements": {
3+
"minimal": "0.0.3",
4+
"recommended": "0.0.12"
5+
}
6+
}

chrome-devtools-extension/injected.js

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,10 @@
3434
let lineNumber = "";
3535

3636
if (fiber._debugSource) {
37-
fileName = this.stripAbsolutePath(fiber._debugSource.fileName);
37+
fileName = fiber._debugSource.fileName;
3838
lineNumber = fiber._debugSource.lineNumber;
3939
} else if (fiber._debugOwner._debugSource) {
40-
fileName = this.stripAbsolutePath(fiber._debugOwner._debugSource.fileName);
40+
fileName = fiber._debugOwner._debugSource.fileName;
4141
lineNumber = fiber._debugOwner._debugSource.lineNumber;
4242
} else if (fiber._debugOwner._debugStack) {
4343
const stack = fiber._debugOwner._debugStack.stack;
@@ -113,14 +113,6 @@
113113
const internals = ["Fragment", "StrictMode", "Profiler", "Suspense"];
114114
return internals.includes(name) || name.startsWith("React.");
115115
}
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-
}
124116
}
125117

126118
const detector = new FrameworkDetector();

chrome-devtools-extension/manifest.json

Lines changed: 2 additions & 2 deletions
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.11",
4+
"version": "0.0.12",
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>"],
@@ -25,7 +25,7 @@
2525
],
2626
"web_accessible_resources": [
2727
{
28-
"resources": ["injected.js"],
28+
"resources": ["injected.js", "config.json"],
2929
"matches": ["<all_urls>"]
3030
}
3131
]

chrome-devtools-extension/panel.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@
8989
.status-dot.disconnected {
9090
background: var(--error-color);
9191
}
92+
.status-dot.warning {
93+
background: #f59e0b;
94+
}
9295
.port-input {
9396
margin-left: 8px;
9497
background: transparent;

chrome-devtools-extension/panel.js

Lines changed: 76 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
let selectedElement = null;
22
let isPicking = false;
3+
let projectRoot = null;
34

45
// Initialize source-map library
56
sourceMap.SourceMapConsumer.initialize({
@@ -233,7 +234,8 @@ document.addEventListener("DOMContentLoaded", function () {
233234
selectedElement.component.file &&
234235
selectedElement.component.file !== "detecting..."
235236
) {
236-
addDetailsRow("File:", selectedElement.component.file);
237+
const displayFile = makePathRelativeToProject(selectedElement.component.file);
238+
addDetailsRow("File:", displayFile);
237239
}
238240
}
239241

@@ -303,7 +305,8 @@ document.addEventListener("DOMContentLoaded", function () {
303305
prompt += `\n- Framework: ${selectedElement.component.framework}`;
304306
}
305307
if (selectedElement.component.file) {
306-
prompt += `\n- File: ${selectedElement.component.file}`;
308+
const displayFile = makePathRelativeToProject(selectedElement.component.file);
309+
prompt += `\n- File: ${displayFile}`;
307310
}
308311
if (
309312
propsOption.classList.contains("enabled") &&
@@ -348,36 +351,86 @@ document.addEventListener("DOMContentLoaded", function () {
348351
return result.serverPort || 47923;
349352
}
350353

354+
function compareVersions(v1, v2) {
355+
const parts1 = v1.split('.').map(Number);
356+
const parts2 = v2.split('.').map(Number);
357+
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
358+
const part1 = parts1[i] || 0;
359+
const part2 = parts2[i] || 0;
360+
if (part1 > part2) return 1;
361+
if (part1 < part2) return -1;
362+
}
363+
return 0;
364+
}
365+
366+
function makePathRelativeToProject(absolutePath) {
367+
if (!projectRoot || !absolutePath) return absolutePath;
368+
369+
const normalizedPath = absolutePath.replace(/\\/g, '/');
370+
const normalizedRoot = projectRoot.replace(/\\/g, '/');
371+
372+
if (normalizedPath.startsWith(normalizedRoot)) {
373+
let relative = normalizedPath.substring(normalizedRoot.length);
374+
if (relative.startsWith('/')) {
375+
relative = relative.substring(1);
376+
}
377+
return relative;
378+
}
379+
380+
return absolutePath;
381+
}
382+
351383
async function checkConnectionStatus() {
352384
const port = portInput.value.trim() || "47923";
385+
const configResponse = await fetch(chrome.runtime.getURL('config.json'));
386+
const config = await configResponse.json();
387+
const MINIMAL_HOST_VERSION = config.host_version_requirements?.minimal || "0.0.0";
388+
const RECOMMENDED_HOST_VERSION = config.host_version_requirements?.recommended || "0.0.0";
389+
353390
try {
354-
const response = await fetch(`http://127.0.0.1:${port}/health`, {
355-
method: "GET",
356-
signal: AbortSignal.timeout(2000), // 2 second timeout
357-
});
358-
if (response.ok) {
359-
const healthData = await response.json();
360-
const version = healthData.version ? `v${healthData.version}` : "";
361-
setConnectionStatus(
362-
true,
363-
version ? `connected ${version}` : "connected"
364-
);
391+
const [healthResponse, projectRootResponse] = await Promise.all([
392+
fetch(`http://127.0.0.1:${port}/health`, {
393+
method: "GET",
394+
signal: AbortSignal.timeout(2000),
395+
}),
396+
fetch(`http://127.0.0.1:${port}/project-root`, {
397+
method: "GET",
398+
signal: AbortSignal.timeout(2000),
399+
}).catch(() => null),
400+
]);
401+
402+
if (healthResponse.ok) {
403+
const healthData = await healthResponse.json();
404+
const version = healthData.version || "0.0.0";
405+
406+
if (projectRootResponse && projectRootResponse.ok) {
407+
const data = await projectRootResponse.json();
408+
projectRoot = data.projectRoot;
409+
}
410+
411+
if (compareVersions(version, MINIMAL_HOST_VERSION) < 0) {
412+
setConnectionStatus("error", `unsupported version (${version})`);
413+
} else if (compareVersions(version, RECOMMENDED_HOST_VERSION) < 0) {
414+
setConnectionStatus("warning", `connected (outdated, not all features may work)`);
415+
} else {
416+
setConnectionStatus("connected", `connected v${version}`);
417+
}
365418
} else {
366-
setConnectionStatus(false, "error");
419+
setConnectionStatus("error", "error");
367420
}
368421
} catch (error) {
369-
setConnectionStatus(false, "disconnected");
422+
setConnectionStatus("error", "disconnected");
370423
}
371424
}
372425

373-
function setConnectionStatus(connected, statusText) {
374-
if (connected) {
375-
statusDot.classList.remove("disconnected");
376-
connectionStatus.textContent = statusText;
377-
} else {
426+
function setConnectionStatus(state, statusText) {
427+
statusDot.classList.remove("disconnected", "warning");
428+
if (state === "error") {
378429
statusDot.classList.add("disconnected");
379-
connectionStatus.textContent = statusText;
430+
} else if (state === "warning") {
431+
statusDot.classList.add("warning");
380432
}
433+
connectionStatus.textContent = statusText;
381434
}
382435

383436
function autoResizeTextarea(textarea) {
@@ -612,6 +665,8 @@ document.addEventListener("DOMContentLoaded", function () {
612665
fileName = fullName.split("?")[0];
613666
}
614667

668+
fileName = makePathRelativeToProject(fileName);
669+
615670
return `${fileName}:${finalLineNumber}`;
616671
}
617672

claude-devtools-host/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@alexstrnik/claude-devtools",
3-
"version": "0.0.11",
3+
"version": "0.0.12",
44
"description": "Host server for Claude DevTools Chrome extension",
55
"main": "server.js",
66
"bin": {

claude-devtools-host/server.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,12 @@ fastify.get("/health", async (request, reply) => {
166166
};
167167
});
168168

169+
fastify.get("/project-root", async (request, reply) => {
170+
return {
171+
projectRoot: process.cwd(),
172+
};
173+
});
174+
169175
const start = async () => {
170176
try {
171177
const port = process.env.PORT || 47923;

0 commit comments

Comments
 (0)