qualia-framework 2.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.
Files changed (261) hide show
  1. package/README.md +50 -0
  2. package/bin/cli.js +519 -0
  3. package/framework/agents/architecture-strategist.md +53 -0
  4. package/framework/agents/backend-agent.md +150 -0
  5. package/framework/agents/code-simplicity-reviewer.md +86 -0
  6. package/framework/agents/frontend-agent.md +111 -0
  7. package/framework/agents/kieran-typescript-reviewer.md +96 -0
  8. package/framework/agents/performance-oracle.md +111 -0
  9. package/framework/agents/qualia-codebase-mapper.md +760 -0
  10. package/framework/agents/qualia-debugger.md +1203 -0
  11. package/framework/agents/qualia-executor.md +881 -0
  12. package/framework/agents/qualia-integration-checker.md +423 -0
  13. package/framework/agents/qualia-phase-researcher.md +453 -0
  14. package/framework/agents/qualia-plan-checker.md +699 -0
  15. package/framework/agents/qualia-planner.md +1241 -0
  16. package/framework/agents/qualia-project-researcher.md +602 -0
  17. package/framework/agents/qualia-research-synthesizer.md +236 -0
  18. package/framework/agents/qualia-roadmapper.md +605 -0
  19. package/framework/agents/qualia-verifier.md +685 -0
  20. package/framework/agents/team-orchestrator.md +228 -0
  21. package/framework/agents/teams/full-stack-team.md +48 -0
  22. package/framework/agents/teams/optimize-team.md +53 -0
  23. package/framework/agents/teams/review-team.md +62 -0
  24. package/framework/agents/teams/ship-team.md +86 -0
  25. package/framework/agents/test-agent.md +182 -0
  26. package/framework/askpass.sh +2 -0
  27. package/framework/commands/design.md +53 -0
  28. package/framework/commands/quick-db.md +22 -0
  29. package/framework/config/retention.json +35 -0
  30. package/framework/core/PRINCIPLES.md +77 -0
  31. package/framework/hooks/auto-format.sh +45 -0
  32. package/framework/hooks/block-env-edit.sh +42 -0
  33. package/framework/hooks/branch-guard.sh +46 -0
  34. package/framework/hooks/confirm-delete.sh +56 -0
  35. package/framework/hooks/migration-validate.sh +68 -0
  36. package/framework/hooks/notification-speak.sh +15 -0
  37. package/framework/hooks/pre-commit.sh +80 -0
  38. package/framework/hooks/pre-compact.sh +55 -0
  39. package/framework/hooks/pre-deploy-gate.sh +151 -0
  40. package/framework/hooks/qualia-colors.sh +32 -0
  41. package/framework/hooks/retention-cleanup.sh +43 -0
  42. package/framework/hooks/save-session-state.sh +153 -0
  43. package/framework/hooks/session-context-loader.sh +28 -0
  44. package/framework/hooks/session-learn.sh +30 -0
  45. package/framework/knowledge/claudecode-bible.md +1384 -0
  46. package/framework/knowledge/client-prefs.md +22 -0
  47. package/framework/knowledge/common-fixes.md +25 -0
  48. package/framework/knowledge/deployment-map.md +35 -0
  49. package/framework/knowledge/email-signature.html +1 -0
  50. package/framework/knowledge/employees.md +8 -0
  51. package/framework/knowledge/learned-patterns.md +51 -0
  52. package/framework/knowledge/optimization-research-2026.md +137 -0
  53. package/framework/knowledge/qualia-context.md +67 -0
  54. package/framework/knowledge/supabase-patterns.md +50 -0
  55. package/framework/knowledge/voice-agent-patterns.md +46 -0
  56. package/framework/qualia-engine/VERSION +1 -0
  57. package/framework/qualia-engine/bin/qualia-tools.js +2160 -0
  58. package/framework/qualia-engine/bin/qualia-tools.test.js +1054 -0
  59. package/framework/qualia-engine/references/checkpoints.md +775 -0
  60. package/framework/qualia-engine/references/continuation-format.md +249 -0
  61. package/framework/qualia-engine/references/decimal-phase-calculation.md +65 -0
  62. package/framework/qualia-engine/references/design-quality.md +56 -0
  63. package/framework/qualia-engine/references/git-integration.md +254 -0
  64. package/framework/qualia-engine/references/git-planning-commit.md +50 -0
  65. package/framework/qualia-engine/references/model-profile-resolution.md +32 -0
  66. package/framework/qualia-engine/references/model-profiles.md +73 -0
  67. package/framework/qualia-engine/references/phase-argument-parsing.md +61 -0
  68. package/framework/qualia-engine/references/planning-config.md +195 -0
  69. package/framework/qualia-engine/references/questioning.md +141 -0
  70. package/framework/qualia-engine/references/tdd.md +263 -0
  71. package/framework/qualia-engine/references/ui-brand.md +160 -0
  72. package/framework/qualia-engine/references/verification-patterns.md +612 -0
  73. package/framework/qualia-engine/templates/DEBUG.md +159 -0
  74. package/framework/qualia-engine/templates/DESIGN.md +81 -0
  75. package/framework/qualia-engine/templates/UAT.md +247 -0
  76. package/framework/qualia-engine/templates/codebase/architecture.md +255 -0
  77. package/framework/qualia-engine/templates/codebase/concerns.md +310 -0
  78. package/framework/qualia-engine/templates/codebase/conventions.md +307 -0
  79. package/framework/qualia-engine/templates/codebase/integrations.md +280 -0
  80. package/framework/qualia-engine/templates/codebase/stack.md +186 -0
  81. package/framework/qualia-engine/templates/codebase/structure.md +285 -0
  82. package/framework/qualia-engine/templates/codebase/testing.md +480 -0
  83. package/framework/qualia-engine/templates/config.json +35 -0
  84. package/framework/qualia-engine/templates/context.md +283 -0
  85. package/framework/qualia-engine/templates/continue-here.md +78 -0
  86. package/framework/qualia-engine/templates/debug-subagent-prompt.md +91 -0
  87. package/framework/qualia-engine/templates/discovery.md +146 -0
  88. package/framework/qualia-engine/templates/milestone-archive.md +123 -0
  89. package/framework/qualia-engine/templates/milestone.md +115 -0
  90. package/framework/qualia-engine/templates/phase-prompt.md +567 -0
  91. package/framework/qualia-engine/templates/planner-subagent-prompt.md +117 -0
  92. package/framework/qualia-engine/templates/project.md +184 -0
  93. package/framework/qualia-engine/templates/projects/ai-agent.md +156 -0
  94. package/framework/qualia-engine/templates/projects/mobile-app.md +181 -0
  95. package/framework/qualia-engine/templates/projects/voice-agent.md +134 -0
  96. package/framework/qualia-engine/templates/projects/website.md +137 -0
  97. package/framework/qualia-engine/templates/requirements.md +231 -0
  98. package/framework/qualia-engine/templates/research-project/ARCHITECTURE.md +204 -0
  99. package/framework/qualia-engine/templates/research-project/FEATURES.md +147 -0
  100. package/framework/qualia-engine/templates/research-project/PITFALLS.md +200 -0
  101. package/framework/qualia-engine/templates/research-project/STACK.md +120 -0
  102. package/framework/qualia-engine/templates/research-project/SUMMARY.md +170 -0
  103. package/framework/qualia-engine/templates/research.md +552 -0
  104. package/framework/qualia-engine/templates/roadmap.md +202 -0
  105. package/framework/qualia-engine/templates/state.md +176 -0
  106. package/framework/qualia-engine/templates/summary-complex.md +59 -0
  107. package/framework/qualia-engine/templates/summary-minimal.md +41 -0
  108. package/framework/qualia-engine/templates/summary-standard.md +48 -0
  109. package/framework/qualia-engine/templates/summary.md +246 -0
  110. package/framework/qualia-engine/templates/user-setup.md +311 -0
  111. package/framework/qualia-engine/templates/verification-report.md +322 -0
  112. package/framework/qualia-engine/workflows/add-phase.md +179 -0
  113. package/framework/qualia-engine/workflows/add-todo.md +157 -0
  114. package/framework/qualia-engine/workflows/audit-milestone.md +241 -0
  115. package/framework/qualia-engine/workflows/check-todos.md +176 -0
  116. package/framework/qualia-engine/workflows/complete-milestone.md +858 -0
  117. package/framework/qualia-engine/workflows/diagnose-issues.md +219 -0
  118. package/framework/qualia-engine/workflows/discovery-phase.md +289 -0
  119. package/framework/qualia-engine/workflows/discuss-phase.md +534 -0
  120. package/framework/qualia-engine/workflows/execute-phase.md +559 -0
  121. package/framework/qualia-engine/workflows/execute-plan.md +438 -0
  122. package/framework/qualia-engine/workflows/help.md +470 -0
  123. package/framework/qualia-engine/workflows/insert-phase.md +220 -0
  124. package/framework/qualia-engine/workflows/list-phase-assumptions.md +178 -0
  125. package/framework/qualia-engine/workflows/map-codebase.md +327 -0
  126. package/framework/qualia-engine/workflows/new-milestone.md +363 -0
  127. package/framework/qualia-engine/workflows/new-project.md +1037 -0
  128. package/framework/qualia-engine/workflows/pause-work.md +122 -0
  129. package/framework/qualia-engine/workflows/plan-milestone-gaps.md +256 -0
  130. package/framework/qualia-engine/workflows/plan-phase.md +422 -0
  131. package/framework/qualia-engine/workflows/progress.md +354 -0
  132. package/framework/qualia-engine/workflows/quick.md +252 -0
  133. package/framework/qualia-engine/workflows/remove-phase.md +326 -0
  134. package/framework/qualia-engine/workflows/research-phase.md +74 -0
  135. package/framework/qualia-engine/workflows/resume-project.md +306 -0
  136. package/framework/qualia-engine/workflows/set-profile.md +80 -0
  137. package/framework/qualia-engine/workflows/settings.md +145 -0
  138. package/framework/qualia-engine/workflows/transition.md +556 -0
  139. package/framework/qualia-engine/workflows/update.md +197 -0
  140. package/framework/qualia-engine/workflows/verify-phase.md +195 -0
  141. package/framework/qualia-engine/workflows/verify-work.md +625 -0
  142. package/framework/rules/context7.md +11 -0
  143. package/framework/rules/deployment.md +29 -0
  144. package/framework/rules/frontend.md +33 -0
  145. package/framework/rules/security.md +12 -0
  146. package/framework/rules/speed.md +20 -0
  147. package/framework/scripts/__pycache__/say.cpython-314.pyc +0 -0
  148. package/framework/scripts/apply-retention.sh +120 -0
  149. package/framework/scripts/bootstrap-pop-os.sh +354 -0
  150. package/framework/scripts/claude-voice +13 -0
  151. package/framework/scripts/cleanup.sh +131 -0
  152. package/framework/scripts/cowork-mode.sh +141 -0
  153. package/framework/scripts/generate-project-claude-md.sh +153 -0
  154. package/framework/scripts/load-test-webhook.js +172 -0
  155. package/framework/scripts/say.py +236 -0
  156. package/framework/scripts/showcase-video-recorder/ffmpeg-builder.js +167 -0
  157. package/framework/scripts/showcase-video-recorder/playwright-helpers.js +216 -0
  158. package/framework/scripts/speak.py +55 -0
  159. package/framework/scripts/speak.sh +18 -0
  160. package/framework/scripts/status.sh +138 -0
  161. package/framework/scripts/sync-to-framework.sh +65 -0
  162. package/framework/scripts/voice-hotkey.py +227 -0
  163. package/framework/scripts/voice-input.sh +51 -0
  164. package/framework/skills/animate/SKILL.md +202 -0
  165. package/framework/skills/bolder/SKILL.md +144 -0
  166. package/framework/skills/browser-qa/SKILL.md +536 -0
  167. package/framework/skills/clarify/SKILL.md +179 -0
  168. package/framework/skills/colorize/SKILL.md +170 -0
  169. package/framework/skills/critique/SKILL.md +126 -0
  170. package/framework/skills/deep-research/SKILL.md +271 -0
  171. package/framework/skills/delight/SKILL.md +329 -0
  172. package/framework/skills/deploy/SKILL.md +261 -0
  173. package/framework/skills/deploy-verify/SKILL.md +377 -0
  174. package/framework/skills/deploy-verify/scripts/canary-check.sh +206 -0
  175. package/framework/skills/deploy-verify/scripts/check-console-errors.js +147 -0
  176. package/framework/skills/deploy-verify/scripts/check-cwv.js +139 -0
  177. package/framework/skills/deploy-verify/scripts/project-detect.sh +84 -0
  178. package/framework/skills/deploy-verify/scripts/verify.sh +548 -0
  179. package/framework/skills/design-quieter/SKILL.md +130 -0
  180. package/framework/skills/distill/SKILL.md +149 -0
  181. package/framework/skills/docs-lookup/SKILL.md +78 -0
  182. package/framework/skills/fcm-notifications/SKILL.md +125 -0
  183. package/framework/skills/financial-ledger/SKILL.md +1039 -0
  184. package/framework/skills/frontend-master/NOTICE.md +4 -0
  185. package/framework/skills/frontend-master/SKILL.md +127 -0
  186. package/framework/skills/frontend-master/reference/color-and-contrast.md +132 -0
  187. package/framework/skills/frontend-master/reference/interaction-design.md +123 -0
  188. package/framework/skills/frontend-master/reference/motion-design.md +99 -0
  189. package/framework/skills/frontend-master/reference/responsive-design.md +114 -0
  190. package/framework/skills/frontend-master/reference/spatial-design.md +100 -0
  191. package/framework/skills/frontend-master/reference/typography.md +131 -0
  192. package/framework/skills/frontend-master/reference/ux-writing.md +107 -0
  193. package/framework/skills/harden/SKILL.md +357 -0
  194. package/framework/skills/i18n-rtl/SKILL.md +752 -0
  195. package/framework/skills/learn/SKILL.md +71 -0
  196. package/framework/skills/memory/SKILL.md +50 -0
  197. package/framework/skills/mobile-expo/SKILL.md +864 -0
  198. package/framework/skills/mobile-expo/references/store-checklist.md +550 -0
  199. package/framework/skills/nestjs-backend/README.md +73 -0
  200. package/framework/skills/nestjs-backend/SKILL.md +446 -0
  201. package/framework/skills/nestjs-backend/references/templates.md +1173 -0
  202. package/framework/skills/normalize/SKILL.md +79 -0
  203. package/framework/skills/onboard/SKILL.md +242 -0
  204. package/framework/skills/polish/SKILL.md +209 -0
  205. package/framework/skills/pr/SKILL.md +66 -0
  206. package/framework/skills/qualia/SKILL.md +153 -0
  207. package/framework/skills/qualia-add-todo/SKILL.md +68 -0
  208. package/framework/skills/qualia-audit-milestone/SKILL.md +92 -0
  209. package/framework/skills/qualia-check-todos/SKILL.md +55 -0
  210. package/framework/skills/qualia-complete-milestone/SKILL.md +108 -0
  211. package/framework/skills/qualia-debug/SKILL.md +149 -0
  212. package/framework/skills/qualia-design/SKILL.md +203 -0
  213. package/framework/skills/qualia-discuss-phase/SKILL.md +72 -0
  214. package/framework/skills/qualia-execute-phase/SKILL.md +86 -0
  215. package/framework/skills/qualia-help/SKILL.md +67 -0
  216. package/framework/skills/qualia-idk/SKILL.md +352 -0
  217. package/framework/skills/qualia-list-phase-assumptions/SKILL.md +67 -0
  218. package/framework/skills/qualia-new-milestone/SKILL.md +72 -0
  219. package/framework/skills/qualia-new-project/SKILL.md +92 -0
  220. package/framework/skills/qualia-optimize/SKILL.md +417 -0
  221. package/framework/skills/qualia-pause-work/SKILL.md +96 -0
  222. package/framework/skills/qualia-plan-milestone-gaps/SKILL.md +57 -0
  223. package/framework/skills/qualia-plan-phase/SKILL.md +101 -0
  224. package/framework/skills/qualia-progress/SKILL.md +53 -0
  225. package/framework/skills/qualia-quick/SKILL.md +89 -0
  226. package/framework/skills/qualia-research-phase/SKILL.md +88 -0
  227. package/framework/skills/qualia-resume-work/SKILL.md +62 -0
  228. package/framework/skills/qualia-review/SKILL.md +263 -0
  229. package/framework/skills/qualia-start/SKILL.md +182 -0
  230. package/framework/skills/qualia-verify-work/SKILL.md +105 -0
  231. package/framework/skills/qualia-workflow/SKILL.md +130 -0
  232. package/framework/skills/rag/SKILL.md +750 -0
  233. package/framework/skills/responsive/SKILL.md +231 -0
  234. package/framework/skills/retro/SKILL.md +284 -0
  235. package/framework/skills/sakani-conventions/SKILL.md +136 -0
  236. package/framework/skills/sakani-conventions/evals/evals.json +23 -0
  237. package/framework/skills/sakani-conventions/references/entities.md +365 -0
  238. package/framework/skills/sakani-conventions/references/error-codes.md +95 -0
  239. package/framework/skills/seo-master/SKILL.md +490 -0
  240. package/framework/skills/seo-master/references/checklist.md +199 -0
  241. package/framework/skills/seo-master/references/structured-data.md +609 -0
  242. package/framework/skills/ship/SKILL.md +202 -0
  243. package/framework/skills/stack-researcher/SKILL.md +215 -0
  244. package/framework/skills/status/SKILL.md +154 -0
  245. package/framework/skills/status/scripts/health-check.sh +562 -0
  246. package/framework/skills/subscription-payments/SKILL.md +250 -0
  247. package/framework/skills/supabase/SKILL.md +973 -0
  248. package/framework/skills/supabase/references/templates.md +159 -0
  249. package/framework/skills/team/SKILL.md +67 -0
  250. package/framework/skills/test-runner/SKILL.md +202 -0
  251. package/framework/skills/voice-agent/SKILL.md +407 -0
  252. package/framework/skills/zoho-workflow/SKILL.md +51 -0
  253. package/framework/statusline-command.sh +117 -0
  254. package/package.json +24 -0
  255. package/profiles/fawzi.json +16 -0
  256. package/profiles/hasan.json +16 -0
  257. package/profiles/moayad.json +16 -0
  258. package/templates/CLAUDE-owner.md +52 -0
  259. package/templates/CLAUDE.md.hbs +58 -0
  260. package/templates/env.claude.template +12 -0
  261. package/templates/settings.json +141 -0
