@thehoneyjar/sigil-hud 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.
@@ -0,0 +1,273 @@
1
+ /**
2
+ * Physics Analysis Component
3
+ *
4
+ * Displays detected effect type and physics values for the current context.
5
+ */
6
+
7
+ import type { EffectType, ComplianceResult } from '../types'
8
+
9
+ /**
10
+ * Props for PhysicsAnalysis
11
+ */
12
+ export interface PhysicsAnalysisProps {
13
+ /** Detected effect type */
14
+ effect: EffectType | null
15
+ /** Physics compliance result */
16
+ compliance?: ComplianceResult | null
17
+ /** Whether analysis is loading */
18
+ isLoading?: boolean
19
+ /** Custom class name */
20
+ className?: string
21
+ }
22
+
23
+ /**
24
+ * Display label for effect types
25
+ */
26
+ const effectLabels: Record<EffectType, string> = {
27
+ financial: 'Financial',
28
+ destructive: 'Destructive',
29
+ 'soft-delete': 'Soft Delete',
30
+ standard: 'Standard',
31
+ local: 'Local State',
32
+ navigation: 'Navigation',
33
+ query: 'Query',
34
+ }
35
+
36
+ /**
37
+ * Color for effect types
38
+ */
39
+ const effectColors: Record<EffectType, string> = {
40
+ financial: '#ef4444', // red
41
+ destructive: '#f97316', // orange
42
+ 'soft-delete': '#eab308', // yellow
43
+ standard: '#22c55e', // green
44
+ local: '#3b82f6', // blue
45
+ navigation: '#8b5cf6', // purple
46
+ query: '#06b6d4', // cyan
47
+ }
48
+
49
+ /**
50
+ * Physics Analysis component
51
+ */
52
+ export function PhysicsAnalysis({
53
+ effect,
54
+ compliance,
55
+ isLoading = false,
56
+ className = '',
57
+ }: PhysicsAnalysisProps) {
58
+ if (isLoading) {
59
+ return (
60
+ <div className={className} style={styles.container}>
61
+ <div style={styles.header}>
62
+ <span style={styles.title}>Physics</span>
63
+ <span style={styles.loading}>Analyzing...</span>
64
+ </div>
65
+ </div>
66
+ )
67
+ }
68
+
69
+ if (!effect) {
70
+ return (
71
+ <div className={className} style={styles.container}>
72
+ <div style={styles.header}>
73
+ <span style={styles.title}>Physics</span>
74
+ </div>
75
+ <div style={styles.empty}>
76
+ No component selected. Select a component to view physics analysis.
77
+ </div>
78
+ </div>
79
+ )
80
+ }
81
+
82
+ const color = effectColors[effect]
83
+ const behavioral = compliance?.behavioral
84
+ const animation = compliance?.animation
85
+ const material = compliance?.material
86
+
87
+ return (
88
+ <div className={className} style={styles.container}>
89
+ {/* Effect Badge */}
90
+ <div style={styles.effectRow}>
91
+ <span
92
+ style={{
93
+ ...styles.effectBadge,
94
+ backgroundColor: `${color}20`,
95
+ borderColor: `${color}50`,
96
+ color,
97
+ }}
98
+ >
99
+ {effectLabels[effect]}
100
+ </span>
101
+ {behavioral && !behavioral.compliant && (
102
+ <span style={styles.warningBadge}>⚠ Non-compliant</span>
103
+ )}
104
+ </div>
105
+
106
+ {/* Physics Grid */}
107
+ {compliance && (
108
+ <div style={styles.grid}>
109
+ {/* Behavioral */}
110
+ <div style={styles.section}>
111
+ <span style={styles.sectionLabel}>Behavioral</span>
112
+ <div style={styles.values}>
113
+ <span style={styles.value}>
114
+ <span style={styles.valueLabel}>Sync:</span>
115
+ <span style={getValueStyle(behavioral?.compliant)}>
116
+ {behavioral?.sync ?? 'unknown'}
117
+ </span>
118
+ </span>
119
+ <span style={styles.value}>
120
+ <span style={styles.valueLabel}>Timing:</span>
121
+ <span style={getValueStyle(behavioral?.compliant)}>
122
+ {behavioral?.timing ? `${behavioral.timing}ms` : 'unknown'}
123
+ </span>
124
+ </span>
125
+ <span style={styles.value}>
126
+ <span style={styles.valueLabel}>Confirm:</span>
127
+ <span style={getValueStyle(behavioral?.compliant)}>
128
+ {behavioral?.confirmation ? 'yes' : 'no'}
129
+ </span>
130
+ </span>
131
+ </div>
132
+ </div>
133
+
134
+ {/* Animation */}
135
+ <div style={styles.section}>
136
+ <span style={styles.sectionLabel}>Animation</span>
137
+ <div style={styles.values}>
138
+ <span style={styles.value}>
139
+ <span style={styles.valueLabel}>Easing:</span>
140
+ <span style={getValueStyle(animation?.compliant)}>
141
+ {animation?.easing ?? 'unknown'}
142
+ </span>
143
+ </span>
144
+ <span style={styles.value}>
145
+ <span style={styles.valueLabel}>Duration:</span>
146
+ <span style={getValueStyle(animation?.compliant)}>
147
+ {animation?.duration ? `${animation.duration}ms` : 'unknown'}
148
+ </span>
149
+ </span>
150
+ </div>
151
+ </div>
152
+
153
+ {/* Material */}
154
+ <div style={styles.section}>
155
+ <span style={styles.sectionLabel}>Material</span>
156
+ <div style={styles.values}>
157
+ <span style={styles.value}>
158
+ <span style={styles.valueLabel}>Surface:</span>
159
+ <span style={getValueStyle(material?.compliant)}>
160
+ {material?.surface ?? 'unknown'}
161
+ </span>
162
+ </span>
163
+ <span style={styles.value}>
164
+ <span style={styles.valueLabel}>Shadow:</span>
165
+ <span style={getValueStyle(material?.compliant)}>
166
+ {material?.shadow ?? 'unknown'}
167
+ </span>
168
+ </span>
169
+ </div>
170
+ </div>
171
+ </div>
172
+ )}
173
+ </div>
174
+ )
175
+ }
176
+
177
+ /**
178
+ * Get value text style based on compliance
179
+ */
180
+ function getValueStyle(compliant?: boolean): React.CSSProperties {
181
+ if (compliant === undefined) return styles.valueText
182
+ return compliant
183
+ ? { ...styles.valueText, color: '#22c55e' }
184
+ : { ...styles.valueText, color: '#ef4444' }
185
+ }
186
+
187
+ /**
188
+ * Styles
189
+ */
190
+ const styles: Record<string, React.CSSProperties> = {
191
+ container: {
192
+ padding: '12px',
193
+ backgroundColor: 'rgba(0, 0, 0, 0.2)',
194
+ borderRadius: '8px',
195
+ border: '1px solid rgba(255, 255, 255, 0.05)',
196
+ },
197
+ header: {
198
+ display: 'flex',
199
+ justifyContent: 'space-between',
200
+ alignItems: 'center',
201
+ marginBottom: '8px',
202
+ },
203
+ title: {
204
+ fontSize: '11px',
205
+ fontWeight: 600,
206
+ color: '#888',
207
+ textTransform: 'uppercase',
208
+ letterSpacing: '0.5px',
209
+ },
210
+ loading: {
211
+ fontSize: '10px',
212
+ color: '#666',
213
+ },
214
+ empty: {
215
+ fontSize: '11px',
216
+ color: '#666',
217
+ fontStyle: 'italic',
218
+ },
219
+ effectRow: {
220
+ display: 'flex',
221
+ alignItems: 'center',
222
+ gap: '8px',
223
+ marginBottom: '12px',
224
+ },
225
+ effectBadge: {
226
+ padding: '4px 8px',
227
+ borderRadius: '4px',
228
+ fontSize: '11px',
229
+ fontWeight: 600,
230
+ border: '1px solid',
231
+ },
232
+ warningBadge: {
233
+ padding: '2px 6px',
234
+ borderRadius: '4px',
235
+ fontSize: '10px',
236
+ backgroundColor: 'rgba(239, 68, 68, 0.1)',
237
+ color: '#ef4444',
238
+ border: '1px solid rgba(239, 68, 68, 0.2)',
239
+ },
240
+ grid: {
241
+ display: 'flex',
242
+ flexDirection: 'column',
243
+ gap: '8px',
244
+ },
245
+ section: {
246
+ display: 'flex',
247
+ flexDirection: 'column',
248
+ gap: '4px',
249
+ },
250
+ sectionLabel: {
251
+ fontSize: '10px',
252
+ fontWeight: 600,
253
+ color: '#666',
254
+ textTransform: 'uppercase',
255
+ letterSpacing: '0.3px',
256
+ },
257
+ values: {
258
+ display: 'flex',
259
+ flexWrap: 'wrap',
260
+ gap: '8px',
261
+ },
262
+ value: {
263
+ display: 'flex',
264
+ gap: '4px',
265
+ fontSize: '11px',
266
+ },
267
+ valueLabel: {
268
+ color: '#888',
269
+ },
270
+ valueText: {
271
+ color: '#fff',
272
+ },
273
+ }
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Simulation Panel Component
3
+ *
4
+ * Panel for transaction simulation controls.
5
+ */
6
+
7
+ import { useHud } from '../providers/HudProvider'
8
+
9
+ /**
10
+ * Props for SimulationPanel
11
+ */
12
+ export interface SimulationPanelProps {
13
+ /** Custom class name */
14
+ className?: string
15
+ }
16
+
17
+ /**
18
+ * Simulation panel for transaction dry-runs
19
+ */
20
+ export function SimulationPanel({ className = '' }: SimulationPanelProps) {
21
+ const { simulationService, forkService, activePanel } = useHud()
22
+
23
+ // Don't render if not the active panel
24
+ if (activePanel !== 'simulation') return null
25
+
26
+ // Show message if simulation service not available
27
+ if (!simulationService) {
28
+ return (
29
+ <div className={className} style={{ color: '#666' }}>
30
+ <p>Simulation service not available.</p>
31
+ <p style={{ fontSize: '10px', marginTop: '8px' }}>
32
+ Install @sigil/simulation to enable transaction simulation.
33
+ </p>
34
+ </div>
35
+ )
36
+ }
37
+
38
+ const forkState = forkService?.getState()
39
+ const forkActive = forkState?.active ?? false
40
+
41
+ return (
42
+ <div className={className}>
43
+ {/* Fork Status */}
44
+ <div style={{ marginBottom: '16px' }}>
45
+ <div
46
+ style={{
47
+ display: 'flex',
48
+ alignItems: 'center',
49
+ gap: '8px',
50
+ marginBottom: '8px',
51
+ }}
52
+ >
53
+ <div
54
+ style={{
55
+ width: '8px',
56
+ height: '8px',
57
+ borderRadius: '50%',
58
+ backgroundColor: forkActive ? '#10b981' : '#666',
59
+ }}
60
+ />
61
+ <span style={{ color: forkActive ? '#10b981' : '#888' }}>
62
+ {forkActive ? 'Fork Active' : 'No Fork'}
63
+ </span>
64
+ </div>
65
+
66
+ {forkState && forkActive && (
67
+ <div
68
+ style={{
69
+ padding: '8px',
70
+ backgroundColor: 'rgba(255, 255, 255, 0.02)',
71
+ borderRadius: '4px',
72
+ fontSize: '10px',
73
+ }}
74
+ >
75
+ <div style={{ color: '#888', marginBottom: '4px' }}>
76
+ Chain: {forkState.chainId}
77
+ </div>
78
+ <div style={{ color: '#888', marginBottom: '4px' }}>
79
+ Block: {forkState.blockNumber?.toString()}
80
+ </div>
81
+ <div style={{ color: '#888' }}>
82
+ Snapshots: {forkState.snapshotCount}
83
+ </div>
84
+ </div>
85
+ )}
86
+ </div>
87
+
88
+ {/* Simulation Info */}
89
+ <div
90
+ style={{
91
+ padding: '12px',
92
+ backgroundColor: 'rgba(59, 130, 246, 0.05)',
93
+ border: '1px solid rgba(59, 130, 246, 0.2)',
94
+ borderRadius: '4px',
95
+ }}
96
+ >
97
+ <div style={{ color: '#3b82f6', fontSize: '11px', marginBottom: '8px' }}>
98
+ Transaction Simulation
99
+ </div>
100
+ <div style={{ color: '#888', fontSize: '10px', lineHeight: 1.6 }}>
101
+ Simulate transactions before sending them on-chain. View gas estimates,
102
+ balance changes, and potential revert reasons.
103
+ </div>
104
+ <div
105
+ style={{
106
+ marginTop: '12px',
107
+ padding: '8px',
108
+ backgroundColor: 'rgba(0, 0, 0, 0.2)',
109
+ borderRadius: '4px',
110
+ fontSize: '10px',
111
+ color: '#666',
112
+ }}
113
+ >
114
+ To simulate a transaction, use the simulation service programmatically:
115
+ <pre
116
+ style={{
117
+ marginTop: '8px',
118
+ fontFamily: 'ui-monospace, monospace',
119
+ color: '#10b981',
120
+ }}
121
+ >
122
+ {`simulationService.simulate({
123
+ from: '0x...',
124
+ to: '0x...',
125
+ value: 1000000n,
126
+ data: '0x...'
127
+ })`}
128
+ </pre>
129
+ </div>
130
+ </div>
131
+
132
+ {/* Features List */}
133
+ <div style={{ marginTop: '16px' }}>
134
+ <label
135
+ style={{
136
+ display: 'block',
137
+ color: '#888',
138
+ fontSize: '10px',
139
+ marginBottom: '8px',
140
+ }}
141
+ >
142
+ Simulation Features
143
+ </label>
144
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
145
+ {[
146
+ { icon: '⛽', label: 'Gas estimation' },
147
+ { icon: '💰', label: 'Balance changes' },
148
+ { icon: '📝', label: 'State changes' },
149
+ { icon: '❌', label: 'Revert reasons' },
150
+ { icon: '📊', label: 'Event logs' },
151
+ ].map((feature) => (
152
+ <div
153
+ key={feature.label}
154
+ style={{
155
+ display: 'flex',
156
+ alignItems: 'center',
157
+ gap: '8px',
158
+ padding: '6px 8px',
159
+ backgroundColor: 'rgba(255, 255, 255, 0.02)',
160
+ borderRadius: '4px',
161
+ fontSize: '11px',
162
+ color: '#888',
163
+ }}
164
+ >
165
+ <span>{feature.icon}</span>
166
+ <span>{feature.label}</span>
167
+ </div>
168
+ ))}
169
+ </div>
170
+ </div>
171
+ </div>
172
+ )
173
+ }
@@ -0,0 +1,238 @@
1
+ /**
2
+ * State Comparison Component
3
+ *
4
+ * Compare state between real and impersonated addresses.
5
+ */
6
+
7
+ import { useHud } from '../providers/HudProvider'
8
+ import { DEFAULT_LENS_STATE } from '../types'
9
+
10
+ /**
11
+ * Props for StateComparison
12
+ */
13
+ export interface StateComparisonProps {
14
+ /** Custom class name */
15
+ className?: string
16
+ }
17
+
18
+ /**
19
+ * State comparison panel
20
+ */
21
+ export function StateComparison({ className = '' }: StateComparisonProps) {
22
+ const { lensService, forkService, activePanel } = useHud()
23
+
24
+ // Don't render if not the active panel
25
+ if (activePanel !== 'state') return null
26
+
27
+ const lensState = lensService?.getState() ?? DEFAULT_LENS_STATE
28
+ const forkState = forkService?.getState()
29
+ const isImpersonating = lensState.enabled && lensState.impersonatedAddress !== null
30
+
31
+ return (
32
+ <div className={className}>
33
+ {/* Addresses */}
34
+ <div style={{ marginBottom: '16px' }}>
35
+ <label
36
+ style={{
37
+ display: 'block',
38
+ color: '#888',
39
+ fontSize: '10px',
40
+ marginBottom: '8px',
41
+ }}
42
+ >
43
+ Address Context
44
+ </label>
45
+
46
+ <div
47
+ style={{
48
+ display: 'flex',
49
+ flexDirection: 'column',
50
+ gap: '8px',
51
+ }}
52
+ >
53
+ {/* Real Address */}
54
+ <div
55
+ style={{
56
+ padding: '8px',
57
+ backgroundColor: 'rgba(255, 255, 255, 0.02)',
58
+ borderRadius: '4px',
59
+ border: !isImpersonating
60
+ ? '1px solid rgba(16, 185, 129, 0.3)'
61
+ : '1px solid rgba(255, 255, 255, 0.05)',
62
+ }}
63
+ >
64
+ <div
65
+ style={{
66
+ display: 'flex',
67
+ justifyContent: 'space-between',
68
+ alignItems: 'center',
69
+ marginBottom: '4px',
70
+ }}
71
+ >
72
+ <span style={{ color: '#888', fontSize: '10px' }}>Real</span>
73
+ {!isImpersonating && (
74
+ <span
75
+ style={{
76
+ fontSize: '9px',
77
+ padding: '2px 6px',
78
+ backgroundColor: 'rgba(16, 185, 129, 0.2)',
79
+ borderRadius: '4px',
80
+ color: '#10b981',
81
+ }}
82
+ >
83
+ Active
84
+ </span>
85
+ )}
86
+ </div>
87
+ <code
88
+ style={{
89
+ fontSize: '10px',
90
+ color: lensState.realAddress ? '#fff' : '#666',
91
+ wordBreak: 'break-all',
92
+ }}
93
+ >
94
+ {lensState.realAddress ?? 'Not connected'}
95
+ </code>
96
+ </div>
97
+
98
+ {/* Impersonated Address */}
99
+ <div
100
+ style={{
101
+ padding: '8px',
102
+ backgroundColor: 'rgba(255, 255, 255, 0.02)',
103
+ borderRadius: '4px',
104
+ border: isImpersonating
105
+ ? '1px solid rgba(16, 185, 129, 0.3)'
106
+ : '1px solid rgba(255, 255, 255, 0.05)',
107
+ }}
108
+ >
109
+ <div
110
+ style={{
111
+ display: 'flex',
112
+ justifyContent: 'space-between',
113
+ alignItems: 'center',
114
+ marginBottom: '4px',
115
+ }}
116
+ >
117
+ <span style={{ color: '#888', fontSize: '10px' }}>Impersonated</span>
118
+ {isImpersonating && (
119
+ <span
120
+ style={{
121
+ fontSize: '9px',
122
+ padding: '2px 6px',
123
+ backgroundColor: 'rgba(16, 185, 129, 0.2)',
124
+ borderRadius: '4px',
125
+ color: '#10b981',
126
+ }}
127
+ >
128
+ Active
129
+ </span>
130
+ )}
131
+ </div>
132
+ <code
133
+ style={{
134
+ fontSize: '10px',
135
+ color: lensState.impersonatedAddress ? '#fff' : '#666',
136
+ wordBreak: 'break-all',
137
+ }}
138
+ >
139
+ {lensState.impersonatedAddress ?? 'None'}
140
+ </code>
141
+ </div>
142
+ </div>
143
+ </div>
144
+
145
+ {/* Fork State */}
146
+ {forkState && (
147
+ <div style={{ marginBottom: '16px' }}>
148
+ <label
149
+ style={{
150
+ display: 'block',
151
+ color: '#888',
152
+ fontSize: '10px',
153
+ marginBottom: '8px',
154
+ }}
155
+ >
156
+ Fork State
157
+ </label>
158
+
159
+ <div
160
+ style={{
161
+ padding: '8px',
162
+ backgroundColor: 'rgba(255, 255, 255, 0.02)',
163
+ borderRadius: '4px',
164
+ }}
165
+ >
166
+ <div
167
+ style={{
168
+ display: 'grid',
169
+ gridTemplateColumns: '1fr 1fr',
170
+ gap: '8px',
171
+ fontSize: '10px',
172
+ }}
173
+ >
174
+ <div>
175
+ <span style={{ color: '#666' }}>Status: </span>
176
+ <span style={{ color: forkState.active ? '#10b981' : '#888' }}>
177
+ {forkState.active ? 'Active' : 'Inactive'}
178
+ </span>
179
+ </div>
180
+ <div>
181
+ <span style={{ color: '#666' }}>Chain: </span>
182
+ <span style={{ color: '#fff' }}>
183
+ {forkState.chainId ?? '—'}
184
+ </span>
185
+ </div>
186
+ <div>
187
+ <span style={{ color: '#666' }}>Block: </span>
188
+ <span style={{ color: '#fff' }}>
189
+ {forkState.blockNumber?.toString() ?? '—'}
190
+ </span>
191
+ </div>
192
+ <div>
193
+ <span style={{ color: '#666' }}>Snapshots: </span>
194
+ <span style={{ color: '#fff' }}>{forkState.snapshotCount}</span>
195
+ </div>
196
+ </div>
197
+
198
+ {forkState.rpcUrl && (
199
+ <div style={{ marginTop: '8px' }}>
200
+ <span style={{ color: '#666', fontSize: '10px' }}>RPC: </span>
201
+ <code
202
+ style={{
203
+ fontSize: '9px',
204
+ color: '#888',
205
+ wordBreak: 'break-all',
206
+ }}
207
+ >
208
+ {forkState.rpcUrl}
209
+ </code>
210
+ </div>
211
+ )}
212
+ </div>
213
+ </div>
214
+ )}
215
+
216
+ {/* State Comparison Info */}
217
+ <div
218
+ style={{
219
+ padding: '12px',
220
+ backgroundColor: 'rgba(251, 191, 36, 0.05)',
221
+ border: '1px solid rgba(251, 191, 36, 0.2)',
222
+ borderRadius: '4px',
223
+ }}
224
+ >
225
+ <div
226
+ style={{ color: '#fbbf24', fontSize: '11px', marginBottom: '8px' }}
227
+ >
228
+ State Comparison
229
+ </div>
230
+ <div style={{ color: '#888', fontSize: '10px', lineHeight: 1.6 }}>
231
+ When impersonating, reads use the impersonated address while writes
232
+ still use your real wallet. This lets you test how the UI looks for
233
+ different users without affecting their funds.
234
+ </div>
235
+ </div>
236
+ </div>
237
+ )
238
+ }