qualia-framework 2.6.0 → 3.2.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 (321) hide show
  1. package/CLAUDE.md +64 -0
  2. package/README.md +103 -30
  3. package/agents/builder.md +110 -0
  4. package/agents/planner.md +134 -0
  5. package/agents/qa-browser.md +186 -0
  6. package/agents/verifier.md +221 -0
  7. package/bin/cli.js +336 -531
  8. package/bin/install.js +570 -0
  9. package/bin/qualia-ui.js +299 -0
  10. package/bin/state.js +630 -0
  11. package/bin/statusline.js +252 -0
  12. package/guide.md +63 -0
  13. package/hooks/auto-update.js +139 -0
  14. package/hooks/branch-guard.js +47 -0
  15. package/hooks/migration-guard.js +60 -0
  16. package/hooks/pre-compact.js +32 -0
  17. package/hooks/pre-deploy-gate.js +110 -0
  18. package/hooks/pre-push.js +33 -0
  19. package/hooks/session-start.js +170 -0
  20. package/package.json +29 -20
  21. package/rules/design-reference.md +179 -0
  22. package/rules/frontend.md +126 -0
  23. package/skills/qualia/SKILL.md +87 -0
  24. package/skills/qualia-build/SKILL.md +97 -0
  25. package/skills/qualia-debug/SKILL.md +87 -0
  26. package/skills/qualia-design/SKILL.md +93 -0
  27. package/skills/qualia-handoff/SKILL.md +66 -0
  28. package/skills/qualia-idk/SKILL.md +8 -0
  29. package/skills/qualia-learn/SKILL.md +88 -0
  30. package/skills/qualia-new/SKILL.md +323 -0
  31. package/{framework/skills → skills}/qualia-optimize/SKILL.md +1 -1
  32. package/skills/qualia-pause/SKILL.md +63 -0
  33. package/skills/qualia-plan/SKILL.md +101 -0
  34. package/skills/qualia-polish/SKILL.md +157 -0
  35. package/skills/qualia-quick/SKILL.md +37 -0
  36. package/skills/qualia-report/SKILL.md +105 -0
  37. package/skills/qualia-resume/SKILL.md +49 -0
  38. package/skills/qualia-review/SKILL.md +76 -0
  39. package/skills/qualia-ship/SKILL.md +90 -0
  40. package/skills/qualia-skill-new/SKILL.md +167 -0
  41. package/skills/qualia-task/SKILL.md +91 -0
  42. package/skills/qualia-verify/SKILL.md +113 -0
  43. package/templates/DESIGN.md +137 -0
  44. package/templates/plan.md +28 -0
  45. package/templates/project.md +22 -0
  46. package/templates/state.md +27 -0
  47. package/templates/tracking.json +20 -0
  48. package/tests/bin.test.sh +673 -0
  49. package/tests/hooks.test.sh +315 -0
  50. package/tests/state.test.sh +535 -0
  51. package/tests/statusline.test.sh +243 -0
  52. package/bin/collect-metrics.sh +0 -62
  53. package/framework/.claudeignore +0 -51
  54. package/framework/CLAUDE.md +0 -51
  55. package/framework/MCP_SETUP.md +0 -229
  56. package/framework/agents/architecture-strategist.md +0 -53
  57. package/framework/agents/backend-agent.md +0 -150
  58. package/framework/agents/code-simplicity-reviewer.md +0 -86
  59. package/framework/agents/frontend-agent.md +0 -111
  60. package/framework/agents/kieran-typescript-reviewer.md +0 -96
  61. package/framework/agents/performance-oracle.md +0 -111
  62. package/framework/agents/qualia-codebase-mapper.md +0 -761
  63. package/framework/agents/qualia-debugger.md +0 -1204
  64. package/framework/agents/qualia-executor.md +0 -882
  65. package/framework/agents/qualia-integration-checker.md +0 -424
  66. package/framework/agents/qualia-phase-researcher.md +0 -457
  67. package/framework/agents/qualia-plan-checker.md +0 -700
  68. package/framework/agents/qualia-planner.md +0 -1245
  69. package/framework/agents/qualia-project-researcher.md +0 -603
  70. package/framework/agents/qualia-research-synthesizer.md +0 -200
  71. package/framework/agents/qualia-roadmapper.md +0 -606
  72. package/framework/agents/qualia-verifier.md +0 -686
  73. package/framework/agents/red-team-qa.md +0 -130
  74. package/framework/agents/security-auditor.md +0 -72
  75. package/framework/agents/team-orchestrator.md +0 -229
  76. package/framework/agents/teams/framework-audit-team.md +0 -66
  77. package/framework/agents/teams/full-stack-team.md +0 -48
  78. package/framework/agents/teams/optimize-team.md +0 -53
  79. package/framework/agents/teams/review-team.md +0 -70
  80. package/framework/agents/teams/ship-team.md +0 -86
  81. package/framework/agents/test-agent.md +0 -182
  82. package/framework/hooks/auto-format.sh +0 -54
  83. package/framework/hooks/block-env-edit.sh +0 -42
  84. package/framework/hooks/branch-guard.sh +0 -43
  85. package/framework/hooks/confirm-delete.sh +0 -59
  86. package/framework/hooks/migration-validate.sh +0 -77
  87. package/framework/hooks/notification-speak.sh +0 -16
  88. package/framework/hooks/pre-commit.sh +0 -100
  89. package/framework/hooks/pre-compact.sh +0 -56
  90. package/framework/hooks/pre-deploy-gate.sh +0 -160
  91. package/framework/hooks/qualia-colors.sh +0 -32
  92. package/framework/hooks/retention-cleanup.sh +0 -62
  93. package/framework/hooks/save-session-state.sh +0 -185
  94. package/framework/hooks/session-context-loader.sh +0 -96
  95. package/framework/hooks/session-learn.sh +0 -32
  96. package/framework/hooks/skill-announce.sh +0 -123
  97. package/framework/hooks/tool-error-announce.sh +0 -27
  98. package/framework/install.ps1 +0 -323
  99. package/framework/install.sh +0 -313
  100. package/framework/qualia-framework/VERSION +0 -1
  101. package/framework/qualia-framework/assets/qualia-logo.png +0 -0
  102. package/framework/qualia-framework/bin/collect-metrics.sh +0 -67
  103. package/framework/qualia-framework/bin/generate-report-docx.py +0 -429
  104. package/framework/qualia-framework/bin/qualia-tools.js +0 -2201
  105. package/framework/qualia-framework/bin/qualia-tools.test.js +0 -1054
  106. package/framework/qualia-framework/references/checkpoints.md +0 -775
  107. package/framework/qualia-framework/references/completion-checklists.md +0 -359
  108. package/framework/qualia-framework/references/continuation-format.md +0 -249
  109. package/framework/qualia-framework/references/continuation-prompt.md +0 -97
  110. package/framework/qualia-framework/references/decimal-phase-calculation.md +0 -65
  111. package/framework/qualia-framework/references/design-quality.md +0 -56
  112. package/framework/qualia-framework/references/employee-guide.md +0 -167
  113. package/framework/qualia-framework/references/git-integration.md +0 -254
  114. package/framework/qualia-framework/references/git-planning-commit.md +0 -50
  115. package/framework/qualia-framework/references/model-profile-resolution.md +0 -32
  116. package/framework/qualia-framework/references/model-profiles.md +0 -73
  117. package/framework/qualia-framework/references/phase-argument-parsing.md +0 -61
  118. package/framework/qualia-framework/references/planning-config.md +0 -195
  119. package/framework/qualia-framework/references/questioning.md +0 -141
  120. package/framework/qualia-framework/references/tdd.md +0 -263
  121. package/framework/qualia-framework/references/ui-brand.md +0 -160
  122. package/framework/qualia-framework/references/verification-patterns.md +0 -612
  123. package/framework/qualia-framework/templates/DEBUG.md +0 -159
  124. package/framework/qualia-framework/templates/DESIGN.md +0 -81
  125. package/framework/qualia-framework/templates/UAT.md +0 -247
  126. package/framework/qualia-framework/templates/codebase/architecture.md +0 -255
  127. package/framework/qualia-framework/templates/codebase/concerns.md +0 -310
  128. package/framework/qualia-framework/templates/codebase/conventions.md +0 -307
  129. package/framework/qualia-framework/templates/codebase/integrations.md +0 -280
  130. package/framework/qualia-framework/templates/codebase/stack.md +0 -186
  131. package/framework/qualia-framework/templates/codebase/structure.md +0 -285
  132. package/framework/qualia-framework/templates/codebase/testing.md +0 -480
  133. package/framework/qualia-framework/templates/config.json +0 -35
  134. package/framework/qualia-framework/templates/context.md +0 -283
  135. package/framework/qualia-framework/templates/continue-here.md +0 -78
  136. package/framework/qualia-framework/templates/debug-subagent-prompt.md +0 -91
  137. package/framework/qualia-framework/templates/discovery.md +0 -146
  138. package/framework/qualia-framework/templates/lab-notes.md +0 -16
  139. package/framework/qualia-framework/templates/milestone-archive.md +0 -123
  140. package/framework/qualia-framework/templates/milestone.md +0 -115
  141. package/framework/qualia-framework/templates/phase-prompt.md +0 -567
  142. package/framework/qualia-framework/templates/planner-subagent-prompt.md +0 -117
  143. package/framework/qualia-framework/templates/project.md +0 -184
  144. package/framework/qualia-framework/templates/projects/ai-agent.md +0 -156
  145. package/framework/qualia-framework/templates/projects/mobile-app.md +0 -181
  146. package/framework/qualia-framework/templates/projects/voice-agent.md +0 -134
  147. package/framework/qualia-framework/templates/projects/website.md +0 -137
  148. package/framework/qualia-framework/templates/requirements.md +0 -231
  149. package/framework/qualia-framework/templates/research-project/ARCHITECTURE.md +0 -204
  150. package/framework/qualia-framework/templates/research-project/FEATURES.md +0 -147
  151. package/framework/qualia-framework/templates/research-project/PITFALLS.md +0 -200
  152. package/framework/qualia-framework/templates/research-project/STACK.md +0 -120
  153. package/framework/qualia-framework/templates/research-project/SUMMARY.md +0 -170
  154. package/framework/qualia-framework/templates/research.md +0 -552
  155. package/framework/qualia-framework/templates/roadmap.md +0 -206
  156. package/framework/qualia-framework/templates/state.md +0 -179
  157. package/framework/qualia-framework/templates/summary-complex.md +0 -59
  158. package/framework/qualia-framework/templates/summary-minimal.md +0 -41
  159. package/framework/qualia-framework/templates/summary-standard.md +0 -48
  160. package/framework/qualia-framework/templates/summary.md +0 -246
  161. package/framework/qualia-framework/templates/user-setup.md +0 -311
  162. package/framework/qualia-framework/templates/verification-report.md +0 -322
  163. package/framework/qualia-framework/workflows/add-phase.md +0 -179
  164. package/framework/qualia-framework/workflows/add-todo.md +0 -157
  165. package/framework/qualia-framework/workflows/audit-milestone.md +0 -241
  166. package/framework/qualia-framework/workflows/check-todos.md +0 -176
  167. package/framework/qualia-framework/workflows/complete-milestone.md +0 -858
  168. package/framework/qualia-framework/workflows/diagnose-issues.md +0 -219
  169. package/framework/qualia-framework/workflows/discovery-phase.md +0 -289
  170. package/framework/qualia-framework/workflows/discuss-phase.md +0 -534
  171. package/framework/qualia-framework/workflows/execute-phase.md +0 -559
  172. package/framework/qualia-framework/workflows/execute-plan.md +0 -438
  173. package/framework/qualia-framework/workflows/help.md +0 -470
  174. package/framework/qualia-framework/workflows/insert-phase.md +0 -220
  175. package/framework/qualia-framework/workflows/list-phase-assumptions.md +0 -178
  176. package/framework/qualia-framework/workflows/map-codebase.md +0 -327
  177. package/framework/qualia-framework/workflows/new-milestone.md +0 -363
  178. package/framework/qualia-framework/workflows/new-project.md +0 -982
  179. package/framework/qualia-framework/workflows/pause-work.md +0 -122
  180. package/framework/qualia-framework/workflows/plan-milestone-gaps.md +0 -256
  181. package/framework/qualia-framework/workflows/plan-phase.md +0 -422
  182. package/framework/qualia-framework/workflows/progress.md +0 -389
  183. package/framework/qualia-framework/workflows/quick.md +0 -252
  184. package/framework/qualia-framework/workflows/remove-phase.md +0 -326
  185. package/framework/qualia-framework/workflows/research-phase.md +0 -74
  186. package/framework/qualia-framework/workflows/resume-project.md +0 -306
  187. package/framework/qualia-framework/workflows/set-profile.md +0 -80
  188. package/framework/qualia-framework/workflows/settings.md +0 -145
  189. package/framework/qualia-framework/workflows/transition.md +0 -556
  190. package/framework/qualia-framework/workflows/update.md +0 -197
  191. package/framework/qualia-framework/workflows/verify-phase.md +0 -195
  192. package/framework/qualia-framework/workflows/verify-work.md +0 -625
  193. package/framework/rules/context7.md +0 -14
  194. package/framework/rules/frontend.md +0 -33
  195. package/framework/rules/speed.md +0 -23
  196. package/framework/scripts/__pycache__/say.cpython-314.pyc +0 -0
  197. package/framework/scripts/apply-retention.sh +0 -120
  198. package/framework/scripts/bootstrap-pop-os.sh +0 -354
  199. package/framework/scripts/claude-voice +0 -13
  200. package/framework/scripts/cleanup.sh +0 -131
  201. package/framework/scripts/cowork-mode.sh +0 -141
  202. package/framework/scripts/generate-project-claude-md.sh +0 -153
  203. package/framework/scripts/load-test-webhook.js +0 -172
  204. package/framework/scripts/say.py +0 -236
  205. package/framework/scripts/showcase-video-recorder/ffmpeg-builder.js +0 -167
  206. package/framework/scripts/showcase-video-recorder/playwright-helpers.js +0 -216
  207. package/framework/scripts/speak.py +0 -55
  208. package/framework/scripts/speak.sh +0 -18
  209. package/framework/scripts/status.sh +0 -138
  210. package/framework/scripts/sync-to-framework.sh +0 -65
  211. package/framework/scripts/voice-hotkey.py +0 -227
  212. package/framework/scripts/voice-input.sh +0 -51
  213. package/framework/skills/animate/SKILL.md +0 -202
  214. package/framework/skills/bolder/SKILL.md +0 -144
  215. package/framework/skills/browser-qa/SKILL.md +0 -536
  216. package/framework/skills/clarify/SKILL.md +0 -179
  217. package/framework/skills/client-handoff/SKILL.md +0 -135
  218. package/framework/skills/collab-onboard/SKILL.md +0 -111
  219. package/framework/skills/colorize/SKILL.md +0 -170
  220. package/framework/skills/critique/SKILL.md +0 -126
  221. package/framework/skills/deep-research/SKILL.md +0 -240
  222. package/framework/skills/delight/SKILL.md +0 -329
  223. package/framework/skills/deploy/SKILL.md +0 -261
  224. package/framework/skills/deploy-verify/SKILL.md +0 -377
  225. package/framework/skills/deploy-verify/scripts/canary-check.sh +0 -206
  226. package/framework/skills/deploy-verify/scripts/check-console-errors.js +0 -147
  227. package/framework/skills/deploy-verify/scripts/check-cwv.js +0 -139
  228. package/framework/skills/deploy-verify/scripts/project-detect.sh +0 -84
  229. package/framework/skills/deploy-verify/scripts/verify.sh +0 -548
  230. package/framework/skills/design-quieter/SKILL.md +0 -130
  231. package/framework/skills/distill/SKILL.md +0 -149
  232. package/framework/skills/docs-lookup/SKILL.md +0 -79
  233. package/framework/skills/fcm-notifications/SKILL.md +0 -125
  234. package/framework/skills/financial-ledger/SKILL.md +0 -1039
  235. package/framework/skills/frontend-master/NOTICE.md +0 -4
  236. package/framework/skills/frontend-master/SKILL.md +0 -127
  237. package/framework/skills/frontend-master/reference/color-and-contrast.md +0 -132
  238. package/framework/skills/frontend-master/reference/interaction-design.md +0 -123
  239. package/framework/skills/frontend-master/reference/motion-design.md +0 -99
  240. package/framework/skills/frontend-master/reference/responsive-design.md +0 -114
  241. package/framework/skills/frontend-master/reference/spatial-design.md +0 -100
  242. package/framework/skills/frontend-master/reference/typography.md +0 -131
  243. package/framework/skills/frontend-master/reference/ux-writing.md +0 -107
  244. package/framework/skills/harden/SKILL.md +0 -357
  245. package/framework/skills/i18n-rtl/SKILL.md +0 -752
  246. package/framework/skills/learn/SKILL.md +0 -95
  247. package/framework/skills/memory/SKILL.md +0 -50
  248. package/framework/skills/mobile-expo/SKILL.md +0 -977
  249. package/framework/skills/mobile-expo/references/store-checklist.md +0 -550
  250. package/framework/skills/nestjs-backend/README.md +0 -73
  251. package/framework/skills/nestjs-backend/SKILL.md +0 -446
  252. package/framework/skills/nestjs-backend/references/templates.md +0 -1173
  253. package/framework/skills/normalize/SKILL.md +0 -79
  254. package/framework/skills/onboard/SKILL.md +0 -242
  255. package/framework/skills/openrouter-agent/SKILL.md +0 -922
  256. package/framework/skills/polish/SKILL.md +0 -209
  257. package/framework/skills/pr/SKILL.md +0 -66
  258. package/framework/skills/qualia/SKILL.md +0 -199
  259. package/framework/skills/qualia-add-todo/SKILL.md +0 -68
  260. package/framework/skills/qualia-audit-milestone/SKILL.md +0 -95
  261. package/framework/skills/qualia-check-todos/SKILL.md +0 -55
  262. package/framework/skills/qualia-complete-milestone/SKILL.md +0 -134
  263. package/framework/skills/qualia-debug/SKILL.md +0 -149
  264. package/framework/skills/qualia-design/SKILL.md +0 -203
  265. package/framework/skills/qualia-discuss-phase/SKILL.md +0 -72
  266. package/framework/skills/qualia-evolve/SKILL.md +0 -200
  267. package/framework/skills/qualia-execute-phase/SKILL.md +0 -89
  268. package/framework/skills/qualia-framework-audit/SKILL.md +0 -604
  269. package/framework/skills/qualia-guide/SKILL.md +0 -32
  270. package/framework/skills/qualia-help/SKILL.md +0 -114
  271. package/framework/skills/qualia-idk/SKILL.md +0 -352
  272. package/framework/skills/qualia-list-phase-assumptions/SKILL.md +0 -67
  273. package/framework/skills/qualia-new-milestone/SKILL.md +0 -72
  274. package/framework/skills/qualia-new-project/SKILL.md +0 -232
  275. package/framework/skills/qualia-pause-work/SKILL.md +0 -96
  276. package/framework/skills/qualia-plan-milestone-gaps/SKILL.md +0 -57
  277. package/framework/skills/qualia-plan-phase/SKILL.md +0 -104
  278. package/framework/skills/qualia-production-check/SKILL.md +0 -0
  279. package/framework/skills/qualia-progress/SKILL.md +0 -53
  280. package/framework/skills/qualia-quick/SKILL.md +0 -89
  281. package/framework/skills/qualia-report/SKILL.md +0 -166
  282. package/framework/skills/qualia-research-phase/SKILL.md +0 -88
  283. package/framework/skills/qualia-resume-work/SKILL.md +0 -62
  284. package/framework/skills/qualia-review/SKILL.md +0 -263
  285. package/framework/skills/qualia-start/SKILL.md +0 -161
  286. package/framework/skills/qualia-verify-work/SKILL.md +0 -132
  287. package/framework/skills/rag/SKILL.md +0 -750
  288. package/framework/skills/responsive/SKILL.md +0 -231
  289. package/framework/skills/retro/SKILL.md +0 -284
  290. package/framework/skills/sakani-conventions/SKILL.md +0 -136
  291. package/framework/skills/sakani-conventions/evals/evals.json +0 -23
  292. package/framework/skills/sakani-conventions/references/entities.md +0 -365
  293. package/framework/skills/sakani-conventions/references/error-codes.md +0 -95
  294. package/framework/skills/seo-master/SKILL.md +0 -490
  295. package/framework/skills/seo-master/references/checklist.md +0 -199
  296. package/framework/skills/seo-master/references/structured-data.md +0 -609
  297. package/framework/skills/ship/SKILL.md +0 -239
  298. package/framework/skills/stack-researcher/SKILL.md +0 -215
  299. package/framework/skills/status/SKILL.md +0 -154
  300. package/framework/skills/status/scripts/health-check.sh +0 -562
  301. package/framework/skills/subscription-payments/SKILL.md +0 -250
  302. package/framework/skills/supabase/SKILL.md +0 -973
  303. package/framework/skills/supabase/references/templates.md +0 -159
  304. package/framework/skills/team/SKILL.md +0 -67
  305. package/framework/skills/test-runner/SKILL.md +0 -202
  306. package/framework/skills/voice-agent/SKILL.md +0 -1312
  307. package/framework/skills/zoho-workflow/SKILL.md +0 -51
  308. package/framework/statusline-command.sh +0 -117
  309. package/framework/teams/default/inboxes/plan-04.json +0 -9
  310. package/framework/teams/review-team.md +0 -75
  311. package/framework/teams/ship-team.md +0 -86
  312. package/profiles/fawzi.json +0 -16
  313. package/profiles/hasan.json +0 -16
  314. package/profiles/moayad.json +0 -16
  315. package/templates/CLAUDE-owner.md +0 -52
  316. package/templates/CLAUDE.md.hbs +0 -58
  317. package/templates/env.claude.template +0 -12
  318. package/templates/settings.json +0 -172
  319. package/uninstall.sh +0 -90
  320. /package/{framework/rules → rules}/deployment.md +0 -0
  321. /package/{framework/rules → rules}/security.md +0 -0
