specweave 1.0.31 → 1.0.33
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/.claude-plugin/marketplace.json +1 -1
- package/CLAUDE.md +205 -148
- package/README.md +0 -2
- package/bin/specweave.js +11 -0
- package/dist/src/cli/commands/init.js +1 -1
- package/dist/src/cli/commands/init.js.map +1 -1
- package/dist/src/cli/commands/update-instructions.d.ts +16 -0
- package/dist/src/cli/commands/update-instructions.d.ts.map +1 -0
- package/dist/src/cli/commands/update-instructions.js +134 -0
- package/dist/src/cli/commands/update-instructions.js.map +1 -0
- package/dist/src/cli/helpers/init/directory-structure.d.ts +28 -1
- package/dist/src/cli/helpers/init/directory-structure.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/directory-structure.js +163 -33
- package/dist/src/cli/helpers/init/directory-structure.js.map +1 -1
- package/dist/src/cli/helpers/init/index.d.ts +2 -1
- package/dist/src/cli/helpers/init/index.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/index.js +3 -1
- package/dist/src/cli/helpers/init/index.js.map +1 -1
- package/dist/src/cli/helpers/init/instruction-file-merger.d.ts +23 -0
- package/dist/src/cli/helpers/init/instruction-file-merger.d.ts.map +1 -0
- package/dist/src/cli/helpers/init/instruction-file-merger.js +243 -0
- package/dist/src/cli/helpers/init/instruction-file-merger.js.map +1 -0
- package/dist/src/cli/helpers/init/plugin-installer.js +49 -0
- package/dist/src/cli/helpers/init/plugin-installer.js.map +1 -1
- package/dist/src/config/types.d.ts +2 -2
- package/dist/src/core/living-docs/external-sync-orchestrator.d.ts +26 -0
- package/dist/src/core/living-docs/external-sync-orchestrator.d.ts.map +1 -1
- package/dist/src/core/living-docs/external-sync-orchestrator.js +61 -0
- package/dist/src/core/living-docs/external-sync-orchestrator.js.map +1 -1
- package/dist/src/core/living-docs/scaffolding/index.d.ts +12 -0
- package/dist/src/core/living-docs/scaffolding/index.d.ts.map +1 -0
- package/dist/src/core/living-docs/scaffolding/index.js +15 -0
- package/dist/src/core/living-docs/scaffolding/index.js.map +1 -0
- package/dist/src/core/living-docs/scaffolding/merger.d.ts +183 -0
- package/dist/src/core/living-docs/scaffolding/merger.d.ts.map +1 -0
- package/dist/src/core/living-docs/scaffolding/merger.js +523 -0
- package/dist/src/core/living-docs/scaffolding/merger.js.map +1 -0
- package/dist/src/core/living-docs/scaffolding/scaffold.d.ts +102 -0
- package/dist/src/core/living-docs/scaffolding/scaffold.d.ts.map +1 -0
- package/dist/src/core/living-docs/scaffolding/scaffold.js +346 -0
- package/dist/src/core/living-docs/scaffolding/scaffold.js.map +1 -0
- package/dist/src/core/living-docs/scaffolding/template-engine.d.ts +108 -0
- package/dist/src/core/living-docs/scaffolding/template-engine.d.ts.map +1 -0
- package/dist/src/core/living-docs/scaffolding/template-engine.js +204 -0
- package/dist/src/core/living-docs/scaffolding/template-engine.js.map +1 -0
- package/dist/src/core/living-docs/sync-helpers/generators.d.ts +38 -2
- package/dist/src/core/living-docs/sync-helpers/generators.d.ts.map +1 -1
- package/dist/src/core/living-docs/sync-helpers/generators.js +65 -10
- package/dist/src/core/living-docs/sync-helpers/generators.js.map +1 -1
- package/dist/src/core/living-docs/sync-helpers/index.d.ts +1 -1
- package/dist/src/core/living-docs/sync-helpers/index.d.ts.map +1 -1
- package/dist/src/core/living-docs/sync-helpers/index.js.map +1 -1
- package/dist/src/core/tools/index.d.ts +11 -0
- package/dist/src/core/tools/index.d.ts.map +1 -0
- package/dist/src/core/tools/index.js +10 -0
- package/dist/src/core/tools/index.js.map +1 -0
- package/dist/src/core/tools/tool-event-bus.d.ts +33 -0
- package/dist/src/core/tools/tool-event-bus.d.ts.map +1 -0
- package/dist/src/core/tools/tool-event-bus.js +84 -0
- package/dist/src/core/tools/tool-event-bus.js.map +1 -0
- package/dist/src/core/tools/tool-index-builder.d.ts +27 -0
- package/dist/src/core/tools/tool-index-builder.d.ts.map +1 -0
- package/dist/src/core/tools/tool-index-builder.js +289 -0
- package/dist/src/core/tools/tool-index-builder.js.map +1 -0
- package/dist/src/core/tools/tool-registry.d.ts +51 -0
- package/dist/src/core/tools/tool-registry.d.ts.map +1 -0
- package/dist/src/core/tools/tool-registry.js +224 -0
- package/dist/src/core/tools/tool-registry.js.map +1 -0
- package/dist/src/core/tools/tool-search-engine.d.ts +22 -0
- package/dist/src/core/tools/tool-search-engine.d.ts.map +1 -0
- package/dist/src/core/tools/tool-search-engine.js +174 -0
- package/dist/src/core/tools/tool-search-engine.js.map +1 -0
- package/dist/src/core/tools/types/tool-registry-types.d.ts +112 -0
- package/dist/src/core/tools/types/tool-registry-types.d.ts.map +1 -0
- package/dist/src/core/tools/types/tool-registry-types.js +7 -0
- package/dist/src/core/tools/types/tool-registry-types.js.map +1 -0
- package/dist/src/init/compliance/types.d.ts +1 -1
- package/package.json +1 -1
- package/plugins/specweave/hooks/hooks.json +3 -13
- package/plugins/specweave/hooks/lib/common-setup.sh +47 -321
- package/plugins/specweave/hooks/lib/migrate-increment-work.sh +5 -5
- package/plugins/specweave/hooks/lib/sync-spec-content.sh +5 -5
- package/plugins/specweave/hooks/universal/dispatcher.mjs +4 -5
- package/plugins/specweave/hooks/universal/fail-fast-wrapper.sh +43 -296
- package/plugins/specweave/hooks/universal/hook-wrapper.sh +3 -1
- package/plugins/specweave/hooks/user-prompt-submit.sh +1 -1
- package/plugins/specweave/hooks/v2/dispatchers/post-tool-use.sh +2 -2
- package/plugins/specweave/hooks/v2/dispatchers/session-start.sh +1 -10
- package/plugins/specweave/hooks/v2/guards/completion-guard.sh +12 -29
- package/plugins/specweave/hooks/v2/guards/increment-duplicate-guard.sh +27 -29
- package/plugins/specweave/hooks/v2/guards/metadata-json-guard.sh +10 -4
- package/plugins/specweave/hooks/v2/guards/spec-validation-guard.sh +139 -0
- package/plugins/specweave/hooks/v2/guards/task-ac-sync-guard.sh +4 -2
- package/plugins/specweave/hooks/v2/session-end.sh +3 -1
- package/plugins/specweave/hooks/v2/session-start.sh +3 -1
- package/plugins/specweave/skills/increment-planner/templates/plan.md +14 -0
- package/plugins/specweave/skills/update-instructions/SKILL.md +80 -0
- package/plugins/specweave-ado/hooks/post-living-docs-update.sh +1 -1
- package/plugins/specweave-mobile/README.md +55 -35
- package/plugins/specweave-mobile/agents/mobile-architect/AGENT.md +805 -329
- package/plugins/specweave-mobile/skills/expo-workflow/SKILL.md +226 -9
- package/plugins/specweave-mobile/skills/native-modules/SKILL.md +221 -20
- package/plugins/specweave-mobile/skills/performance-optimization/SKILL.md +186 -14
- package/plugins/specweave-mobile/skills/react-native-setup/SKILL.md +151 -54
- package/plugins/specweave-release/commands/npm.md +61 -17
- package/plugins/specweave-release/hooks/post-task-completion.sh +2 -3
- package/src/templates/AGENTS.md.template +34 -0
- package/src/templates/CLAUDE.md.template +121 -155
- package/plugins/specweave/hooks/config-env-separator.sh +0 -99
- package/plugins/specweave/hooks/github-metadata-guard.sh +0 -73
- package/plugins/specweave/hooks/lib/circuit-breaker.sh +0 -381
- package/plugins/specweave/hooks/lib/crash-prevention.sh +0 -336
- package/plugins/specweave/hooks/lib/logging.sh +0 -231
- package/plugins/specweave/hooks/lib/metrics.sh +0 -347
- package/plugins/specweave/hooks/lib/semaphore.sh +0 -216
- package/plugins/specweave/hooks/project-folder-guard.sh +0 -274
- package/plugins/specweave/hooks/spec-project-validator.sh +0 -210
- package/plugins/specweave/hooks/v2/guards/bash-file-guard.sh +0 -212
- package/plugins/specweave/hooks/v2/guards/bash-file-guard.test.sh +0 -163
- package/plugins/specweave/hooks/v2/guards/features-folder-guard.sh +0 -51
- package/plugins/specweave/hooks/v2/guards/increment-root-guard.sh +0 -63
- package/plugins/specweave/hooks/v2/guards/per-us-project-validator.sh +0 -335
- package/plugins/specweave/hooks/v2/guards/per-us-project-validator.test.sh +0 -406
|
@@ -1,20 +1,36 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: expo-workflow
|
|
3
|
-
description: Expert in Expo development workflows, EAS Build, EAS Update, Expo
|
|
3
|
+
description: Expert in Expo SDK 54+ development workflows, EAS Build, EAS Update, native tabs, Expo Router v6, expo-video, expo-audio, dev clients, expo-cli commands, app configuration, and deployment strategies. Activates for expo, expo go, eas build, eas update, expo config, app.json, eas.json, expo dev client, expo prebuild, expo eject, over-the-air updates, expo doctor, expo install, managed workflow, bare workflow, expo router, native tabs, liquid glass.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Expo Workflow Expert
|
|
6
|
+
# Expo Workflow Expert (SDK 54+)
|
|
7
7
|
|
|
8
|
-
Comprehensive expertise in Expo development workflows, EAS (Expo Application Services), and optimization strategies for rapid mobile development. Specializes in
|
|
8
|
+
Comprehensive expertise in Expo SDK 54+ development workflows, EAS (Expo Application Services), and optimization strategies for rapid mobile development. Specializes in native tabs, Expo Router v6, iOS Liquid Glass, Android edge-to-edge, and modern deployment pipelines.
|
|
9
9
|
|
|
10
10
|
## What I Know
|
|
11
11
|
|
|
12
|
+
### Expo SDK 54 Features (August 2025)
|
|
13
|
+
|
|
14
|
+
**What's New in SDK 54**
|
|
15
|
+
- **Native Tab Bar Navigation**: True native tabs via React Navigation 7
|
|
16
|
+
- **iOS Liquid Glass Support**: Translucent glass effects (iOS 26+)
|
|
17
|
+
- **Android Edge-to-Edge**: Default immersive display
|
|
18
|
+
- **expo-video & expo-audio**: New media APIs replacing expo-av
|
|
19
|
+
- **expo-image v2**: useImage hook for imperative loading
|
|
20
|
+
- **React Native 0.81**: Foundation with New Architecture
|
|
21
|
+
- **Improved Developer Experience**: Faster builds, better error messages
|
|
22
|
+
|
|
23
|
+
**Breaking Changes from SDK 53**
|
|
24
|
+
- `expo-av` deprecated → use `expo-video` and `expo-audio`
|
|
25
|
+
- Tab navigation API changes for native tabs
|
|
26
|
+
- Android edge-to-edge now default (adjust padding)
|
|
27
|
+
|
|
12
28
|
### Expo Fundamentals
|
|
13
29
|
|
|
14
30
|
**Managed vs Bare Workflow**
|
|
15
31
|
- Managed workflow: Full Expo SDK, minimal native code
|
|
16
32
|
- Bare workflow: Full native code access with Expo modules
|
|
17
|
-
-
|
|
33
|
+
- **CNG (Continuous Native Generation)**: Best of both worlds
|
|
18
34
|
- Migration strategies between workflows
|
|
19
35
|
|
|
20
36
|
**Expo Go vs Development Builds**
|
|
@@ -24,10 +40,10 @@ Comprehensive expertise in Expo development workflows, EAS (Expo Application Ser
|
|
|
24
40
|
- Creating custom dev clients with EAS Build
|
|
25
41
|
|
|
26
42
|
**Expo SDK & Modules**
|
|
27
|
-
- Core Expo modules (expo-camera, expo-location,
|
|
43
|
+
- Core Expo modules (expo-camera, expo-location, expo-video, expo-audio)
|
|
28
44
|
- Third-party native module compatibility
|
|
29
|
-
- Module installation
|
|
30
|
-
- Autolinking
|
|
45
|
+
- Module installation: `npx expo install <package>`
|
|
46
|
+
- Autolinking handles native setup automatically
|
|
31
47
|
|
|
32
48
|
### EAS Build (Cloud Builds)
|
|
33
49
|
|
|
@@ -134,7 +150,7 @@ Comprehensive expertise in Expo development workflows, EAS (Expo Application Ser
|
|
|
134
150
|
## When to Use This Skill
|
|
135
151
|
|
|
136
152
|
Ask me when you need help with:
|
|
137
|
-
- Setting up Expo development workflow
|
|
153
|
+
- Setting up Expo SDK 54+ development workflow
|
|
138
154
|
- Creating development builds with EAS Build
|
|
139
155
|
- Configuring app.json or eas.json
|
|
140
156
|
- Setting up over-the-air updates with EAS Update
|
|
@@ -144,9 +160,14 @@ Ask me when you need help with:
|
|
|
144
160
|
- Configuring deep linking and URL schemes
|
|
145
161
|
- Setting up CI/CD pipelines for Expo apps
|
|
146
162
|
- Deploying to App Store or Play Store
|
|
147
|
-
- Understanding Expo SDK capabilities
|
|
163
|
+
- Understanding Expo SDK 54 capabilities
|
|
148
164
|
- Migrating from Expo Go to dev client
|
|
149
165
|
- Handling native modules in Expo projects
|
|
166
|
+
- **Implementing native tab navigation**
|
|
167
|
+
- **Setting up iOS Liquid Glass effects**
|
|
168
|
+
- **Configuring Android edge-to-edge display**
|
|
169
|
+
- **Migrating from expo-av to expo-video/expo-audio**
|
|
170
|
+
- **Using Expo Router v6 file-based routing**
|
|
150
171
|
|
|
151
172
|
## Essential Expo Commands
|
|
152
173
|
|
|
@@ -417,6 +438,202 @@ eas build --profile development --platform all
|
|
|
417
438
|
eas update --branch development
|
|
418
439
|
```
|
|
419
440
|
|
|
441
|
+
### 9. Native Tab Navigation (SDK 54+)
|
|
442
|
+
|
|
443
|
+
Enable true native tab bars with Expo Router v6:
|
|
444
|
+
|
|
445
|
+
```typescript
|
|
446
|
+
// app/(tabs)/_layout.tsx
|
|
447
|
+
import { Tabs } from 'expo-router';
|
|
448
|
+
import { Platform } from 'react-native';
|
|
449
|
+
|
|
450
|
+
export default function TabLayout() {
|
|
451
|
+
return (
|
|
452
|
+
<Tabs
|
|
453
|
+
screenOptions={{
|
|
454
|
+
// Enable native tabs (iOS has translucent effect by default)
|
|
455
|
+
tabBarStyle: Platform.select({
|
|
456
|
+
ios: { position: 'absolute' }, // For Liquid Glass
|
|
457
|
+
default: {},
|
|
458
|
+
}),
|
|
459
|
+
}}
|
|
460
|
+
>
|
|
461
|
+
<Tabs.Screen
|
|
462
|
+
name="index"
|
|
463
|
+
options={{ title: 'Home', tabBarIcon: ({ color }) => <HomeIcon color={color} /> }}
|
|
464
|
+
/>
|
|
465
|
+
<Tabs.Screen
|
|
466
|
+
name="profile"
|
|
467
|
+
options={{ title: 'Profile', tabBarIcon: ({ color }) => <ProfileIcon color={color} /> }}
|
|
468
|
+
/>
|
|
469
|
+
</Tabs>
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
### 10. iOS Liquid Glass (SDK 54+ / iOS 26+)
|
|
475
|
+
|
|
476
|
+
Create beautiful translucent effects:
|
|
477
|
+
|
|
478
|
+
```typescript
|
|
479
|
+
// components/GlassCard.tsx
|
|
480
|
+
import { View, StyleSheet, Platform } from 'react-native';
|
|
481
|
+
import { BlurView } from 'expo-blur';
|
|
482
|
+
|
|
483
|
+
export function GlassCard({ children }) {
|
|
484
|
+
if (Platform.OS === 'ios' && parseInt(Platform.Version, 10) >= 26) {
|
|
485
|
+
return (
|
|
486
|
+
<BlurView
|
|
487
|
+
style={styles.card}
|
|
488
|
+
intensity={60}
|
|
489
|
+
tint="systemMaterial" // Liquid Glass tint
|
|
490
|
+
>
|
|
491
|
+
{children}
|
|
492
|
+
</BlurView>
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
return <View style={[styles.card, styles.fallback]}>{children}</View>;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const styles = StyleSheet.create({
|
|
500
|
+
card: {
|
|
501
|
+
borderRadius: 16,
|
|
502
|
+
overflow: 'hidden',
|
|
503
|
+
},
|
|
504
|
+
fallback: {
|
|
505
|
+
backgroundColor: 'rgba(255, 255, 255, 0.8)',
|
|
506
|
+
},
|
|
507
|
+
});
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
### 11. Android Edge-to-Edge (SDK 54+)
|
|
511
|
+
|
|
512
|
+
Handle immersive display properly:
|
|
513
|
+
|
|
514
|
+
```typescript
|
|
515
|
+
// app/_layout.tsx
|
|
516
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
517
|
+
import { View, StyleSheet, Platform } from 'react-native';
|
|
518
|
+
|
|
519
|
+
export default function RootLayout() {
|
|
520
|
+
const insets = useSafeAreaInsets();
|
|
521
|
+
|
|
522
|
+
return (
|
|
523
|
+
<View
|
|
524
|
+
style={[
|
|
525
|
+
styles.container,
|
|
526
|
+
{
|
|
527
|
+
// Account for edge-to-edge on Android 15+
|
|
528
|
+
paddingTop: insets.top,
|
|
529
|
+
paddingBottom: insets.bottom,
|
|
530
|
+
},
|
|
531
|
+
]}
|
|
532
|
+
>
|
|
533
|
+
<Slot />
|
|
534
|
+
</View>
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
```javascript
|
|
540
|
+
// app.json - enable edge-to-edge
|
|
541
|
+
{
|
|
542
|
+
"expo": {
|
|
543
|
+
"android": {
|
|
544
|
+
"edgeToEdge": true // SDK 54 default
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
### 12. expo-video (Replacing expo-av)
|
|
551
|
+
|
|
552
|
+
Modern video playback:
|
|
553
|
+
|
|
554
|
+
```typescript
|
|
555
|
+
// components/VideoPlayer.tsx
|
|
556
|
+
import { useVideoPlayer, VideoView } from 'expo-video';
|
|
557
|
+
import { useEvent } from 'expo';
|
|
558
|
+
import { StyleSheet, View } from 'react-native';
|
|
559
|
+
|
|
560
|
+
export function VideoPlayer({ source }: { source: string }) {
|
|
561
|
+
const player = useVideoPlayer(source, (player) => {
|
|
562
|
+
player.loop = true;
|
|
563
|
+
player.play();
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
useEvent(player, 'statusChange', ({ status }) => {
|
|
567
|
+
console.log('Player status:', status);
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
return (
|
|
571
|
+
<View style={styles.container}>
|
|
572
|
+
<VideoView
|
|
573
|
+
style={styles.video}
|
|
574
|
+
player={player}
|
|
575
|
+
allowsFullscreen
|
|
576
|
+
allowsPictureInPicture
|
|
577
|
+
/>
|
|
578
|
+
</View>
|
|
579
|
+
);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
const styles = StyleSheet.create({
|
|
583
|
+
container: { flex: 1 },
|
|
584
|
+
video: { width: '100%', aspectRatio: 16 / 9 },
|
|
585
|
+
});
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
### 13. expo-audio (Replacing expo-av)
|
|
589
|
+
|
|
590
|
+
Modern audio handling:
|
|
591
|
+
|
|
592
|
+
```typescript
|
|
593
|
+
// hooks/useAudio.ts
|
|
594
|
+
import { useAudioPlayer, useAudioPlayerStatus } from 'expo-audio';
|
|
595
|
+
|
|
596
|
+
export function useAudio(source: string) {
|
|
597
|
+
const player = useAudioPlayer(source);
|
|
598
|
+
const status = useAudioPlayerStatus(player);
|
|
599
|
+
|
|
600
|
+
return {
|
|
601
|
+
play: () => player.play(),
|
|
602
|
+
pause: () => player.pause(),
|
|
603
|
+
seek: (position: number) => player.seekTo(position),
|
|
604
|
+
isPlaying: status.playing,
|
|
605
|
+
position: status.currentTime,
|
|
606
|
+
duration: status.duration,
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
### 14. expo-image v2 with useImage
|
|
612
|
+
|
|
613
|
+
Imperative image loading:
|
|
614
|
+
|
|
615
|
+
```typescript
|
|
616
|
+
import { useImage, Image } from 'expo-image';
|
|
617
|
+
|
|
618
|
+
export function PreloadedImage({ uri }: { uri: string }) {
|
|
619
|
+
const image = useImage(uri, {
|
|
620
|
+
onError: (error) => console.error('Failed to load image:', error),
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
if (!image) {
|
|
624
|
+
return <ActivityIndicator />;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
return (
|
|
628
|
+
<Image
|
|
629
|
+
source={image}
|
|
630
|
+
style={{ width: image.width, height: image.height }}
|
|
631
|
+
contentFit="cover"
|
|
632
|
+
/>
|
|
633
|
+
);
|
|
634
|
+
}
|
|
635
|
+
```
|
|
636
|
+
|
|
420
637
|
## Integration with SpecWeave
|
|
421
638
|
|
|
422
639
|
**Increment Planning**
|
|
@@ -1,28 +1,34 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: native-modules
|
|
3
|
-
description: Expert in React Native native modules,
|
|
3
|
+
description: Expert in React Native 0.83+ native modules, Turbo Modules with Codegen, Fabric renderer, JSI (JavaScript Interface), New Architecture migration, bridging JavaScript and native code, iOS Swift modules, Android Kotlin modules, expo config plugins. Activates for native module, native code, bridge, turbo module, JSI, fabric, autolinking, custom native module, ios module, android module, swift, kotlin, objective-c, java native code, codegen, new architecture.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Native Modules Expert
|
|
6
|
+
# Native Modules Expert (RN 0.83+ New Architecture)
|
|
7
7
|
|
|
8
|
-
Specialized in React Native native module integration
|
|
8
|
+
Specialized in React Native 0.83+ native module integration with New Architecture (enabled by default). Expert in Turbo Modules, JSI, Fabric, Codegen, and modern native development patterns.
|
|
9
9
|
|
|
10
10
|
## What I Know
|
|
11
11
|
|
|
12
12
|
### Native Module Fundamentals
|
|
13
13
|
|
|
14
14
|
**What Are Native Modules?**
|
|
15
|
-
-
|
|
16
|
-
- Access platform-specific APIs (Bluetooth, NFC, etc.)
|
|
17
|
-
- Performance-critical operations
|
|
15
|
+
- Direct interface between JavaScript and native platform code
|
|
16
|
+
- Access platform-specific APIs (Bluetooth, NFC, HealthKit, etc.)
|
|
17
|
+
- Performance-critical operations via JSI
|
|
18
18
|
- Integration with existing native SDKs
|
|
19
19
|
|
|
20
|
-
**
|
|
21
|
-
- **
|
|
22
|
-
- **
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
**New Architecture (Default in RN 0.76+)**
|
|
21
|
+
- **JSI** (JavaScript Interface): Direct JS ↔ Native communication (no JSON serialization)
|
|
22
|
+
- **Turbo Modules**: Lazy-loaded, type-safe native modules with Codegen
|
|
23
|
+
- **Fabric**: New concurrent rendering engine
|
|
24
|
+
- **Codegen**: TypeScript → Native type generation
|
|
25
|
+
|
|
26
|
+
**Key Benefits of New Architecture**
|
|
27
|
+
- 10-100x faster than old bridge
|
|
28
|
+
- Synchronous method calls possible
|
|
29
|
+
- Type safety across JS/Native boundary
|
|
30
|
+
- Lazy module loading (better startup)
|
|
31
|
+
- Concurrent rendering with Fabric
|
|
26
32
|
|
|
27
33
|
### Using Third-Party Native Modules
|
|
28
34
|
|
|
@@ -241,28 +247,146 @@ function MyComponent() {
|
|
|
241
247
|
}
|
|
242
248
|
```
|
|
243
249
|
|
|
244
|
-
### Turbo Modules (New Architecture)
|
|
250
|
+
### Turbo Modules (New Architecture - Default in RN 0.76+)
|
|
251
|
+
|
|
252
|
+
**Creating a Turbo Module with Codegen**
|
|
245
253
|
|
|
246
|
-
|
|
254
|
+
Step 1: Create the TypeScript spec (source of truth for types):
|
|
247
255
|
|
|
248
256
|
```typescript
|
|
249
|
-
// NativeCalendarModule.ts
|
|
257
|
+
// specs/NativeCalendarModule.ts
|
|
250
258
|
import type { TurboModule } from 'react-native';
|
|
251
259
|
import { TurboModuleRegistry } from 'react-native';
|
|
252
260
|
|
|
253
261
|
export interface Spec extends TurboModule {
|
|
254
|
-
|
|
262
|
+
// Sync method (fast, blocks JS thread)
|
|
263
|
+
getConstants(): {
|
|
264
|
+
DEFAULT_REMINDER_MINUTES: number;
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
// Async methods (recommended for most cases)
|
|
268
|
+
createEvent(name: string, location: string, date: number): Promise<string>;
|
|
255
269
|
findEvents(): Promise<string[]>;
|
|
270
|
+
deleteEvent(eventId: string): Promise<boolean>;
|
|
271
|
+
|
|
272
|
+
// Callback-based (legacy pattern, prefer Promise)
|
|
273
|
+
getEventsWithCallback(callback: (events: string[]) => void): void;
|
|
256
274
|
}
|
|
257
275
|
|
|
258
276
|
export default TurboModuleRegistry.getEnforcing<Spec>('CalendarModule');
|
|
259
277
|
```
|
|
260
278
|
|
|
279
|
+
Step 2: Configure Codegen in package.json:
|
|
280
|
+
|
|
281
|
+
```json
|
|
282
|
+
{
|
|
283
|
+
"codegenConfig": {
|
|
284
|
+
"name": "CalendarModuleSpec",
|
|
285
|
+
"type": "modules",
|
|
286
|
+
"jsSrcsDir": "specs",
|
|
287
|
+
"android": {
|
|
288
|
+
"javaPackageName": "com.myapp.calendar"
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
Step 3: Implement the native side (iOS - Swift):
|
|
295
|
+
|
|
296
|
+
```swift
|
|
297
|
+
// CalendarModule.swift
|
|
298
|
+
import Foundation
|
|
299
|
+
|
|
300
|
+
@objc(CalendarModule)
|
|
301
|
+
class CalendarModule: NSObject {
|
|
302
|
+
|
|
303
|
+
@objc static func moduleName() -> String! {
|
|
304
|
+
return "CalendarModule"
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
@objc static func requiresMainQueueSetup() -> Bool {
|
|
308
|
+
return false
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
@objc func getConstants() -> [String: Any] {
|
|
312
|
+
return ["DEFAULT_REMINDER_MINUTES": 15]
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
@objc func createEvent(_ name: String, location: String, date: Double,
|
|
316
|
+
resolve: @escaping RCTPromiseResolveBlock,
|
|
317
|
+
reject: @escaping RCTPromiseRejectBlock) {
|
|
318
|
+
DispatchQueue.global().async {
|
|
319
|
+
// Native implementation
|
|
320
|
+
let eventId = UUID().uuidString
|
|
321
|
+
resolve(eventId)
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
@objc func findEvents(_ resolve: @escaping RCTPromiseResolveBlock,
|
|
326
|
+
reject: @escaping RCTPromiseRejectBlock) {
|
|
327
|
+
DispatchQueue.global().async {
|
|
328
|
+
let events = ["Meeting", "Lunch", "Call"]
|
|
329
|
+
resolve(events)
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
@objc func deleteEvent(_ eventId: String,
|
|
334
|
+
resolve: @escaping RCTPromiseResolveBlock,
|
|
335
|
+
reject: @escaping RCTPromiseRejectBlock) {
|
|
336
|
+
resolve(true)
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
Step 4: Implement the native side (Android - Kotlin):
|
|
342
|
+
|
|
343
|
+
```kotlin
|
|
344
|
+
// CalendarModule.kt
|
|
345
|
+
package com.myapp.calendar
|
|
346
|
+
|
|
347
|
+
import com.facebook.react.bridge.*
|
|
348
|
+
import com.facebook.react.module.annotations.ReactModule
|
|
349
|
+
|
|
350
|
+
@ReactModule(name = CalendarModule.NAME)
|
|
351
|
+
class CalendarModule(reactContext: ReactApplicationContext) :
|
|
352
|
+
NativeCalendarModuleSpec(reactContext) {
|
|
353
|
+
|
|
354
|
+
companion object {
|
|
355
|
+
const val NAME = "CalendarModule"
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
override fun getName(): String = NAME
|
|
359
|
+
|
|
360
|
+
override fun getConstants(): MutableMap<String, Any> {
|
|
361
|
+
return mutableMapOf("DEFAULT_REMINDER_MINUTES" to 15)
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
override fun createEvent(name: String, location: String, date: Double, promise: Promise) {
|
|
365
|
+
val eventId = java.util.UUID.randomUUID().toString()
|
|
366
|
+
promise.resolve(eventId)
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
override fun findEvents(promise: Promise) {
|
|
370
|
+
val events = Arguments.createArray().apply {
|
|
371
|
+
pushString("Meeting")
|
|
372
|
+
pushString("Lunch")
|
|
373
|
+
pushString("Call")
|
|
374
|
+
}
|
|
375
|
+
promise.resolve(events)
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
override fun deleteEvent(eventId: String, promise: Promise) {
|
|
379
|
+
promise.resolve(true)
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
```
|
|
383
|
+
|
|
261
384
|
**Benefits of Turbo Modules**
|
|
262
|
-
- Lazy loading
|
|
263
|
-
- Type safety
|
|
264
|
-
-
|
|
265
|
-
-
|
|
385
|
+
- **Lazy loading**: Only loaded when first accessed
|
|
386
|
+
- **Type safety**: Codegen generates native interfaces from TypeScript
|
|
387
|
+
- **10x faster**: Direct JSI calls, no JSON serialization
|
|
388
|
+
- **Synchronous calls**: getConstants() can be sync
|
|
389
|
+
- **Better DX**: TypeScript errors caught at build time
|
|
266
390
|
|
|
267
391
|
### Native UI Components
|
|
268
392
|
|
|
@@ -397,6 +521,11 @@ Ask me when you need help with:
|
|
|
397
521
|
- Creating custom native UI components
|
|
398
522
|
- Handling platform-specific APIs
|
|
399
523
|
- Resolving autolinking issues
|
|
524
|
+
- **Setting up Codegen for type-safe modules**
|
|
525
|
+
- **Creating Fabric components (New Architecture UI)**
|
|
526
|
+
- **JSI bindings for synchronous native calls**
|
|
527
|
+
- **Expo config plugins for native configuration**
|
|
528
|
+
- **Interop layer for legacy Bridge modules**
|
|
400
529
|
|
|
401
530
|
## Essential Commands
|
|
402
531
|
|
|
@@ -554,6 +683,78 @@ console.log(deviceId); // Returns immediately
|
|
|
554
683
|
|
|
555
684
|
**Warning**: Synchronous methods block the JS thread. Use only for very fast operations (<5ms).
|
|
556
685
|
|
|
686
|
+
### 5. Expo Config Plugins (SDK 54+)
|
|
687
|
+
|
|
688
|
+
For Expo projects, use config plugins to modify native code:
|
|
689
|
+
|
|
690
|
+
```typescript
|
|
691
|
+
// plugins/withCalendarPermission.ts
|
|
692
|
+
import { ConfigPlugin, withInfoPlist, withAndroidManifest } from '@expo/config-plugins';
|
|
693
|
+
|
|
694
|
+
const withCalendarPermission: ConfigPlugin = (config) => {
|
|
695
|
+
// iOS: Modify Info.plist
|
|
696
|
+
config = withInfoPlist(config, (config) => {
|
|
697
|
+
config.modResults.NSCalendarsUsageDescription =
|
|
698
|
+
'This app needs calendar access to schedule events';
|
|
699
|
+
return config;
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
// Android: Modify AndroidManifest.xml
|
|
703
|
+
config = withAndroidManifest(config, (config) => {
|
|
704
|
+
const mainApplication = config.modResults.manifest.application?.[0];
|
|
705
|
+
if (mainApplication) {
|
|
706
|
+
// Add permissions
|
|
707
|
+
config.modResults.manifest['uses-permission'] = [
|
|
708
|
+
...(config.modResults.manifest['uses-permission'] || []),
|
|
709
|
+
{ $: { 'android:name': 'android.permission.READ_CALENDAR' } },
|
|
710
|
+
{ $: { 'android:name': 'android.permission.WRITE_CALENDAR' } },
|
|
711
|
+
];
|
|
712
|
+
}
|
|
713
|
+
return config;
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
return config;
|
|
717
|
+
};
|
|
718
|
+
|
|
719
|
+
export default withCalendarPermission;
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
```json
|
|
723
|
+
// app.json
|
|
724
|
+
{
|
|
725
|
+
"expo": {
|
|
726
|
+
"plugins": [
|
|
727
|
+
"./plugins/withCalendarPermission"
|
|
728
|
+
]
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
```
|
|
732
|
+
|
|
733
|
+
### 6. Interop Layer for Legacy Bridge Modules
|
|
734
|
+
|
|
735
|
+
RN 0.76+ includes an interop layer for Bridge modules in New Architecture:
|
|
736
|
+
|
|
737
|
+
```typescript
|
|
738
|
+
// For legacy modules that don't support Turbo Modules yet
|
|
739
|
+
import { NativeModules, TurboModuleRegistry } from 'react-native';
|
|
740
|
+
|
|
741
|
+
// This works in New Architecture via interop layer
|
|
742
|
+
const LegacyModule = NativeModules.LegacyBridgeModule;
|
|
743
|
+
|
|
744
|
+
// Or use the Turbo Module if available
|
|
745
|
+
const TurboModule = TurboModuleRegistry.get('ModernModule');
|
|
746
|
+
|
|
747
|
+
// Recommended: Create a wrapper that handles both
|
|
748
|
+
export function getCalendarModule() {
|
|
749
|
+
// Try Turbo Module first
|
|
750
|
+
const turbo = TurboModuleRegistry.get('CalendarModule');
|
|
751
|
+
if (turbo) return turbo;
|
|
752
|
+
|
|
753
|
+
// Fall back to Bridge module via interop
|
|
754
|
+
return NativeModules.CalendarModule;
|
|
755
|
+
}
|
|
756
|
+
```
|
|
757
|
+
|
|
557
758
|
## Integration with SpecWeave
|
|
558
759
|
|
|
559
760
|
**Native Module Planning**
|