@rn-ave/core 0.1.0
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/README.md +31 -0
- package/dist/appRegistry.d.ts +11 -0
- package/dist/appRegistry.js +52 -0
- package/dist/auto.d.ts +15 -0
- package/dist/auto.js +35 -0
- package/dist/components/InspectorButton.d.ts +7 -0
- package/dist/components/InspectorButton.js +125 -0
- package/dist/components/InspectorOverlay.d.ts +11 -0
- package/dist/components/InspectorOverlay.js +60 -0
- package/dist/context/extractor.d.ts +7 -0
- package/dist/context/extractor.js +235 -0
- package/dist/context/formatter.d.ts +13 -0
- package/dist/context/formatter.js +95 -0
- package/dist/devMenu.d.ts +26 -0
- package/dist/devMenu.js +80 -0
- package/dist/editor.d.ts +20 -0
- package/dist/editor.js +93 -0
- package/dist/fiber/hook.d.ts +18 -0
- package/dist/fiber/hook.js +103 -0
- package/dist/fiber/source.d.ts +21 -0
- package/dist/fiber/source.js +94 -0
- package/dist/fiber/traversal.d.ts +27 -0
- package/dist/fiber/traversal.js +163 -0
- package/dist/gesture/detector.d.ts +17 -0
- package/dist/gesture/detector.js +84 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +8 -0
- package/dist/overlay/highlight.d.ts +14 -0
- package/dist/overlay/highlight.js +177 -0
- package/dist/overlay/tooltip.d.ts +15 -0
- package/dist/overlay/tooltip.js +467 -0
- package/dist/provider.d.ts +10 -0
- package/dist/provider.js +264 -0
- package/dist/types.d.ts +94 -0
- package/dist/types.js +2 -0
- package/package.json +58 -0
package/README.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# @rn-ave/core
|
|
2
|
+
|
|
3
|
+
Core library for `rn-ave` - Agent Visual Edit for React Native.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @rn-ave/core
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
The easiest way to use `rn-ave` is with auto-initialization:
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
import '@rn-ave/core/auto';
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Add this line to the top of your `App.tsx` or `index.js`.
|
|
20
|
+
|
|
21
|
+
## Features
|
|
22
|
+
|
|
23
|
+
- **Visual Selection**: Tap components to select them.
|
|
24
|
+
- **Context Extraction**: Extracts component name, props, hierarchy, and source location.
|
|
25
|
+
- **AI Agent Integration**: Sends context to Claude Code, OpenCode, or Cursor.
|
|
26
|
+
- **Open in Cursor**: Opens the exact file and line in Cursor.
|
|
27
|
+
|
|
28
|
+
## Requirements
|
|
29
|
+
|
|
30
|
+
- React Native 0.60+
|
|
31
|
+
- Works best with Cursor editor.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AppRegistry wrapper to automatically inject Inspector provider
|
|
3
|
+
* around registered React Native components
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Wrap AppRegistry.registerComponent to automatically inject RNAVEProvider
|
|
7
|
+
*
|
|
8
|
+
* This allows users to enable the inspector with a single import statement,
|
|
9
|
+
* without manually wrapping their app component.
|
|
10
|
+
*/
|
|
11
|
+
export declare function wrapAppRegistry(): void;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* AppRegistry wrapper to automatically inject Inspector provider
|
|
4
|
+
* around registered React Native components
|
|
5
|
+
*/
|
|
6
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
7
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
8
|
+
};
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.wrapAppRegistry = wrapAppRegistry;
|
|
11
|
+
const react_native_1 = require("react-native");
|
|
12
|
+
const react_1 = __importDefault(require("react"));
|
|
13
|
+
const provider_1 = require("./provider");
|
|
14
|
+
// Track if we've already wrapped AppRegistry (hot reload protection)
|
|
15
|
+
let isWrapped = false;
|
|
16
|
+
/**
|
|
17
|
+
* Wrap AppRegistry.registerComponent to automatically inject RNAVEProvider
|
|
18
|
+
*
|
|
19
|
+
* This allows users to enable the inspector with a single import statement,
|
|
20
|
+
* without manually wrapping their app component.
|
|
21
|
+
*/
|
|
22
|
+
function wrapAppRegistry() {
|
|
23
|
+
// Prevent double-wrapping on hot reload
|
|
24
|
+
if (isWrapped) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
isWrapped = true;
|
|
28
|
+
// Store original registerComponent method
|
|
29
|
+
const originalRegister = react_native_1.AppRegistry.registerComponent;
|
|
30
|
+
// Replace with our wrapper
|
|
31
|
+
react_native_1.AppRegistry.registerComponent = function (appKey, componentProvider) {
|
|
32
|
+
// Create a wrapped component provider
|
|
33
|
+
const wrappedProvider = () => {
|
|
34
|
+
const OriginalComponent = componentProvider();
|
|
35
|
+
// Create the wrapped component with Inspector
|
|
36
|
+
const WrappedComponent = (props) => {
|
|
37
|
+
return react_1.default.createElement(provider_1.RNAVEProvider, null, react_1.default.createElement(OriginalComponent, props));
|
|
38
|
+
};
|
|
39
|
+
// Preserve display name for React DevTools debugging
|
|
40
|
+
const originalName = OriginalComponent.displayName || OriginalComponent.name || 'Component';
|
|
41
|
+
WrappedComponent.displayName = `RNAVE(${originalName})`;
|
|
42
|
+
return WrappedComponent;
|
|
43
|
+
};
|
|
44
|
+
// Preserve the original component provider's name
|
|
45
|
+
Object.defineProperty(wrappedProvider, 'name', {
|
|
46
|
+
value: componentProvider.name || 'ComponentProvider',
|
|
47
|
+
writable: false,
|
|
48
|
+
});
|
|
49
|
+
// Call the original registerComponent with our wrapped version
|
|
50
|
+
return originalRegister.call(this, appKey, wrappedProvider);
|
|
51
|
+
};
|
|
52
|
+
}
|
package/dist/auto.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-initialization module for rn-ave
|
|
3
|
+
*
|
|
4
|
+
* Simply import this module to automatically enable the Inspector:
|
|
5
|
+
* ```
|
|
6
|
+
* import '@rn-ave/core/auto';
|
|
7
|
+
* ```
|
|
8
|
+
*
|
|
9
|
+
* This module:
|
|
10
|
+
* 1. Installs React DevTools hook early
|
|
11
|
+
* 2. Wraps AppRegistry to inject Inspector provider automatically
|
|
12
|
+
*
|
|
13
|
+
* Only Cursor editor is supported.
|
|
14
|
+
*/
|
|
15
|
+
export {};
|
package/dist/auto.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Auto-initialization module for rn-ave
|
|
4
|
+
*
|
|
5
|
+
* Simply import this module to automatically enable the Inspector:
|
|
6
|
+
* ```
|
|
7
|
+
* import '@rn-ave/core/auto';
|
|
8
|
+
* ```
|
|
9
|
+
*
|
|
10
|
+
* This module:
|
|
11
|
+
* 1. Installs React DevTools hook early
|
|
12
|
+
* 2. Wraps AppRegistry to inject Inspector provider automatically
|
|
13
|
+
*
|
|
14
|
+
* Only Cursor editor is supported.
|
|
15
|
+
*/
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
const hook_1 = require("./fiber/hook");
|
|
18
|
+
const appRegistry_1 = require("./appRegistry");
|
|
19
|
+
// Only run in development mode
|
|
20
|
+
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
21
|
+
// 1. Install React DevTools hook FIRST (critical timing)
|
|
22
|
+
// This must happen before React Native initializes
|
|
23
|
+
try {
|
|
24
|
+
(0, hook_1.installHook)();
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
// Silent fail
|
|
28
|
+
}
|
|
29
|
+
// 2. Wrap AppRegistry to auto-inject Inspector
|
|
30
|
+
// This must happen before any AppRegistry.registerComponent calls
|
|
31
|
+
(0, appRegistry_1.wrapAppRegistry)();
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
// Production mode - do nothing
|
|
35
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.InspectorButton = InspectorButton;
|
|
37
|
+
const react_1 = __importStar(require("react"));
|
|
38
|
+
const react_native_1 = require("react-native");
|
|
39
|
+
const SearchIcon = ({ active }) => (<react_native_1.View style={styles.iconContainer}>
|
|
40
|
+
<react_native_1.View style={[styles.lens, active && styles.lensActive]}/>
|
|
41
|
+
<react_native_1.View style={[styles.handle, active && styles.handleActive]}/>
|
|
42
|
+
</react_native_1.View>);
|
|
43
|
+
function InspectorButton({ isInspecting, onToggle }) {
|
|
44
|
+
const [position] = (0, react_1.useState)(new react_native_1.Animated.ValueXY({ x: react_native_1.Dimensions.get('window').width - 70, y: 100 }));
|
|
45
|
+
const panResponder = react_native_1.PanResponder.create({
|
|
46
|
+
onStartShouldSetPanResponder: () => true,
|
|
47
|
+
onMoveShouldSetPanResponder: () => true,
|
|
48
|
+
onPanResponderGrant: () => {
|
|
49
|
+
position.setOffset({
|
|
50
|
+
x: position.x._value,
|
|
51
|
+
y: position.y._value,
|
|
52
|
+
});
|
|
53
|
+
position.setValue({ x: 0, y: 0 });
|
|
54
|
+
},
|
|
55
|
+
onPanResponderMove: react_native_1.Animated.event([null, { dx: position.x, dy: position.y }], {
|
|
56
|
+
useNativeDriver: false,
|
|
57
|
+
}),
|
|
58
|
+
onPanResponderRelease: () => {
|
|
59
|
+
position.flattenOffset();
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
return (<react_native_1.Animated.View style={[styles.container, { transform: position.getTranslateTransform() }]} {...panResponder.panHandlers}>
|
|
63
|
+
<react_native_1.TouchableOpacity style={[styles.button, isInspecting && styles.buttonActive]} onPress={onToggle} activeOpacity={0.8}>
|
|
64
|
+
<SearchIcon active={isInspecting}/>
|
|
65
|
+
</react_native_1.TouchableOpacity>
|
|
66
|
+
</react_native_1.Animated.View>);
|
|
67
|
+
}
|
|
68
|
+
const styles = react_native_1.StyleSheet.create({
|
|
69
|
+
container: {
|
|
70
|
+
position: 'absolute',
|
|
71
|
+
zIndex: 9999,
|
|
72
|
+
},
|
|
73
|
+
button: {
|
|
74
|
+
width: 48,
|
|
75
|
+
height: 48,
|
|
76
|
+
borderRadius: 24,
|
|
77
|
+
backgroundColor: '#1C1C1E',
|
|
78
|
+
justifyContent: 'center',
|
|
79
|
+
alignItems: 'center',
|
|
80
|
+
shadowColor: '#000',
|
|
81
|
+
shadowOffset: { width: 0, height: 4 },
|
|
82
|
+
shadowOpacity: 0.3,
|
|
83
|
+
shadowRadius: 8,
|
|
84
|
+
elevation: 8,
|
|
85
|
+
borderWidth: 1,
|
|
86
|
+
borderColor: '#3A3A3C',
|
|
87
|
+
},
|
|
88
|
+
buttonActive: {
|
|
89
|
+
backgroundColor: '#007AFF',
|
|
90
|
+
borderColor: '#007AFF',
|
|
91
|
+
shadowColor: '#007AFF',
|
|
92
|
+
shadowOpacity: 0.5,
|
|
93
|
+
},
|
|
94
|
+
iconContainer: {
|
|
95
|
+
width: 20,
|
|
96
|
+
height: 20,
|
|
97
|
+
justifyContent: 'center',
|
|
98
|
+
alignItems: 'center',
|
|
99
|
+
},
|
|
100
|
+
lens: {
|
|
101
|
+
width: 14,
|
|
102
|
+
height: 14,
|
|
103
|
+
borderRadius: 7,
|
|
104
|
+
borderWidth: 2,
|
|
105
|
+
borderColor: '#FFFFFF',
|
|
106
|
+
position: 'absolute',
|
|
107
|
+
top: 0,
|
|
108
|
+
left: 0,
|
|
109
|
+
},
|
|
110
|
+
lensActive: {
|
|
111
|
+
borderColor: '#FFFFFF',
|
|
112
|
+
},
|
|
113
|
+
handle: {
|
|
114
|
+
width: 2,
|
|
115
|
+
height: 6,
|
|
116
|
+
backgroundColor: '#FFFFFF',
|
|
117
|
+
position: 'absolute',
|
|
118
|
+
bottom: 1,
|
|
119
|
+
right: 1,
|
|
120
|
+
transform: [{ rotate: '-45deg' }],
|
|
121
|
+
},
|
|
122
|
+
handleActive: {
|
|
123
|
+
backgroundColor: '#FFFFFF',
|
|
124
|
+
},
|
|
125
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
interface InspectorOverlayProps {
|
|
3
|
+
isActive: boolean;
|
|
4
|
+
onTapAtPosition: (x: number, y: number) => void;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* InspectorOverlay displays a semi-transparent overlay when inspect mode is active.
|
|
8
|
+
* Tapping anywhere triggers coordinate-based element detection.
|
|
9
|
+
*/
|
|
10
|
+
export declare function InspectorOverlay({ isActive, onTapAtPosition }: InspectorOverlayProps): React.JSX.Element | null;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.InspectorOverlay = InspectorOverlay;
|
|
7
|
+
const react_1 = __importDefault(require("react"));
|
|
8
|
+
const react_native_1 = require("react-native");
|
|
9
|
+
/**
|
|
10
|
+
* InspectorOverlay displays a semi-transparent overlay when inspect mode is active.
|
|
11
|
+
* Tapping anywhere triggers coordinate-based element detection.
|
|
12
|
+
*/
|
|
13
|
+
function InspectorOverlay({ isActive, onTapAtPosition }) {
|
|
14
|
+
console.log('[rn-ave] InspectorOverlay render, isActive:', isActive);
|
|
15
|
+
if (!isActive)
|
|
16
|
+
return null;
|
|
17
|
+
const handlePress = (event) => {
|
|
18
|
+
const { pageX, pageY } = event.nativeEvent;
|
|
19
|
+
console.log('\n🎯 [rn-ave] InspectorOverlay handlePress');
|
|
20
|
+
console.log(' pageX:', pageX, 'pageY:', pageY);
|
|
21
|
+
onTapAtPosition(pageX, pageY);
|
|
22
|
+
};
|
|
23
|
+
return (<react_native_1.View style={react_native_1.StyleSheet.absoluteFill} pointerEvents="box-only">
|
|
24
|
+
<react_native_1.Pressable style={styles.overlay} onPress={handlePress}>
|
|
25
|
+
<react_native_1.View style={styles.hint}>
|
|
26
|
+
<react_native_1.Text style={styles.hintText}>Tap any element to inspect</react_native_1.Text>
|
|
27
|
+
</react_native_1.View>
|
|
28
|
+
</react_native_1.Pressable>
|
|
29
|
+
</react_native_1.View>);
|
|
30
|
+
}
|
|
31
|
+
const styles = react_native_1.StyleSheet.create({
|
|
32
|
+
overlay: {
|
|
33
|
+
...react_native_1.StyleSheet.absoluteFillObject,
|
|
34
|
+
backgroundColor: 'rgba(0, 122, 255, 0.08)',
|
|
35
|
+
},
|
|
36
|
+
hint: {
|
|
37
|
+
position: 'absolute',
|
|
38
|
+
top: 60,
|
|
39
|
+
left: 0,
|
|
40
|
+
right: 0,
|
|
41
|
+
alignItems: 'center',
|
|
42
|
+
},
|
|
43
|
+
hintText: {
|
|
44
|
+
backgroundColor: '#1C1C1E',
|
|
45
|
+
color: '#FFFFFF',
|
|
46
|
+
paddingHorizontal: 20,
|
|
47
|
+
paddingVertical: 10,
|
|
48
|
+
borderRadius: 24,
|
|
49
|
+
fontSize: 14,
|
|
50
|
+
fontWeight: '600',
|
|
51
|
+
overflow: 'hidden',
|
|
52
|
+
shadowColor: '#000',
|
|
53
|
+
shadowOffset: { width: 0, height: 4 },
|
|
54
|
+
shadowOpacity: 0.3,
|
|
55
|
+
shadowRadius: 8,
|
|
56
|
+
elevation: 6,
|
|
57
|
+
borderWidth: 1,
|
|
58
|
+
borderColor: '#3A3A3C',
|
|
59
|
+
},
|
|
60
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Fiber, ExtractedContext } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Extracts full context from a fiber node
|
|
4
|
+
* @param fiber - The composite fiber (user's component)
|
|
5
|
+
* @param hostFiber - Optional host fiber (the actual tapped element) to extract source from __rnAve prop
|
|
6
|
+
*/
|
|
7
|
+
export declare function extractContext(fiber: Fiber, hostFiber?: Fiber): ExtractedContext;
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.extractContext = extractContext;
|
|
4
|
+
const traversal_1 = require("../fiber/traversal");
|
|
5
|
+
const source_1 = require("../fiber/source");
|
|
6
|
+
/**
|
|
7
|
+
* Default box model edges (all zeros)
|
|
8
|
+
*/
|
|
9
|
+
function defaultEdges() {
|
|
10
|
+
return { top: 0, right: 0, bottom: 0, left: 0 };
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Extracts box model values from a style object
|
|
14
|
+
* Handles both shorthand (margin: 8) and individual values (marginTop: 8)
|
|
15
|
+
*/
|
|
16
|
+
function extractBoxModel(style) {
|
|
17
|
+
const boxModel = {
|
|
18
|
+
margin: defaultEdges(),
|
|
19
|
+
padding: defaultEdges(),
|
|
20
|
+
border: defaultEdges(),
|
|
21
|
+
width: 0,
|
|
22
|
+
height: 0,
|
|
23
|
+
};
|
|
24
|
+
if (!style)
|
|
25
|
+
return boxModel;
|
|
26
|
+
// Flatten style arrays (RN allows style={[style1, style2]})
|
|
27
|
+
const flatStyle = Array.isArray(style)
|
|
28
|
+
? style.reduce((acc, s) => ({ ...acc, ...(s || {}) }), {})
|
|
29
|
+
: style;
|
|
30
|
+
// Extract dimensions
|
|
31
|
+
boxModel.width = flatStyle.width || 0;
|
|
32
|
+
boxModel.height = flatStyle.height || 0;
|
|
33
|
+
// Extract margin
|
|
34
|
+
if (flatStyle.margin !== undefined) {
|
|
35
|
+
const m = flatStyle.margin;
|
|
36
|
+
boxModel.margin = { top: m, right: m, bottom: m, left: m };
|
|
37
|
+
}
|
|
38
|
+
if (flatStyle.marginVertical !== undefined) {
|
|
39
|
+
boxModel.margin.top = flatStyle.marginVertical;
|
|
40
|
+
boxModel.margin.bottom = flatStyle.marginVertical;
|
|
41
|
+
}
|
|
42
|
+
if (flatStyle.marginHorizontal !== undefined) {
|
|
43
|
+
boxModel.margin.left = flatStyle.marginHorizontal;
|
|
44
|
+
boxModel.margin.right = flatStyle.marginHorizontal;
|
|
45
|
+
}
|
|
46
|
+
if (flatStyle.marginTop !== undefined)
|
|
47
|
+
boxModel.margin.top = flatStyle.marginTop;
|
|
48
|
+
if (flatStyle.marginRight !== undefined)
|
|
49
|
+
boxModel.margin.right = flatStyle.marginRight;
|
|
50
|
+
if (flatStyle.marginBottom !== undefined)
|
|
51
|
+
boxModel.margin.bottom = flatStyle.marginBottom;
|
|
52
|
+
if (flatStyle.marginLeft !== undefined)
|
|
53
|
+
boxModel.margin.left = flatStyle.marginLeft;
|
|
54
|
+
// Extract padding
|
|
55
|
+
if (flatStyle.padding !== undefined) {
|
|
56
|
+
const p = flatStyle.padding;
|
|
57
|
+
boxModel.padding = { top: p, right: p, bottom: p, left: p };
|
|
58
|
+
}
|
|
59
|
+
if (flatStyle.paddingVertical !== undefined) {
|
|
60
|
+
boxModel.padding.top = flatStyle.paddingVertical;
|
|
61
|
+
boxModel.padding.bottom = flatStyle.paddingVertical;
|
|
62
|
+
}
|
|
63
|
+
if (flatStyle.paddingHorizontal !== undefined) {
|
|
64
|
+
boxModel.padding.left = flatStyle.paddingHorizontal;
|
|
65
|
+
boxModel.padding.right = flatStyle.paddingHorizontal;
|
|
66
|
+
}
|
|
67
|
+
if (flatStyle.paddingTop !== undefined)
|
|
68
|
+
boxModel.padding.top = flatStyle.paddingTop;
|
|
69
|
+
if (flatStyle.paddingRight !== undefined)
|
|
70
|
+
boxModel.padding.right = flatStyle.paddingRight;
|
|
71
|
+
if (flatStyle.paddingBottom !== undefined)
|
|
72
|
+
boxModel.padding.bottom = flatStyle.paddingBottom;
|
|
73
|
+
if (flatStyle.paddingLeft !== undefined)
|
|
74
|
+
boxModel.padding.left = flatStyle.paddingLeft;
|
|
75
|
+
// Extract border
|
|
76
|
+
if (flatStyle.borderWidth !== undefined) {
|
|
77
|
+
const b = flatStyle.borderWidth;
|
|
78
|
+
boxModel.border = { top: b, right: b, bottom: b, left: b };
|
|
79
|
+
}
|
|
80
|
+
if (flatStyle.borderTopWidth !== undefined)
|
|
81
|
+
boxModel.border.top = flatStyle.borderTopWidth;
|
|
82
|
+
if (flatStyle.borderRightWidth !== undefined)
|
|
83
|
+
boxModel.border.right = flatStyle.borderRightWidth;
|
|
84
|
+
if (flatStyle.borderBottomWidth !== undefined)
|
|
85
|
+
boxModel.border.bottom = flatStyle.borderBottomWidth;
|
|
86
|
+
if (flatStyle.borderLeftWidth !== undefined)
|
|
87
|
+
boxModel.border.left = flatStyle.borderLeftWidth;
|
|
88
|
+
return boxModel;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Sanitizes props to make them serializable
|
|
92
|
+
*/
|
|
93
|
+
function sanitizeProps(props) {
|
|
94
|
+
const result = {};
|
|
95
|
+
for (const [key, value] of Object.entries(props)) {
|
|
96
|
+
// Skip internal props
|
|
97
|
+
if (key.startsWith('__'))
|
|
98
|
+
continue;
|
|
99
|
+
if (key === 'children')
|
|
100
|
+
continue;
|
|
101
|
+
// Handle different value types
|
|
102
|
+
if (typeof value === 'function') {
|
|
103
|
+
result[key] = '[Function]';
|
|
104
|
+
}
|
|
105
|
+
else if (typeof value === 'object' && value !== null) {
|
|
106
|
+
try {
|
|
107
|
+
// Test if serializable
|
|
108
|
+
JSON.stringify(value);
|
|
109
|
+
result[key] = value;
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
result[key] = '[Object]';
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
result[key] = value;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Generates a JSX-like preview of the element
|
|
123
|
+
*/
|
|
124
|
+
function generateJSXPreview(fiber) {
|
|
125
|
+
const name = (0, traversal_1.getDisplayName)(fiber);
|
|
126
|
+
const props = (0, traversal_1.getCleanProps)(fiber);
|
|
127
|
+
const sanitized = sanitizeProps(props);
|
|
128
|
+
// Build prop string
|
|
129
|
+
const propStrings = [];
|
|
130
|
+
for (const [key, value] of Object.entries(sanitized)) {
|
|
131
|
+
if (typeof value === 'string') {
|
|
132
|
+
propStrings.push(`${key}="${value}"`);
|
|
133
|
+
}
|
|
134
|
+
else if (typeof value === 'number' || typeof value === 'boolean') {
|
|
135
|
+
propStrings.push(`${key}={${value}}`);
|
|
136
|
+
}
|
|
137
|
+
else if (value === '[Function]') {
|
|
138
|
+
propStrings.push(`${key}={[Function]}`);
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
propStrings.push(`${key}={...}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (propStrings.length === 0) {
|
|
145
|
+
return `<${name} />`;
|
|
146
|
+
}
|
|
147
|
+
if (propStrings.length <= 2) {
|
|
148
|
+
return `<${name} ${propStrings.join(' ')} />`;
|
|
149
|
+
}
|
|
150
|
+
// Multi-line for many props
|
|
151
|
+
return `<${name}\n ${propStrings.join('\n ')}\n/>`;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Extracts source location by traversing UP from a fiber to find any source info.
|
|
155
|
+
* The Babel plugin injects __rnAve on JSX elements, but those become composite fibers
|
|
156
|
+
* which then render host fibers. So we traverse up to find the best source.
|
|
157
|
+
*/
|
|
158
|
+
function getSourceFromFiberTree(startFiber) {
|
|
159
|
+
let current = startFiber;
|
|
160
|
+
let firstSource = null;
|
|
161
|
+
console.log(' [source-walk] Starting walk from:', (0, traversal_1.getDisplayName)(startFiber));
|
|
162
|
+
// Traverse up the fiber tree to find a fiber with source info
|
|
163
|
+
while (current) {
|
|
164
|
+
const name = (0, traversal_1.getDisplayName)(current);
|
|
165
|
+
const source = (0, source_1.getSourceLocation)(current);
|
|
166
|
+
// Debug: Log props of every fiber we visit
|
|
167
|
+
const props = current.memoizedProps || {};
|
|
168
|
+
const propKeys = Object.keys(props);
|
|
169
|
+
if (propKeys.some(k => k.startsWith('__rn'))) {
|
|
170
|
+
const metaKey = propKeys.find(k => k.startsWith('__rn'));
|
|
171
|
+
console.log(` [source-walk] Fiber ${name} has metadata prop ${metaKey}:`, JSON.stringify(props[metaKey]));
|
|
172
|
+
}
|
|
173
|
+
if (source && source.file) {
|
|
174
|
+
console.log(` [source-walk] Fiber ${name} has source:`, source.file);
|
|
175
|
+
// Save the first source we find as a fallback
|
|
176
|
+
if (!firstSource) {
|
|
177
|
+
firstSource = source;
|
|
178
|
+
}
|
|
179
|
+
// If it's NOT node_modules, this is definitely what we want
|
|
180
|
+
if (!source.file.includes('node_modules')) {
|
|
181
|
+
console.log(' [source-walk] FOUND USER SOURCE:', source.file);
|
|
182
|
+
return source;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
current = current.return;
|
|
186
|
+
}
|
|
187
|
+
if (firstSource) {
|
|
188
|
+
console.log(' [source-walk] Falling back to node_modules source:', firstSource.file);
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
console.log(' [source-walk] No source info found in fiber tree');
|
|
192
|
+
}
|
|
193
|
+
return firstSource;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Extracts full context from a fiber node
|
|
197
|
+
* @param fiber - The composite fiber (user's component)
|
|
198
|
+
* @param hostFiber - Optional host fiber (the actual tapped element) to extract source from __rnAve prop
|
|
199
|
+
*/
|
|
200
|
+
function extractContext(fiber, hostFiber) {
|
|
201
|
+
console.log('\n🔍 [rn-ave] extractContext called');
|
|
202
|
+
console.log(' fiber type:', fiber.type);
|
|
203
|
+
console.log(' fiber tag:', fiber.tag);
|
|
204
|
+
console.log(' hostFiber provided:', !!hostFiber);
|
|
205
|
+
const hierarchy = (0, traversal_1.getComponentHierarchy)(fiber);
|
|
206
|
+
// Sanitize props for all components in the hierarchy
|
|
207
|
+
const sanitizedHierarchy = hierarchy.map(item => ({
|
|
208
|
+
...item,
|
|
209
|
+
props: sanitizeProps(item.props)
|
|
210
|
+
}));
|
|
211
|
+
// Try to get source from fiber tree's __rnAve prop first (Babel plugin)
|
|
212
|
+
// Then fall back to composite fiber's source (recursive)
|
|
213
|
+
let source = hostFiber ? getSourceFromFiberTree(hostFiber) : null;
|
|
214
|
+
if (!source) {
|
|
215
|
+
source = (0, source_1.getSourceLocationRecursive)(fiber);
|
|
216
|
+
}
|
|
217
|
+
const props = (0, traversal_1.getCleanProps)(fiber);
|
|
218
|
+
const safeProps = sanitizeProps(props);
|
|
219
|
+
const jsxPreview = generateJSXPreview(fiber);
|
|
220
|
+
// Get box model from host fiber's style (the actual rendered element)
|
|
221
|
+
const styleSource = hostFiber || fiber;
|
|
222
|
+
const boxModel = extractBoxModel(styleSource.memoizedProps?.style);
|
|
223
|
+
console.log(' extracted source:', source);
|
|
224
|
+
console.log(' componentName:', (0, traversal_1.getDisplayName)(fiber));
|
|
225
|
+
console.log(' boxModel:', boxModel);
|
|
226
|
+
return {
|
|
227
|
+
componentName: (0, traversal_1.getDisplayName)(fiber),
|
|
228
|
+
componentHierarchy: sanitizedHierarchy,
|
|
229
|
+
props: safeProps,
|
|
230
|
+
source,
|
|
231
|
+
jsxPreview,
|
|
232
|
+
timestamp: Date.now(),
|
|
233
|
+
boxModel,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ExtractedContext } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Formats extracted context as markdown for AI agents
|
|
4
|
+
*/
|
|
5
|
+
export declare function formatForAgent(context: ExtractedContext): string;
|
|
6
|
+
/**
|
|
7
|
+
* Formats context as compact single-line (for clipboard)
|
|
8
|
+
*/
|
|
9
|
+
export declare function formatCompact(context: ExtractedContext): string;
|
|
10
|
+
/**
|
|
11
|
+
* Formats context for terminal/console output
|
|
12
|
+
*/
|
|
13
|
+
export declare function formatForTerminal(context: ExtractedContext): string;
|