@@ -0,0 +1,864 @@
1
+ ---
2
+ name: mobile-expo
3
+ description: "React Native Expo mobile app development — project setup, navigation (Expo Router), native modules, push notifications, secure storage, app store submission, and mobile-specific patterns. Use whenever building or modifying a React Native or Expo mobile app, creating screens, navigation flows, native integrations, or preparing for iOS/Android store submission. Triggers on: React Native, Expo, mobile app, iOS, Android, app store, screen, navigation, native module, mobile UI, push notification mobile, secure storage, EAS build."
4
+ tags: [react-native, expo, mobile, ios, android]
5
+ ---
6
+
7
+ # React Native Expo Mobile Development
8
+
9
+ This skill covers React Native Expo mobile app development for Qualia workflow projects.
10
+
11
+ ## Project Setup
12
+
13
+ ### Expo SDK 52+
14
+ - Always use latest stable Expo SDK (52+)
15
+ - TypeScript is mandatory for all projects
16
+ - Use Expo Router for file-based routing (like Next.js)
17
+ - Configured via `app.json` or `app.config.ts`
18
+
19
+ ### Directory Structure
20
+
21
+ ```
22
+ app/
23
+ ├── (auth)/ # Protected routes (require authentication)
24
+ │ ├── (tabs)/ # Bottom tab navigator (protected)
25
+ │ │ ├── home.tsx
26
+ │ │ ├── units.tsx
27
+ │ │ └── profile.tsx
28
+ │ │ └── _layout.tsx # Tab layout configuration
29
+ │ ├── _layout.tsx # Auth wrapper layout
30
+ │ └── [route].tsx # Catch-all protected route
31
+ ├── (public)/ # Unauthenticated screens
32
+ │ ├── login.tsx
33
+ │ ├── register.tsx
34
+ │ ├── verify-otp.tsx
35
+ │ └── _layout.tsx
36
+ ├── _layout.tsx # Root layout
37
+ ├── index.tsx # Root screen (can redirect)
38
+ └── +not-found.tsx # 404 screen
39
+
40
+ components/
41
+ ├── ui/ # Reusable, unstyled UI primitives
42
+ │ ├── Button.tsx
43
+ │ ├── Input.tsx
44
+ │ ├── Modal.tsx
45
+ │ └── Card.tsx
46
+ ├── forms/ # Form-specific components
47
+ │ ├── LoginForm.tsx
48
+ │ ├── OTPForm.tsx
49
+ │ └── ProfileForm.tsx
50
+ ├── auth/ # Auth-related components
51
+ │ ├── ProtectedRoute.tsx
52
+ │ └── AuthGuard.tsx
53
+ └── [feature]/ # Feature-specific components
54
+ ├── UnitsCard.tsx
55
+ └── UnitsList.tsx
56
+
57
+ lib/
58
+ ├── api/ # API client layer
59
+ │ ├── client.ts # Axios/fetch with interceptors
60
+ │ ├── endpoints.ts # API endpoint definitions
61
+ │ └── schemas.ts # Zod schemas (shared with backend)
62
+ ├── auth/ # Authentication logic
63
+ │ ├── context.tsx # Auth context provider
64
+ │ ├── tokens.ts # Token management
65
+ │ └── useAuth.ts # Auth hook
66
+ ├── storage/ # Secure storage wrapper
67
+ │ ├── secureStore.ts # expo-secure-store wrapper
68
+ │ └── useSecureStorage.ts # Custom hook
69
+ ├── i18n/ # Internationalization (if needed)
70
+ │ └── i18n.ts
71
+ ├── utils/ # Helper functions
72
+ └── constants.ts # App-wide constants
73
+
74
+ hooks/
75
+ ├── useAuthCheck.ts # Check if user is authenticated
76
+ ├── useNavigation.ts # Type-safe navigation hook
77
+ ├── useFetch.ts # Custom hook for API calls
78
+ └── useKeyboard.ts # Keyboard handling
79
+
80
+ constants/
81
+ ├── theme.ts # Colors, typography, spacing
82
+ └── config.ts # API base URL, feature flags
83
+
84
+ assets/
85
+ ├── icons/
86
+ ├── images/
87
+ └── animations/
88
+
89
+ App.tsx or app.json # Entry point configuration
90
+ ```
91
+
92
+ ## Navigation with Expo Router
93
+
94
+ ### File-Based Routing
95
+ - Expo Router uses file system paths as routes
96
+ - `app/home.tsx` → `/home` route
97
+ - `app/(tabs)/home.tsx` → `/home` with tab layout
98
+ - `app/(auth)/profile.tsx` → Protected `/profile` route
99
+ - Deep linking is automatic with proper configuration
100
+
101
+ ### Layout Groups
102
+ Layout groups with parentheses don't create route segments:
103
+ - `(auth)` group shares auth layout without adding to URL
104
+ - `(public)` group for unauthenticated screens
105
+ - `(tabs)` group for tab navigation within auth group
106
+
107
+ ### Protected Routes Pattern
108
+
109
+ ```tsx
110
+ // app/(auth)/_layout.tsx
111
+ import { useAuth } from '@/lib/auth/useAuth';
112
+ import { Redirect, Stack } from 'expo-router';
113
+
114
+ export default function AuthLayout() {
115
+ const { isAuthenticated, isLoading } = useAuth();
116
+
117
+ if (isLoading) return <SplashScreen />;
118
+ if (!isAuthenticated) return <Redirect href="/login" />;
119
+
120
+ return <Stack />;
121
+ }
122
+
123
+ // app/(auth)/(tabs)/_layout.tsx
124
+ import { Tabs } from 'expo-router';
125
+
126
+ export default function TabsLayout() {
127
+ return (
128
+ <Tabs>
129
+ <Tabs.Screen name="home" options={{ title: 'Home' }} />
130
+ <Tabs.Screen name="units" options={{ title: 'Units' }} />
131
+ <Tabs.Screen name="profile" options={{ title: 'Profile' }} />
132
+ </Tabs>
133
+ );
134
+ }
135
+ ```
136
+
137
+ ### Type-Safe Navigation
138
+ Create a custom hook for navigation with proper typing:
139
+
140
+ ```tsx
141
+ // lib/navigation/useTypedNavigation.ts
142
+ import { useNavigation } from '@react-navigation/native';
143
+ import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
144
+
145
+ type RootStackParamList = {
146
+ '/(public)/login': undefined;
147
+ '/(auth)/(tabs)/home': undefined;
148
+ '/(auth)/(tabs)/units': { unitId: string };
149
+ '/(auth)/profile': undefined;
150
+ };
151
+
152
+ export function useTypedNavigation() {
153
+ return useNavigation<NativeStackNavigationProp<RootStackParamList>>();
154
+ }
155
+ ```
156
+
157
+ ### Deep Linking
158
+ Configure in `app.json`:
159
+
160
+ ```json
161
+ {
162
+ "scheme": "sakani",
163
+ "plugins": [
164
+ ["expo-router", {
165
+ "origin": "https://sakani.app"
166
+ }]
167
+ ]
168
+ }
169
+ ```
170
+
171
+ ## API Client Setup
172
+
173
+ ### Centralized API Client
174
+
175
+ ```tsx
176
+ // lib/api/client.ts
177
+ import axios, { AxiosInstance, AxiosError } from 'axios';
178
+ import { getAccessToken, getRefreshToken, setAccessToken } from '@/lib/auth/tokens';
179
+
180
+ const API_BASE_URL = process.env.EXPO_PUBLIC_API_URL || 'http://localhost:3000';
181
+
182
+ let client: AxiosInstance;
183
+
184
+ export function createApiClient(): AxiosInstance {
185
+ client = axios.create({
186
+ baseURL: API_BASE_URL,
187
+ headers: {
188
+ 'Content-Type': 'application/json',
189
+ },
190
+ });
191
+
192
+ // Request interceptor: attach access token
193
+ client.interceptors.request.use(async (config) => {
194
+ const token = await getAccessToken();
195
+ if (token) {
196
+ config.headers.Authorization = `Bearer ${token}`;
197
+ }
198
+ return config;
199
+ });
200
+
201
+ // Response interceptor: handle 401 with refresh
202
+ client.interceptors.response.use(
203
+ (response) => response,
204
+ async (error: AxiosError) => {
205
+ const originalRequest = error.config as any;
206
+
207
+ if (error.response?.status === 401 && !originalRequest._retry) {
208
+ originalRequest._retry = true;
209
+
210
+ try {
211
+ const refreshToken = await getRefreshToken();
212
+ const { data } = await axios.post(`${API_BASE_URL}/auth/refresh`, {
213
+ refreshToken,
214
+ });
215
+
216
+ await setAccessToken(data.accessToken);
217
+ originalRequest.headers.Authorization = `Bearer ${data.accessToken}`;
218
+ return client(originalRequest);
219
+ } catch (refreshError) {
220
+ // Refresh failed, redirect to login
221
+ // Trigger logout in auth context
222
+ throw refreshError;
223
+ }
224
+ }
225
+
226
+ return Promise.reject(error);
227
+ }
228
+ );
229
+
230
+ return client;
231
+ }
232
+
233
+ export function getApiClient(): AxiosInstance {
234
+ return client || createApiClient();
235
+ }
236
+ ```
237
+
238
+ ### Endpoint Definitions
239
+
240
+ ```tsx
241
+ // lib/api/endpoints.ts
242
+ export const API_ENDPOINTS = {
243
+ AUTH: {
244
+ LOGIN: '/auth/login',
245
+ VERIFY_OTP: '/auth/verify-otp',
246
+ REFRESH: '/auth/refresh',
247
+ LOGOUT: '/auth/logout',
248
+ },
249
+ UNITS: {
250
+ LIST: '/units',
251
+ GET: (id: string) => `/units/${id}`,
252
+ CREATE: '/units',
253
+ UPDATE: (id: string) => `/units/${id}`,
254
+ },
255
+ PROFILE: {
256
+ GET: '/profile',
257
+ UPDATE: '/profile',
258
+ },
259
+ } as const;
260
+ ```
261
+
262
+ ## Authentication Flow (Mobile)
263
+
264
+ ### Token Management
265
+
266
+ Access tokens are short-lived (15-30 min) and stored in-memory.
267
+ Refresh tokens are long-lived and stored in secure storage.
268
+
269
+ ```tsx
270
+ // lib/auth/tokens.ts
271
+ import * as SecureStore from 'expo-secure-store';
272
+
273
+ const ACCESS_TOKEN_KEY = 'accessToken';
274
+ const REFRESH_TOKEN_KEY = 'refreshToken';
275
+
276
+ let accessToken: string | null = null;
277
+
278
+ export async function setTokens(access: string, refresh: string) {
279
+ accessToken = access;
280
+ await SecureStore.setItemAsync(REFRESH_TOKEN_KEY, refresh);
281
+ }
282
+
283
+ export async function getAccessToken(): Promise<string | null> {
284
+ return accessToken;
285
+ }
286
+
287
+ export async function getRefreshToken(): Promise<string | null> {
288
+ return await SecureStore.getItemAsync(REFRESH_TOKEN_KEY);
289
+ }
290
+
291
+ export async function clearTokens() {
292
+ accessToken = null;
293
+ await SecureStore.deleteItemAsync(REFRESH_TOKEN_KEY);
294
+ }
295
+
296
+ export async function initializeTokens() {
297
+ // Called on app start
298
+ // Access token stays in memory, refresh token loaded from secure store
299
+ // Don't restore access token to memory (it expired)
300
+ }
301
+ ```
302
+
303
+ ### Auth Context
304
+
305
+ ```tsx
306
+ // lib/auth/context.tsx
307
+ import React, { createContext, useState, useEffect } from 'react';
308
+ import { AppState } from 'react-native';
309
+ import { getApiClient } from '@/lib/api/client';
310
+ import { setTokens, clearTokens, getRefreshToken } from './tokens';
311
+
312
+ interface AuthContextType {
313
+ isAuthenticated: boolean;
314
+ isLoading: boolean;
315
+ user: { id: string; email: string } | null;
316
+ login: (email: string, otp: string) => Promise<void>;
317
+ logout: () => Promise<void>;
318
+ refreshAuth: () => Promise<void>;
319
+ }
320
+
321
+ export const AuthContext = createContext<AuthContextType | undefined>(undefined);
322
+
323
+ export function AuthProvider({ children }: { children: React.ReactNode }) {
324
+ const [isAuthenticated, setIsAuthenticated] = useState(false);
325
+ const [isLoading, setIsLoading] = useState(true);
326
+ const [user, setUser] = useState<AuthContextType['user']>(null);
327
+
328
+ // Initialize auth on app start
329
+ useEffect(() => {
330
+ const initAuth = async () => {
331
+ try {
332
+ const refreshToken = await getRefreshToken();
333
+ if (refreshToken) {
334
+ await refreshAuth();
335
+ } else {
336
+ setIsAuthenticated(false);
337
+ }
338
+ } finally {
339
+ setIsLoading(false);
340
+ }
341
+ };
342
+
343
+ initAuth();
344
+ }, []);
345
+
346
+ // Listen for app foreground (auto-refresh tokens)
347
+ useEffect(() => {
348
+ const subscription = AppState.addEventListener('focus', refreshAuth);
349
+ return () => subscription.remove();
350
+ }, []);
351
+
352
+ const login = async (email: string, otp: string) => {
353
+ const api = getApiClient();
354
+ const { data } = await api.post('/auth/verify-otp', { email, otp });
355
+ await setTokens(data.accessToken, data.refreshToken);
356
+ setUser(data.user);
357
+ setIsAuthenticated(true);
358
+ };
359
+
360
+ const logout = async () => {
361
+ await clearTokens();
362
+ setUser(null);
363
+ setIsAuthenticated(false);
364
+ };
365
+
366
+ const refreshAuth = async () => {
367
+ try {
368
+ const api = getApiClient();
369
+ const { data } = await api.post('/auth/refresh');
370
+ await setTokens(data.accessToken, data.refreshToken);
371
+ setUser(data.user);
372
+ setIsAuthenticated(true);
373
+ } catch {
374
+ await logout();
375
+ }
376
+ };
377
+
378
+ return (
379
+ <AuthContext.Provider value={{ isAuthenticated, isLoading, user, login, logout, refreshAuth }}>
380
+ {children}
381
+ </AuthContext.Provider>
382
+ );
383
+ }
384
+
385
+ export function useAuth() {
386
+ const context = React.useContext(AuthContext);
387
+ if (!context) {
388
+ throw new Error('useAuth must be used within AuthProvider');
389
+ }
390
+ return context;
391
+ }
392
+ ```
393
+
394
+ ### OTP Login Screen
395
+
396
+ ```tsx
397
+ // app/(public)/verify-otp.tsx
398
+ import { useState } from 'react';
399
+ import { View, Text, TextInput, TouchableOpacity, Alert } from 'react-native';
400
+ import { useAuth } from '@/lib/auth/context';
401
+ import { useRouter } from 'expo-router';
402
+
403
+ export default function VerifyOTPScreen() {
404
+ const [otp, setOtp] = useState('');
405
+ const [loading, setLoading] = useState(false);
406
+ const { login } = useAuth();
407
+ const router = useRouter();
408
+
409
+ const handleVerify = async () => {
410
+ try {
411
+ setLoading(true);
412
+ await login('user@example.com', otp); // Email should come from previous screen
413
+ router.replace('/(auth)/(tabs)/home');
414
+ } catch (error) {
415
+ Alert.alert('Error', 'Invalid OTP');
416
+ } finally {
417
+ setLoading(false);
418
+ }
419
+ };
420
+
421
+ return (
422
+ <View style={{ flex: 1, padding: 16, justifyContent: 'center' }}>
423
+ <Text style={{ fontSize: 20, marginBottom: 16 }}>Enter OTP</Text>
424
+ <TextInput
425
+ placeholder="000000"
426
+ value={otp}
427
+ onChangeText={setOtp}
428
+ keyboardType="number-pad"
429
+ maxLength={6}
430
+ style={{ borderWidth: 1, padding: 12, marginBottom: 16 }}
431
+ />
432
+ <TouchableOpacity
433
+ onPress={handleVerify}
434
+ disabled={loading}
435
+ style={{ backgroundColor: '#007AFF', padding: 12, borderRadius: 8 }}
436
+ >
437
+ <Text style={{ color: 'white', textAlign: 'center' }}>Verify</Text>
438
+ </TouchableOpacity>
439
+ </View>
440
+ );
441
+ }
442
+ ```
443
+
444
+ ## Secure Storage
445
+
446
+ ### SecureStore for Sensitive Data
447
+
448
+ Never use AsyncStorage for tokens or secrets. Use `expo-secure-store`:
449
+
450
+ ```tsx
451
+ // lib/storage/secureStore.ts
452
+ import * as SecureStore from 'expo-secure-store';
453
+
454
+ const CHUNK_SIZE = 1800; // SecureStore limit is ~2KB per key
455
+
456
+ export async function secureSet(key: string, value: string): Promise<void> {
457
+ if (value.length > CHUNK_SIZE) {
458
+ // Chunk large values
459
+ for (let i = 0; i < value.length; i += CHUNK_SIZE) {
460
+ const chunk = value.substring(i, i + CHUNK_SIZE);
461
+ const chunkKey = `${key}_${Math.floor(i / CHUNK_SIZE)}`;
462
+ await SecureStore.setItemAsync(chunkKey, chunk);
463
+ }
464
+ await SecureStore.setItemAsync(`${key}_chunks`, Math.ceil(value.length / CHUNK_SIZE).toString());
465
+ } else {
466
+ await SecureStore.setItemAsync(key, value);
467
+ }
468
+ }
469
+
470
+ export async function secureGet(key: string): Promise<string | null> {
471
+ const chunks = await SecureStore.getItemAsync(`${key}_chunks`);
472
+ if (chunks) {
473
+ let value = '';
474
+ for (let i = 0; i < parseInt(chunks); i++) {
475
+ const chunk = await SecureStore.getItemAsync(`${key}_${i}`);
476
+ if (chunk) value += chunk;
477
+ }
478
+ return value || null;
479
+ }
480
+ return await SecureStore.getItemAsync(key);
481
+ }
482
+
483
+ export async function secureRemove(key: string): Promise<void> {
484
+ const chunks = await SecureStore.getItemAsync(`${key}_chunks`);
485
+ if (chunks) {
486
+ for (let i = 0; i < parseInt(chunks); i++) {
487
+ await SecureStore.deleteItemAsync(`${key}_${i}`);
488
+ }
489
+ await SecureStore.deleteItemAsync(`${key}_chunks`);
490
+ } else {
491
+ await SecureStore.deleteItemAsync(key);
492
+ }
493
+ }
494
+ ```
495
+
496
+ ## State Management
497
+
498
+ ### React Context (Auth)
499
+ Use Context for small, global state like authentication.
500
+
501
+ ### TanStack Query (Server State)
502
+ Use React Query for API data caching, refetching, and synchronization:
503
+
504
+ ```tsx
505
+ // lib/api/queries.ts
506
+ import { useQuery, useMutation } from '@tanstack/react-query';
507
+ import { getApiClient } from './client';
508
+
509
+ export function useUnits() {
510
+ return useQuery({
511
+ queryKey: ['units'],
512
+ queryFn: async () => {
513
+ const { data } = await getApiClient().get('/units');
514
+ return data;
515
+ },
516
+ });
517
+ }
518
+
519
+ export function useCreateUnit() {
520
+ return useMutation({
521
+ mutationFn: async (unit: any) => {
522
+ const { data } = await getApiClient().post('/units', unit);
523
+ return data;
524
+ },
525
+ onSuccess: (data, variables, context) => {
526
+ // Invalidate and refetch units list
527
+ queryClient.invalidateQueries({ queryKey: ['units'] });
528
+ },
529
+ });
530
+ }
531
+ ```
532
+
533
+ ### Zustand (Complex Client State)
534
+ Use Zustand for complex client state if needed:
535
+
536
+ ```tsx
537
+ // lib/store/appStore.ts
538
+ import { create } from 'zustand';
539
+
540
+ interface AppState {
541
+ theme: 'light' | 'dark';
542
+ setTheme: (theme: 'light' | 'dark') => void;
543
+ }
544
+
545
+ export const useAppStore = create<AppState>((set) => ({
546
+ theme: 'light',
547
+ setTheme: (theme) => set({ theme }),
548
+ }));
549
+ ```
550
+
551
+ ## Push Notifications
552
+
553
+ ### Setup with expo-notifications
554
+
555
+ ```bash
556
+ npx expo install expo-notifications expo-device
557
+ ```
558
+
559
+ ### Request Permissions
560
+
561
+ ```tsx
562
+ // lib/notifications/index.ts
563
+ import * as Notifications from 'expo-notifications';
564
+ import * as Device from 'expo-device';
565
+
566
+ export async function requestNotificationPermissions(): Promise<boolean> {
567
+ if (!Device.isDevice) {
568
+ console.log('Must use physical device for push notifications');
569
+ return false;
570
+ }
571
+
572
+ const { status: existingStatus } = await Notifications.getPermissionsAsync();
573
+ let finalStatus = existingStatus;
574
+
575
+ if (existingStatus !== 'granted') {
576
+ const { status } = await Notifications.requestPermissionsAsync();
577
+ finalStatus = status;
578
+ }
579
+
580
+ return finalStatus === 'granted';
581
+ }
582
+
583
+ export async function getDeviceToken(): Promise<string | null> {
584
+ const token = (await Notifications.getExpoPushTokenAsync()).data;
585
+ return token;
586
+ }
587
+
588
+ // Register device token with backend
589
+ export async function registerDeviceToken(token: string) {
590
+ const api = getApiClient();
591
+ await api.post('/notifications/register', { deviceToken: token });
592
+ }
593
+ ```
594
+
595
+ ### Listen for Notifications
596
+
597
+ ```tsx
598
+ // Set default notification handler
599
+ Notifications.setNotificationHandler({
600
+ handleNotification: async () => ({
601
+ shouldShowAlert: true,
602
+ shouldPlaySound: true,
603
+ shouldSetBadge: true,
604
+ }),
605
+ });
606
+
607
+ // Listen in your app
608
+ useEffect(() => {
609
+ const subscription = Notifications.addNotificationResponseReceivedListener((response) => {
610
+ // Handle notification tap
611
+ const route = response.notification.request.content.data?.route;
612
+ if (route) {
613
+ router.push(route);
614
+ }
615
+ });
616
+
617
+ return () => subscription.remove();
618
+ }, []);
619
+ ```
620
+
621
+ ## Forms with react-hook-form + Zod
622
+
623
+ ```tsx
624
+ // lib/api/schemas.ts (shared with backend)
625
+ import { z } from 'zod';
626
+
627
+ export const LoginSchema = z.object({
628
+ email: z.string().email('Invalid email'),
629
+ otp: z.string().min(6, 'OTP must be 6 digits'),
630
+ });
631
+
632
+ export const UnitSchema = z.object({
633
+ name: z.string().min(2, 'Name is required'),
634
+ address: z.string().min(5, 'Address is required'),
635
+ price: z.number().positive('Price must be positive'),
636
+ });
637
+ ```
638
+
639
+ ```tsx
640
+ // components/forms/LoginForm.tsx
641
+ import { useForm, Controller } from 'react-hook-form';
642
+ import { zodResolver } from '@hookform/resolvers/zod';
643
+ import { LoginSchema } from '@/lib/api/schemas';
644
+
645
+ export function LoginForm({ onSubmit }: { onSubmit: (data: any) => void }) {
646
+ const { control, handleSubmit, formState: { errors } } = useForm({
647
+ resolver: zodResolver(LoginSchema),
648
+ });
649
+
650
+ return (
651
+ <View>
652
+ <Controller
653
+ control={control}
654
+ name="email"
655
+ render={({ field: { onChange, onBlur, value } }) => (
656
+ <>
657
+ <TextInput
658
+ placeholder="Email"
659
+ value={value}
660
+ onChangeText={onChange}
661
+ onBlur={onBlur}
662
+ keyboardType="email-address"
663
+ style={{ borderWidth: 1, padding: 12, marginBottom: 8 }}
664
+ />
665
+ {errors.email && <Text style={{ color: 'red' }}>{errors.email.message}</Text>}
666
+ </>
667
+ )}
668
+ />
669
+ <TouchableOpacity onPress={handleSubmit(onSubmit)}>
670
+ <Text>Submit</Text>
671
+ </TouchableOpacity>
672
+ </View>
673
+ );
674
+ }
675
+ ```
676
+
677
+ ## Performance Optimization
678
+
679
+ ### Use FlashList Instead of FlatList
680
+
681
+ ```tsx
682
+ import { FlashList } from '@shopify/flash-list';
683
+
684
+ <FlashList
685
+ data={units}
686
+ renderItem={({ item }) => <UnitCard unit={item} />}
687
+ keyExtractor={(item) => item.id}
688
+ estimatedItemSize={100}
689
+ />
690
+ ```
691
+
692
+ ### Image Optimization with expo-image
693
+
694
+ ```tsx
695
+ import { Image } from 'expo-image';
696
+
697
+ <Image
698
+ source={require('@/assets/image.png')}
699
+ style={{ width: 200, height: 200 }}
700
+ contentFit="cover"
701
+ transition={200}
702
+ />
703
+ ```
704
+
705
+ ### Avoid Re-renders
706
+
707
+ ```tsx
708
+ const MemoizedCard = React.memo(({ unit }: { unit: Unit }) => (
709
+ <UnitCard unit={unit} />
710
+ ));
711
+
712
+ const handlePress = useCallback(() => {
713
+ navigation.navigate('details', { id });
714
+ }, [id, navigation]);
715
+ ```
716
+
717
+ ### Loading States (Not Spinners)
718
+
719
+ ```tsx
720
+ // Use skeleton loaders instead of spinners
721
+ <SkeletonLoader height={100} width="100%" count={3} />
722
+ ```
723
+
724
+ ## Testing
725
+
726
+ ### Unit & Component Tests
727
+
728
+ ```tsx
729
+ // Use Jest + React Native Testing Library
730
+ import { render, screen } from '@testing-library/react-native';
731
+ import { LoginForm } from '@/components/forms/LoginForm';
732
+
733
+ describe('LoginForm', () => {
734
+ it('renders email input', () => {
735
+ render(<LoginForm onSubmit={jest.fn()} />);
736
+ expect(screen.getByPlaceholderText('Email')).toBeTruthy();
737
+ });
738
+ });
739
+ ```
740
+
741
+ ### E2E Tests
742
+
743
+ Use Detox or Maestro for end-to-end testing on iOS/Android simulators.
744
+
745
+ ## Common Gotchas
746
+
747
+ ### Platform-Specific Code
748
+
749
+ ```tsx
750
+ import { Platform } from 'react-native';
751
+
752
+ // Option 1: Platform check
753
+ {Platform.OS === 'ios' && <View>{/* iOS only */}</View>}
754
+
755
+ // Option 2: Platform extensions
756
+ // Button.ios.tsx, Button.android.tsx
757
+ // import Button from '@/components/Button'
758
+ ```
759
+
760
+ ### SafeAreaView for Notch/Dynamic Island
761
+
762
+ ```tsx
763
+ import { SafeAreaView } from 'react-native-safe-area-context';
764
+
765
+ <SafeAreaView style={{ flex: 1 }}>
766
+ {/* Content */}
767
+ </SafeAreaView>
768
+ ```
769
+
770
+ ### Keyboard Handling
771
+
772
+ ```tsx
773
+ import { KeyboardAvoidingView } from 'react-native';
774
+
775
+ <KeyboardAvoidingView behavior="padding" style={{ flex: 1 }}>
776
+ {/* Form content */}
777
+ </KeyboardAvoidingView>
778
+ ```
779
+
780
+ ### Android Back Button
781
+
782
+ ```tsx
783
+ useEffect(() => {
784
+ const backAction = () => {
785
+ // Handle back navigation
786
+ return true; // Return true to prevent default behavior
787
+ };
788
+
789
+ const backHandler = BackHandler.addEventListener('hardwareBackPress', backAction);
790
+ return () => backHandler.remove();
791
+ }, []);
792
+ ```
793
+
794
+ ### iOS Swipe-to-Go-Back
795
+
796
+ ```tsx
797
+ // Disable in specific screens if needed
798
+ useNavigationOptions({
799
+ gestureEnabled: false,
800
+ });
801
+ ```
802
+
803
+ ## Dependencies
804
+
805
+ ### Core
806
+ - `expo@52+`
807
+ - `expo-router` (file-based navigation)
808
+ - `react-native` (via Expo)
809
+ - `typescript`
810
+
811
+ ### State & Data
812
+ - `@tanstack/react-query`
813
+ - `zustand` (optional, for complex client state)
814
+
815
+ ### Forms
816
+ - `react-hook-form`
817
+ - `@hookform/resolvers`
818
+ - `zod`
819
+
820
+ ### API
821
+ - `axios`
822
+
823
+ ### Storage
824
+ - `expo-secure-store` (tokens, secrets)
825
+
826
+ ### Notifications
827
+ - `expo-notifications`
828
+ - `expo-device`
829
+
830
+ ### Auth
831
+ - `expo-local-authentication` (biometric unlock)
832
+
833
+ ### Images
834
+ - `expo-image`
835
+
836
+ ### Lists
837
+ - `@shopify/flash-list`
838
+
839
+ ### Testing
840
+ - `jest`
841
+ - `@testing-library/react-native`
842
+ - `detox` or `maestro` (E2E)
843
+
844
+ ### Dev
845
+ - `@types/react-native`
846
+ - `@types/react`
847
+ - `expo-dev-client` (for custom dev builds)
848
+
849
+ ## Quick Start
850
+
851
+ ```bash
852
+ npx create-expo-app sakani --template
853
+ cd sakani
854
+ npx expo install expo-router
855
+ npx expo customize tsconfig
856
+ npx expo install axios zustand @tanstack/react-query zod react-hook-form @hookform/resolvers
857
+ npx expo install expo-secure-store expo-notifications expo-device expo-local-authentication expo-image @shopify/flash-list react-native-safe-area-context
858
+
859
+ # Create basic structure
860
+ mkdir -p app/(auth)/(tabs) app/(public) components/{ui,forms} lib/{api,auth,storage} hooks constants
861
+
862
+ # Start dev server
863
+ npx expo start
864
+ ```