erne-universal 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/.claude-plugin/plugin.json +92 -0
- package/LICENSE +21 -0
- package/README.md +73 -0
- package/agents/architect.md +64 -0
- package/agents/code-reviewer.md +72 -0
- package/agents/expo-config-resolver.md +77 -0
- package/agents/native-bridge-builder.md +98 -0
- package/agents/performance-profiler.md +89 -0
- package/agents/tdd-guide.md +86 -0
- package/agents/ui-designer.md +100 -0
- package/agents/upgrade-assistant.md +106 -0
- package/bin/cli.js +55 -0
- package/commands/animate.md +70 -0
- package/commands/build-fix.md +57 -0
- package/commands/code-review.md +51 -0
- package/commands/component.md +93 -0
- package/commands/debug.md +74 -0
- package/commands/deploy.md +82 -0
- package/commands/learn.md +56 -0
- package/commands/native-module.md +51 -0
- package/commands/navigate.md +69 -0
- package/commands/perf.md +68 -0
- package/commands/plan.md +49 -0
- package/commands/quality-gate.md +80 -0
- package/commands/retrospective.md +70 -0
- package/commands/setup-device.md +99 -0
- package/commands/tdd.md +51 -0
- package/commands/upgrade.md +78 -0
- package/contexts/dev.md +29 -0
- package/contexts/review.md +32 -0
- package/contexts/vibe.md +44 -0
- package/docs/agents.md +41 -0
- package/docs/commands.md +53 -0
- package/docs/creating-skills.md +63 -0
- package/docs/getting-started.md +60 -0
- package/docs/hooks-profiles.md +73 -0
- package/docs/superpowers/plans/2026-03-10-erne-plan-1-infrastructure-hooks.md +3973 -0
- package/docs/superpowers/plans/2026-03-10-erne-plan-2-content-layer.md +4496 -0
- package/docs/superpowers/plans/2026-03-10-erne-plan-3-skills-knowledge-base.md +1952 -0
- package/docs/superpowers/plans/2026-03-10-erne-plan-4-install-cli-distribution.md +1624 -0
- package/docs/superpowers/specs/2026-03-10-everything-react-native-expo-design.md +581 -0
- package/examples/claude-md-bare-rn.md +46 -0
- package/examples/claude-md-expo-managed.md +45 -0
- package/examples/eas-json-standard.json +41 -0
- package/hooks/hooks.json +113 -0
- package/hooks/profiles/minimal.json +9 -0
- package/hooks/profiles/standard.json +17 -0
- package/hooks/profiles/strict.json +22 -0
- package/install.sh +50 -0
- package/mcp-configs/agent-device.json +10 -0
- package/mcp-configs/appstore-connect.json +15 -0
- package/mcp-configs/expo-api.json +13 -0
- package/mcp-configs/figma.json +13 -0
- package/mcp-configs/firebase.json +14 -0
- package/mcp-configs/github.json +13 -0
- package/mcp-configs/memory.json +13 -0
- package/mcp-configs/play-console.json +14 -0
- package/mcp-configs/sentry.json +15 -0
- package/mcp-configs/supabase.json +14 -0
- package/package.json +50 -0
- package/rules/bare-rn/coding-style.md +62 -0
- package/rules/bare-rn/patterns.md +54 -0
- package/rules/bare-rn/security.md +58 -0
- package/rules/bare-rn/testing.md +78 -0
- package/rules/common/coding-style.md +50 -0
- package/rules/common/development-workflow.md +55 -0
- package/rules/common/git-workflow.md +40 -0
- package/rules/common/navigation.md +56 -0
- package/rules/common/patterns.md +59 -0
- package/rules/common/performance.md +55 -0
- package/rules/common/security.md +64 -0
- package/rules/common/state-management.md +86 -0
- package/rules/common/testing.md +61 -0
- package/rules/expo/coding-style.md +54 -0
- package/rules/expo/patterns.md +71 -0
- package/rules/expo/security.md +41 -0
- package/rules/expo/testing.md +68 -0
- package/rules/native-android/coding-style.md +81 -0
- package/rules/native-android/patterns.md +77 -0
- package/rules/native-android/security.md +80 -0
- package/rules/native-android/testing.md +94 -0
- package/rules/native-ios/coding-style.md +72 -0
- package/rules/native-ios/patterns.md +72 -0
- package/rules/native-ios/security.md +59 -0
- package/rules/native-ios/testing.md +79 -0
- package/schemas/hooks.schema.json +34 -0
- package/schemas/plugin.schema.json +55 -0
- package/scripts/hooks/accessibility-check.js +117 -0
- package/scripts/hooks/bundle-size-check.js +31 -0
- package/scripts/hooks/check-console-log.js +37 -0
- package/scripts/hooks/check-expo-config.js +40 -0
- package/scripts/hooks/check-platform-specific.js +40 -0
- package/scripts/hooks/check-reanimated-worklet.js +51 -0
- package/scripts/hooks/continuous-learning-observer.js +24 -0
- package/scripts/hooks/evaluate-session.js +26 -0
- package/scripts/hooks/lib/hook-utils.js +52 -0
- package/scripts/hooks/native-compat-check.js +42 -0
- package/scripts/hooks/performance-budget.js +57 -0
- package/scripts/hooks/post-edit-format.js +38 -0
- package/scripts/hooks/post-edit-typecheck.js +31 -0
- package/scripts/hooks/pre-commit-lint.js +44 -0
- package/scripts/hooks/pre-edit-test-gate.js +68 -0
- package/scripts/hooks/run-with-flags.js +93 -0
- package/scripts/hooks/security-scan.js +65 -0
- package/scripts/hooks/session-start.js +77 -0
- package/scripts/lint-content.js +62 -0
- package/scripts/validate-all.js +137 -0
- package/skills/coding-standards/SKILL.md +88 -0
- package/skills/continuous-learning-v2/SKILL.md +61 -0
- package/skills/continuous-learning-v2/agent-prompts/pattern-analyzer.md +51 -0
- package/skills/continuous-learning-v2/agent-prompts/skill-generator.md +74 -0
- package/skills/continuous-learning-v2/config.json +25 -0
- package/skills/continuous-learning-v2/hook-templates/evaluate-session.cjs.template +69 -0
- package/skills/continuous-learning-v2/hook-templates/observer-hook.cjs.template +54 -0
- package/skills/continuous-learning-v2/scripts/analyze-patterns.js +50 -0
- package/skills/continuous-learning-v2/scripts/extract-session-patterns.js +54 -0
- package/skills/continuous-learning-v2/scripts/validate-content.js +88 -0
- package/skills/native-module-scaffold/SKILL.md +118 -0
- package/skills/performance-optimization/SKILL.md +103 -0
- package/skills/security-review/SKILL.md +99 -0
- package/skills/tdd-workflow/SKILL.md +142 -0
- package/skills/upgrade-workflow/SKILL.md +140 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Testing patterns for bare React Native projects
|
|
3
|
+
globs: "**/*.test.{ts,tsx}"
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Bare React Native Testing
|
|
8
|
+
|
|
9
|
+
## Native Unit Tests
|
|
10
|
+
Run native tests alongside JS tests for full coverage:
|
|
11
|
+
|
|
12
|
+
### iOS (XCTest)
|
|
13
|
+
```swift
|
|
14
|
+
// ios/MyAppTests/MyModuleTests.swift
|
|
15
|
+
import XCTest
|
|
16
|
+
@testable import MyApp
|
|
17
|
+
|
|
18
|
+
class MyModuleTests: XCTestCase {
|
|
19
|
+
func testDataProcessing() {
|
|
20
|
+
let module = MyModule()
|
|
21
|
+
let result = module.processData("input")
|
|
22
|
+
XCTAssertEqual(result, "expected_output")
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Android (JUnit)
|
|
28
|
+
```kotlin
|
|
29
|
+
// android/app/src/test/java/com/myapp/MyModuleTest.kt
|
|
30
|
+
import org.junit.Test
|
|
31
|
+
import org.junit.Assert.*
|
|
32
|
+
|
|
33
|
+
class MyModuleTest {
|
|
34
|
+
@Test
|
|
35
|
+
fun testDataProcessing() {
|
|
36
|
+
val module = MyModule()
|
|
37
|
+
val result = module.processData("input")
|
|
38
|
+
assertEquals("expected_output", result)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## E2E with Detox (Bare Setup)
|
|
44
|
+
- Configure Detox directly in project (no EAS Build needed)
|
|
45
|
+
- Build test binaries locally: `detox build --configuration ios.sim.debug`
|
|
46
|
+
- Run: `detox test --configuration ios.sim.debug`
|
|
47
|
+
|
|
48
|
+
```js
|
|
49
|
+
// .detoxrc.js
|
|
50
|
+
module.exports = {
|
|
51
|
+
testRunner: { args: { config: 'e2e/jest.config.js' } },
|
|
52
|
+
apps: {
|
|
53
|
+
'ios.debug': {
|
|
54
|
+
type: 'ios.app',
|
|
55
|
+
binaryPath: 'ios/build/Build/Products/Debug-iphonesimulator/MyApp.app',
|
|
56
|
+
build: 'xcodebuild -workspace ios/MyApp.xcworkspace -scheme MyApp -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build',
|
|
57
|
+
},
|
|
58
|
+
'android.debug': {
|
|
59
|
+
type: 'android.apk',
|
|
60
|
+
binaryPath: 'android/app/build/outputs/apk/debug/app-debug.apk',
|
|
61
|
+
build: 'cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug',
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
devices: {
|
|
65
|
+
simulator: { type: 'ios.simulator', device: { type: 'iPhone 16' } },
|
|
66
|
+
emulator: { type: 'android.emulator', device: { avdName: 'Pixel_7' } },
|
|
67
|
+
},
|
|
68
|
+
configurations: {
|
|
69
|
+
'ios.sim.debug': { device: 'simulator', app: 'ios.debug' },
|
|
70
|
+
'android.emu.debug': { device: 'emulator', app: 'android.debug' },
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Integration Testing
|
|
76
|
+
- Test native module bridges with integration tests
|
|
77
|
+
- Verify Turbo Module codegen output matches specs
|
|
78
|
+
- Test platform-specific behavior on both iOS and Android
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: TypeScript and React Native coding style conventions — enforced for all project types
|
|
3
|
+
globs: "**/*.{ts,tsx,js,jsx}"
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Coding Style
|
|
8
|
+
|
|
9
|
+
## TypeScript
|
|
10
|
+
- Enable `strict` mode in tsconfig.json
|
|
11
|
+
- No `any` types — use `unknown` + type guards or proper generics
|
|
12
|
+
- Use type inference where possible; annotate function signatures explicitly
|
|
13
|
+
- Prefer `interface` for object shapes, `type` for unions/intersections
|
|
14
|
+
- Use `as const` for literal types instead of enums
|
|
15
|
+
|
|
16
|
+
## Components
|
|
17
|
+
- Functional components only — no class components
|
|
18
|
+
- Named exports only — no default exports
|
|
19
|
+
- One component per file (colocated helpers are fine)
|
|
20
|
+
- Props interface named `[Component]Props` (e.g., `ButtonProps`)
|
|
21
|
+
- Destructure props in function signature
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
// GOOD
|
|
25
|
+
export function UserCard({ name, avatar }: UserCardProps) { ... }
|
|
26
|
+
|
|
27
|
+
// BAD
|
|
28
|
+
export default class UserCard extends Component { ... }
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## File Naming
|
|
32
|
+
- Components: `PascalCase.tsx` (e.g., `UserCard.tsx`)
|
|
33
|
+
- Hooks: `camelCase.ts` prefixed with `use` (e.g., `useAuth.ts`)
|
|
34
|
+
- Utils/helpers: `camelCase.ts` (e.g., `formatDate.ts`)
|
|
35
|
+
- Types: `camelCase.ts` or colocated in component file
|
|
36
|
+
- Tests: `[name].test.ts(x)` adjacent to source
|
|
37
|
+
- Platform-specific: `[name].ios.tsx` / `[name].android.tsx`
|
|
38
|
+
|
|
39
|
+
## Imports
|
|
40
|
+
- Use path aliases (`@/` maps to `src/`)
|
|
41
|
+
- No barrel files (`index.ts` re-exports) — import directly
|
|
42
|
+
- Group imports: react → react-native → expo → third-party → local
|
|
43
|
+
- Use `import type` for type-only imports
|
|
44
|
+
|
|
45
|
+
## General
|
|
46
|
+
- Max line length: 100 characters (Prettier enforced)
|
|
47
|
+
- Trailing commas in multiline structures
|
|
48
|
+
- Single quotes for strings
|
|
49
|
+
- Semicolons required
|
|
50
|
+
- No `var` — use `const` by default, `let` when reassignment needed
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Development environment and workflow conventions
|
|
3
|
+
globs: ""
|
|
4
|
+
alwaysApply: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Development Workflow
|
|
8
|
+
|
|
9
|
+
## Development Client
|
|
10
|
+
- Use `expo-dev-client` instead of Expo Go for projects with native modules
|
|
11
|
+
- Expo Go is fine for pure JS/TS projects without custom native code
|
|
12
|
+
- Create development builds: `eas build --profile development`
|
|
13
|
+
|
|
14
|
+
## Build Profiles
|
|
15
|
+
|
|
16
|
+
| Profile | Use Case | Command |
|
|
17
|
+
|---------|----------|---------|
|
|
18
|
+
| development | Local testing with dev tools | `eas build --profile development` |
|
|
19
|
+
| preview | QA testing, stakeholder review | `eas build --profile preview` |
|
|
20
|
+
| production | App Store / Play Store release | `eas build --profile production` |
|
|
21
|
+
|
|
22
|
+
## Environment Management
|
|
23
|
+
- `.env.development`, `.env.preview`, `.env.production`
|
|
24
|
+
- Use `expo-constants` to access env vars at runtime
|
|
25
|
+
- Never commit `.env` files (add to `.gitignore`)
|
|
26
|
+
- Use EAS Secrets for CI/CD environment variables
|
|
27
|
+
|
|
28
|
+
## Local Development
|
|
29
|
+
```bash
|
|
30
|
+
# Start Metro bundler
|
|
31
|
+
npx expo start
|
|
32
|
+
|
|
33
|
+
# Run on specific platform
|
|
34
|
+
npx expo run:ios
|
|
35
|
+
npx expo run:android
|
|
36
|
+
|
|
37
|
+
# Clear cache when things break
|
|
38
|
+
npx expo start --clear
|
|
39
|
+
|
|
40
|
+
# Regenerate native projects
|
|
41
|
+
npx expo prebuild --clean
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Debugging
|
|
45
|
+
- Use React Native DevTools (built-in with Expo SDK 50+)
|
|
46
|
+
- Console.log for quick debugging (remove before commit)
|
|
47
|
+
- React DevTools for component inspection
|
|
48
|
+
- Flipper/React Native Debugger for network and performance
|
|
49
|
+
- `LogBox.ignoreLogs()` only for known harmless warnings
|
|
50
|
+
|
|
51
|
+
## CI/CD
|
|
52
|
+
- EAS Build for cloud builds
|
|
53
|
+
- EAS Submit for store submissions
|
|
54
|
+
- EAS Update for OTA updates (non-native changes)
|
|
55
|
+
- GitHub Actions for lint/test/typecheck on PRs
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Git workflow and commit conventions
|
|
3
|
+
globs: ""
|
|
4
|
+
alwaysApply: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Git Workflow
|
|
8
|
+
|
|
9
|
+
## Commit Messages
|
|
10
|
+
Follow Conventional Commits:
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
<type>(<scope>): <description>
|
|
14
|
+
|
|
15
|
+
[optional body]
|
|
16
|
+
|
|
17
|
+
[optional footer(s)]
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Types: `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `chore`, `ci`
|
|
21
|
+
|
|
22
|
+
Examples:
|
|
23
|
+
- `feat(auth): add biometric login support`
|
|
24
|
+
- `fix(navigation): prevent double-tap on tab bar`
|
|
25
|
+
- `perf(list): switch FlatList to FlashList for feed`
|
|
26
|
+
|
|
27
|
+
## Branch Naming
|
|
28
|
+
- Feature: `feat/short-description`
|
|
29
|
+
- Fix: `fix/issue-number-description`
|
|
30
|
+
- Chore: `chore/description`
|
|
31
|
+
|
|
32
|
+
## PR Guidelines
|
|
33
|
+
- Keep PRs under 400 lines when possible
|
|
34
|
+
- One logical change per PR
|
|
35
|
+
- Include screenshots/recordings for UI changes
|
|
36
|
+
- Update tests for changed behavior
|
|
37
|
+
- Run full test suite before requesting review
|
|
38
|
+
|
|
39
|
+
## Hooks Integration
|
|
40
|
+
The `pre-commit-lint.js` hook runs ESLint + Prettier on staged files before commit.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Expo Router navigation conventions and patterns
|
|
3
|
+
globs: "app/**/*.{ts,tsx}"
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Navigation
|
|
8
|
+
|
|
9
|
+
## Expo Router File Conventions
|
|
10
|
+
- File-based routing in `app/` directory
|
|
11
|
+
- `_layout.tsx` for layout definitions (Stack, Tabs, Drawer)
|
|
12
|
+
- `[param].tsx` for dynamic routes
|
|
13
|
+
- `[...catchAll].tsx` for catch-all routes
|
|
14
|
+
- `+not-found.tsx` for 404 handling
|
|
15
|
+
- `(group)` parentheses for layout groups (no URL impact)
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
app/
|
|
19
|
+
_layout.tsx # Root layout (Stack)
|
|
20
|
+
index.tsx # / (home)
|
|
21
|
+
(tabs)/
|
|
22
|
+
_layout.tsx # Tab layout
|
|
23
|
+
home.tsx # /home tab
|
|
24
|
+
profile.tsx # /profile tab
|
|
25
|
+
(auth)/
|
|
26
|
+
_layout.tsx # Auth stack (no tabs)
|
|
27
|
+
login.tsx # /login
|
|
28
|
+
register.tsx # /register
|
|
29
|
+
settings/
|
|
30
|
+
_layout.tsx # Settings stack
|
|
31
|
+
index.tsx # /settings
|
|
32
|
+
[section].tsx # /settings/notifications
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Typed Routes
|
|
36
|
+
- Use `href` with type-safe route paths
|
|
37
|
+
- Define route params with `useLocalSearchParams<{ id: string }>()`
|
|
38
|
+
- Use `router.push()` / `router.replace()` / `router.back()`
|
|
39
|
+
- Prefer `<Link>` component for declarative navigation
|
|
40
|
+
|
|
41
|
+
## Deep Linking
|
|
42
|
+
- Define scheme in `app.json` (`expo.scheme`)
|
|
43
|
+
- Map deep links to file routes
|
|
44
|
+
- Validate incoming URLs before navigating
|
|
45
|
+
- Test deep links: `npx uri-scheme open [url] --ios/--android`
|
|
46
|
+
|
|
47
|
+
## Modal Patterns
|
|
48
|
+
- Use `presentation: 'modal'` in layout options
|
|
49
|
+
- Full-screen modals: separate route in layout group
|
|
50
|
+
- Bottom sheets: `@gorhom/bottom-sheet` (not navigation)
|
|
51
|
+
|
|
52
|
+
## Best Practices
|
|
53
|
+
- Keep navigation state minimal (pass IDs, not full objects)
|
|
54
|
+
- Prefetch data for likely next screens
|
|
55
|
+
- Use `initialRouteName` for proper back navigation
|
|
56
|
+
- Handle "not found" routes gracefully
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: React Native architectural patterns and best practices
|
|
3
|
+
globs: "**/*.{ts,tsx}"
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Patterns
|
|
8
|
+
|
|
9
|
+
## State Management
|
|
10
|
+
- **Client state**: Zustand (simple) or Jotai (atomic) — NOT Redux for new projects
|
|
11
|
+
- **Server state**: TanStack Query (React Query) — handles caching, refetching, optimistic updates
|
|
12
|
+
- **Form state**: React Hook Form or controlled components (small forms)
|
|
13
|
+
- Avoid prop drilling beyond 2 levels — use Zustand store or composition
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
// GOOD: Zustand store
|
|
17
|
+
const useAuthStore = create<AuthState>((set) => ({
|
|
18
|
+
user: null,
|
|
19
|
+
setUser: (user) => set({ user }),
|
|
20
|
+
logout: () => set({ user: null }),
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
// BAD: Deep prop drilling
|
|
24
|
+
<App user={user}>
|
|
25
|
+
<Layout user={user}>
|
|
26
|
+
<Header user={user}>
|
|
27
|
+
<Avatar user={user} />
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Component Patterns
|
|
31
|
+
- **Compound components** for complex UI (Header + Body + Footer)
|
|
32
|
+
- **Custom hooks** for shared logic (extract `useAuth`, `useTheme`)
|
|
33
|
+
- **Colocation**: keep feature files together
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
features/
|
|
37
|
+
auth/
|
|
38
|
+
LoginScreen.tsx
|
|
39
|
+
useAuth.ts
|
|
40
|
+
auth.store.ts
|
|
41
|
+
auth.test.tsx
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
- **Render props / children** for flexible containers
|
|
45
|
+
- **forwardRef** for imperative handles (scroll, focus)
|
|
46
|
+
|
|
47
|
+
## Data Fetching
|
|
48
|
+
- TanStack Query for all API calls
|
|
49
|
+
- Define query keys as constants (`['users', userId]`)
|
|
50
|
+
- Use `queryClient.prefetchQuery` for anticipated navigation
|
|
51
|
+
- Optimistic updates for user-initiated mutations
|
|
52
|
+
- Error boundaries per screen (not global)
|
|
53
|
+
|
|
54
|
+
## Error Handling
|
|
55
|
+
- Error boundaries at screen level (catch rendering crashes)
|
|
56
|
+
- Try/catch at API call level (handle network errors)
|
|
57
|
+
- Graceful degradation (offline placeholder, retry button)
|
|
58
|
+
- Report errors to monitoring (Sentry/Crashlytics)
|
|
59
|
+
- Never swallow errors silently
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: React Native performance optimization rules
|
|
3
|
+
globs: "**/*.{ts,tsx}"
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Performance
|
|
8
|
+
|
|
9
|
+
## Rendering
|
|
10
|
+
- Use `React.memo` on components rendered in lists or receiving stable props
|
|
11
|
+
- Wrap callbacks with `useCallback` when passed to memoized children
|
|
12
|
+
- Use `useMemo` for expensive computations (sorting, filtering large arrays)
|
|
13
|
+
- Never define functions or objects inline in JSX within loops/lists
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
// GOOD
|
|
17
|
+
const renderItem = useCallback(({ item }: { item: User }) => (
|
|
18
|
+
<UserRow user={item} onPress={handlePress} />
|
|
19
|
+
), [handlePress]);
|
|
20
|
+
|
|
21
|
+
// BAD — creates new object every render
|
|
22
|
+
<FlatList renderItem={({ item }) => <UserRow user={item} />} />
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Lists
|
|
26
|
+
- `FlatList` or `FlashList` for lists > 20 items (never ScrollView)
|
|
27
|
+
- Set `keyExtractor` with stable, unique keys
|
|
28
|
+
- Use `getItemLayout` when item heights are fixed
|
|
29
|
+
- Configure `windowSize`, `maxToRenderPerBatch`, `initialNumToRender`
|
|
30
|
+
- `removeClippedSubviews={true}` for long lists on Android
|
|
31
|
+
|
|
32
|
+
## Images
|
|
33
|
+
- Use `expo-image` (not `<Image>` or react-native-fast-image)
|
|
34
|
+
- Set explicit `width` and `height` (avoid layout shifts)
|
|
35
|
+
- Use `contentFit="cover"` and `placeholder` for loading states
|
|
36
|
+
- Optimize source images (WebP, appropriate resolution)
|
|
37
|
+
- Use `cachePolicy="memory-disk"` for frequently accessed images
|
|
38
|
+
|
|
39
|
+
## Bundle Size
|
|
40
|
+
- Import specific modules, not entire packages (`lodash/get` not `lodash`)
|
|
41
|
+
- Use `React.lazy` + `Suspense` for code splitting heavy screens
|
|
42
|
+
- Analyze bundle with `npx react-native-bundle-visualizer`
|
|
43
|
+
- Target < 5MB JS bundle for production
|
|
44
|
+
|
|
45
|
+
## Animations
|
|
46
|
+
- Use `react-native-reanimated` for all animations (not Animated API)
|
|
47
|
+
- Run animations on UI thread via worklets
|
|
48
|
+
- Never read shared values from JS thread in hot paths
|
|
49
|
+
- Use `useAnimatedStyle` instead of inline animated styles
|
|
50
|
+
|
|
51
|
+
## Startup
|
|
52
|
+
- Use Hermes engine (enabled by default in Expo SDK 50+)
|
|
53
|
+
- Inline requires for heavy modules (`require('heavy-lib')` inside function)
|
|
54
|
+
- Minimize `useEffect` chains on app startup
|
|
55
|
+
- Defer non-critical initialization with `InteractionManager.runAfterInteractions`
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Mobile security rules for React Native applications
|
|
3
|
+
globs: "**/*.{ts,tsx,js,jsx}"
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Security
|
|
8
|
+
|
|
9
|
+
## Secrets Management
|
|
10
|
+
- NEVER hardcode API keys, tokens, or secrets in JS code
|
|
11
|
+
- Use `expo-secure-store` for sensitive data (NOT AsyncStorage)
|
|
12
|
+
- Environment variables via `.env` files (excluded from git)
|
|
13
|
+
- Build-time secrets via EAS Secrets (for CI/CD)
|
|
14
|
+
- Runtime secrets via secure backend API
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
// GOOD
|
|
18
|
+
import * as SecureStore from 'expo-secure-store';
|
|
19
|
+
const token = await SecureStore.getItemAsync('auth_token');
|
|
20
|
+
|
|
21
|
+
// BAD
|
|
22
|
+
const API_KEY = 'sk-1234567890abcdef';
|
|
23
|
+
await AsyncStorage.setItem('auth_token', token);
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Deep Linking
|
|
27
|
+
- Validate ALL incoming deep link URLs before navigation
|
|
28
|
+
- Whitelist allowed hosts and paths
|
|
29
|
+
- Never pass deep link params directly to sensitive operations
|
|
30
|
+
- Sanitize query parameters
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
// GOOD
|
|
34
|
+
function handleDeepLink(url: string) {
|
|
35
|
+
const parsed = Linking.parse(url);
|
|
36
|
+
if (ALLOWED_HOSTS.includes(parsed.hostname)) {
|
|
37
|
+
router.push(parsed.path);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Network Security
|
|
43
|
+
- HTTPS only — no HTTP requests
|
|
44
|
+
- Certificate pinning for critical API endpoints
|
|
45
|
+
- Timeout all network requests (15s default)
|
|
46
|
+
- Handle network errors gracefully (offline mode)
|
|
47
|
+
|
|
48
|
+
## WebView
|
|
49
|
+
- Always set `originWhitelist` (never `['*']` in production)
|
|
50
|
+
- Disable JavaScript if not needed
|
|
51
|
+
- Never load untrusted URLs
|
|
52
|
+
- Use `onShouldStartLoadWithRequest` to filter navigation
|
|
53
|
+
|
|
54
|
+
## Input Validation
|
|
55
|
+
- Sanitize all user input before display (XSS prevention)
|
|
56
|
+
- Validate form data on client AND server
|
|
57
|
+
- Use parameterized queries (never string concatenation for queries)
|
|
58
|
+
- Limit input lengths to prevent abuse
|
|
59
|
+
|
|
60
|
+
## Data Storage
|
|
61
|
+
- Sensitive data: `expo-secure-store` (Keychain/Keystore)
|
|
62
|
+
- Non-sensitive preferences: `AsyncStorage` or `expo-file-system`
|
|
63
|
+
- Never store PII in logs or crash reports
|
|
64
|
+
- Clear sensitive data on logout
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: State management guidelines — Zustand for client, TanStack Query for server
|
|
3
|
+
globs: "**/*.{ts,tsx}"
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# State Management
|
|
8
|
+
|
|
9
|
+
## Architecture
|
|
10
|
+
|
|
11
|
+
| State Type | Tool | When |
|
|
12
|
+
|-----------|------|------|
|
|
13
|
+
| **Client state** | Zustand | UI state, user preferences, app mode |
|
|
14
|
+
| **Server state** | TanStack Query | API data, caching, pagination, optimistic updates |
|
|
15
|
+
| **Form state** | React Hook Form | Complex forms with validation |
|
|
16
|
+
| **Ephemeral state** | useState | Component-local, non-shared |
|
|
17
|
+
| **Derived state** | useMemo | Computed from other state |
|
|
18
|
+
|
|
19
|
+
## Zustand Patterns
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
// Store definition — one store per domain
|
|
23
|
+
interface AuthStore {
|
|
24
|
+
user: User | null;
|
|
25
|
+
token: string | null;
|
|
26
|
+
setUser: (user: User) => void;
|
|
27
|
+
logout: () => void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const useAuthStore = create<AuthStore>((set) => ({
|
|
31
|
+
user: null,
|
|
32
|
+
token: null,
|
|
33
|
+
setUser: (user) => set({ user }),
|
|
34
|
+
logout: () => set({ user: null, token: null }),
|
|
35
|
+
}));
|
|
36
|
+
|
|
37
|
+
// Usage — select only what you need
|
|
38
|
+
const userName = useAuthStore((s) => s.user?.name);
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## TanStack Query Patterns
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
// Query keys as constants
|
|
45
|
+
export const userKeys = {
|
|
46
|
+
all: ['users'] as const,
|
|
47
|
+
detail: (id: string) => ['users', id] as const,
|
|
48
|
+
lists: () => ['users', 'list'] as const,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Query hook
|
|
52
|
+
function useUser(id: string) {
|
|
53
|
+
return useQuery({
|
|
54
|
+
queryKey: userKeys.detail(id),
|
|
55
|
+
queryFn: () => api.getUser(id),
|
|
56
|
+
staleTime: 5 * 60 * 1000,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Mutation with optimistic update
|
|
61
|
+
function useUpdateUser() {
|
|
62
|
+
const queryClient = useQueryClient();
|
|
63
|
+
return useMutation({
|
|
64
|
+
mutationFn: api.updateUser,
|
|
65
|
+
onMutate: async (updated) => {
|
|
66
|
+
await queryClient.cancelQueries({ queryKey: userKeys.detail(updated.id) });
|
|
67
|
+
const previous = queryClient.getQueryData(userKeys.detail(updated.id));
|
|
68
|
+
queryClient.setQueryData(userKeys.detail(updated.id), updated);
|
|
69
|
+
return { previous };
|
|
70
|
+
},
|
|
71
|
+
onError: (err, vars, context) => {
|
|
72
|
+
queryClient.setQueryData(userKeys.detail(vars.id), context?.previous);
|
|
73
|
+
},
|
|
74
|
+
onSettled: (data, err, vars) => {
|
|
75
|
+
queryClient.invalidateQueries({ queryKey: userKeys.detail(vars.id) });
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Rules
|
|
82
|
+
- Context API only for truly global, rarely-changing values (theme, locale)
|
|
83
|
+
- No prop drilling beyond 2 component levels
|
|
84
|
+
- Keep stores small and domain-focused
|
|
85
|
+
- Never store derived data — compute with `useMemo` or selectors
|
|
86
|
+
- Persist critical state with `zustand/middleware` persist
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Testing standards for React Native projects
|
|
3
|
+
globs: "**/*.test.{ts,tsx}"
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Testing
|
|
8
|
+
|
|
9
|
+
## Stack
|
|
10
|
+
- **Unit/Component**: Jest + React Native Testing Library (RNTL)
|
|
11
|
+
- **E2E**: Detox (with EAS Build for Expo projects)
|
|
12
|
+
- **Coverage**: Jest built-in, target 80% lines, 70% branches
|
|
13
|
+
|
|
14
|
+
## Principles
|
|
15
|
+
- Test behavior, not implementation
|
|
16
|
+
- Query by role, text, or label — avoid testID unless necessary
|
|
17
|
+
- One logical assertion per test
|
|
18
|
+
- Mock at boundaries (API, native modules), not internals
|
|
19
|
+
- No snapshot tests as primary strategy (smoke checks only)
|
|
20
|
+
|
|
21
|
+
## Component Tests
|
|
22
|
+
```tsx
|
|
23
|
+
// GOOD: Tests behavior
|
|
24
|
+
test('disables submit when form is invalid', () => {
|
|
25
|
+
render(<LoginForm />);
|
|
26
|
+
const button = screen.getByRole('button', { name: 'Submit' });
|
|
27
|
+
expect(button).toBeDisabled();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// BAD: Tests implementation
|
|
31
|
+
test('sets isValid state to false', () => {
|
|
32
|
+
const { result } = renderHook(() => useForm());
|
|
33
|
+
expect(result.current.isValid).toBe(false);
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Mocking
|
|
38
|
+
```tsx
|
|
39
|
+
// Mock native module
|
|
40
|
+
jest.mock('expo-secure-store', () => ({
|
|
41
|
+
getItemAsync: jest.fn().mockResolvedValue('token'),
|
|
42
|
+
setItemAsync: jest.fn(),
|
|
43
|
+
}));
|
|
44
|
+
|
|
45
|
+
// Mock navigation
|
|
46
|
+
jest.mock('expo-router', () => ({
|
|
47
|
+
useRouter: () => ({ push: jest.fn(), back: jest.fn() }),
|
|
48
|
+
useLocalSearchParams: () => ({}),
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
// Mock API — use MSW or manual mock
|
|
52
|
+
const server = setupServer(
|
|
53
|
+
rest.get('/api/users', (req, res, ctx) => res(ctx.json([]))),
|
|
54
|
+
);
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## File Organization
|
|
58
|
+
- Tests adjacent to source: `UserCard.test.tsx` next to `UserCard.tsx`
|
|
59
|
+
- Or in `__tests__/` directory at feature level
|
|
60
|
+
- Shared test utilities in `tests/helpers/`
|
|
61
|
+
- E2E tests in `e2e/` directory at project root
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Expo managed workflow coding conventions
|
|
3
|
+
globs: "**/*.{ts,tsx,js,jsx}"
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Expo Coding Style
|
|
8
|
+
|
|
9
|
+
## SDK Module Preference
|
|
10
|
+
- Always prefer Expo SDK modules over community alternatives
|
|
11
|
+
- `expo-image` over `react-native-fast-image`
|
|
12
|
+
- `expo-file-system` over `react-native-fs`
|
|
13
|
+
- `expo-camera` over `react-native-camera`
|
|
14
|
+
- `expo-notifications` over `react-native-push-notification`
|
|
15
|
+
- `expo-secure-store` over `react-native-keychain`
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
// GOOD — Expo SDK module
|
|
19
|
+
import * as FileSystem from 'expo-file-system';
|
|
20
|
+
const content = await FileSystem.readAsStringAsync(uri);
|
|
21
|
+
|
|
22
|
+
// BAD — community alternative in Expo project
|
|
23
|
+
import RNFS from 'react-native-fs';
|
|
24
|
+
const content = await RNFS.readFile(path);
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Config Plugins Over Manual Native Edits
|
|
28
|
+
- NEVER modify `ios/` or `android/` directories directly in managed workflow
|
|
29
|
+
- Use config plugins for native configuration
|
|
30
|
+
- Run `npx expo prebuild --clean` to regenerate native projects
|
|
31
|
+
- Add native config in `app.config.ts` with plugins array
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
// app.config.ts
|
|
35
|
+
export default ({ config }) => ({
|
|
36
|
+
...config,
|
|
37
|
+
plugins: [
|
|
38
|
+
['expo-camera', { cameraPermission: 'Allow camera for scanning' }],
|
|
39
|
+
['expo-location', { locationAlwaysAndWhenInUsePermission: 'Allow location' }],
|
|
40
|
+
'./plugins/custom-splash.js',
|
|
41
|
+
],
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Expo Router Conventions
|
|
46
|
+
- Use file-based routing exclusively (no manual route registration)
|
|
47
|
+
- Layout files (`_layout.tsx`) define navigation structure
|
|
48
|
+
- Use typed routes for navigation
|
|
49
|
+
- Group routes with parentheses for logical organization
|
|
50
|
+
|
|
51
|
+
## Module Resolution
|
|
52
|
+
- Use `expo-modules-core` for creating native modules
|
|
53
|
+
- Prefer `expo-constants` for accessing build-time configuration
|
|
54
|
+
- Use `expo-updates` API for checking/fetching OTA updates
|