@zargaryanvh/react-component-inspector 1.0.3 → 1.0.6

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/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,274 +1,321 @@
1
- # React Component Inspector
2
-
3
- > **A powerful development tool for inspecting React components with AI-friendly metadata extraction. Fully designed by Cursor AI.**
4
-
5
- ## 🎯 What is This?
6
-
7
- React Component Inspector is a development-only tool that helps you identify, inspect, and extract detailed metadata from React components in your application. It's designed to work seamlessly with AI coding assistants (like Cursor) by providing structured, copyable metadata about any component in your UI.
8
-
9
- ## 🔍 What Problem Does It Solve?
10
-
11
- ### The Challenge
12
- When working with AI assistants to fix or modify frontend code, you often need to:
13
- - Identify which component is responsible for a specific UI element
14
- - Understand the component's props, variants, and usage context
15
- - Get the exact file path and component structure
16
- - Extract CSS selectors and element identifiers for precise targeting
17
-
18
- **Without this tool**, you'd need to:
19
- - Manually inspect the DOM
20
- - Search through codebases
21
- - Guess component names and file locations
22
- - Manually extract element information
23
-
24
- ### The Solution
25
- React Component Inspector provides:
26
- - **One-click component identification** - Just hold CTRL and hover
27
- - **Rich metadata extraction** - Component name, props, file path, usage context
28
- - **AI-optimized format** - Copy-paste ready metadata for AI assistants
29
- - **Zero production overhead** - Completely disabled in production builds
30
-
31
- ## 📊 What Data Does It Provide?
32
-
33
- When you inspect a component, you get:
34
-
35
- ### Element Identification
36
- - Element type (HTML tag)
37
- - Element text/label content
38
- - Element ID
39
- - CSS classes
40
- - CSS selector
41
- - Position and size
42
- - Role and accessibility attributes
43
-
44
- ### Component Metadata
45
- - Component name
46
- - Component ID (unique instance identifier)
47
- - Variant (if applicable)
48
- - Usage path (component hierarchy)
49
- - Instance index
50
- - Props signature (key props affecting behavior)
51
- - Source file path
52
-
53
- ### Example Output
54
- ```
55
- === ELEMENT IDENTIFICATION ===
56
- Element Type: button
57
- Element Text/Label: "Save Transaction"
58
- Element ID: save-button
59
- Element Classes: MuiButton-root, primary-button
60
- CSS Selector: button#save-button
61
- Position: (450, 320)
62
- Size: 120x36px
63
-
64
- === COMPONENT METADATA ===
65
- Component Name: SaveButton
66
- Component ID: save-button-0
67
- Variant: primary
68
- Usage Path: ActivityPage > EditTransactionModal > TransactionForm
69
- Instance: 0
70
- Props: variant=primary, disabled=false
71
- Source File: src/components/buttons/SaveButton.tsx
72
- ```
73
-
74
- ## 🚀 How to Use This Data for AI-Powered Frontend Optimization
75
-
76
- ### 1. **Precise Component Targeting**
77
- Copy the metadata and ask your AI assistant:
78
- ```
79
- "I need to modify the SaveButton component. Here's the metadata:
80
- [paste metadata]
81
-
82
- Change the button color to green and add an icon."
83
- ```
84
-
85
- ### 2. **Context-Aware Refactoring**
86
- The usage path tells you exactly where the component is used:
87
- ```
88
- "Refactor the TransactionCard component used in:
89
- ActivityPage > TransactionList > TransactionCard
90
-
91
- Make it accept a new 'priority' prop."
92
- ```
93
-
94
- ### 3. **CSS Selector Generation**
95
- Use the CSS selector for automated testing or styling:
96
- ```javascript
97
- // The metadata provides: button#save-button
98
- const saveButton = document.querySelector('button#save-button');
99
- ```
100
-
101
- ### 4. **Component Discovery**
102
- Find all instances of a component:
103
- ```
104
- "Find all instances of TransactionCard in the codebase.
105
- The component is defined in: src/components/transactions/TransactionCard.tsx"
106
- ```
107
-
108
- ### 5. **AI-Powered Debugging**
109
- Share component metadata with AI to debug issues:
110
- ```
111
- "This button isn't working. Component metadata:
112
- [paste metadata]
113
-
114
- The onClick handler should be in: src/components/buttons/SaveButton.tsx"
115
- ```
116
-
117
- ## 📦 Installation
118
-
119
- ```bash
120
- npm install react-component-inspector
121
- ```
122
-
123
- ## 🔧 Setup
124
-
125
- ### 1. Wrap Your App
126
-
127
- ```tsx
128
- import { InspectionProvider } from 'react-component-inspector';
129
- import { InspectionTooltip } from 'react-component-inspector';
130
- import { InspectionHighlight } from 'react-component-inspector';
131
- import { setupInterceptors } from 'react-component-inspector';
132
-
133
- function App() {
134
- // Setup request interceptors (optional - blocks API calls when CTRL is held)
135
- useEffect(() => {
136
- setupInterceptors();
137
- }, []);
138
-
139
- return (
140
- <InspectionProvider>
141
- <YourApp />
142
- <InspectionTooltip />
143
- <InspectionHighlight />
144
- </InspectionProvider>
145
- );
146
- }
147
- ```
148
-
149
- ### 2. Add Metadata to Components
150
-
151
- #### Option A: Using the Hook (Recommended)
152
-
153
- ```tsx
154
- import { useInspectionMetadata } from 'react-component-inspector';
155
-
156
- function MyButton({ variant, disabled, onClick }) {
157
- const inspectionProps = useInspectionMetadata({
158
- componentName: "MyButton",
159
- variant: variant,
160
- usagePath: "HomePage > ActionBar",
161
- props: { variant, disabled },
162
- sourceFile: "src/components/MyButton.tsx",
163
- });
164
-
165
- return (
166
- <button {...inspectionProps} onClick={onClick}>
167
- Click me
168
- </button>
169
- );
170
- }
171
- ```
172
-
173
- #### Option B: Using the Wrapper Component
174
-
175
- ```tsx
176
- import { InspectionWrapper } from 'react-component-inspector';
177
-
178
- function MyComponent({ variant, children }) {
179
- return (
180
- <InspectionWrapper
181
- componentName="MyComponent"
182
- variant={variant}
183
- usagePath="HomePage > ContentArea"
184
- props={{ variant }}
185
- sourceFile="src/components/MyComponent.tsx"
186
- >
187
- <div>{children}</div>
188
- </InspectionWrapper>
189
- );
190
- }
191
- ```
192
-
193
- #### Option C: Using Data Attributes (Manual)
194
-
195
- ```tsx
196
- <div
197
- data-inspection-name="MyComponent"
198
- data-inspection-id="my-component-0"
199
- data-inspection-variant="primary"
200
- data-inspection-usage-path="HomePage > ContentArea"
201
- data-inspection-instance="0"
202
- data-inspection-props="variant=primary"
203
- data-inspection-file="src/components/MyComponent.tsx"
204
- >
205
- Content
206
- </div>
207
- ```
208
-
209
- ## 🎮 Usage
210
-
211
- 1. **Activate**: Hold the `CTRL` key (or `Cmd` on Mac)
212
- 2. **Inspect**: Hover over any component with inspection metadata
213
- 3. **View**: A tooltip appears showing component metadata
214
- 4. **Lock**: Press `CTRL+H` to lock the tooltip position
215
- 5. **Copy**: Click the copy icon to copy metadata to clipboard
216
- 6. **Deactivate**: Release `CTRL` to exit inspection mode
217
-
218
- ## 🛡️ Safety Features
219
-
220
- - **Development Only**: Completely disabled in production (`NODE_ENV !== "development"`)
221
- - **Request Blocking**: When CTRL is held, all API/Firebase requests are blocked to prevent accidental mutations
222
- - **Zero Overhead**: No code included in production builds
223
- - **Non-Intrusive**: Doesn't modify your components or affect their behavior
224
-
225
- ## 📚 Documentation
226
-
227
- - [Quick Start Guide](./docs/QUICK_START.md)
228
- - [API Reference](./docs/API.md)
229
- - [Advanced Usage](./docs/ADVANCED.md)
230
- - [AI Integration Guide](./docs/AI_INTEGRATION.md)
231
-
232
- ## 🎨 Features
233
-
234
- - Visual component highlighting
235
- - ✅ Rich metadata extraction
236
- - Copy-to-clipboard functionality
237
- - ✅ Automatic component detection
238
- - CSS selector generation
239
- - Usage path tracking
240
- - Instance indexing
241
- - Props signature extraction
242
- - Request blocking during inspection
243
- - ✅ Production-safe (zero overhead)
244
-
245
- ## 🤖 Designed by Cursor AI
246
-
247
- This tool was fully designed and developed using [Cursor](https://cursor.sh), an AI-powered code editor. The entire codebase, architecture, and documentation were created through AI-assisted development, demonstrating the power of AI in building developer tools.
248
-
249
- ## 📄 License
250
-
251
- MIT
252
-
253
- ## 🤝 Contributing
254
-
255
- Contributions are welcome! Please feel free to submit a Pull Request.
256
-
257
- ## ⚠️ Important Notes
258
-
259
- - This tool is **development-only** and will not work in production
260
- - Requires Material-UI (MUI) for the tooltip UI components
261
- - Works best with TypeScript but supports JavaScript
262
- - Request interceptors are optional but recommended for safety
263
-
264
- ## 💡 Tips for AI Integration
265
-
266
- 1. **Always copy the full metadata** - It contains all context needed
267
- 2. **Include the usage path** - Helps AI understand component hierarchy
268
- 3. **Share the source file** - Directs AI to the exact location
269
- 4. **Use CSS selectors** - For precise element targeting in AI prompts
270
- 5. **Copy element text** - Helps AI understand component purpose
271
-
272
- ---
273
-
274
- **Made with ❤️ using Cursor AI**
1
+ # React Component Inspector
2
+
3
+ > **A powerful development tool for inspecting React components with AI-friendly metadata extraction. Fully designed by Cursor AI.**
4
+
5
+ ## 🎯 What is This?
6
+
7
+ React Component Inspector is a development-only tool that helps you identify, inspect, and extract detailed metadata from React components in your application. It's designed to work seamlessly with AI coding assistants (like Cursor) by providing structured, copyable metadata about any component in your UI.
8
+
9
+ ## 🔍 What Problem Does It Solve?
10
+
11
+ ### The Challenge
12
+ When working with AI assistants to fix or modify frontend code, you often need to:
13
+ - Identify which component is responsible for a specific UI element
14
+ - Understand the component's props, variants, and usage context
15
+ - Get the exact file path and component structure
16
+ - Extract CSS selectors and element identifiers for precise targeting
17
+
18
+ **Without this tool**, you'd need to:
19
+ - Manually inspect the DOM
20
+ - Search through codebases
21
+ - Guess component names and file locations
22
+ - Manually extract element information
23
+
24
+ ### The Solution
25
+ React Component Inspector provides:
26
+ - **One-click component identification** - Hold CTRL and hover over any element
27
+ - **Margin, padding & gap inspection** - Hold CTRL+ALT to see spacing (margin, padding, flex/grid gap) and copy it for Cursor
28
+ - **Rich metadata extraction** - Component name, props, file path, usage context
29
+ - **AI-optimized format** - Copy-paste ready metadata for AI assistants (Component, Margin, Padding, Gap)
30
+ - **How to find in code** - Tooltip shows DOM path, target element, and step-by-step how to find and modify in Cursor
31
+ - **Zero production overhead** - Completely disabled in production builds
32
+
33
+ ## 📊 What Data Does It Provide?
34
+
35
+ When you hold **CTRL** and hover over an element, a tooltip appears showing component metadata, copy buttons (Component / Margin / Padding / Gap), and the "How to find in code" section:
36
+
37
+ ![Inspector tooltip showing component data, copy buttons, and how to find in code](docs/screenshots/tooltip-preview.png)
38
+
39
+ When you inspect a component, you get:
40
+
41
+ ### Element Identification
42
+ - Element type (HTML tag)
43
+ - Element text/label content
44
+ - Element ID
45
+ - CSS classes
46
+ - CSS selector
47
+ - Position and size
48
+ - Role and accessibility attributes
49
+
50
+ ### Component Metadata
51
+ - Component name
52
+ - Component ID (unique instance identifier)
53
+ - Variant (if applicable)
54
+ - Usage path (component hierarchy)
55
+ - Instance index
56
+ - Props signature (key props affecting behavior)
57
+ - Source file path
58
+
59
+ ### Example Output (Copy Component)
60
+ ```
61
+ === TYPE: COMPONENT ===
62
+
63
+ === ELEMENT IDENTIFICATION ===
64
+ Element Type: button
65
+ Element Text/Label: "Save Transaction"
66
+ ...
67
+ DOM Path: body > div#root > div.MuiBox-root > button.MuiButton-root
68
+ Parent: div.MuiBox-root
69
+ Role in tree: leaf element; parent: div.MuiBox-root
70
+
71
+ === TARGET (use this to instruct Cursor) ===
72
+ TARGET: The SaveButton with class MuiButton-root - position (450, 320), size 120x36px. It is the element in the DOM path above, NOT a child.
73
+
74
+ === HOW TO FIND AND MODIFY THIS COMPONENT IN CODE ===
75
+ 1. Use the DOM Path to locate the correct element...
76
+ 2. Open src/components/buttons/SaveButton.tsx and find the component...
77
+ 3. Modify the component's props, sx, or styles as needed.
78
+
79
+ === COMPONENT METADATA ===
80
+ ...
81
+ ```
82
+
83
+ When you copy **Margin**, **Padding**, or **Gap**, you get the same structure with current values and instructions for changing that spacing in code (e.g. `sx={{ m: 1 }}`, `sx={{ p: 0.5 }}`, `sx={{ gap: 1 }}`).
84
+
85
+ ## 🔎 How to Find and Modify in Cursor
86
+
87
+ The tooltip shows a **“How to find in code”** section (same on desktop and mobile):
88
+
89
+ - **DOM Path** – Full path from `body` to the element (e.g. `body > div#root > div.MuiBox-root > main.MuiBox-root > …`)
90
+ - **Parent** – Direct parent selector
91
+ - **Role in tree** e.g. “has 3 child element(s); parent: div.MuiStack-root”
92
+ - **TARGET** – One-line description for Cursor: “The Card with class MuiPaper-root – the element with margin 0px 0px 16px 0px. It is the PARENT in the DOM path above, NOT a child.”
93
+ - **Steps** – Numbered steps: use DOM path, open source file, then how to change (margin, padding, gap, or component)
94
+
95
+ When you click **Copy Component**, **Copy Margin**, **Copy Padding**, or **Copy Gap**, the clipboard gets the full block (element identification, DOM path, TARGET, and “How to find and modify in code”). Paste that into Cursor and ask it to change the component, margin, padding, or gap; the text is written so Cursor can locate the right element and apply the change.
96
+
97
+ ## 🚀 How to Use This Data for AI-Powered Frontend Optimization
98
+
99
+ ### 1. **Precise Component Targeting**
100
+ Copy the metadata and ask your AI assistant:
101
+ ```
102
+ "I need to modify the SaveButton component. Here's the metadata:
103
+ [paste metadata]
104
+
105
+ Change the button color to green and add an icon."
106
+ ```
107
+
108
+ ### 2. **Context-Aware Refactoring**
109
+ The usage path tells you exactly where the component is used:
110
+ ```
111
+ "Refactor the TransactionCard component used in:
112
+ ActivityPage > TransactionList > TransactionCard
113
+
114
+ Make it accept a new 'priority' prop."
115
+ ```
116
+
117
+ ### 3. **CSS Selector Generation**
118
+ Use the CSS selector for automated testing or styling:
119
+ ```javascript
120
+ // The metadata provides: button#save-button
121
+ const saveButton = document.querySelector('button#save-button');
122
+ ```
123
+
124
+ ### 4. **Component Discovery**
125
+ Find all instances of a component:
126
+ ```
127
+ "Find all instances of TransactionCard in the codebase.
128
+ The component is defined in: src/components/transactions/TransactionCard.tsx"
129
+ ```
130
+
131
+ ### 5. **AI-Powered Debugging**
132
+ Share component metadata with AI to debug issues:
133
+ ```
134
+ "This button isn't working. Component metadata:
135
+ [paste metadata]
136
+
137
+ The onClick handler should be in: src/components/buttons/SaveButton.tsx"
138
+ ```
139
+
140
+ ## 📦 Installation
141
+
142
+ ```bash
143
+ npm install react-component-inspector
144
+ ```
145
+
146
+ ## 🔧 Setup
147
+
148
+ ### 1. Wrap Your App
149
+
150
+ ```tsx
151
+ import { InspectionProvider } from 'react-component-inspector';
152
+ import { InspectionTooltip } from 'react-component-inspector';
153
+ import { InspectionHighlight } from 'react-component-inspector';
154
+ import { setupInterceptors } from 'react-component-inspector';
155
+
156
+ function App() {
157
+ // Setup request interceptors (optional - blocks API calls when CTRL is held)
158
+ useEffect(() => {
159
+ setupInterceptors();
160
+ }, []);
161
+
162
+ return (
163
+ <InspectionProvider>
164
+ <YourApp />
165
+ <InspectionTooltip />
166
+ <InspectionHighlight />
167
+ </InspectionProvider>
168
+ );
169
+ }
170
+ ```
171
+
172
+ ### 2. Add Metadata to Components
173
+
174
+ #### Option A: Using the Hook (Recommended)
175
+
176
+ ```tsx
177
+ import { useInspectionMetadata } from 'react-component-inspector';
178
+
179
+ function MyButton({ variant, disabled, onClick }) {
180
+ const inspectionProps = useInspectionMetadata({
181
+ componentName: "MyButton",
182
+ variant: variant,
183
+ usagePath: "HomePage > ActionBar",
184
+ props: { variant, disabled },
185
+ sourceFile: "src/components/MyButton.tsx",
186
+ });
187
+
188
+ return (
189
+ <button {...inspectionProps} onClick={onClick}>
190
+ Click me
191
+ </button>
192
+ );
193
+ }
194
+ ```
195
+
196
+ #### Option B: Using the Wrapper Component
197
+
198
+ ```tsx
199
+ import { InspectionWrapper } from 'react-component-inspector';
200
+
201
+ function MyComponent({ variant, children }) {
202
+ return (
203
+ <InspectionWrapper
204
+ componentName="MyComponent"
205
+ variant={variant}
206
+ usagePath="HomePage > ContentArea"
207
+ props={{ variant }}
208
+ sourceFile="src/components/MyComponent.tsx"
209
+ >
210
+ <div>{children}</div>
211
+ </InspectionWrapper>
212
+ );
213
+ }
214
+ ```
215
+
216
+ #### Option C: Using Data Attributes (Manual)
217
+
218
+ ```tsx
219
+ <div
220
+ data-inspection-name="MyComponent"
221
+ data-inspection-id="my-component-0"
222
+ data-inspection-variant="primary"
223
+ data-inspection-usage-path="HomePage > ContentArea"
224
+ data-inspection-instance="0"
225
+ data-inspection-props="variant=primary"
226
+ data-inspection-file="src/components/MyComponent.tsx"
227
+ >
228
+ Content
229
+ </div>
230
+ ```
231
+
232
+ ## 🎮 Usage
233
+
234
+ ### Keyboard shortcuts (desktop)
235
+
236
+ | Shortcut | Action |
237
+ |----------|--------|
238
+ | **Hold CTRL** | Enter inspection mode. Hover to inspect component (box/element). |
239
+ | **Release CTRL** | Exit inspection mode and clear tooltip. |
240
+ | **Hold CTRL+ALT** | Enter margin/padding mode. See orange (margin), green (padding), purple (gap). |
241
+ | **CTRL+H** | Lock tooltip position so you can click copy buttons. |
242
+ | **CTRL+Shift+R** | Hard refresh (browser default; not captured by the inspector). |
243
+
244
+ ### Basic flow
245
+
246
+ 1. **Activate**: Hold `CTRL` (or `Cmd` on Mac)
247
+ 2. **Inspect**: Hover over any element tooltip shows component metadata and **How to find in code**
248
+ 3. **Copy**: Use the copy icon (component) or **Margin** / **Padding** / **Gap** buttons to copy metadata to clipboard
249
+ 4. **Lock** (optional): Press `CTRL+H` to lock the tooltip so it doesn’t follow the cursor
250
+ 5. **Deactivate**: Release `CTRL` to exit inspection mode
251
+
252
+ ### Margin, padding & gap inspection
253
+
254
+ - **Hold CTRL+ALT** (while holding CTRL) to enter margin/padding mode. Overlays show:
255
+ - **Orange** = margin (outside the element)
256
+ - **Green** = padding (inside the element)
257
+ - **Purple dashed** = parent’s flex/grid gap (when the parent has `gap`)
258
+ - If the current element has no margin, **dashed orange** outlines show ancestors that have margin; **click an outline** to inspect that ancestor.
259
+ - Use the **Margin**, **Padding**, or **Gap** copy buttons in the tooltip to copy Cursor-ready text for that spacing.
260
+ - The tooltip always shows **DOM Path**, **Parent**, **Role in tree**, **TARGET**, and **How to find** steps so you (and Cursor) know exactly which element to change.
261
+
262
+ ## 🛡️ Safety Features
263
+
264
+ - **Development Only**: Completely disabled in production (`NODE_ENV !== "development"`)
265
+ - **Request Blocking**: When CTRL is held, all API/Firebase requests are blocked to prevent accidental mutations
266
+ - **Zero Overhead**: No code included in production builds
267
+ - **Non-Intrusive**: Doesn't modify your components or affect their behavior
268
+
269
+ ## 📚 Documentation
270
+
271
+ - [Quick Start Guide](./docs/QUICK_START.md)
272
+ - [API Reference](./docs/API.md)
273
+ - [Advanced Usage](./docs/ADVANCED.md)
274
+ - [AI Integration Guide](./docs/AI_INTEGRATION.md)
275
+
276
+ ## 🎨 Features
277
+
278
+ - ✅ Visual component highlighting (box/element outline)
279
+ - ✅ **Margin, padding & gap inspection** (hold CTRL+ALT) with orange/green/purple overlays
280
+ - ✅ **Ancestor margin detection** – when the element has no margin, dashed outlines show ancestors with margin; click to inspect
281
+ - ✅ **Copy Component / Margin / Padding / Gap** – Cursor-ready text with DOM path, TARGET, and how to find in code
282
+ - ✅ **“How to find in code” in tooltip** – DOM path, parent, role in tree, TARGET, and numbered steps (desktop & mobile)
283
+ - ✅ Rich metadata extraction
284
+ - ✅ Automatic component detection (with or without `data-inspection-*`)
285
+ - ✅ CSS selector generation
286
+ - ✅ Usage path tracking
287
+ - ✅ Instance indexing
288
+ - ✅ Props signature extraction
289
+ - ✅ Request blocking during inspection (when CTRL is held)
290
+ - ✅ Production-safe (zero overhead)
291
+
292
+ ## 🤖 Designed by Cursor AI
293
+
294
+ This tool was fully designed and developed using [Cursor](https://cursor.sh), an AI-powered code editor. The entire codebase, architecture, and documentation were created through AI-assisted development, demonstrating the power of AI in building developer tools.
295
+
296
+ ## 📄 License
297
+
298
+ MIT
299
+
300
+ ## 🤝 Contributing
301
+
302
+ Contributions are welcome! Please feel free to submit a Pull Request.
303
+
304
+ ## ⚠️ Important Notes
305
+
306
+ - This tool is **development-only** and will not work in production
307
+ - Requires Material-UI (MUI) for the tooltip UI components
308
+ - Works best with TypeScript but supports JavaScript
309
+ - Request interceptors are optional but recommended for safety
310
+
311
+ ## 💡 Tips for AI Integration
312
+
313
+ 1. **Always copy the full metadata** - It contains all context needed
314
+ 2. **Include the usage path** - Helps AI understand component hierarchy
315
+ 3. **Share the source file** - Directs AI to the exact location
316
+ 4. **Use CSS selectors** - For precise element targeting in AI prompts
317
+ 5. **Copy element text** - Helps AI understand component purpose
318
+
319
+ ---
320
+
321
+ **Made with ❤️ using Cursor AI**
@@ -8,19 +8,18 @@ const InspectionContext = createContext(undefined);
8
8
  */
9
9
  export const InspectionProvider = ({ children }) => {
10
10
  const [ctrlHeld, setCtrlHeld] = useState(false);
11
- const [isStickyInspection, setIsStickyInspection] = useState(false);
12
- const isInspectionActive = ctrlHeld || isStickyInspection;
13
- const [isLocked, setIsLocked] = useState(false);
14
11
  const [isMarginPaddingMode, setIsMarginPaddingMode] = useState(false);
12
+ const [isLocked, setIsLocked] = useState(false);
13
+ // Inspection active only when CTRL is held (or CTRL+ALT for margin/padding). Release CTRL = stop inspecting.
14
+ const isInspectionActive = ctrlHeld || isMarginPaddingMode;
15
15
  const [hoveredComponent, setHoveredComponentState] = useState(null);
16
16
  const [hoveredElement, setHoveredElement] = useState(null);
17
17
  // Use refs to always access latest state values in event handlers
18
18
  const isInspectionActiveRef = useRef(isInspectionActive);
19
19
  const isLockedRef = useRef(isLocked);
20
- const isStickyInspectionRef = useRef(isStickyInspection);
21
20
  const hoveredComponentRef = useRef(hoveredComponent);
22
21
  const hKeyPressedRef = useRef(false);
23
- // Touch support for locking only (3/4 finger activation removed - use Ctrl+Shift+R on laptop)
22
+ // Touch support for locking only (double-tap to lock tooltip)
24
23
  const lastTapRef = useRef(0);
25
24
  const [isMobile, setIsMobile] = useState(false);
26
25
  useEffect(() => {
@@ -43,10 +42,7 @@ export const InspectionProvider = ({ children }) => {
43
42
  useEffect(() => {
44
43
  hoveredComponentRef.current = hoveredComponent;
45
44
  }, [hoveredComponent]);
46
- useEffect(() => {
47
- isStickyInspectionRef.current = isStickyInspection;
48
- }, [isStickyInspection]);
49
- // Only block API/fetch when CTRL is physically held (not when sticky inspection is on)
45
+ // Only block API/fetch when CTRL is physically held
50
46
  useEffect(() => {
51
47
  setInspectionActive(ctrlHeld);
52
48
  }, [ctrlHeld]);
@@ -74,37 +70,18 @@ export const InspectionProvider = ({ children }) => {
74
70
  lastTapRef.current = now;
75
71
  }
76
72
  };
77
- // Keyboard: CTRL, CTRL+Shift+R (toggle inspection), CTRL+M (margin/padding), CTRL+H (lock)
73
+ // Keyboard: CTRL (hold = inspection), CTRL+Shift+R = hard refresh (do not capture), CTRL+ALT (margin/padding), CTRL+H (lock)
78
74
  const handleKeyDown = (e) => {
79
- // R key with CTRL+Shift - toggle inspection on/off (sticky, for mobile viewport on laptop)
75
+ // Do NOT capture CTRL+Shift+R - let browser do hard refresh
80
76
  if (e.key && e.key.toLowerCase() === "r" && e.ctrlKey && e.shiftKey) {
81
- e.preventDefault();
82
- e.stopPropagation();
83
- if (!e.repeat) {
84
- setIsStickyInspection(prev => {
85
- const next = !prev;
86
- if (!next) {
87
- setHoveredComponentState(null);
88
- setHoveredElement(null);
89
- setIsLocked(false);
90
- }
91
- if (process.env.NODE_ENV === "development") {
92
- console.log("[Inspection] Inspection toggled (Ctrl+Shift+R):", next ? "ON" : "OFF");
93
- }
94
- return next;
95
- });
96
- }
97
77
  return;
98
78
  }
99
- // M key with CTRL (hold) - margin/padding mode while held, inspect on mouse move
100
- if (e.key && e.key.toLowerCase() === "m" && e.ctrlKey && !e.shiftKey && !e.altKey) {
101
- e.preventDefault();
102
- e.stopPropagation();
103
- if (!e.repeat) {
104
- setIsMarginPaddingMode(true);
105
- setCtrlHeld(true);
106
- }
107
- return;
79
+ // CTRL+ALT (hold both) - margin/padding/box mode; desktop and mobile
80
+ if (e.key === "Control" && e.altKey && !e.repeat) {
81
+ setIsMarginPaddingMode(true);
82
+ }
83
+ if (e.key === "Alt" && e.ctrlKey && !e.repeat) {
84
+ setIsMarginPaddingMode(true);
108
85
  }
109
86
  // H key pressed while CTRL is held - lock tooltip position
110
87
  if (e.key && e.key.toLowerCase() === "h" && e.ctrlKey) {
@@ -148,20 +125,18 @@ export const InspectionProvider = ({ children }) => {
148
125
  }
149
126
  return;
150
127
  }
151
- // M key released - turn off margin/padding mode (hold-to-use, no toggle)
152
- if (e.key && e.key.toLowerCase() === "m") {
128
+ // ALT or CTRL released - turn off margin/padding mode (CTRL+ALT only, hold to use)
129
+ if (e.key === "Alt") {
153
130
  setIsMarginPaddingMode(false);
154
131
  }
155
- // CTRL key released - clear only if not in sticky mode
132
+ // CTRL key released - stop inspecting and clear state
156
133
  if (e.key === "Control") {
157
134
  setCtrlHeld(false);
158
135
  hKeyPressedRef.current = false;
159
- if (!isStickyInspectionRef.current) {
160
- setIsMarginPaddingMode(false);
161
- setIsLocked(false);
162
- setHoveredComponentState(null);
163
- setHoveredElement(null);
164
- }
136
+ setIsMarginPaddingMode(false);
137
+ setIsLocked(false);
138
+ setHoveredComponentState(null);
139
+ setHoveredElement(null);
165
140
  }
166
141
  };
167
142
  window.addEventListener("keydown", handleKeyDown);
@@ -19,7 +19,7 @@ const parsePx = (value) => {
19
19
  };
20
20
  /**
21
21
  * Highlight overlay that shows the boundary of the hovered component
22
- * When hold CTRL+M: orange = margin, green = padding, purple = gap (hold-to-use, release to exit)
22
+ * When hold CTRL+ALT: orange = margin, green = padding, purple = gap (hold-to-use, release to exit)
23
23
  * Otherwise: blue outline for component
24
24
  */
25
25
  const stripStyle = (left, top, width, height, color, bg) => ({
@@ -1,9 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useState, useEffect, useRef, useMemo } from "react";
3
- import { Box, Paper, Typography, IconButton, Tooltip as MuiTooltip, Divider, Menu, MenuItem } from "@mui/material";
3
+ import { Box, Paper, Typography, IconButton, Tooltip as MuiTooltip, Divider } from "@mui/material";
4
4
  import ContentCopyIcon from "@mui/icons-material/ContentCopy";
5
5
  import { useInspection } from "./InspectionContext";
6
- import { formatMetadataForClipboard, getParentWithGap } from "./inspection";
6
+ import { formatMetadataForClipboard, getParentWithGap, getTooltipHowToFindInfo } from "./inspection";
7
7
  import { parseInspectionMetadata } from "./autoInspection";
8
8
  /**
9
9
  * Helper: Get element text content (first 100 chars)
@@ -87,7 +87,6 @@ export const InspectionTooltip = () => {
87
87
  const [position, setPosition] = useState({ x: 0, y: 0 });
88
88
  const [stablePosition, setStablePosition] = useState(null);
89
89
  const [copied, setCopied] = useState(null);
90
- const [copyMenuAnchor, setCopyMenuAnchor] = useState(null);
91
90
  const tooltipRef = useRef(null);
92
91
  // Update position based on cursor, but keep it stable when locked or mouse is near tooltip
93
92
  useEffect(() => {
@@ -137,8 +136,29 @@ export const InspectionTooltip = () => {
137
136
  propsSignature: "default",
138
137
  sourceFile: "DOM",
139
138
  } : null);
139
+ // On mobile (or when locking) there is no cursor; position tooltip over/near the inspected element
140
+ const positionFromElement = useMemo(() => {
141
+ if (!hoveredElement || !document.body.contains(hoveredElement))
142
+ return null;
143
+ const padding = 10;
144
+ const estimatedWidth = 400;
145
+ const estimatedHeight = 200;
146
+ const rect = hoveredElement.getBoundingClientRect();
147
+ // Prefer below and to the right of the element, clamped to viewport
148
+ let x = rect.left + 15;
149
+ let y = rect.bottom + 10;
150
+ if (x + estimatedWidth > window.innerWidth - padding)
151
+ x = window.innerWidth - estimatedWidth - padding;
152
+ if (x < padding)
153
+ x = padding;
154
+ if (y + estimatedHeight > window.innerHeight - padding)
155
+ y = rect.top - estimatedHeight - 10;
156
+ if (y < padding)
157
+ y = padding;
158
+ return { x, y };
159
+ }, [hoveredElement]);
140
160
  // Calculate adjusted position to avoid going off-screen
141
- // Use stable position if available, otherwise calculate from cursor position
161
+ // Use stable position if available; on mobile or when locked use element-based position so tooltip appears over inspected area
142
162
  const adjustedPosition = useMemo(() => {
143
163
  if (!displayComponent) {
144
164
  return { x: position.x + 15, y: position.y + 15 };
@@ -147,7 +167,11 @@ export const InspectionTooltip = () => {
147
167
  if (stablePosition) {
148
168
  return stablePosition;
149
169
  }
150
- // Calculate new position from cursor (only when stablePosition is null)
170
+ // Mobile or just locked: place tooltip near the inspected element (not cursor)
171
+ if ((isMobile || isLocked) && positionFromElement) {
172
+ return positionFromElement;
173
+ }
174
+ // Desktop: calculate from cursor position
151
175
  const padding = 10;
152
176
  let x = position.x + 15;
153
177
  let y = position.y + 15;
@@ -170,7 +194,7 @@ export const InspectionTooltip = () => {
170
194
  y = padding;
171
195
  }
172
196
  return { x, y };
173
- }, [position, displayComponent, stablePosition]);
197
+ }, [position, displayComponent, stablePosition, isMobile, isLocked, positionFromElement]);
174
198
  // Set stable position once when tooltip first appears for a new element, or when locked
175
199
  const lastComponentIdRef = useRef(null);
176
200
  useEffect(() => {
@@ -232,7 +256,6 @@ export const InspectionTooltip = () => {
232
256
  const metadata = parseInspectionMetadata(parentWithGap);
233
257
  if (!metadata)
234
258
  return;
235
- setCopyMenuAnchor(null);
236
259
  const text = formatMetadataForClipboard(metadata, parentWithGap, "gap");
237
260
  const showCopied = () => {
238
261
  setCopied("gap");
@@ -260,7 +283,6 @@ export const InspectionTooltip = () => {
260
283
  }
261
284
  if (!displayComponent)
262
285
  return;
263
- setCopyMenuAnchor(null);
264
286
  const text = formatMetadataForClipboard(displayComponent, hoveredElement, type);
265
287
  const showCopied = () => {
266
288
  setCopied(type);
@@ -292,13 +314,6 @@ export const InspectionTooltip = () => {
292
314
  setStablePosition(null);
293
315
  }
294
316
  }, [hoveredElement, isLocked]);
295
- // Close copy menu when tooltip is hidden so MUI Menu never has a detached anchorEl
296
- const tooltipVisible = isInspectionActive && !!displayComponent && !(isMobile && !isLocked);
297
- useEffect(() => {
298
- if (!tooltipVisible) {
299
- setCopyMenuAnchor(null);
300
- }
301
- }, [tooltipVisible]);
302
317
  const parentWithGap = hoveredElement ? getParentWithGap(hoveredElement) : null;
303
318
  // Show tooltip when inspection active; on mobile only when locked (H pressed or double-tap)
304
319
  if (!isInspectionActive || !displayComponent) {
@@ -320,15 +335,21 @@ export const InspectionTooltip = () => {
320
335
  backdropFilter: "blur(8px)",
321
336
  border: "1px solid rgba(255, 255, 255, 0.1)",
322
337
  transition: stablePosition ? "none" : "left 0.1s ease-out, top 0.1s ease-out",
323
- }, children: [_jsxs(Box, { sx: { display: "flex", justifyContent: "space-between", alignItems: "flex-start", mb: 1 }, children: [_jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 1 }, children: [_jsx(Typography, { variant: "subtitle2", sx: { color: "#fff", fontWeight: 600, fontSize: "0.875rem" }, children: "Component Inspector" }), isMarginPaddingMode && (_jsx(Typography, { variant: "caption", sx: { color: "#ff9800", fontSize: "0.65rem" }, children: "M/P mode" })), isLocked && (_jsx(Typography, { variant: "caption", sx: { color: "#4caf50", fontSize: "0.7rem", fontStyle: "italic" }, children: "(Locked - Release H to unlock)" }))] }), isMobile ? (_jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 0.75 }, children: [_jsx(IconButton, { size: "small", onPointerDown: (e) => { e.preventDefault(); handleCopy("component"); }, sx: { color: copied === "component" ? "#4caf50" : "#fff", p: 0.5, minWidth: 36, minHeight: 36 }, "aria-label": "Copy component", children: _jsx(ContentCopyIcon, { fontSize: "small" }) }), _jsxs(Box, { sx: {
338
+ }, children: [_jsxs(Box, { sx: { display: "flex", justifyContent: "space-between", alignItems: "flex-start", mb: 1 }, children: [_jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 1 }, children: [_jsx(Typography, { variant: "subtitle2", sx: { color: "#fff", fontWeight: 600, fontSize: "0.875rem" }, children: "Component Inspector" }), isMarginPaddingMode && (_jsx(Typography, { variant: "caption", sx: { color: "#ff9800", fontSize: "0.65rem" }, children: "M/P mode" })), isLocked && (_jsx(Typography, { variant: "caption", sx: { color: "#4caf50", fontSize: "0.7rem", fontStyle: "italic" }, children: "(Locked - Release H to unlock)" }))] }), _jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 0.75 }, children: [_jsx(MuiTooltip, { title: copied === "component" ? "Copied!" : "Copy component", children: _jsx(IconButton, { size: "small", onClick: (e) => { e.preventDefault(); handleCopy("component"); }, onPointerDown: (e) => { e.preventDefault(); handleCopy("component"); }, sx: {
339
+ color: copied === "component" ? "#4caf50" : "#fff",
340
+ "&:hover": { backgroundColor: "rgba(255, 255, 255, 0.1)" },
341
+ p: 0.5,
342
+ minWidth: 36,
343
+ minHeight: 36,
344
+ }, "aria-label": "Copy component", children: _jsx(ContentCopyIcon, { fontSize: "small" }) }) }), _jsxs(Box, { sx: {
324
345
  display: "flex",
325
346
  alignItems: "stretch",
326
347
  border: "1px solid rgba(255,255,255,0.25)",
327
348
  borderRadius: 1,
328
349
  overflow: "hidden",
329
- }, children: [_jsx(Typography, { component: "button", type: "button", variant: "caption", onPointerDown: (e) => { e.preventDefault(); handleCopy("margin"); }, sx: {
350
+ }, children: [_jsx(Typography, { component: "button", type: "button", variant: "caption", onClick: (e) => { e.preventDefault(); handleCopy("margin"); }, onPointerDown: (e) => { e.preventDefault(); handleCopy("margin"); }, sx: {
330
351
  color: copied === "margin" ? "#4caf50" : "rgba(255,255,255,0.9)",
331
- fontSize: "0.65rem",
352
+ fontSize: isMobile ? "0.65rem" : "0.7rem",
332
353
  cursor: "pointer",
333
354
  background: "none",
334
355
  border: "none",
@@ -336,9 +357,10 @@ export const InspectionTooltip = () => {
336
357
  padding: "4px 6px",
337
358
  fontFamily: "inherit",
338
359
  minWidth: 44,
339
- }, children: "Margin" }), _jsx(Typography, { component: "button", type: "button", variant: "caption", onPointerDown: (e) => { e.preventDefault(); handleCopy("padding"); }, sx: {
360
+ "&:hover": { backgroundColor: "rgba(255,255,255,0.08)" },
361
+ }, children: "Margin" }), _jsx(Typography, { component: "button", type: "button", variant: "caption", onClick: (e) => { e.preventDefault(); handleCopy("padding"); }, onPointerDown: (e) => { e.preventDefault(); handleCopy("padding"); }, sx: {
340
362
  color: copied === "padding" ? "#4caf50" : "rgba(255,255,255,0.9)",
341
- fontSize: "0.65rem",
363
+ fontSize: isMobile ? "0.65rem" : "0.7rem",
342
364
  cursor: "pointer",
343
365
  background: "none",
344
366
  border: "none",
@@ -346,20 +368,18 @@ export const InspectionTooltip = () => {
346
368
  padding: "4px 6px",
347
369
  fontFamily: "inherit",
348
370
  minWidth: 44,
349
- }, children: "Padding" }), parentWithGap && (_jsx(Typography, { component: "button", type: "button", variant: "caption", onPointerDown: (e) => { e.preventDefault(); handleCopy("gap"); }, sx: {
371
+ "&:hover": { backgroundColor: "rgba(255,255,255,0.08)" },
372
+ }, children: "Padding" }), parentWithGap && (_jsx(Typography, { component: "button", type: "button", variant: "caption", onClick: (e) => { e.preventDefault(); handleCopy("gap"); }, onPointerDown: (e) => { e.preventDefault(); handleCopy("gap"); }, sx: {
350
373
  color: copied === "gap" ? "#4caf50" : "rgba(156, 39, 176, 0.95)",
351
- fontSize: "0.65rem",
374
+ fontSize: isMobile ? "0.65rem" : "0.7rem",
352
375
  cursor: "pointer",
353
376
  background: "none",
354
377
  border: "none",
355
378
  padding: "4px 6px",
356
379
  fontFamily: "inherit",
357
380
  minWidth: 44,
358
- }, children: "Gap" }))] })] })) : (_jsxs(_Fragment, { children: [_jsx(MuiTooltip, { title: copied ? `Copied ${copied}!` : "Copy: Component / Margin / Padding", children: _jsx(IconButton, { size: "small", onClick: (e) => setCopyMenuAnchor(e.currentTarget), sx: {
359
- color: copied ? "#4caf50" : "#fff",
360
- "&:hover": { backgroundColor: "rgba(255, 255, 255, 0.1)" },
361
- p: 0.5,
362
- }, children: _jsx(ContentCopyIcon, { fontSize: "small" }) }) }), _jsxs(Menu, { anchorEl: copyMenuAnchor && document.body.contains(copyMenuAnchor) ? copyMenuAnchor : null, open: !!copyMenuAnchor && document.body.contains(copyMenuAnchor), onClose: () => setCopyMenuAnchor(null), anchorOrigin: { vertical: "bottom", horizontal: "right" }, transformOrigin: { vertical: "top", horizontal: "right" }, PaperProps: { sx: { backgroundColor: "rgba(18, 18, 18, 0.95)", minWidth: 140 } }, MenuListProps: { dense: true }, children: [_jsx(MenuItem, { onClick: () => handleCopy("component"), sx: { color: "#fff", fontSize: "0.8rem" }, children: "Copy Component" }), _jsx(MenuItem, { onClick: () => handleCopy("margin"), sx: { color: "#fff", fontSize: "0.8rem" }, children: "Copy Margin" }), _jsx(MenuItem, { onClick: () => handleCopy("padding"), sx: { color: "#fff", fontSize: "0.8rem" }, children: "Copy Padding" }), parentWithGap && (_jsx(MenuItem, { onClick: () => handleCopy("gap"), sx: { color: "#9c27b0", fontSize: "0.8rem" }, children: "Copy Gap (parent)" }))] })] }))] }), isMarginPaddingMode && hoveredElement && (() => {
381
+ "&:hover": { backgroundColor: "rgba(255,255,255,0.08)" },
382
+ }, children: "Gap" }))] })] })] }), isMarginPaddingMode && hoveredElement && (() => {
363
383
  try {
364
384
  const cs = window.getComputedStyle(hoveredElement);
365
385
  const mt = cs.marginTop;
@@ -378,5 +398,9 @@ export const InspectionTooltip = () => {
378
398
  catch {
379
399
  return (_jsxs(Box, { sx: { mb: 1, p: 0.75, borderRadius: 1, backgroundColor: "rgba(255,255,255,0.06)" }, children: [_jsx(Typography, { variant: "caption", sx: { color: "#ff9800" }, children: "Orange = Margin (outside)" }), _jsx(Typography, { variant: "caption", sx: { color: "#4caf50", display: "block" }, children: "Green = Padding (inside)" })] }));
380
400
  }
381
- })(), _jsxs(Box, { sx: { display: "flex", flexDirection: "column", gap: 1 }, children: [_jsx(Typography, { variant: "body2", sx: { color: "#fff", fontWeight: 500, mb: 0.5 }, children: displayComponent.componentName }), _jsx(Divider, { sx: { borderColor: "rgba(255, 255, 255, 0.1)", my: 0.5 } }), _jsx(ElementIdentificationSection, { element: hoveredElement, role: displayComponent.role }), _jsx(Divider, { sx: { borderColor: "rgba(255, 255, 255, 0.1)", my: 0.5 } }), _jsx(ComponentMetadataSection, { metadata: displayComponent })] })] }));
401
+ })(), _jsxs(Box, { sx: { display: "flex", flexDirection: "column", gap: 1 }, children: [_jsx(Typography, { variant: "body2", sx: { color: "#fff", fontWeight: 500, mb: 0.5 }, children: displayComponent.componentName }), _jsx(Divider, { sx: { borderColor: "rgba(255, 255, 255, 0.1)", my: 0.5 } }), _jsx(ElementIdentificationSection, { element: hoveredElement, role: displayComponent.role }), _jsx(Divider, { sx: { borderColor: "rgba(255, 255, 255, 0.1)", my: 0.5 } }), hoveredElement && (() => {
402
+ const howToType = isMarginPaddingMode ? "margin" : "component";
403
+ const info = getTooltipHowToFindInfo(displayComponent, hoveredElement, howToType);
404
+ return (_jsxs(_Fragment, { children: [_jsx(Typography, { variant: "caption", sx: { color: "rgba(255, 255, 255, 0.9)", fontSize: "0.7rem", fontWeight: 600 }, children: "=== HOW TO FIND IN CODE ===" }), _jsxs(Box, { sx: { display: "flex", flexDirection: "column", gap: 0.25, pl: 0.5 }, children: [_jsxs(Typography, { variant: "caption", sx: { color: "rgba(255, 255, 255, 0.7)", fontSize: "0.65rem", wordBreak: "break-all" }, children: [_jsx("strong", { children: "DOM Path:" }), " ", info.domPath] }), info.parent && (_jsxs(Typography, { variant: "caption", sx: { color: "rgba(255, 255, 255, 0.7)", fontSize: "0.65rem" }, children: [_jsx("strong", { children: "Parent:" }), " ", info.parent] })), _jsxs(Typography, { variant: "caption", sx: { color: "rgba(255, 255, 255, 0.7)", fontSize: "0.65rem" }, children: [_jsx("strong", { children: "Role in tree:" }), " ", info.roleInTree] }), _jsxs(Typography, { variant: "caption", sx: { color: "rgba(255, 255, 255, 0.85)", fontSize: "0.65rem", mt: 0.5 }, children: [_jsx("strong", { children: "TARGET:" }), " ", info.target] }), _jsx(Box, { component: "ol", sx: { m: 0, pl: 1.5, fontSize: "0.65rem", color: "rgba(255, 255, 255, 0.7)" }, children: info.howToFindSteps.map((step, i) => (_jsx(Typography, { component: "li", variant: "caption", sx: { color: "rgba(255, 255, 255, 0.7)", fontSize: "0.65rem", mb: 0.25 }, children: step }, i))) })] }), _jsx(Divider, { sx: { borderColor: "rgba(255, 255, 255, 0.1)", my: 0.5 } })] }));
405
+ })(), _jsx(ComponentMetadataSection, { metadata: displayComponent })] })] }));
382
406
  };
package/dist/index.d.ts CHANGED
@@ -5,6 +5,6 @@ export { InspectionOverlays } from './InspectionOverlays';
5
5
  export { InspectionWrapper, withInspection } from './InspectionWrapper';
6
6
  export { useInspectionMetadata } from './useInspectionMetadata';
7
7
  export { setupInterceptors, setInspectionActive, shouldBlockRequest } from './inspectionInterceptors';
8
- export { generateComponentId, formatPropsSignature, formatMetadataForClipboard, formatMarginForClipboard, formatPaddingForClipboard, formatGapForClipboard, getParentWithGap, getAncestorsWithMargin, getComponentName, getNextInstanceIndex } from './inspection';
8
+ export { generateComponentId, formatPropsSignature, formatMetadataForClipboard, formatMarginForClipboard, formatPaddingForClipboard, formatGapForClipboard, getParentWithGap, getAncestorsWithMargin, getTooltipHowToFindInfo, getComponentName, getNextInstanceIndex } from './inspection';
9
9
  export type { CopyType } from './inspection';
10
10
  export { setupAutoInspection, parseInspectionMetadata } from './autoInspection';
package/dist/index.js CHANGED
@@ -6,5 +6,5 @@ export { InspectionOverlays } from './InspectionOverlays';
6
6
  export { InspectionWrapper, withInspection } from './InspectionWrapper';
7
7
  export { useInspectionMetadata } from './useInspectionMetadata';
8
8
  export { setupInterceptors, setInspectionActive, shouldBlockRequest } from './inspectionInterceptors';
9
- export { generateComponentId, formatPropsSignature, formatMetadataForClipboard, formatMarginForClipboard, formatPaddingForClipboard, formatGapForClipboard, getParentWithGap, getAncestorsWithMargin, getComponentName, getNextInstanceIndex } from './inspection';
9
+ export { generateComponentId, formatPropsSignature, formatMetadataForClipboard, formatMarginForClipboard, formatPaddingForClipboard, formatGapForClipboard, getParentWithGap, getAncestorsWithMargin, getTooltipHowToFindInfo, getComponentName, getNextInstanceIndex } from './inspection';
10
10
  export { setupAutoInspection, parseInspectionMetadata } from './autoInspection';
@@ -30,6 +30,18 @@ export declare const getAncestorsWithMargin: (element: HTMLElement, maxCount?: n
30
30
  mb: number;
31
31
  ml: number;
32
32
  }>;
33
+ /**
34
+ * Build DOM path from body to element (helps Cursor identify exact element)
35
+ */
36
+ export declare const buildDomPath: (element: HTMLElement) => string;
37
+ /**
38
+ * Build parent element description
39
+ */
40
+ export declare const buildParentInfo: (element: HTMLElement) => string | null;
41
+ /**
42
+ * Build role/disambiguation (outer vs inner, position in tree)
43
+ */
44
+ export declare const buildRoleInTree: (element: HTMLElement, type: "margin" | "padding" | "component") => string;
33
45
  /**
34
46
  * Format metadata for clipboard with full element details
35
47
  */
@@ -46,6 +58,16 @@ export declare const formatPaddingForClipboard: (metadata: ComponentMetadata, el
46
58
  * Format gap info for clipboard (alias for formatMetadataForClipboard with type="gap")
47
59
  */
48
60
  export declare const formatGapForClipboard: (metadata: ComponentMetadata, element: HTMLElement) => string;
61
+ /**
62
+ * Get "how to find" display info for tooltip (same logic for desktop and mobile)
63
+ */
64
+ export declare const getTooltipHowToFindInfo: (metadata: ComponentMetadata, element: HTMLElement, type: CopyType) => {
65
+ domPath: string;
66
+ parent: string | null;
67
+ roleInTree: string;
68
+ target: string;
69
+ howToFindSteps: string[];
70
+ };
49
71
  /**
50
72
  * Get next instance index for a component
51
73
  */
@@ -103,7 +103,7 @@ export const getAncestorsWithMargin = (element, maxCount = 2) => {
103
103
  /**
104
104
  * Build DOM path from body to element (helps Cursor identify exact element)
105
105
  */
106
- const buildDomPath = (element) => {
106
+ export const buildDomPath = (element) => {
107
107
  const segments = [];
108
108
  let current = element;
109
109
  while (current && current !== document.body) {
@@ -122,7 +122,7 @@ const buildDomPath = (element) => {
122
122
  /**
123
123
  * Build parent element description
124
124
  */
125
- const buildParentInfo = (element) => {
125
+ export const buildParentInfo = (element) => {
126
126
  const parent = element.parentElement;
127
127
  if (!parent || parent === document.body)
128
128
  return null;
@@ -138,7 +138,7 @@ const buildParentInfo = (element) => {
138
138
  /**
139
139
  * Build role/disambiguation (outer vs inner, position in tree)
140
140
  */
141
- const buildRoleInTree = (element, type) => {
141
+ export const buildRoleInTree = (element, type) => {
142
142
  const parent = element.parentElement;
143
143
  const children = element.children;
144
144
  const childCount = Array.from(children).length;
@@ -400,6 +400,79 @@ export const formatPaddingForClipboard = (metadata, element) => {
400
400
  export const formatGapForClipboard = (metadata, element) => {
401
401
  return formatMetadataForClipboard(metadata, element, "gap");
402
402
  };
403
+ /**
404
+ * Get "how to find" display info for tooltip (same logic for desktop and mobile)
405
+ */
406
+ export const getTooltipHowToFindInfo = (metadata, element, type) => {
407
+ const domPath = buildDomPath(element);
408
+ const parent = buildParentInfo(element);
409
+ if (type === "component") {
410
+ const rect = element.getBoundingClientRect();
411
+ const classNameStr = element.className ? (typeof element.className === "string" ? element.className : String(element.className)) : "";
412
+ const firstClass = classNameStr.split(/\s+/).find((c) => c && (c.startsWith("Mui") || c.startsWith("css-")));
413
+ const desc = firstClass ? `${metadata.componentName} with class ${firstClass}` : metadata.componentName;
414
+ const target = `The ${desc} - position (${Math.round(rect.left)}, ${Math.round(rect.top)}), size ${Math.round(rect.width)}x${Math.round(rect.height)}px. It is the element in the DOM path above, NOT a child.`;
415
+ const steps = [
416
+ "Use the DOM Path to locate the correct element - do NOT change a child if the path shows this element is the parent.",
417
+ metadata.sourceFile !== "DOM"
418
+ ? `Open ${metadata.sourceFile} and find the component that renders this element.`
419
+ : "Search for the parent component that renders this layout. Look for the component in Usage Path or by Element Text/Label.",
420
+ "Modify the component's props, sx, or styles as needed.",
421
+ ];
422
+ return { domPath, parent, roleInTree: buildRoleInTree(element, "component"), target, howToFindSteps: steps };
423
+ }
424
+ if (type === "margin" || type === "padding") {
425
+ const cs = window.getComputedStyle(element);
426
+ const classNameStr = element.className ? (typeof element.className === "string" ? element.className : String(element.className)) : "";
427
+ const firstClass = classNameStr.split(/\s+/).find((c) => c && (c.startsWith("Mui") || c.startsWith("css-")));
428
+ const desc = firstClass ? `${metadata.componentName} with class ${firstClass}` : metadata.componentName;
429
+ if (type === "margin") {
430
+ const mt = getCssValue(cs.marginTop);
431
+ const mr = getCssValue(cs.marginRight);
432
+ const mb = getCssValue(cs.marginBottom);
433
+ const ml = getCssValue(cs.marginLeft);
434
+ const target = `The ${desc} - the element with margin ${mt} ${mr} ${mb} ${ml}. It is the PARENT in the DOM path above, NOT a child.`;
435
+ const steps = [
436
+ "Use the DOM Path to locate the correct element - do NOT change a child (e.g. CardContent, MuiBox) if the path shows this element is the parent.",
437
+ metadata.sourceFile !== "DOM"
438
+ ? `Open ${metadata.sourceFile} and find the component that renders this element.`
439
+ : "Search for the parent component that renders this layout. Look for MUI components (Box, Card, etc.) - the element with these margin values may use sx={{ m: ... }} or style props.",
440
+ "Change margin: sx={{ margin: 0 }} or margin: \"4px\" or mt: 1, mr: 1, mb: 1, ml: 1 (MUI theme spacing).",
441
+ ];
442
+ return { domPath, parent, roleInTree: buildRoleInTree(element, "margin"), target, howToFindSteps: steps };
443
+ }
444
+ else {
445
+ const pt = getCssValue(cs.paddingTop);
446
+ const pr = getCssValue(cs.paddingRight);
447
+ const pb = getCssValue(cs.paddingBottom);
448
+ const pl = getCssValue(cs.paddingLeft);
449
+ const target = `The ${desc} - the element with padding ${pt} ${pr} ${pb} ${pl}. It is the PARENT in the DOM path above, NOT a child.`;
450
+ const steps = [
451
+ "Use the DOM Path to locate the correct element - do NOT change a child (e.g. CardContent, MuiBox) if the path shows this element is the parent.",
452
+ metadata.sourceFile !== "DOM"
453
+ ? `Open ${metadata.sourceFile} and find the component that renders this element.`
454
+ : "Search for the parent component that renders this layout. Look for MUI components (Box, Card, CardContent, etc.) - the element with these exact padding values may use sx={{ p: ... }} or padding props.",
455
+ "Change padding: sx={{ padding: 0 }} or p: \"4px\" or pt: 1, pr: 1, pb: 1, pl: 1 (MUI theme spacing).",
456
+ ];
457
+ return { domPath, parent, roleInTree: buildRoleInTree(element, "padding"), target, howToFindSteps: steps };
458
+ }
459
+ }
460
+ // type === "gap" - element is the parent with gap
461
+ const cs = window.getComputedStyle(element);
462
+ const gap = getCssValue(cs.gap);
463
+ const classNameStr = element.className ? (typeof element.className === "string" ? element.className : String(element.className)) : "";
464
+ const firstClass = classNameStr.split(/\s+/).find((c) => c && (c.startsWith("Mui") || c.startsWith("css-")));
465
+ const desc = firstClass ? `${metadata.componentName} with class ${firstClass}` : metadata.componentName;
466
+ const target = `The ${desc} - the flex/grid container with gap ${gap}. It is the PARENT in the DOM path above.`;
467
+ const steps = [
468
+ "Use the DOM Path to locate the flex/grid container.",
469
+ metadata.sourceFile !== "DOM"
470
+ ? `Open ${metadata.sourceFile} and find the component that renders this element.`
471
+ : "Search for the component that renders this layout (Box, Stack, Grid, etc.).",
472
+ "Change gap: sx={{ gap: 1 }} or gap: \"8px\" or rowGap/columnGap (MUI theme spacing).",
473
+ ];
474
+ return { domPath, parent, roleInTree: "flex/grid container", target, howToFindSteps: steps };
475
+ };
403
476
  /**
404
477
  * Track component instances for instance indexing
405
478
  */
package/package.json CHANGED
@@ -1,54 +1,55 @@
1
- {
2
- "name": "@zargaryanvh/react-component-inspector",
3
- "version": "1.0.3",
4
- "description": "A development tool for inspecting React components with AI-friendly metadata extraction",
5
- "main": "dist/index.js",
6
- "types": "dist/index.d.ts",
7
- "exports": {
8
- ".": {
9
- "import": "./dist/index.js",
10
- "require": "./dist/index.js",
11
- "types": "./dist/index.d.ts"
12
- }
13
- },
14
- "files": [
15
- "dist/**/*",
16
- "README.md",
17
- "LICENSE",
18
- "package.json"
19
- ],
20
- "scripts": {
21
- "build": "tsc",
22
- "prepublishOnly": "npm run build",
23
- "test": "echo \"Error: no test specified\" && exit 1"
24
- },
25
- "keywords": [
26
- "react",
27
- "component",
28
- "inspection",
29
- "development",
30
- "debugging",
31
- "ai",
32
- "metadata",
33
- "tool",
34
- "cursor",
35
- "cursor-ai"
36
- ],
37
- "author": "zargaryanvh",
38
- "license": "MIT",
39
- "peerDependencies": {
40
- "react": ">=16.8.0",
41
- "react-dom": ">=16.8.0",
42
- "@mui/material": ">=5.0.0",
43
- "@mui/icons-material": ">=5.0.0"
44
- },
45
- "devDependencies": {
46
- "@types/react": "^18.0.0",
47
- "@types/react-dom": "^18.0.0",
48
- "typescript": "^5.0.0"
49
- },
50
- "repository": {
51
- "type": "git",
52
- "url": "git+https://github.com/zargaryanvh/react-component-inspector.git"
53
- }
54
- }
1
+ {
2
+ "name": "@zargaryanvh/react-component-inspector",
3
+ "version": "1.0.6",
4
+ "description": "A development tool for inspecting React components with AI-friendly metadata extraction",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./dist/index.js",
10
+ "require": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist/**/*",
16
+ "README.md",
17
+ "LICENSE",
18
+ "package.json"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsc",
22
+ "prepublishOnly": "npm run build",
23
+ "test": "echo \"Error: no test specified\" && exit 1"
24
+ },
25
+ "keywords": [
26
+ "react",
27
+ "component",
28
+ "inspection",
29
+ "development",
30
+ "debugging",
31
+ "ai",
32
+ "metadata",
33
+ "tool",
34
+ "cursor",
35
+ "cursor-ai"
36
+ ],
37
+ "author": "zargaryanvh",
38
+ "license": "MIT",
39
+ "peerDependencies": {
40
+ "react": ">=16.8.0",
41
+ "react-dom": ">=16.8.0",
42
+ "@mui/material": ">=5.0.0",
43
+ "@mui/icons-material": ">=5.0.0"
44
+ },
45
+ "devDependencies": {
46
+ "@types/node": "^20.0.0",
47
+ "@types/react": "^18.0.0",
48
+ "@types/react-dom": "^18.0.0",
49
+ "typescript": "^5.0.0"
50
+ },
51
+ "repository": {
52
+ "type": "git",
53
+ "url": "git+https://github.com/zargaryanvh/react-component-inspector.git"
54
+ }
55
+ }