codecruise 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/LICENSE +21 -0
- package/README.md +111 -0
- package/bin/codecruise.js +68 -0
- package/config/CLAUDE.md +107 -0
- package/config/agents/analyst.md +48 -0
- package/config/agents/architect-reviewer.md +161 -0
- package/config/agents/architect.md +119 -0
- package/config/agents/critic.md +63 -0
- package/config/agents/developer.md +96 -0
- package/config/agents/devops.md +81 -0
- package/config/agents/orchestrator.md +91 -0
- package/config/agents/planner.md +139 -0
- package/config/agents/retro.md +52 -0
- package/config/agents/reviewer.md +101 -0
- package/config/agents/security-reviewer.md +57 -0
- package/config/agents/stack/expo/AGENT.md +473 -0
- package/config/agents/stack/expo/rules/critical.md +427 -0
- package/config/agents/stack/expo/rules/native.md +455 -0
- package/config/agents/stack/expo/rules/navigation.md +445 -0
- package/config/agents/stack/expo/rules/performance.md +415 -0
- package/config/agents/stack/fastify/AGENT.md +397 -0
- package/config/agents/stack/fastify/rules/api-design.md +283 -0
- package/config/agents/stack/fastify/rules/critical.md +232 -0
- package/config/agents/stack/fastify/rules/queues.md +303 -0
- package/config/agents/stack/fastify/rules/security.md +384 -0
- package/config/agents/stack/index.yaml +48 -0
- package/config/agents/stack/nextjs/AGENT.md +421 -0
- package/config/agents/stack/nextjs/rules/components.md +413 -0
- package/config/agents/stack/nextjs/rules/critical.md +391 -0
- package/config/agents/stack/nextjs/rules/performance.md +403 -0
- package/config/agents/stack/nextjs/rules/styling.md +334 -0
- package/config/agents/stack/shared-ts/AGENT.md +384 -0
- package/config/agents/stack/shared-ts/rules/critical.md +315 -0
- package/config/agents/stack/shared-ts/rules/patterns.md +384 -0
- package/config/agents/stack/shared-ts/rules/zod.md +427 -0
- package/config/agents/tester.md +79 -0
- package/config/commands/architect-discuss.md +366 -0
- package/config/commands/architect-list.md +160 -0
- package/config/commands/architect-review.md +111 -0
- package/config/commands/architect.md +118 -0
- package/config/commands/compact.md +118 -0
- package/config/commands/companion.md +279 -0
- package/config/commands/dashboard.md +152 -0
- package/config/commands/doctor.md +227 -0
- package/config/commands/dogfood-report.md +101 -0
- package/config/commands/flags/run-autonomous.md +110 -0
- package/config/commands/flags/run-pause.md +80 -0
- package/config/commands/ingest.md +173 -0
- package/config/commands/init.md +128 -0
- package/config/commands/metrics.md +87 -0
- package/config/commands/parallel.md +320 -0
- package/config/commands/pause.md +55 -0
- package/config/commands/plan-review.md +130 -0
- package/config/commands/plan.md +216 -0
- package/config/commands/production-check.md +308 -0
- package/config/commands/refine.md +323 -0
- package/config/commands/resume.md +72 -0
- package/config/commands/retro.md +121 -0
- package/config/commands/retry.md +75 -0
- package/config/commands/role.md +310 -0
- package/config/commands/run.md +417 -0
- package/config/commands/scope.md +85 -0
- package/config/commands/setup-permissions.md +104 -0
- package/config/commands/skip.md +75 -0
- package/config/commands/spec-forge.md +213 -0
- package/config/commands/spec-help.md +194 -0
- package/config/commands/spec-patch.md +342 -0
- package/config/commands/spec-resolve.md +110 -0
- package/config/commands/spec-review.md +153 -0
- package/config/commands/status.md +114 -0
- package/config/commands/sync.md +131 -0
- package/config/commands/task.md +138 -0
- package/config/commands/verify.md +124 -0
- package/config/hooks/README.md +632 -0
- package/config/hooks/activity-log.sh +187 -0
- package/config/hooks/anti-rationalize.sh +52 -0
- package/config/hooks/capture-verification.sh +112 -0
- package/config/hooks/collect-metrics.sh +135 -0
- package/config/hooks/enforce-file-scope.sh +75 -0
- package/config/hooks/enforce-state-machine.sh +161 -0
- package/config/hooks/enforce-tdd.sh +180 -0
- package/config/hooks/format.sh +40 -0
- package/config/hooks/lib/activity-helpers.sh +162 -0
- package/config/hooks/lib/read-settings.sh +71 -0
- package/config/hooks/load-context-skills.sh +95 -0
- package/config/hooks/notify.sh +81 -0
- package/config/hooks/pre-commit.sample +35 -0
- package/config/hooks/protect-files.sh +63 -0
- package/config/hooks/track-agents.sh +41 -0
- package/config/hooks/track-commands.sh +37 -0
- package/config/hooks/track-enforcement.sh +44 -0
- package/config/hooks/track-ooda.sh +77 -0
- package/config/hooks/validate-commit-msg.sh +35 -0
- package/config/hooks/validate-plan.sh +213 -0
- package/config/hooks/verify-criteria.sh +46 -0
- package/config/hooks/verify-todo-completion.sh +140 -0
- package/config/rules/comments.md +25 -0
- package/config/rules/decision-rules.md +308 -0
- package/config/rules/hygiene.md +247 -0
- package/config/rules/pattern-detection.md +372 -0
- package/config/rules/profiles.md +193 -0
- package/config/rules/recovery.md +83 -0
- package/config/rules/scope-detection.md +213 -0
- package/config/rules/standards.md +127 -0
- package/config/rules/workflow.md +121 -0
- package/config/schemas.md +767 -0
- package/config/settings.json +195 -0
- package/config/skills/backend/SKILL.md +734 -0
- package/config/skills/database/SKILL.md +426 -0
- package/config/skills/frontend/SKILL.md +434 -0
- package/config/skills/git/SKILL.md +396 -0
- package/config/skills/index.yaml +36 -0
- package/config/skills/observability/SKILL.md +430 -0
- package/config/skills/package-dev/SKILL.md +498 -0
- package/config/skills/performance/SKILL.md +378 -0
- package/config/skills/resilience/SKILL.md +573 -0
- package/config/skills/testing/SKILL.md +398 -0
- package/config/skills/testing-patterns/SKILL.md +276 -0
- package/config/skills/typescript/SKILL.md +152 -0
- package/config/templates/CLAUDE.md +70 -0
- package/config/templates/README.md +117 -0
- package/config/templates/steering/adr-template.md +102 -0
- package/config/templates/steering/product.md +60 -0
- package/config/templates/steering/rfc-template.md +159 -0
- package/config/templates/steering/structure.md +146 -0
- package/config/templates/steering/tech.md +85 -0
- package/package.json +40 -0
- package/src/install.js +163 -0
- package/src/report.js +310 -0
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
# Performance Rules - React Native
|
|
2
|
+
|
|
3
|
+
Optimization patterns for smooth 60fps apps.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Lists
|
|
8
|
+
|
|
9
|
+
### PERF-001: Use FlashList for long lists
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
import { FlashList } from '@shopify/flash-list';
|
|
13
|
+
|
|
14
|
+
// GOOD - FlashList for 50+ items
|
|
15
|
+
<FlashList
|
|
16
|
+
data={items}
|
|
17
|
+
keyExtractor={(item) => item.id}
|
|
18
|
+
renderItem={({ item }) => <ItemCard item={item} />}
|
|
19
|
+
estimatedItemSize={80} // Required! Measure your items
|
|
20
|
+
contentContainerStyle={{ padding: 16 }}
|
|
21
|
+
/>
|
|
22
|
+
|
|
23
|
+
// FlatList OK for short lists
|
|
24
|
+
<FlatList
|
|
25
|
+
data={shortList} // < 50 items
|
|
26
|
+
renderItem={renderItem}
|
|
27
|
+
/>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### PERF-002: Optimize list items
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import { memo } from 'react';
|
|
34
|
+
|
|
35
|
+
// Memoize list items
|
|
36
|
+
const ItemCard = memo(function ItemCard({ item, onPress }: Props) {
|
|
37
|
+
return (
|
|
38
|
+
<Pressable onPress={() => onPress(item.id)}>
|
|
39
|
+
<Text>{item.title}</Text>
|
|
40
|
+
</Pressable>
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Stable callbacks
|
|
45
|
+
function ItemList() {
|
|
46
|
+
const handlePress = useCallback((id: string) => {
|
|
47
|
+
router.push(`/items/${id}`);
|
|
48
|
+
}, []);
|
|
49
|
+
|
|
50
|
+
const renderItem = useCallback(
|
|
51
|
+
({ item }) => <ItemCard item={item} onPress={handlePress} />,
|
|
52
|
+
[handlePress]
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<FlashList
|
|
57
|
+
data={items}
|
|
58
|
+
renderItem={renderItem}
|
|
59
|
+
estimatedItemSize={80}
|
|
60
|
+
/>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### PERF-003: List configuration
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
<FlashList
|
|
69
|
+
data={items}
|
|
70
|
+
renderItem={renderItem}
|
|
71
|
+
estimatedItemSize={80}
|
|
72
|
+
|
|
73
|
+
// Performance tweaks
|
|
74
|
+
removeClippedSubviews={true} // Unmount off-screen items
|
|
75
|
+
maxToRenderPerBatch={10} // Batch size
|
|
76
|
+
windowSize={5} // Render window (screens)
|
|
77
|
+
initialNumToRender={10} // Initial render count
|
|
78
|
+
|
|
79
|
+
// Smooth scrolling
|
|
80
|
+
maintainVisibleContentPosition={{
|
|
81
|
+
minIndexForVisible: 0,
|
|
82
|
+
}}
|
|
83
|
+
/>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Images
|
|
89
|
+
|
|
90
|
+
### PERF-004: Optimize images
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
import { Image } from 'expo-image';
|
|
94
|
+
|
|
95
|
+
// Use expo-image (better caching)
|
|
96
|
+
<Image
|
|
97
|
+
source={{ uri: imageUrl }}
|
|
98
|
+
style={{ width: 200, height: 200 }}
|
|
99
|
+
contentFit="cover"
|
|
100
|
+
placeholder={blurhash}
|
|
101
|
+
transition={200}
|
|
102
|
+
cachePolicy="memory-disk"
|
|
103
|
+
/>
|
|
104
|
+
|
|
105
|
+
// Resize on upload
|
|
106
|
+
import * as ImageManipulator from 'expo-image-manipulator';
|
|
107
|
+
|
|
108
|
+
async function resizeImage(uri: string) {
|
|
109
|
+
const result = await ImageManipulator.manipulateAsync(
|
|
110
|
+
uri,
|
|
111
|
+
[{ resize: { width: 800 } }],
|
|
112
|
+
{ compress: 0.8, format: ImageManipulator.SaveFormat.JPEG }
|
|
113
|
+
);
|
|
114
|
+
return result.uri;
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### PERF-005: Progressive loading
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
import { Image } from 'expo-image';
|
|
122
|
+
|
|
123
|
+
// With blurhash placeholder
|
|
124
|
+
<Image
|
|
125
|
+
source={{ uri: fullImageUrl }}
|
|
126
|
+
placeholder={item.blurhash}
|
|
127
|
+
contentFit="cover"
|
|
128
|
+
transition={300}
|
|
129
|
+
style={{ width: '100%', aspectRatio: 16/9 }}
|
|
130
|
+
/>
|
|
131
|
+
|
|
132
|
+
// Generate blurhash on backend, store with image
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Rendering
|
|
138
|
+
|
|
139
|
+
### PERF-006: Avoid re-renders
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
// BAD - Inline objects/functions
|
|
143
|
+
<View style={{ padding: 16 }}> {/* New object every render */}
|
|
144
|
+
<Pressable onPress={() => doSomething()}> {/* New function */}
|
|
145
|
+
|
|
146
|
+
// GOOD - Stable references
|
|
147
|
+
const styles = StyleSheet.create({ container: { padding: 16 } });
|
|
148
|
+
// Or with NativeWind
|
|
149
|
+
<View className="p-4">
|
|
150
|
+
|
|
151
|
+
const handlePress = useCallback(() => doSomething(), []);
|
|
152
|
+
<Pressable onPress={handlePress}>
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### PERF-007: Memoize computed values
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
// BAD - Computed every render
|
|
159
|
+
function UserList({ users, filter }) {
|
|
160
|
+
const filtered = users.filter(u => u.role === filter); // Every render!
|
|
161
|
+
return <FlashList data={filtered} ... />;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// GOOD - Memoized
|
|
165
|
+
function UserList({ users, filter }) {
|
|
166
|
+
const filtered = useMemo(
|
|
167
|
+
() => users.filter(u => u.role === filter),
|
|
168
|
+
[users, filter]
|
|
169
|
+
);
|
|
170
|
+
return <FlashList data={filtered} ... />;
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### PERF-008: Defer expensive operations
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
import { InteractionManager } from 'react-native';
|
|
178
|
+
|
|
179
|
+
// Run after animations complete
|
|
180
|
+
function Screen() {
|
|
181
|
+
useEffect(() => {
|
|
182
|
+
const task = InteractionManager.runAfterInteractions(() => {
|
|
183
|
+
// Expensive operation
|
|
184
|
+
loadHeavyData();
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
return () => task.cancel();
|
|
188
|
+
}, []);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Or use startTransition
|
|
192
|
+
import { startTransition } from 'react';
|
|
193
|
+
|
|
194
|
+
function SearchInput() {
|
|
195
|
+
const [query, setQuery] = useState('');
|
|
196
|
+
|
|
197
|
+
const handleChange = (text: string) => {
|
|
198
|
+
setQuery(text); // Urgent - update input
|
|
199
|
+
|
|
200
|
+
startTransition(() => {
|
|
201
|
+
// Non-urgent - can be interrupted
|
|
202
|
+
setSearchResults(search(text));
|
|
203
|
+
});
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## Animations
|
|
211
|
+
|
|
212
|
+
### PERF-009: Use native driver
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
import Animated, {
|
|
216
|
+
useAnimatedStyle,
|
|
217
|
+
withSpring,
|
|
218
|
+
useSharedValue,
|
|
219
|
+
} from 'react-native-reanimated';
|
|
220
|
+
|
|
221
|
+
function AnimatedCard() {
|
|
222
|
+
const scale = useSharedValue(1);
|
|
223
|
+
|
|
224
|
+
const animatedStyle = useAnimatedStyle(() => ({
|
|
225
|
+
transform: [{ scale: scale.value }],
|
|
226
|
+
}));
|
|
227
|
+
|
|
228
|
+
const handlePressIn = () => {
|
|
229
|
+
scale.value = withSpring(0.95);
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const handlePressOut = () => {
|
|
233
|
+
scale.value = withSpring(1);
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
return (
|
|
237
|
+
<Pressable onPressIn={handlePressIn} onPressOut={handlePressOut}>
|
|
238
|
+
<Animated.View style={animatedStyle}>
|
|
239
|
+
<Text>Card</Text>
|
|
240
|
+
</Animated.View>
|
|
241
|
+
</Pressable>
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### PERF-010: Avoid layout animations
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
// BAD - Triggers layout (slow)
|
|
250
|
+
const animatedStyle = useAnimatedStyle(() => ({
|
|
251
|
+
width: width.value, // Layout property
|
|
252
|
+
height: height.value, // Layout property
|
|
253
|
+
marginTop: margin.value, // Layout property
|
|
254
|
+
}));
|
|
255
|
+
|
|
256
|
+
// GOOD - Transform only (fast, native thread)
|
|
257
|
+
const animatedStyle = useAnimatedStyle(() => ({
|
|
258
|
+
transform: [
|
|
259
|
+
{ translateX: x.value },
|
|
260
|
+
{ translateY: y.value },
|
|
261
|
+
{ scale: scale.value },
|
|
262
|
+
{ rotate: `${rotation.value}deg` },
|
|
263
|
+
],
|
|
264
|
+
opacity: opacity.value,
|
|
265
|
+
}));
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## Data Loading
|
|
271
|
+
|
|
272
|
+
### PERF-011: Prefetch data
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
import { useQueryClient } from '@tanstack/react-query';
|
|
276
|
+
import { Link } from 'expo-router';
|
|
277
|
+
|
|
278
|
+
function UserListItem({ user }) {
|
|
279
|
+
const queryClient = useQueryClient();
|
|
280
|
+
|
|
281
|
+
// Prefetch on press-in (before navigation)
|
|
282
|
+
const handlePressIn = () => {
|
|
283
|
+
queryClient.prefetchQuery({
|
|
284
|
+
queryKey: ['user', user.id],
|
|
285
|
+
queryFn: () => fetchUser(user.id),
|
|
286
|
+
});
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
return (
|
|
290
|
+
<Link href={`/users/${user.id}`} asChild>
|
|
291
|
+
<Pressable onPressIn={handlePressIn}>
|
|
292
|
+
<Text>{user.name}</Text>
|
|
293
|
+
</Pressable>
|
|
294
|
+
</Link>
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### PERF-012: Pagination
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
import { useInfiniteQuery } from '@tanstack/react-query';
|
|
303
|
+
|
|
304
|
+
function usePaginatedPosts() {
|
|
305
|
+
return useInfiniteQuery({
|
|
306
|
+
queryKey: ['posts'],
|
|
307
|
+
queryFn: ({ pageParam = 1 }) => fetchPosts(pageParam),
|
|
308
|
+
getNextPageParam: (lastPage) => lastPage.nextPage,
|
|
309
|
+
initialPageParam: 1,
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function PostList() {
|
|
314
|
+
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = usePaginatedPosts();
|
|
315
|
+
|
|
316
|
+
const posts = data?.pages.flatMap(page => page.items) ?? [];
|
|
317
|
+
|
|
318
|
+
return (
|
|
319
|
+
<FlashList
|
|
320
|
+
data={posts}
|
|
321
|
+
renderItem={renderPost}
|
|
322
|
+
onEndReached={() => hasNextPage && fetchNextPage()}
|
|
323
|
+
onEndReachedThreshold={0.5}
|
|
324
|
+
ListFooterComponent={isFetchingNextPage ? <Spinner /> : null}
|
|
325
|
+
estimatedItemSize={100}
|
|
326
|
+
/>
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
333
|
+
## Bundle Size
|
|
334
|
+
|
|
335
|
+
### PERF-013: Tree-shake imports
|
|
336
|
+
|
|
337
|
+
```typescript
|
|
338
|
+
// BAD - Imports entire library
|
|
339
|
+
import { format } from 'date-fns';
|
|
340
|
+
import _ from 'lodash';
|
|
341
|
+
|
|
342
|
+
// GOOD - Specific imports
|
|
343
|
+
import format from 'date-fns/format';
|
|
344
|
+
import debounce from 'lodash/debounce';
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### PERF-014: Lazy load screens
|
|
348
|
+
|
|
349
|
+
```typescript
|
|
350
|
+
// Heavy screens loaded on demand
|
|
351
|
+
import { lazy } from 'react';
|
|
352
|
+
|
|
353
|
+
const HeavyEditor = lazy(() => import('./HeavyEditor'));
|
|
354
|
+
|
|
355
|
+
function Screen() {
|
|
356
|
+
const [showEditor, setShowEditor] = useState(false);
|
|
357
|
+
|
|
358
|
+
return (
|
|
359
|
+
<View>
|
|
360
|
+
<Button onPress={() => setShowEditor(true)}>Open Editor</Button>
|
|
361
|
+
{showEditor && (
|
|
362
|
+
<Suspense fallback={<Spinner />}>
|
|
363
|
+
<HeavyEditor />
|
|
364
|
+
</Suspense>
|
|
365
|
+
)}
|
|
366
|
+
</View>
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
## Profiling
|
|
374
|
+
|
|
375
|
+
### PERF-015: Measure performance
|
|
376
|
+
|
|
377
|
+
```typescript
|
|
378
|
+
// Console timing
|
|
379
|
+
console.time('render');
|
|
380
|
+
// ... operation
|
|
381
|
+
console.timeEnd('render');
|
|
382
|
+
|
|
383
|
+
// React DevTools Profiler
|
|
384
|
+
import { Profiler } from 'react';
|
|
385
|
+
|
|
386
|
+
function onRenderCallback(id, phase, actualDuration) {
|
|
387
|
+
console.log(`${id} ${phase}: ${actualDuration}ms`);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
<Profiler id="UserList" onRender={onRenderCallback}>
|
|
391
|
+
<UserList />
|
|
392
|
+
</Profiler>
|
|
393
|
+
|
|
394
|
+
// Flipper for detailed profiling
|
|
395
|
+
// Use React Native Performance Monitor
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
### PERF-016: Monitor in production
|
|
399
|
+
|
|
400
|
+
```typescript
|
|
401
|
+
// Track slow renders
|
|
402
|
+
import * as Sentry from '@sentry/react-native';
|
|
403
|
+
|
|
404
|
+
Sentry.init({
|
|
405
|
+
tracesSampleRate: 0.1,
|
|
406
|
+
enableAutoPerformanceTracking: true,
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
// Custom spans
|
|
410
|
+
const transaction = Sentry.startTransaction({ name: 'loadUsers' });
|
|
411
|
+
const span = transaction.startChild({ op: 'fetch' });
|
|
412
|
+
await fetchUsers();
|
|
413
|
+
span.finish();
|
|
414
|
+
transaction.finish();
|
|
415
|
+
```
|