@@ -1,977 +0,0 @@
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": "myapp",
163
- "plugins": [
164
- ["expo-router", {
165
- "origin": "https://myapp.com"
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
- ## EAS Build & Submit (App Store Deployment)
804
-
805
- ### Setup EAS
806
-
807
- ```bash
808
- # Install EAS CLI
809
- npm install -g eas-cli
810
-
811
- # Log in to Expo account
812
- eas login
813
-
814
- # Initialize EAS in your project
815
- eas build:configure
816
- ```
817
-
818
- This creates `eas.json` with build profiles:
819
-
820
- ```json
821
- {
822
- "cli": { "version": ">= 3.0.0" },
823
- "build": {
824
- "development": {
825
- "developmentClient": true,
826
- "distribution": "internal"
827
- },
828
- "preview": {
829
- "distribution": "internal"
830
- },
831
- "production": {}
832
- },
833
- "submit": {
834
- "production": {
835
- "ios": { "ascAppId": "YOUR_APP_STORE_CONNECT_APP_ID" },
836
- "android": { "track": "production" }
837
- }
838
- }
839
- }
840
- ```
841
-
842
- ### Building for App Stores
843
-
844
- ```bash
845
- # iOS production build
846
- eas build --platform ios --profile production
847
-
848
- # Android production build
849
- eas build --platform android --profile production
850
-
851
- # Both platforms
852
- eas build --platform all --profile production
853
- ```
854
-
855
- ### Code Signing
856
-
857
- **iOS:**
858
- - EAS handles provisioning profiles and certificates automatically
859
- - First build prompts to log in to Apple Developer account
860
- - Choose "Let EAS handle it" for automatic management
861
-
862
- **Android:**
863
- - EAS generates and manages the keystore automatically
864
- - First build creates a keystore stored securely on EAS servers
865
- - For existing keystores: `eas credentials` to upload
866
-
867
- ### Submitting to App Stores
868
-
869
- ```bash
870
- # Submit to Apple App Store (requires App Store Connect setup)
871
- eas submit --platform ios --latest
872
-
873
- # Submit to Google Play Store (requires Google Play Console setup)
874
- eas submit --platform android --latest
875
-
876
- # Submit specific build
877
- eas submit --platform ios --id BUILD_ID
878
- ```
879
-
880
- ### Pre-submission Checklist
881
-
882
- - [ ] App icon (1024x1024 for iOS, 512x512 for Android)
883
- - [ ] Splash screen configured
884
- - [ ] `app.json` has correct bundle identifier and version
885
- - [ ] Privacy policy URL set (required for both stores)
886
- - [ ] App Store screenshots prepared (iPhone, iPad if applicable)
887
- - [ ] Google Play feature graphic (1024x500)
888
- - [ ] App description and keywords
889
- - [ ] Age rating questionnaire completed
890
- - [ ] In-app purchases configured (if applicable)
891
-
892
- ### OTA Updates (Over-The-Air)
893
-
894
- For JS-only updates without rebuilding:
895
-
896
- ```bash
897
- # Push an update to production
898
- eas update --branch production --message "Fix: corrected button alignment"
899
-
900
- # Push to preview
901
- eas update --branch preview --message "Feature: new onboarding flow"
902
- ```
903
-
904
- Configure in `app.json`:
905
- ```json
906
- {
907
- "expo": {
908
- "updates": {
909
- "url": "https://u.expo.dev/YOUR_PROJECT_ID"
910
- },
911
- "runtimeVersion": { "policy": "appVersion" }
912
- }
913
- }
914
- ```
915
-
916
- ## Dependencies
917
-
918
- ### Core
919
- - `expo@52+`
920
- - `expo-router` (file-based navigation)
921
- - `react-native` (via Expo)
922
- - `typescript`
923
-
924
- ### State & Data
925
- - `@tanstack/react-query`
926
- - `zustand` (optional, for complex client state)
927
-
928
- ### Forms
929
- - `react-hook-form`
930
- - `@hookform/resolvers`
931
- - `zod`
932
-
933
- ### API
934
- - `axios`
935
-
936
- ### Storage
937
- - `expo-secure-store` (tokens, secrets)
938
-
939
- ### Notifications
940
- - `expo-notifications`
941
- - `expo-device`
942
-
943
- ### Auth
944
- - `expo-local-authentication` (biometric unlock)
945
-
946
- ### Images
947
- - `expo-image`
948
-
949
- ### Lists
950
- - `@shopify/flash-list`
951
-
952
- ### Testing
953
- - `jest`
954
- - `@testing-library/react-native`
955
- - `detox` or `maestro` (E2E)
956
-
957
- ### Dev
958
- - `@types/react-native`
959
- - `@types/react`
960
- - `expo-dev-client` (for custom dev builds)
961
-
962
- ## Quick Start
963
-
964
- ```bash
965
- npx create-expo-app my-app --template
966
- cd my-app
967
- npx expo install expo-router
968
- npx expo customize tsconfig
969
- npx expo install axios zustand @tanstack/react-query zod react-hook-form @hookform/resolvers
970
- npx expo install expo-secure-store expo-notifications expo-device expo-local-authentication expo-image @shopify/flash-list react-native-safe-area-context
971
-
972
- # Create basic structure
973
- mkdir -p app/(auth)/(tabs) app/(public) components/{ui,forms} lib/{api,auth,storage} hooks constants
974
-
975
- # Start dev server
976
- npx expo start
977
- ```