chrometools-mcp 2.5.0 → 3.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +420 -0
- package/COMPONENT_MAPPING_SPEC.md +1217 -0
- package/README.md +406 -38
- package/bridge/bridge-client.js +472 -0
- package/bridge/bridge-service.js +399 -0
- package/bridge/install.js +241 -0
- package/browser/browser-manager.js +107 -2
- package/browser/page-manager.js +226 -69
- package/docs/CHROME_EXTENSION.md +219 -0
- package/docs/PAGE_OBJECT_MODEL_CONCEPT.md +1756 -0
- package/extension/background.js +643 -0
- package/extension/content.js +715 -0
- package/extension/icons/create-icons.js +164 -0
- package/extension/icons/icon128.png +0 -0
- package/extension/icons/icon16.png +0 -0
- package/extension/icons/icon48.png +0 -0
- package/extension/manifest.json +58 -0
- package/extension/popup/popup.css +437 -0
- package/extension/popup/popup.html +102 -0
- package/extension/popup/popup.js +415 -0
- package/extension/recorder-overlay.css +93 -0
- package/index.js +3347 -2901
- package/models/BaseInputModel.js +93 -0
- package/models/CheckboxGroupModel.js +199 -0
- package/models/CheckboxModel.js +103 -0
- package/models/ColorInputModel.js +53 -0
- package/models/DateInputModel.js +67 -0
- package/models/RadioGroupModel.js +126 -0
- package/models/RangeInputModel.js +60 -0
- package/models/SelectModel.js +97 -0
- package/models/TextInputModel.js +34 -0
- package/models/TextareaModel.js +59 -0
- package/models/TimeInputModel.js +49 -0
- package/models/index.js +122 -0
- package/package.json +3 -2
- package/pom/apom-converter.js +267 -0
- package/pom/apom-tree-converter.js +515 -0
- package/pom/element-id-generator.js +175 -0
- package/recorder/page-object-generator.js +16 -0
- package/recorder/scenario-executor.js +80 -2
- package/server/tool-definitions.js +839 -713
- package/server/tool-groups.js +1 -1
- package/server/tool-schemas.js +367 -326
- package/server/websocket-bridge.js +447 -0
- package/utils/selector-resolver.js +186 -0
- package/utils/ui-framework-detector.js +392 -0
- package/RELEASE_NOTES_v2.5.0.md +0 -109
- package/npm_publish_output.txt +0 -0
|
@@ -0,0 +1,1217 @@
|
|
|
1
|
+
# Component Mapping Specification
|
|
2
|
+
|
|
3
|
+
**Status:** 🔴 Draft / Under Discussion
|
|
4
|
+
**Created:** 2026-01-26
|
|
5
|
+
**Goal:** Link DOM elements to component source code (React/Vue/Angular/Svelte)
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Problem Statement
|
|
10
|
+
|
|
11
|
+
AI agents can interact with DOM elements but don't understand the relationship between:
|
|
12
|
+
- HTML elements in browser → Component code in codebase
|
|
13
|
+
- Button on page → `Button.jsx:42` in source files
|
|
14
|
+
|
|
15
|
+
This makes it difficult for AI to:
|
|
16
|
+
- Find which file to edit when user wants to change UI
|
|
17
|
+
- Understand component hierarchy and data flow
|
|
18
|
+
- Debug state management issues
|
|
19
|
+
- Navigate between visual elements and code
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Existing Solutions Analysis
|
|
24
|
+
|
|
25
|
+
### React DevTools Approach
|
|
26
|
+
|
|
27
|
+
React stores component metadata in DOM via Fiber:
|
|
28
|
+
|
|
29
|
+
```javascript
|
|
30
|
+
domElement.__reactFiber$xxxxx = fiberNode
|
|
31
|
+
|
|
32
|
+
// Fiber contains:
|
|
33
|
+
{
|
|
34
|
+
type: Component, // Component function/class
|
|
35
|
+
_debugSource: { // Source map info
|
|
36
|
+
fileName: "src/Button.jsx",
|
|
37
|
+
lineNumber: 42,
|
|
38
|
+
columnNumber: 10
|
|
39
|
+
},
|
|
40
|
+
elementType: Button,
|
|
41
|
+
stateNode: domElement,
|
|
42
|
+
return: parentFiber, // Parent component
|
|
43
|
+
memoizedState: {...}, // Hooks state
|
|
44
|
+
memoizedProps: {...} // Props
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Pros:**
|
|
49
|
+
- ✅ Works out of the box in dev mode
|
|
50
|
+
- ✅ Accurate file paths and line numbers
|
|
51
|
+
- ✅ Access to component hierarchy
|
|
52
|
+
|
|
53
|
+
**Cons:**
|
|
54
|
+
- ⚠️ Limited in production (minified, no debug info)
|
|
55
|
+
- ⚠️ Internal API can change between React versions
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
### Vue DevTools Approach
|
|
60
|
+
|
|
61
|
+
Vue stores component instance directly:
|
|
62
|
+
|
|
63
|
+
```javascript
|
|
64
|
+
// Vue 3
|
|
65
|
+
domElement.__vueParentComponent = componentInstance
|
|
66
|
+
|
|
67
|
+
// Component instance contains:
|
|
68
|
+
{
|
|
69
|
+
type: {
|
|
70
|
+
__file: "src/Button.vue", // File path
|
|
71
|
+
name: "Button"
|
|
72
|
+
},
|
|
73
|
+
props: {...},
|
|
74
|
+
setupState: {...}, // Composition API state
|
|
75
|
+
data: {...} // Options API data
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Pros:**
|
|
80
|
+
- ✅ Excellent state access (reactive properties)
|
|
81
|
+
- ✅ Clear component boundaries
|
|
82
|
+
- ✅ Works well in both dev and production
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
### Angular Approach
|
|
87
|
+
|
|
88
|
+
```javascript
|
|
89
|
+
// Angular debug utilities
|
|
90
|
+
ng.getComponent(domElement)
|
|
91
|
+
ng.getContext(domElement)
|
|
92
|
+
|
|
93
|
+
// Returns:
|
|
94
|
+
{
|
|
95
|
+
componentRef: ComponentRef,
|
|
96
|
+
viewData: ViewData
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Proposed Approaches
|
|
103
|
+
|
|
104
|
+
### Approach A: Passive Detection (Lightweight)
|
|
105
|
+
|
|
106
|
+
**Description:** Inject detection script that scans existing framework metadata
|
|
107
|
+
|
|
108
|
+
**Implementation:**
|
|
109
|
+
|
|
110
|
+
```javascript
|
|
111
|
+
// Inject via content.js or executeScript
|
|
112
|
+
function detectComponentMapping() {
|
|
113
|
+
const map = new Map(); // DOM element → Component info
|
|
114
|
+
|
|
115
|
+
// React detection
|
|
116
|
+
function scanReactFiber(element) {
|
|
117
|
+
const fiberKey = Object.keys(element).find(key =>
|
|
118
|
+
key.startsWith('__reactFiber') ||
|
|
119
|
+
key.startsWith('__reactInternalInstance')
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
if (fiberKey) {
|
|
123
|
+
const fiber = element[fiberKey];
|
|
124
|
+
return extractReactComponentInfo(fiber);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Vue detection
|
|
129
|
+
function scanVueComponent(element) {
|
|
130
|
+
const vueKey = Object.keys(element).find(key =>
|
|
131
|
+
key.startsWith('__vue') ||
|
|
132
|
+
key === '__vueParentComponent'
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
if (vueKey) {
|
|
136
|
+
const instance = element[vueKey];
|
|
137
|
+
return extractVueComponentInfo(instance);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Scan all elements
|
|
142
|
+
document.querySelectorAll('*').forEach(el => {
|
|
143
|
+
const reactInfo = scanReactFiber(el);
|
|
144
|
+
const vueInfo = scanVueComponent(el);
|
|
145
|
+
|
|
146
|
+
if (reactInfo || vueInfo) {
|
|
147
|
+
map.set(el, reactInfo || vueInfo);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
return map;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function extractReactComponentInfo(fiber) {
|
|
155
|
+
// Walk up fiber tree to find component
|
|
156
|
+
let current = fiber;
|
|
157
|
+
while (current) {
|
|
158
|
+
if (current.type && typeof current.type === 'function') {
|
|
159
|
+
return {
|
|
160
|
+
framework: 'react',
|
|
161
|
+
componentName: current.type.name || current.type.displayName,
|
|
162
|
+
fileName: current._debugSource?.fileName,
|
|
163
|
+
lineNumber: current._debugSource?.lineNumber,
|
|
164
|
+
props: current.memoizedProps,
|
|
165
|
+
state: current.memoizedState,
|
|
166
|
+
componentPath: buildComponentPath(current)
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
current = current.return;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function buildComponentPath(fiber) {
|
|
174
|
+
const path = [];
|
|
175
|
+
let current = fiber;
|
|
176
|
+
while (current) {
|
|
177
|
+
if (current.type && typeof current.type === 'function') {
|
|
178
|
+
path.unshift(current.type.name || 'Anonymous');
|
|
179
|
+
}
|
|
180
|
+
current = current.return;
|
|
181
|
+
}
|
|
182
|
+
return path.join(' > ');
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
**Pros:**
|
|
187
|
+
- ✅ No build configuration required
|
|
188
|
+
- ✅ Works immediately in dev mode
|
|
189
|
+
- ✅ Universal for different frameworks
|
|
190
|
+
|
|
191
|
+
**Cons:**
|
|
192
|
+
- ⚠️ Limited in production builds (minified)
|
|
193
|
+
- ⚠️ Depends on framework internals
|
|
194
|
+
- ⚠️ May break with framework updates
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
### Approach B: Active Integration (Build Plugin)
|
|
199
|
+
|
|
200
|
+
**Description:** Babel/Vite plugin that injects metadata during build
|
|
201
|
+
|
|
202
|
+
**Implementation:**
|
|
203
|
+
|
|
204
|
+
```javascript
|
|
205
|
+
// react-chrometools-plugin.js
|
|
206
|
+
export function ChromeToolsReactPlugin() {
|
|
207
|
+
return {
|
|
208
|
+
name: 'chrometools-react-plugin',
|
|
209
|
+
|
|
210
|
+
// Babel plugin to add metadata
|
|
211
|
+
visitor: {
|
|
212
|
+
JSXElement(path, state) {
|
|
213
|
+
const componentName = getComponentName(path);
|
|
214
|
+
const fileName = state.file.opts.filename;
|
|
215
|
+
const { line, column } = path.node.loc.start;
|
|
216
|
+
|
|
217
|
+
// Add data-attributes with component info
|
|
218
|
+
path.node.openingElement.attributes.push(
|
|
219
|
+
t.jsxAttribute(
|
|
220
|
+
t.jsxIdentifier('data-chrometools-component'),
|
|
221
|
+
t.stringLiteral(componentName)
|
|
222
|
+
),
|
|
223
|
+
t.jsxAttribute(
|
|
224
|
+
t.jsxIdentifier('data-chrometools-file'),
|
|
225
|
+
t.stringLiteral(fileName)
|
|
226
|
+
),
|
|
227
|
+
t.jsxAttribute(
|
|
228
|
+
t.jsxIdentifier('data-chrometools-line'),
|
|
229
|
+
t.stringLiteral(String(line))
|
|
230
|
+
)
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
**Usage:**
|
|
239
|
+
|
|
240
|
+
```javascript
|
|
241
|
+
// vite.config.js
|
|
242
|
+
import { ChromeToolsReactPlugin } from 'chrometools-react-plugin';
|
|
243
|
+
|
|
244
|
+
export default {
|
|
245
|
+
plugins: [
|
|
246
|
+
react({
|
|
247
|
+
babel: {
|
|
248
|
+
plugins: [ChromeToolsReactPlugin()]
|
|
249
|
+
}
|
|
250
|
+
})
|
|
251
|
+
]
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
**Result in DOM:**
|
|
256
|
+
|
|
257
|
+
```html
|
|
258
|
+
<button
|
|
259
|
+
data-chrometools-component="Button"
|
|
260
|
+
data-chrometools-file="src/components/Button.jsx"
|
|
261
|
+
data-chrometools-line="42"
|
|
262
|
+
>
|
|
263
|
+
Click me
|
|
264
|
+
</button>
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
**Pros:**
|
|
268
|
+
- ✅ Works in production builds
|
|
269
|
+
- ✅ Accurate metadata always available
|
|
270
|
+
- ✅ No framework API dependencies
|
|
271
|
+
|
|
272
|
+
**Cons:**
|
|
273
|
+
- ❌ Requires build configuration
|
|
274
|
+
- ❌ Increases bundle size (data attributes)
|
|
275
|
+
- ❌ User must install and configure plugin
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
### Approach C: Hybrid (Recommended?)
|
|
280
|
+
|
|
281
|
+
**Description:** Combine passive detection + source map resolution
|
|
282
|
+
|
|
283
|
+
**Flow:**
|
|
284
|
+
|
|
285
|
+
```javascript
|
|
286
|
+
// 1. Passive detection (works always)
|
|
287
|
+
const componentInfo = detectFromDevToolsAPI(element);
|
|
288
|
+
|
|
289
|
+
// 2. Source map resolution (if available)
|
|
290
|
+
if (componentInfo.fileName && componentInfo.lineNumber) {
|
|
291
|
+
const sourceMapInfo = await resolveSourceMap(
|
|
292
|
+
componentInfo.fileName,
|
|
293
|
+
componentInfo.lineNumber
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
componentInfo.originalFile = sourceMapInfo.source;
|
|
297
|
+
componentInfo.originalLine = sourceMapInfo.line;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// 3. Code search in codebase (optional)
|
|
301
|
+
if (componentInfo.componentName) {
|
|
302
|
+
const codebaseMatches = await searchComponentInCodebase(
|
|
303
|
+
componentInfo.componentName,
|
|
304
|
+
componentInfo.originalFile
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
componentInfo.possibleFiles = codebaseMatches;
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
**Source Map Resolution:**
|
|
312
|
+
|
|
313
|
+
```javascript
|
|
314
|
+
// server/source-map-resolver.js
|
|
315
|
+
import { SourceMapConsumer } from 'source-map';
|
|
316
|
+
|
|
317
|
+
export async function resolveSourceMap(fileName, line, column) {
|
|
318
|
+
// Find corresponding .map file
|
|
319
|
+
const mapUrl = fileName + '.map';
|
|
320
|
+
const mapContent = await fetchSourceMap(mapUrl);
|
|
321
|
+
|
|
322
|
+
const consumer = await new SourceMapConsumer(mapContent);
|
|
323
|
+
const original = consumer.originalPositionFor({ line, column });
|
|
324
|
+
|
|
325
|
+
return {
|
|
326
|
+
source: original.source, // "webpack://./src/Button.jsx"
|
|
327
|
+
line: original.line,
|
|
328
|
+
column: original.column,
|
|
329
|
+
name: original.name
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
**Pros:**
|
|
335
|
+
- ✅ Works out of the box in dev
|
|
336
|
+
- ✅ Can work in production with source maps
|
|
337
|
+
- ✅ No build configuration required (but enhanced if configured)
|
|
338
|
+
|
|
339
|
+
**Cons:**
|
|
340
|
+
- ⚠️ More complex implementation
|
|
341
|
+
- ⚠️ Source maps may not be available in production
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
## Comparison Table
|
|
346
|
+
|
|
347
|
+
| Approach | Complexity | User Setup Required | Accuracy | Production Support |
|
|
348
|
+
|----------|------------|---------------------|----------|-------------------|
|
|
349
|
+
| **Passive Detection** | Low | None | Medium | ⚠️ Depends on minification |
|
|
350
|
+
| **Active Plugin** | High | Yes (build config) | High | ✅ Always works |
|
|
351
|
+
| **Hybrid** | Medium | Optional | High | ✅ Works in both modes |
|
|
352
|
+
|
|
353
|
+
---
|
|
354
|
+
|
|
355
|
+
## Proposed MCP Tools
|
|
356
|
+
|
|
357
|
+
### Tool 1: `getComponentMapping`
|
|
358
|
+
|
|
359
|
+
```javascript
|
|
360
|
+
{
|
|
361
|
+
name: "getComponentMapping",
|
|
362
|
+
description: "Get React/Vue/Angular component information for DOM elements",
|
|
363
|
+
inputSchema: {
|
|
364
|
+
type: "object",
|
|
365
|
+
properties: {
|
|
366
|
+
selector: {
|
|
367
|
+
type: "string",
|
|
368
|
+
description: "CSS selector (optional, scans all if not provided)"
|
|
369
|
+
},
|
|
370
|
+
includeProps: {
|
|
371
|
+
type: "boolean",
|
|
372
|
+
description: "Include component props/state",
|
|
373
|
+
default: false
|
|
374
|
+
},
|
|
375
|
+
includeState: {
|
|
376
|
+
type: "boolean",
|
|
377
|
+
description: "Include component state",
|
|
378
|
+
default: false
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
**Response Format:**
|
|
386
|
+
|
|
387
|
+
```json
|
|
388
|
+
{
|
|
389
|
+
"framework": "react",
|
|
390
|
+
"mappings": [
|
|
391
|
+
{
|
|
392
|
+
"selector": "button.MuiButton-root",
|
|
393
|
+
"apomId": "button_45",
|
|
394
|
+
"component": {
|
|
395
|
+
"name": "Button",
|
|
396
|
+
"displayName": "Button",
|
|
397
|
+
"fileName": "src/components/Button.jsx",
|
|
398
|
+
"lineNumber": 42,
|
|
399
|
+
"columnNumber": 10,
|
|
400
|
+
"componentPath": "App > Layout > Header > Button",
|
|
401
|
+
"props": {
|
|
402
|
+
"variant": "contained",
|
|
403
|
+
"color": "primary",
|
|
404
|
+
"onClick": "[Function]"
|
|
405
|
+
},
|
|
406
|
+
"state": null
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
]
|
|
410
|
+
}
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
---
|
|
414
|
+
|
|
415
|
+
### Tool 2: Enhanced `analyzePage`
|
|
416
|
+
|
|
417
|
+
Add component mapping to existing analyzePage:
|
|
418
|
+
|
|
419
|
+
```javascript
|
|
420
|
+
await analyzePage({
|
|
421
|
+
includeComponents: true // ⭐ NEW option
|
|
422
|
+
});
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
**Response includes component info:**
|
|
426
|
+
|
|
427
|
+
```json
|
|
428
|
+
{
|
|
429
|
+
"buttons": [
|
|
430
|
+
{
|
|
431
|
+
"id": "button_45",
|
|
432
|
+
"text": "Submit",
|
|
433
|
+
"component": {
|
|
434
|
+
"name": "Button",
|
|
435
|
+
"file": "src/components/Button.jsx",
|
|
436
|
+
"line": 42,
|
|
437
|
+
"path": "App > Layout > Header > Button"
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
]
|
|
441
|
+
}
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
---
|
|
445
|
+
|
|
446
|
+
### Tool 3: `findComponentInCodebase`
|
|
447
|
+
|
|
448
|
+
Search for component definition in project files:
|
|
449
|
+
|
|
450
|
+
```javascript
|
|
451
|
+
{
|
|
452
|
+
name: "findComponentInCodebase",
|
|
453
|
+
description: "Search for component definition in codebase",
|
|
454
|
+
inputSchema: {
|
|
455
|
+
type: "object",
|
|
456
|
+
properties: {
|
|
457
|
+
componentName: {
|
|
458
|
+
type: "string",
|
|
459
|
+
description: "Component name (e.g., 'Button')"
|
|
460
|
+
},
|
|
461
|
+
projectPath: {
|
|
462
|
+
type: "string",
|
|
463
|
+
description: "Project root path"
|
|
464
|
+
}
|
|
465
|
+
},
|
|
466
|
+
required: ["componentName"]
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
**Implementation:**
|
|
472
|
+
|
|
473
|
+
```javascript
|
|
474
|
+
async function findComponentInCodebase(componentName, projectPath) {
|
|
475
|
+
// 1. Search by filename
|
|
476
|
+
const fileMatches = await glob(`**/${componentName}.{jsx,tsx,vue}`, {
|
|
477
|
+
cwd: projectPath
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
// 2. Search by content (function/class declaration)
|
|
481
|
+
const contentMatches = await grep({
|
|
482
|
+
pattern: `(function|class|const)\\s+${componentName}`,
|
|
483
|
+
path: projectPath,
|
|
484
|
+
glob: "**/*.{js,jsx,ts,tsx,vue}"
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
return {
|
|
488
|
+
exactFileMatches: fileMatches,
|
|
489
|
+
contentMatches: contentMatches,
|
|
490
|
+
confidence: calculateConfidence(fileMatches, contentMatches)
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
---
|
|
496
|
+
|
|
497
|
+
## Component State Access
|
|
498
|
+
|
|
499
|
+
### React State Extraction
|
|
500
|
+
|
|
501
|
+
**Class Components:**
|
|
502
|
+
|
|
503
|
+
```javascript
|
|
504
|
+
function getReactComponentState(element) {
|
|
505
|
+
const fiber = element.__reactFiber$xxxxx;
|
|
506
|
+
|
|
507
|
+
// Class component
|
|
508
|
+
if (fiber.stateNode && fiber.stateNode.state) {
|
|
509
|
+
return {
|
|
510
|
+
type: 'class',
|
|
511
|
+
state: fiber.stateNode.state,
|
|
512
|
+
componentName: fiber.type?.name
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// Function component (hooks)
|
|
517
|
+
if (fiber.memoizedState) {
|
|
518
|
+
return {
|
|
519
|
+
type: 'hooks',
|
|
520
|
+
state: extractHooksState(fiber.memoizedState),
|
|
521
|
+
componentName: fiber.type?.name
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
**Hooks Extraction:**
|
|
528
|
+
|
|
529
|
+
```javascript
|
|
530
|
+
function extractHooksState(memoizedState) {
|
|
531
|
+
const hooks = [];
|
|
532
|
+
let current = memoizedState;
|
|
533
|
+
let hookIndex = 0;
|
|
534
|
+
|
|
535
|
+
while (current) {
|
|
536
|
+
hooks.push({
|
|
537
|
+
index: hookIndex,
|
|
538
|
+
value: current.memoizedState,
|
|
539
|
+
type: detectHookType(current)
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
current = current.next;
|
|
543
|
+
hookIndex++;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
return hooks;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function detectHookType(hook) {
|
|
550
|
+
if (hook.queue) return 'useState';
|
|
551
|
+
if (hook.create) return 'useEffect';
|
|
552
|
+
if (hook.memoizedState && !hook.next) return 'useMemo';
|
|
553
|
+
return 'unknown';
|
|
554
|
+
}
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
**React Hooks Problem:**
|
|
558
|
+
|
|
559
|
+
```javascript
|
|
560
|
+
// Component code:
|
|
561
|
+
function TodoList() {
|
|
562
|
+
const [todos, setTodos] = useState([]);
|
|
563
|
+
const [filter, setFilter] = useState('all');
|
|
564
|
+
const [username, setUsername] = useState('');
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// What we see in fiber:
|
|
568
|
+
{
|
|
569
|
+
memoizedState: value1, // todos - NO VARIABLE NAME!
|
|
570
|
+
next: {
|
|
571
|
+
memoizedState: value2, // filter - NO NAME!
|
|
572
|
+
next: {
|
|
573
|
+
memoizedState: value3, // username - NO NAME!
|
|
574
|
+
next: null
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
**Problem:** No variable names, only indices!
|
|
581
|
+
|
|
582
|
+
**Solution 1: Heuristics for guessing names**
|
|
583
|
+
|
|
584
|
+
```javascript
|
|
585
|
+
function guessHookName(hookValue, componentName) {
|
|
586
|
+
if (Array.isArray(hookValue)) {
|
|
587
|
+
if (hookValue.length > 0 && hookValue[0].id) {
|
|
588
|
+
return 'items/todos/list'; // Array of objects with id
|
|
589
|
+
}
|
|
590
|
+
return 'array';
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
if (typeof hookValue === 'string') {
|
|
594
|
+
if (hookValue.includes('@')) return 'email';
|
|
595
|
+
if (hookValue.length < 50) return 'text/name/title';
|
|
596
|
+
return 'string';
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
if (typeof hookValue === 'boolean') {
|
|
600
|
+
return 'isLoading/isOpen/isActive';
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
return 'unknown';
|
|
604
|
+
}
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
**Solution 2: Babel plugin for dev builds**
|
|
608
|
+
|
|
609
|
+
```javascript
|
|
610
|
+
// Add metadata to hooks during compilation:
|
|
611
|
+
const [todos, setTodos] = useState([]);
|
|
612
|
+
|
|
613
|
+
// ↓ compiles to:
|
|
614
|
+
const [todos, setTodos] = useState([], {
|
|
615
|
+
__devHookName: 'todos',
|
|
616
|
+
__devHookType: 'useState'
|
|
617
|
+
});
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
---
|
|
621
|
+
|
|
622
|
+
### Vue State Extraction
|
|
623
|
+
|
|
624
|
+
Vue provides direct access to reactive state:
|
|
625
|
+
|
|
626
|
+
```javascript
|
|
627
|
+
function getVueComponentState(element) {
|
|
628
|
+
// Vue 3
|
|
629
|
+
const vueInstance = element.__vueParentComponent;
|
|
630
|
+
if (vueInstance) {
|
|
631
|
+
return {
|
|
632
|
+
framework: 'vue3',
|
|
633
|
+
componentName: vueInstance.type?.name,
|
|
634
|
+
|
|
635
|
+
// Data
|
|
636
|
+
data: vueInstance.data,
|
|
637
|
+
|
|
638
|
+
// Setup state (Composition API)
|
|
639
|
+
setupState: vueInstance.setupState,
|
|
640
|
+
|
|
641
|
+
// Props
|
|
642
|
+
props: vueInstance.props,
|
|
643
|
+
|
|
644
|
+
// Computed
|
|
645
|
+
computed: extractComputedProperties(vueInstance)
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// Vue 2
|
|
650
|
+
const vue2Instance = element.__vue__;
|
|
651
|
+
if (vue2Instance) {
|
|
652
|
+
return {
|
|
653
|
+
framework: 'vue2',
|
|
654
|
+
componentName: vue2Instance.$options.name,
|
|
655
|
+
data: vue2Instance._data,
|
|
656
|
+
props: vue2Instance._props,
|
|
657
|
+
computed: vue2Instance._computedWatchers
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
**Example Response:**
|
|
664
|
+
|
|
665
|
+
```json
|
|
666
|
+
{
|
|
667
|
+
"framework": "vue3",
|
|
668
|
+
"componentName": "TodoList",
|
|
669
|
+
"setupState": {
|
|
670
|
+
"todos": [
|
|
671
|
+
{ "id": 1, "text": "Buy milk", "done": false },
|
|
672
|
+
{ "id": 2, "text": "Write code", "done": true }
|
|
673
|
+
],
|
|
674
|
+
"username": "John Doe",
|
|
675
|
+
"isLoading": false
|
|
676
|
+
},
|
|
677
|
+
"props": {
|
|
678
|
+
"title": "My Todos"
|
|
679
|
+
},
|
|
680
|
+
"computed": {
|
|
681
|
+
"completedCount": 1,
|
|
682
|
+
"remainingCount": 1
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
---
|
|
688
|
+
|
|
689
|
+
### Angular State Extraction
|
|
690
|
+
|
|
691
|
+
```javascript
|
|
692
|
+
function getAngularComponentState(element) {
|
|
693
|
+
const component = ng.getComponent(element);
|
|
694
|
+
const context = ng.getContext(element);
|
|
695
|
+
|
|
696
|
+
if (!component) return null;
|
|
697
|
+
|
|
698
|
+
// Get all public properties
|
|
699
|
+
const state = {};
|
|
700
|
+
for (const key in component) {
|
|
701
|
+
if (!key.startsWith('_') && typeof component[key] !== 'function') {
|
|
702
|
+
state[key] = component[key];
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
return {
|
|
707
|
+
framework: 'angular',
|
|
708
|
+
componentName: component.constructor.name,
|
|
709
|
+
state: state,
|
|
710
|
+
inputs: extractInputs(component),
|
|
711
|
+
outputs: extractOutputs(component)
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
---
|
|
717
|
+
|
|
718
|
+
## Tool 4: `getComponentState`
|
|
719
|
+
|
|
720
|
+
```javascript
|
|
721
|
+
{
|
|
722
|
+
name: "getComponentState",
|
|
723
|
+
description: "Get component state (React/Vue/Angular) for element",
|
|
724
|
+
inputSchema: {
|
|
725
|
+
type: "object",
|
|
726
|
+
properties: {
|
|
727
|
+
selector: {
|
|
728
|
+
type: "string",
|
|
729
|
+
description: "CSS selector or APOM ID"
|
|
730
|
+
},
|
|
731
|
+
includeProps: {
|
|
732
|
+
type: "boolean",
|
|
733
|
+
description: "Include component props",
|
|
734
|
+
default: true
|
|
735
|
+
},
|
|
736
|
+
includeComputed: {
|
|
737
|
+
type: "boolean",
|
|
738
|
+
description: "Include computed properties (Vue)",
|
|
739
|
+
default: false
|
|
740
|
+
},
|
|
741
|
+
depth: {
|
|
742
|
+
type: "number",
|
|
743
|
+
description: "Max depth for nested objects (default: 3)",
|
|
744
|
+
default: 3
|
|
745
|
+
}
|
|
746
|
+
},
|
|
747
|
+
required: ["selector"]
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
```
|
|
751
|
+
|
|
752
|
+
**Response Format:**
|
|
753
|
+
|
|
754
|
+
```json
|
|
755
|
+
{
|
|
756
|
+
"framework": "react",
|
|
757
|
+
"componentName": "TodoList",
|
|
758
|
+
"componentPath": "App > Dashboard > TodoList",
|
|
759
|
+
"file": "src/components/TodoList.jsx",
|
|
760
|
+
"line": 15,
|
|
761
|
+
|
|
762
|
+
"state": {
|
|
763
|
+
"type": "hooks",
|
|
764
|
+
"hooks": [
|
|
765
|
+
{
|
|
766
|
+
"index": 0,
|
|
767
|
+
"type": "useState",
|
|
768
|
+
"guessedName": "todos",
|
|
769
|
+
"value": [
|
|
770
|
+
{ "id": 1, "text": "Buy milk", "done": false },
|
|
771
|
+
{ "id": 2, "text": "Write code", "done": true }
|
|
772
|
+
],
|
|
773
|
+
"confidence": "high"
|
|
774
|
+
},
|
|
775
|
+
{
|
|
776
|
+
"index": 1,
|
|
777
|
+
"type": "useState",
|
|
778
|
+
"guessedName": "filter",
|
|
779
|
+
"value": "all",
|
|
780
|
+
"confidence": "medium"
|
|
781
|
+
}
|
|
782
|
+
]
|
|
783
|
+
},
|
|
784
|
+
|
|
785
|
+
"props": {
|
|
786
|
+
"title": "My Todos",
|
|
787
|
+
"onComplete": "[Function]",
|
|
788
|
+
"userId": 42
|
|
789
|
+
},
|
|
790
|
+
|
|
791
|
+
"context": {
|
|
792
|
+
"theme": "dark",
|
|
793
|
+
"user": { "name": "John", "role": "admin" }
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
```
|
|
797
|
+
|
|
798
|
+
---
|
|
799
|
+
|
|
800
|
+
## State Serialization
|
|
801
|
+
|
|
802
|
+
Handle circular references and non-serializable values:
|
|
803
|
+
|
|
804
|
+
```javascript
|
|
805
|
+
function serializeState(state, depth = 3, visited = new WeakSet()) {
|
|
806
|
+
if (depth === 0) return '[Max Depth]';
|
|
807
|
+
if (state === null || state === undefined) return state;
|
|
808
|
+
|
|
809
|
+
// Primitive types
|
|
810
|
+
if (typeof state !== 'object') {
|
|
811
|
+
return state;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
// Circular reference
|
|
815
|
+
if (visited.has(state)) {
|
|
816
|
+
return '[Circular]';
|
|
817
|
+
}
|
|
818
|
+
visited.add(state);
|
|
819
|
+
|
|
820
|
+
// Function
|
|
821
|
+
if (typeof state === 'function') {
|
|
822
|
+
return `[Function: ${state.name || 'anonymous'}]`;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// Promise
|
|
826
|
+
if (state instanceof Promise) {
|
|
827
|
+
return '[Promise]';
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
// Array
|
|
831
|
+
if (Array.isArray(state)) {
|
|
832
|
+
return state.map(item => serializeState(item, depth - 1, visited));
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
// Object
|
|
836
|
+
const serialized = {};
|
|
837
|
+
for (const key in state) {
|
|
838
|
+
try {
|
|
839
|
+
serialized[key] = serializeState(state[key], depth - 1, visited);
|
|
840
|
+
} catch (err) {
|
|
841
|
+
serialized[key] = `[Error: ${err.message}]`;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
return serialized;
|
|
846
|
+
}
|
|
847
|
+
```
|
|
848
|
+
|
|
849
|
+
---
|
|
850
|
+
|
|
851
|
+
## State Support Matrix
|
|
852
|
+
|
|
853
|
+
| Framework | State Access | Confidence | Notes |
|
|
854
|
+
|-----------|-------------|------------|-------|
|
|
855
|
+
| **React Class** | ✅ Full | High | Direct access to `this.state` |
|
|
856
|
+
| **React Hooks** | ✅ Partial | Medium | No variable names, only indices |
|
|
857
|
+
| **Vue 2** | ✅ Full | High | Reactive `_data` |
|
|
858
|
+
| **Vue 3** | ✅ Full | High | `setupState` and reactive props |
|
|
859
|
+
| **Angular** | ✅ Good | High | Public properties accessible |
|
|
860
|
+
| **Svelte** | ❓ TBD | Unknown | Needs investigation |
|
|
861
|
+
|
|
862
|
+
---
|
|
863
|
+
|
|
864
|
+
## Production vs Development
|
|
865
|
+
|
|
866
|
+
### Development Mode (More Info Available):
|
|
867
|
+
|
|
868
|
+
```json
|
|
869
|
+
{
|
|
870
|
+
"state": {
|
|
871
|
+
"hooks": [
|
|
872
|
+
{
|
|
873
|
+
"index": 0,
|
|
874
|
+
"type": "useState",
|
|
875
|
+
"guessedName": "todos",
|
|
876
|
+
"value": [...],
|
|
877
|
+
"debugInfo": {
|
|
878
|
+
"fileName": "TodoList.jsx",
|
|
879
|
+
"lineNumber": 15
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
]
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
```
|
|
886
|
+
|
|
887
|
+
### Production Mode (Minimal):
|
|
888
|
+
|
|
889
|
+
```json
|
|
890
|
+
{
|
|
891
|
+
"state": {
|
|
892
|
+
"hooks": [
|
|
893
|
+
{
|
|
894
|
+
"index": 0,
|
|
895
|
+
"type": "unknown",
|
|
896
|
+
"guessedName": "array_of_objects",
|
|
897
|
+
"value": [...],
|
|
898
|
+
"confidence": "low"
|
|
899
|
+
}
|
|
900
|
+
]
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
```
|
|
904
|
+
|
|
905
|
+
---
|
|
906
|
+
|
|
907
|
+
## Architecture Diagram
|
|
908
|
+
|
|
909
|
+
```
|
|
910
|
+
┌─────────────────────────────────────────────────────────┐
|
|
911
|
+
│ Chrome Browser │
|
|
912
|
+
│ ┌──────────────────────────────────────────────────┐ │
|
|
913
|
+
│ │ DOM Elements with Framework Metadata │ │
|
|
914
|
+
│ │ ├─ button.__reactFiber → Component Info │ │
|
|
915
|
+
│ │ ├─ div.__vue → Vue Instance │ │
|
|
916
|
+
│ │ └─ data-chrometools-* attributes (if plugin) │ │
|
|
917
|
+
│ └──────────────────────────────────────────────────┘ │
|
|
918
|
+
└───────────────────────┬─────────────────────────────────┘
|
|
919
|
+
│
|
|
920
|
+
↓
|
|
921
|
+
┌─────────────────────────────────────────────────────────┐
|
|
922
|
+
│ Component Detection Script │
|
|
923
|
+
│ (Injected via Extension/Puppeteer) │
|
|
924
|
+
│ ├─ detectReactFiber() │
|
|
925
|
+
│ ├─ detectVueComponent() │
|
|
926
|
+
│ ├─ detectAngularComponent() │
|
|
927
|
+
│ └─ extractComponentMetadata() │
|
|
928
|
+
└───────────────────────┬─────────────────────────────────┘
|
|
929
|
+
│
|
|
930
|
+
↓
|
|
931
|
+
┌─────────────────────────────────────────────────────────┐
|
|
932
|
+
│ MCP Server (chrometools-mcp) │
|
|
933
|
+
│ ┌──────────────────────────────────────────────────┐ │
|
|
934
|
+
│ │ getComponentMapping() │ │
|
|
935
|
+
│ │ ├─ Get component info from page │ │
|
|
936
|
+
│ │ ├─ Resolve source maps (if production) │ │
|
|
937
|
+
│ │ └─ Enrich with file paths │ │
|
|
938
|
+
│ └──────────────────────────────────────────────────┘ │
|
|
939
|
+
│ ┌──────────────────────────────────────────────────┐ │
|
|
940
|
+
│ │ getComponentState() │ │
|
|
941
|
+
│ │ ├─ Extract state from framework APIs │ │
|
|
942
|
+
│ │ ├─ Serialize with depth limit │ │
|
|
943
|
+
│ │ └─ Guess variable names (React Hooks) │ │
|
|
944
|
+
│ └──────────────────────────────────────────────────┘ │
|
|
945
|
+
│ ┌──────────────────────────────────────────────────┐ │
|
|
946
|
+
│ │ analyzePage({ includeComponents: true }) │ │
|
|
947
|
+
│ │ ├─ Standard APOM analysis │ │
|
|
948
|
+
│ │ ├─ Merge with component info │ │
|
|
949
|
+
│ │ └─ Return enriched elements │ │
|
|
950
|
+
│ └──────────────────────────────────────────────────┘ │
|
|
951
|
+
└───────────────────────┬─────────────────────────────────┘
|
|
952
|
+
│
|
|
953
|
+
↓
|
|
954
|
+
┌─────────────────────────────────────────────────────────┐
|
|
955
|
+
│ Optional: Codebase Integration │
|
|
956
|
+
│ ┌──────────────────────────────────────────────────┐ │
|
|
957
|
+
│ │ findComponentInCodebase() │ │
|
|
958
|
+
│ │ ├─ Search by file name (glob) │ │
|
|
959
|
+
│ │ ├─ Search by content (grep) │ │
|
|
960
|
+
│ │ └─ Return file paths + confidence │ │
|
|
961
|
+
│ └──────────────────────────────────────────────────┘ │
|
|
962
|
+
└─────────────────────────────────────────────────────────┘
|
|
963
|
+
```
|
|
964
|
+
|
|
965
|
+
---
|
|
966
|
+
|
|
967
|
+
## Implementation Phases
|
|
968
|
+
|
|
969
|
+
### Phase 1: Passive Detection (2-3 days)
|
|
970
|
+
- [ ] Create detection script for React
|
|
971
|
+
- [ ] Create detection script for Vue
|
|
972
|
+
- [ ] Integrate with analyzePage
|
|
973
|
+
- [ ] Add tool `getComponentMapping`
|
|
974
|
+
- [ ] Testing on dev builds
|
|
975
|
+
|
|
976
|
+
### Phase 2: State Extraction (2-3 days)
|
|
977
|
+
- [ ] React Class Component state
|
|
978
|
+
- [ ] React Hooks state with guessing
|
|
979
|
+
- [ ] Vue 2/3 state
|
|
980
|
+
- [ ] Add tool `getComponentState`
|
|
981
|
+
- [ ] State serialization with depth limit
|
|
982
|
+
|
|
983
|
+
### Phase 3: Source Map Resolution (2-3 days)
|
|
984
|
+
- [ ] Add source-map library
|
|
985
|
+
- [ ] Implement resolveSourceMap()
|
|
986
|
+
- [ ] Integrate with component detection
|
|
987
|
+
- [ ] Testing on production builds
|
|
988
|
+
|
|
989
|
+
### Phase 4: Codebase Search (1-2 days)
|
|
990
|
+
- [ ] Implement findComponentInCodebase()
|
|
991
|
+
- [ ] Add confidence scoring
|
|
992
|
+
- [ ] Integrate with MCP tools
|
|
993
|
+
|
|
994
|
+
### Phase 5: Advanced Features (Optional)
|
|
995
|
+
- [ ] Angular support
|
|
996
|
+
- [ ] Svelte support
|
|
997
|
+
- [ ] Component hierarchy visualization
|
|
998
|
+
- [ ] Hot reload detection
|
|
999
|
+
- [ ] Build plugin for data-attributes
|
|
1000
|
+
|
|
1001
|
+
---
|
|
1002
|
+
|
|
1003
|
+
## Open Questions
|
|
1004
|
+
|
|
1005
|
+
❓ **Which frameworks are priority?**
|
|
1006
|
+
- React, Vue, Angular, Svelte?
|
|
1007
|
+
- Order of implementation?
|
|
1008
|
+
|
|
1009
|
+
❓ **Should we support production builds?**
|
|
1010
|
+
- Source map resolution adds complexity
|
|
1011
|
+
- Many apps don't publish source maps
|
|
1012
|
+
- Worth the effort?
|
|
1013
|
+
|
|
1014
|
+
❓ **Should we integrate with codebase search?**
|
|
1015
|
+
- File search via glob/grep
|
|
1016
|
+
- Requires project path
|
|
1017
|
+
- Confidence scoring needed
|
|
1018
|
+
|
|
1019
|
+
❓ **Should we require build plugin or work without config?**
|
|
1020
|
+
- Passive detection = no setup, but limited
|
|
1021
|
+
- Build plugin = requires config, but always accurate
|
|
1022
|
+
- Hybrid approach?
|
|
1023
|
+
|
|
1024
|
+
❓ **How important is state access vs just component mapping?**
|
|
1025
|
+
- State adds significant complexity
|
|
1026
|
+
- React Hooks have no variable names
|
|
1027
|
+
- Is it worth the effort for debugging use cases?
|
|
1028
|
+
|
|
1029
|
+
❓ **Performance concerns?**
|
|
1030
|
+
- Scanning all DOM elements can be slow
|
|
1031
|
+
- State serialization can be expensive
|
|
1032
|
+
- Should we cache? Lazy load?
|
|
1033
|
+
|
|
1034
|
+
❓ **Security considerations?**
|
|
1035
|
+
- State may contain sensitive data (tokens, passwords)
|
|
1036
|
+
- Should we have opt-in for state access?
|
|
1037
|
+
- Depth limits sufficient?
|
|
1038
|
+
|
|
1039
|
+
---
|
|
1040
|
+
|
|
1041
|
+
## Use Case Examples
|
|
1042
|
+
|
|
1043
|
+
### Example 1: AI Agent Finding Component to Edit
|
|
1044
|
+
|
|
1045
|
+
**Without component mapping:**
|
|
1046
|
+
```
|
|
1047
|
+
AI: "Submit button found at button.MuiButton-root"
|
|
1048
|
+
User: "Where is this in code?"
|
|
1049
|
+
AI: "I don't know, need to search codebase"
|
|
1050
|
+
```
|
|
1051
|
+
|
|
1052
|
+
**With component mapping:**
|
|
1053
|
+
```
|
|
1054
|
+
AI: "Submit button found at button.MuiButton-root
|
|
1055
|
+
Component: Button (src/components/Button.jsx:42)
|
|
1056
|
+
Path: App > Layout > Header > Button
|
|
1057
|
+
Props: { variant: 'contained', onClick: handleSubmit }"
|
|
1058
|
+
|
|
1059
|
+
User: "Change color to secondary"
|
|
1060
|
+
AI: "Opening src/components/Button.jsx:42 and changing color prop..."
|
|
1061
|
+
```
|
|
1062
|
+
|
|
1063
|
+
---
|
|
1064
|
+
|
|
1065
|
+
### Example 2: Debugging Form State
|
|
1066
|
+
|
|
1067
|
+
**With component state:**
|
|
1068
|
+
```
|
|
1069
|
+
AI: analyzePage({ includeState: true })
|
|
1070
|
+
|
|
1071
|
+
AI: "Form TodoForm has state:
|
|
1072
|
+
- inputValue: 'Buy groceries'
|
|
1073
|
+
- errors: { text: 'Too short' }
|
|
1074
|
+
- isSubmitting: false
|
|
1075
|
+
|
|
1076
|
+
I see validation error. Text 'Buy groceries' (13 chars)
|
|
1077
|
+
is considered too short. Checking component..."
|
|
1078
|
+
|
|
1079
|
+
AI: getComponentState({ selector: 'form.todo-form' })
|
|
1080
|
+
|
|
1081
|
+
AI: "Found in src/components/TodoForm.jsx:42
|
|
1082
|
+
Minimum text length: 15 characters
|
|
1083
|
+
Should we change to 10?"
|
|
1084
|
+
```
|
|
1085
|
+
|
|
1086
|
+
---
|
|
1087
|
+
|
|
1088
|
+
## Performance Considerations
|
|
1089
|
+
|
|
1090
|
+
### Optimization 1: Caching
|
|
1091
|
+
|
|
1092
|
+
```javascript
|
|
1093
|
+
// Cache component info between requests
|
|
1094
|
+
const componentCache = new Map(); // selector → component info
|
|
1095
|
+
|
|
1096
|
+
// Invalidate on DOM mutations
|
|
1097
|
+
const observer = new MutationObserver(() => {
|
|
1098
|
+
componentCache.clear();
|
|
1099
|
+
});
|
|
1100
|
+
```
|
|
1101
|
+
|
|
1102
|
+
### Optimization 2: Lazy Loading for Large State
|
|
1103
|
+
|
|
1104
|
+
```javascript
|
|
1105
|
+
{
|
|
1106
|
+
"state": {
|
|
1107
|
+
"todos": {
|
|
1108
|
+
"type": "array",
|
|
1109
|
+
"length": 1000,
|
|
1110
|
+
"preview": [...first 10 items...],
|
|
1111
|
+
"loadMore": "Use getComponentState({ selector, expandPath: 'todos' })"
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
```
|
|
1116
|
+
|
|
1117
|
+
### Optimization 3: Selective Scanning
|
|
1118
|
+
|
|
1119
|
+
```javascript
|
|
1120
|
+
// Don't scan entire page if selector provided
|
|
1121
|
+
if (selector) {
|
|
1122
|
+
const element = document.querySelector(selector);
|
|
1123
|
+
return detectComponent(element);
|
|
1124
|
+
} else {
|
|
1125
|
+
// Scan only interactive elements
|
|
1126
|
+
return scanInteractiveElements();
|
|
1127
|
+
}
|
|
1128
|
+
```
|
|
1129
|
+
|
|
1130
|
+
---
|
|
1131
|
+
|
|
1132
|
+
## Security & Privacy
|
|
1133
|
+
|
|
1134
|
+
### Concerns:
|
|
1135
|
+
|
|
1136
|
+
1. **Sensitive data in state**
|
|
1137
|
+
- Passwords, tokens, PII
|
|
1138
|
+
- Should be filtered or masked
|
|
1139
|
+
|
|
1140
|
+
2. **Function serialization**
|
|
1141
|
+
- May expose business logic
|
|
1142
|
+
- Convert to `[Function: name]`
|
|
1143
|
+
|
|
1144
|
+
3. **Source maps in production**
|
|
1145
|
+
- Exposes original source code paths
|
|
1146
|
+
- Many companies don't publish source maps for this reason
|
|
1147
|
+
|
|
1148
|
+
### Mitigations:
|
|
1149
|
+
|
|
1150
|
+
```javascript
|
|
1151
|
+
// Option 1: Opt-in for state access
|
|
1152
|
+
getComponentState({
|
|
1153
|
+
includeState: true, // Explicit opt-in
|
|
1154
|
+
maskSensitive: true // Mask fields like 'password', 'token'
|
|
1155
|
+
})
|
|
1156
|
+
|
|
1157
|
+
// Option 2: Depth limits
|
|
1158
|
+
serializeState(state, depth = 3)
|
|
1159
|
+
|
|
1160
|
+
// Option 3: Exclude patterns
|
|
1161
|
+
{
|
|
1162
|
+
excludeKeys: ['password', 'token', 'secret', 'key', 'auth']
|
|
1163
|
+
}
|
|
1164
|
+
```
|
|
1165
|
+
|
|
1166
|
+
---
|
|
1167
|
+
|
|
1168
|
+
## Alternative Approaches Considered
|
|
1169
|
+
|
|
1170
|
+
### Browser Extension with Native Hooks
|
|
1171
|
+
|
|
1172
|
+
**Pros:**
|
|
1173
|
+
- Could hook into React DevTools APIs directly
|
|
1174
|
+
- More stable than internal APIs
|
|
1175
|
+
|
|
1176
|
+
**Cons:**
|
|
1177
|
+
- Requires separate extension installation
|
|
1178
|
+
- Chrome-only (not cross-browser)
|
|
1179
|
+
|
|
1180
|
+
### Server-Side Rendering (SSR) Metadata
|
|
1181
|
+
|
|
1182
|
+
**Pros:**
|
|
1183
|
+
- Component info embedded during SSR
|
|
1184
|
+
- Works without client-side detection
|
|
1185
|
+
|
|
1186
|
+
**Cons:**
|
|
1187
|
+
- Only works for SSR apps
|
|
1188
|
+
- Client-side components not covered
|
|
1189
|
+
|
|
1190
|
+
### AST Analysis of Build Output
|
|
1191
|
+
|
|
1192
|
+
**Pros:**
|
|
1193
|
+
- Works offline (no browser needed)
|
|
1194
|
+
- Can analyze before deployment
|
|
1195
|
+
|
|
1196
|
+
**Cons:**
|
|
1197
|
+
- Doesn't show runtime state
|
|
1198
|
+
- Complex with code splitting
|
|
1199
|
+
|
|
1200
|
+
---
|
|
1201
|
+
|
|
1202
|
+
## References
|
|
1203
|
+
|
|
1204
|
+
- [React DevTools Implementation](https://github.com/facebook/react/tree/main/packages/react-devtools-shared)
|
|
1205
|
+
- [Vue DevTools Source](https://github.com/vuejs/devtools)
|
|
1206
|
+
- [Source Map Specification](https://sourcemaps.info/spec.html)
|
|
1207
|
+
- [Babel Plugin Handbook](https://github.com/jamiebuilds/babel-handbook)
|
|
1208
|
+
|
|
1209
|
+
---
|
|
1210
|
+
|
|
1211
|
+
**Status:** 🔴 Awaiting decision on approach and priorities
|
|
1212
|
+
|
|
1213
|
+
**Next Steps:**
|
|
1214
|
+
1. Choose approach (A, B, or C)
|
|
1215
|
+
2. Prioritize frameworks
|
|
1216
|
+
3. Decide on state access scope
|
|
1217
|
+
4. Create implementation plan
|