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.
Files changed (48) hide show
  1. package/CHANGELOG.md +420 -0
  2. package/COMPONENT_MAPPING_SPEC.md +1217 -0
  3. package/README.md +406 -38
  4. package/bridge/bridge-client.js +472 -0
  5. package/bridge/bridge-service.js +399 -0
  6. package/bridge/install.js +241 -0
  7. package/browser/browser-manager.js +107 -2
  8. package/browser/page-manager.js +226 -69
  9. package/docs/CHROME_EXTENSION.md +219 -0
  10. package/docs/PAGE_OBJECT_MODEL_CONCEPT.md +1756 -0
  11. package/extension/background.js +643 -0
  12. package/extension/content.js +715 -0
  13. package/extension/icons/create-icons.js +164 -0
  14. package/extension/icons/icon128.png +0 -0
  15. package/extension/icons/icon16.png +0 -0
  16. package/extension/icons/icon48.png +0 -0
  17. package/extension/manifest.json +58 -0
  18. package/extension/popup/popup.css +437 -0
  19. package/extension/popup/popup.html +102 -0
  20. package/extension/popup/popup.js +415 -0
  21. package/extension/recorder-overlay.css +93 -0
  22. package/index.js +3347 -2901
  23. package/models/BaseInputModel.js +93 -0
  24. package/models/CheckboxGroupModel.js +199 -0
  25. package/models/CheckboxModel.js +103 -0
  26. package/models/ColorInputModel.js +53 -0
  27. package/models/DateInputModel.js +67 -0
  28. package/models/RadioGroupModel.js +126 -0
  29. package/models/RangeInputModel.js +60 -0
  30. package/models/SelectModel.js +97 -0
  31. package/models/TextInputModel.js +34 -0
  32. package/models/TextareaModel.js +59 -0
  33. package/models/TimeInputModel.js +49 -0
  34. package/models/index.js +122 -0
  35. package/package.json +3 -2
  36. package/pom/apom-converter.js +267 -0
  37. package/pom/apom-tree-converter.js +515 -0
  38. package/pom/element-id-generator.js +175 -0
  39. package/recorder/page-object-generator.js +16 -0
  40. package/recorder/scenario-executor.js +80 -2
  41. package/server/tool-definitions.js +839 -713
  42. package/server/tool-groups.js +1 -1
  43. package/server/tool-schemas.js +367 -326
  44. package/server/websocket-bridge.js +447 -0
  45. package/utils/selector-resolver.js +186 -0
  46. package/utils/ui-framework-detector.js +392 -0
  47. package/RELEASE_NOTES_v2.5.0.md +0 -109
  48. 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