vibefast-cli 1.2.1 → 1.3.1

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 (348) hide show
  1. package/README.md +30 -95
  2. package/dist/__tests__/recipes.test.js +94 -91
  3. package/dist/__tests__/recipes.test.js.map +1 -1
  4. package/dist/commands/add.d.ts.map +1 -1
  5. package/dist/commands/add.js +301 -125
  6. package/dist/commands/add.js.map +1 -1
  7. package/dist/commands/checklist.d.ts.map +1 -1
  8. package/dist/commands/checklist.js +85 -44
  9. package/dist/commands/checklist.js.map +1 -1
  10. package/dist/commands/health.d.ts.map +1 -1
  11. package/dist/commands/health.js +13 -4
  12. package/dist/commands/health.js.map +1 -1
  13. package/dist/commands/init.d.ts.map +1 -1
  14. package/dist/commands/init.js +118 -26
  15. package/dist/commands/init.js.map +1 -1
  16. package/dist/commands/migrate.d.ts +3 -0
  17. package/dist/commands/migrate.d.ts.map +1 -0
  18. package/dist/commands/migrate.js +202 -0
  19. package/dist/commands/migrate.js.map +1 -0
  20. package/dist/commands/remove.d.ts.map +1 -1
  21. package/dist/commands/remove.js +61 -3
  22. package/dist/commands/remove.js.map +1 -1
  23. package/dist/core/auth.d.ts.map +1 -1
  24. package/dist/core/auth.js +20 -18
  25. package/dist/core/auth.js.map +1 -1
  26. package/dist/core/codemod.d.ts +33 -0
  27. package/dist/core/codemod.d.ts.map +1 -1
  28. package/dist/core/codemod.js +116 -0
  29. package/dist/core/codemod.js.map +1 -1
  30. package/dist/core/detect.d.ts.map +1 -1
  31. package/dist/core/detect.js +24 -7
  32. package/dist/core/detect.js.map +1 -1
  33. package/dist/core/journal.d.ts +1 -0
  34. package/dist/core/journal.d.ts.map +1 -1
  35. package/dist/core/journal.js.map +1 -1
  36. package/dist/core/recipes.d.ts.map +1 -1
  37. package/dist/core/recipes.js +25 -7
  38. package/dist/core/recipes.js.map +1 -1
  39. package/dist/index.js +2 -2
  40. package/dist/index.js.map +1 -1
  41. package/docs/architecture.md +50 -0
  42. package/docs/commands.md +78 -0
  43. package/docs/contributing.md +27 -0
  44. package/docs/quickstart.md +50 -0
  45. package/docs/recipes.md +57 -0
  46. package/docs/troubleshooting.md +31 -0
  47. package/package.json +2 -2
  48. package/recipes/0/apps/native/src/components/advanced-ui/timeline/demo.tsx +445 -0
  49. package/recipes/0/apps/native/src/components/advanced-ui/timeline/timeline-view.tsx +355 -0
  50. package/recipes/0/apps/native/src/components/advanced-ui/timeline/types.ts +31 -0
  51. package/recipes/0/recipe.json +18 -0
  52. package/recipes/animated-chip/apps/native/src/components/advanced-ui/chip/demo.tsx +2 -1
  53. package/recipes/animated-chip/recipe.json +5 -2
  54. package/recipes/animated-chip-native@latest.zip +0 -0
  55. package/recipes/animated-chip@latest.zip +0 -0
  56. package/recipes/animated-switch/apps/native/src/components/advanced-ui/switch/demo.tsx +1 -1
  57. package/recipes/animated-switch/recipe.json +5 -2
  58. package/recipes/animated-switch-native@latest.zip +0 -0
  59. package/recipes/animated-switch@latest.zip +0 -0
  60. package/recipes/audio-recorder/apps/native/src/features/audio-recorder/components/audio-recorder.tsx +2 -1
  61. package/recipes/audio-recorder/apps/native/src/features/audio-recorder/demo/with-recording-list-demo.tsx +2 -2
  62. package/recipes/audio-recorder/recipe.json +7 -2
  63. package/recipes/audio-recorder-native@latest.zip +0 -0
  64. package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/components/audio-recorder.tsx +2 -1
  65. package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/with-recording-list-demo.tsx +2 -1
  66. package/recipes/audio-recorder-supabase/recipe.json +12 -16
  67. package/recipes/audio-recorder-supabase-native@latest.zip +0 -0
  68. package/recipes/audio-recorder-supabase@latest.zip +0 -0
  69. package/recipes/audio-recorder@latest.zip +0 -0
  70. package/recipes/charts/apps/native/src/app/charts/index.tsx +3 -0
  71. package/recipes/charts/apps/native/src/features/charts/components/bar-chart.tsx +3 -1
  72. package/recipes/charts/apps/native/src/features/charts/components/candlestick-chart.tsx +3 -1
  73. package/recipes/charts/apps/native/src/features/charts/components/column-chart.tsx +3 -1
  74. package/recipes/charts/apps/native/src/features/charts/components/doughnut-chart.tsx +3 -1
  75. package/recipes/charts/apps/native/src/features/charts/components/line-chart.tsx +3 -1
  76. package/recipes/charts/apps/native/src/features/charts/components/radar-chart.tsx +3 -1
  77. package/recipes/charts/apps/native/src/features/charts/components/stacked-bar-chart.tsx +3 -1
  78. package/recipes/charts/recipe.json +13 -4
  79. package/recipes/charts-native@latest.zip +0 -0
  80. package/recipes/charts@latest.zip +0 -0
  81. package/recipes/chatbot/apps/native/src/features/chatbot/components/chat-markdown.tsx +86 -86
  82. package/recipes/chatbot/apps/native/src/features/chatbot/components/markdown/code-block.tsx +4 -4
  83. package/recipes/chatbot/recipe.json +3 -40
  84. package/recipes/chatbot-native@latest.zip +0 -0
  85. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/chat-markdown.tsx +4 -1
  86. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/markdown/code-block.tsx +86 -53
  87. package/recipes/chatbot-supabase/recipe.json +3 -42
  88. package/recipes/chatbot-supabase-native@latest.zip +0 -0
  89. package/recipes/chatbot-supabase@latest.zip +0 -0
  90. package/recipes/chatbot@latest.zip +0 -0
  91. package/recipes/glowing-button/recipe.json +6 -2
  92. package/recipes/glowing-button-native@latest.zip +0 -0
  93. package/recipes/glowing-button@latest.zip +0 -0
  94. package/recipes/image-analysis/apps/native/src/app/analysis/[type]/_layout.tsx +5 -0
  95. package/recipes/image-analysis/apps/native/src/app/analysis/[type]/analysis-options.tsx +50 -0
  96. package/recipes/image-analysis/apps/native/src/app/analysis/[type]/camera.tsx +2 -0
  97. package/recipes/image-analysis/apps/native/src/app/analysis/[type]/index.tsx +50 -0
  98. package/recipes/image-analysis/apps/native/src/app/analysis/[type]/loading.tsx +50 -0
  99. package/recipes/image-analysis/apps/native/src/app/analysis/[type]/results.tsx +2 -0
  100. package/recipes/image-analysis/apps/native/src/app/analysis/[type]/trait-details.tsx +3 -0
  101. package/recipes/image-analysis/apps/native/src/features/image-analyzer/app/analysis-options-screen.tsx +2 -2
  102. package/recipes/image-analysis/apps/native/src/features/image-analyzer/app/camera.tsx +72 -65
  103. package/recipes/image-analysis/apps/native/src/features/image-analyzer/app/image-capture-screen.tsx +65 -47
  104. package/recipes/image-analysis/apps/native/src/features/image-analyzer/app/loading-screen.tsx +43 -2
  105. package/recipes/image-analysis/apps/native/src/features/image-analyzer/app/loading.tsx +34 -1
  106. package/recipes/image-analysis/apps/native/src/features/image-analyzer/hooks/use-image-analysis.ts +83 -2
  107. package/recipes/image-analysis/recipe.json +11 -19
  108. package/recipes/image-analysis-native@latest.zip +0 -0
  109. package/recipes/image-analysis-supabase/apps/native/src/app/analysis/[type]/_layout.tsx +5 -0
  110. package/recipes/image-analysis-supabase/apps/native/src/app/analysis/[type]/analysis-options.tsx +50 -0
  111. package/recipes/image-analysis-supabase/apps/native/src/app/analysis/[type]/camera.tsx +2 -0
  112. package/recipes/image-analysis-supabase/apps/native/src/app/analysis/[type]/index.tsx +50 -0
  113. package/recipes/image-analysis-supabase/apps/native/src/app/analysis/[type]/loading.tsx +50 -0
  114. package/recipes/image-analysis-supabase/apps/native/src/app/analysis/[type]/results.tsx +2 -0
  115. package/recipes/image-analysis-supabase/apps/native/src/app/analysis/[type]/trait-details.tsx +3 -0
  116. package/recipes/image-analysis-supabase/recipe.json +10 -37
  117. package/recipes/image-analysis-supabase-native@latest.zip +0 -0
  118. package/recipes/image-analysis-supabase@latest.zip +0 -0
  119. package/recipes/image-analysis@latest.zip +0 -0
  120. package/recipes/image-analyzer/apps/native/src/app/(root)/(protected)/image-analyzer/index.tsx +2 -0
  121. package/recipes/image-generator/apps/native/src/app/image-generator/gallery.tsx +3 -0
  122. package/recipes/image-generator/apps/native/src/app/image-generator/index.tsx +3 -0
  123. package/recipes/image-generator/recipe.json +8 -18
  124. package/recipes/image-generator-native@latest.zip +0 -0
  125. package/recipes/image-generator-supabase/recipe.json +6 -35
  126. package/recipes/image-generator-supabase-native@latest.zip +0 -0
  127. package/recipes/image-generator-supabase@latest.zip +0 -0
  128. package/recipes/image-generator@latest.zip +0 -0
  129. package/recipes/ios-widget/recipe.json +18 -119
  130. package/recipes/ios-widget-native@latest.zip +0 -0
  131. package/recipes/ios-widget@latest.zip +0 -0
  132. package/recipes/number-stepper/apps/native/src/components/advanced-ui/stepper/demo.tsx +1 -1
  133. package/recipes/number-stepper/recipe.json +5 -2
  134. package/recipes/number-stepper-native@latest.zip +0 -0
  135. package/recipes/number-stepper@latest.zip +0 -0
  136. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/interactive-onboarding.tsx +11 -18
  137. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/ai-tone-step.tsx +5 -7
  138. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/currency-step.tsx +9 -7
  139. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/feature-ai-step.tsx +8 -7
  140. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/feature-chatbot-step.tsx +6 -5
  141. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/feature-manual-step.tsx +4 -3
  142. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/feature-scan-step.tsx +6 -5
  143. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/main-reason-step.tsx +5 -7
  144. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/notification-step.tsx +7 -6
  145. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/overspend-step.tsx +5 -7
  146. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/personalizing-step.tsx +8 -7
  147. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/rating-step.tsx +6 -5
  148. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/reminder-step.tsx +5 -6
  149. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/safety-step.tsx +5 -4
  150. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/struggle-step.tsx +5 -7
  151. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/welcome-step.tsx +7 -6
  152. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/ui/onboarding-header.tsx +4 -3
  153. package/recipes/onboarding/recipe.json +9 -6
  154. package/recipes/onboarding-native@latest.zip +0 -0
  155. package/recipes/onboarding@latest.zip +0 -0
  156. package/recipes/payments/apps/native/src/app/paywall/index.tsx +74 -0
  157. package/recipes/payments/apps/native/src/app/paywall/local.tsx +25 -0
  158. package/recipes/payments/apps/native/src/app/paywall/remote.tsx +23 -0
  159. package/recipes/payments/packages/backend/convex/payments.ts +21 -3
  160. package/recipes/payments/recipe.json +14 -34
  161. package/recipes/payments-native@latest.zip +0 -0
  162. package/recipes/payments-supabase/apps/native/src/app/paywall/index.tsx +74 -0
  163. package/recipes/payments-supabase/apps/native/src/app/paywall/local.tsx +25 -0
  164. package/recipes/payments-supabase/apps/native/src/app/paywall/remote.tsx +23 -0
  165. package/recipes/payments-supabase/recipe.json +16 -23
  166. package/recipes/payments-supabase-native@latest.zip +0 -0
  167. package/recipes/payments-supabase@latest.zip +0 -0
  168. package/recipes/payments@latest.zip +0 -0
  169. package/recipes/posthog/apps/native/src/components/analytics/navigation-tracker.tsx +14 -0
  170. package/recipes/posthog/apps/native/src/lib/hooks/use-navigation-analytics.ts +44 -0
  171. package/recipes/posthog/apps/native/src/providers/posthog-provider.tsx +51 -0
  172. package/recipes/posthog/recipe.json +60 -0
  173. package/recipes/posthog-native@latest.zip +0 -0
  174. package/recipes/progress-circle/apps/native/src/components/advanced-ui/progress-bars/progress-circle-page.tsx +1 -1
  175. package/recipes/progress-circle/recipe.json +5 -2
  176. package/recipes/progress-circle-native@latest.zip +0 -0
  177. package/recipes/progress-circle@latest.zip +0 -0
  178. package/recipes/quiz/apps/native/src/app/quiz/index.tsx +47 -0
  179. package/recipes/quiz/recipe.json +9 -6
  180. package/recipes/quiz-native@latest.zip +0 -0
  181. package/recipes/quiz@latest.zip +0 -0
  182. package/recipes/screen-kits/apps/native/src/app/screen-kits/_layout.tsx +12 -0
  183. package/recipes/screen-kits/apps/native/src/app/screen-kits/index.tsx +114 -0
  184. package/recipes/screen-kits/apps/native/src/features/screen-kits/index.ts +1 -0
  185. package/recipes/screen-kits/apps/native/src/features/screen-kits/types.ts +28 -0
  186. package/recipes/screen-kits/recipe.json +26 -0
  187. package/recipes/screen-kits-duolingo/apps/native/src/app/screen-kits/duolingo/_layout.tsx +12 -0
  188. package/recipes/screen-kits-duolingo/apps/native/src/app/screen-kits/duolingo/home.tsx +5 -0
  189. package/recipes/screen-kits-duolingo/apps/native/src/app/screen-kits/duolingo/index.tsx +5 -0
  190. package/recipes/screen-kits-duolingo/apps/native/src/app/screen-kits/duolingo/lesson-complete.tsx +5 -0
  191. package/recipes/screen-kits-duolingo/apps/native/src/app/screen-kits/duolingo/lesson-fail.tsx +5 -0
  192. package/recipes/screen-kits-duolingo/apps/native/src/app/screen-kits/duolingo/lesson.tsx +5 -0
  193. package/recipes/screen-kits-duolingo/apps/native/src/app/screen-kits/duolingo/skill-tree.tsx +5 -0
  194. package/recipes/screen-kits-duolingo/apps/native/src/features/screen-kits/duolingo/components/duo-button.tsx +174 -0
  195. package/recipes/screen-kits-duolingo/apps/native/src/features/screen-kits/duolingo/components/skill-button.tsx +186 -0
  196. package/recipes/screen-kits-duolingo/apps/native/src/features/screen-kits/duolingo/components/xp-header.tsx +115 -0
  197. package/recipes/screen-kits-duolingo/apps/native/src/features/screen-kits/duolingo/constants.ts +89 -0
  198. package/recipes/screen-kits-duolingo/apps/native/src/features/screen-kits/duolingo/index.ts +3 -0
  199. package/recipes/screen-kits-duolingo/apps/native/src/features/screen-kits/duolingo/screens/home-screen.tsx +225 -0
  200. package/recipes/screen-kits-duolingo/apps/native/src/features/screen-kits/duolingo/screens/lesson-complete-screen.tsx +485 -0
  201. package/recipes/screen-kits-duolingo/apps/native/src/features/screen-kits/duolingo/screens/lesson-fail-screen.tsx +105 -0
  202. package/recipes/screen-kits-duolingo/apps/native/src/features/screen-kits/duolingo/screens/lesson-screen.tsx +384 -0
  203. package/recipes/screen-kits-duolingo/recipe.json +58 -0
  204. package/recipes/screen-kits-duolingo-native@latest.zip +0 -0
  205. package/recipes/screen-kits-finance/apps/native/src/app/screen-kits/finance/_layout.tsx +45 -0
  206. package/recipes/screen-kits-finance/apps/native/src/app/screen-kits/finance/asset-detail.tsx +3 -0
  207. package/recipes/screen-kits-finance/apps/native/src/app/screen-kits/finance/notifications.tsx +3 -0
  208. package/recipes/screen-kits-finance/apps/native/src/app/screen-kits/finance/receive.tsx +3 -0
  209. package/recipes/screen-kits-finance/apps/native/src/app/screen-kits/finance/send.tsx +3 -0
  210. package/recipes/screen-kits-finance/apps/native/src/app/screen-kits/finance/swap.tsx +3 -0
  211. package/recipes/screen-kits-finance/apps/native/src/app/screen-kits/finance/wallet.tsx +3 -0
  212. package/recipes/screen-kits-finance/apps/native/src/features/screen-kits/finance/components/ActionButtons.tsx +78 -0
  213. package/recipes/screen-kits-finance/apps/native/src/features/screen-kits/finance/components/AssetRow.tsx +94 -0
  214. package/recipes/screen-kits-finance/apps/native/src/features/screen-kits/finance/components/BalanceCard.tsx +118 -0
  215. package/recipes/screen-kits-finance/apps/native/src/features/screen-kits/finance/constants.ts +85 -0
  216. package/recipes/screen-kits-finance/apps/native/src/features/screen-kits/finance/screens/asset-detail.tsx +378 -0
  217. package/recipes/screen-kits-finance/apps/native/src/features/screen-kits/finance/screens/notifications.tsx +210 -0
  218. package/recipes/screen-kits-finance/apps/native/src/features/screen-kits/finance/screens/receive-modal.tsx +317 -0
  219. package/recipes/screen-kits-finance/apps/native/src/features/screen-kits/finance/screens/send-modal.tsx +420 -0
  220. package/recipes/screen-kits-finance/apps/native/src/features/screen-kits/finance/screens/swap-modal.tsx +363 -0
  221. package/recipes/screen-kits-finance/apps/native/src/features/screen-kits/finance/screens/wallet-dashboard.tsx +281 -0
  222. package/recipes/screen-kits-finance/recipe.json +46 -0
  223. package/recipes/screen-kits-finance-native@latest.zip +0 -0
  224. package/recipes/screen-kits-fitness/apps/native/assets/sounds/timer-beep.wav +0 -0
  225. package/recipes/screen-kits-fitness/apps/native/src/app/screen-kits/fitness/_layout.tsx +10 -0
  226. package/recipes/screen-kits-fitness/apps/native/src/app/screen-kits/fitness/index.tsx +6 -0
  227. package/recipes/screen-kits-fitness/apps/native/src/app/screen-kits/fitness/timer.tsx +3 -0
  228. package/recipes/screen-kits-fitness/apps/native/src/app/screen-kits/fitness/workout.tsx +3 -0
  229. package/recipes/screen-kits-fitness/apps/native/src/features/screen-kits/fitness/components/timer-components.tsx +500 -0
  230. package/recipes/screen-kits-fitness/apps/native/src/features/screen-kits/fitness/components/timer-settings-modal.tsx +352 -0
  231. package/recipes/screen-kits-fitness/apps/native/src/features/screen-kits/fitness/components/workout-card.tsx +105 -0
  232. package/recipes/screen-kits-fitness/apps/native/src/features/screen-kits/fitness/constants.ts +189 -0
  233. package/recipes/screen-kits-fitness/apps/native/src/features/screen-kits/fitness/hooks/use-timer.ts +307 -0
  234. package/recipes/screen-kits-fitness/apps/native/src/features/screen-kits/fitness/index.ts +1 -0
  235. package/recipes/screen-kits-fitness/apps/native/src/features/screen-kits/fitness/screens/timer-screen.tsx +278 -0
  236. package/recipes/screen-kits-fitness/apps/native/src/features/screen-kits/fitness/screens/workout-dashboard.tsx +350 -0
  237. package/recipes/screen-kits-fitness/recipe.json +63 -0
  238. package/recipes/screen-kits-fitness-native@latest.zip +0 -0
  239. package/recipes/screen-kits-habits/apps/native/src/app/screen-kits/productivity/habits.tsx +1 -0
  240. package/recipes/screen-kits-habits/apps/native/src/app/screen-kits/productivity/kanban.tsx +1 -0
  241. package/recipes/screen-kits-habits/apps/native/src/app/screen-kits/productivity/routes.ts +4 -0
  242. package/recipes/screen-kits-habits/apps/native/src/features/screen-kits/productivity/components/AddTaskModal.tsx +246 -0
  243. package/recipes/screen-kits-habits/apps/native/src/features/screen-kits/productivity/components/DraggableTaskCard.tsx +92 -0
  244. package/recipes/screen-kits-habits/apps/native/src/features/screen-kits/productivity/components/KanbanColumn.tsx +238 -0
  245. package/recipes/screen-kits-habits/apps/native/src/features/screen-kits/productivity/components/TaskCard.tsx +144 -0
  246. package/recipes/screen-kits-habits/apps/native/src/features/screen-kits/productivity/components/add-habit-modal.tsx +271 -0
  247. package/recipes/screen-kits-habits/apps/native/src/features/screen-kits/productivity/constants.ts +295 -0
  248. package/recipes/screen-kits-habits/apps/native/src/features/screen-kits/productivity/kanban-utils.ts +62 -0
  249. package/recipes/screen-kits-habits/apps/native/src/features/screen-kits/productivity/screens/habit-tracker.tsx +1160 -0
  250. package/recipes/screen-kits-habits/apps/native/src/features/screen-kits/productivity/screens/kanban-board.tsx +432 -0
  251. package/recipes/screen-kits-habits/recipe.json +52 -0
  252. package/recipes/screen-kits-habits-native@latest.zip +0 -0
  253. package/recipes/screen-kits-native@latest.zip +0 -0
  254. package/recipes/sentry/apps/native/src/providers/sentry-provider.tsx +64 -0
  255. package/recipes/sentry/recipe.json +39 -0
  256. package/recipes/sentry-native@latest.zip +0 -0
  257. package/recipes/swipe-slider/apps/native/src/components/advanced-ui/sliders/swipe-slider-page.tsx +1 -1
  258. package/recipes/swipe-slider/recipe.json +5 -2
  259. package/recipes/swipe-slider-native@latest.zip +0 -0
  260. package/recipes/swipe-slider@latest.zip +0 -0
  261. package/recipes/timeline/apps/native/src/components/advanced-ui/timeline/demo.tsx +2 -1
  262. package/recipes/timeline/recipe.json +5 -2
  263. package/recipes/timeline-native@latest.zip +0 -0
  264. package/recipes/timeline@latest.zip +0 -0
  265. package/recipes/tracker-app/apps/native/src/app/tracker-app/index.tsx +1 -0
  266. package/recipes/tracker-app/recipe.json +10 -7
  267. package/recipes/tracker-app-native@latest.zip +0 -0
  268. package/recipes/tracker-app@latest.zip +0 -0
  269. package/recipes/upload-all.sh +8 -31
  270. package/recipes/voice-bot/apps/native/src/app/voice-bot/index.tsx +56 -0
  271. package/recipes/voice-bot/recipe.json +31 -7
  272. package/recipes/voice-bot-native@latest.zip +0 -0
  273. package/recipes/voice-bot@latest.zip +0 -0
  274. package/recipes/wake-word/apps/native/src/app/{(root)/(protected)/test-wake-word.tsx → test-wake-word.tsx} +43 -4
  275. package/recipes/wake-word/recipe.json +16 -26
  276. package/recipes/wake-word-native@latest.zip +0 -0
  277. package/recipes/wake-word@latest.zip +0 -0
  278. package/scripts/create-advanced-ui-recipes.sh +46 -19
  279. package/scripts/create-recipes.mjs +471 -117
  280. package/scripts/package-recipes.mjs +76 -0
  281. package/scripts/publish-all.sh +6 -2
  282. package/CHANGELOG.md +0 -198
  283. package/docs/archive/AUTO-DETECT-DEPS.md +0 -607
  284. package/docs/archive/FINAL-PACKAGE-STRATEGY.md +0 -583
  285. package/docs/archive/FINAL-SIMPLE-PLAN.md +0 -487
  286. package/docs/archive/FINAL-STATUS.md +0 -144
  287. package/docs/archive/FLOW-DIAGRAM.md +0 -1629
  288. package/docs/archive/GOTCHAS-AND-RISKS.md +0 -801
  289. package/docs/archive/IMPLEMENTATION-PLAN.md +0 -1360
  290. package/docs/archive/PLAN.md +0 -453
  291. package/docs/archive/PRODUCTION-READINESS.md +0 -684
  292. package/docs/archive/PRODUCTION-TEST-RESULTS.md +0 -465
  293. package/docs/archive/SIMPLIFIED-PLAN.md +0 -578
  294. package/docs/archive/STATUS.md +0 -199
  295. package/docs/archive/SUCCESS.md +0 -259
  296. package/docs/archive/TEST-SUMMARY.md +0 -261
  297. package/docs/archive/TESTING-CHECKLIST.md +0 -450
  298. package/docs/archive/USER-MODIFICATIONS.md +0 -448
  299. package/docs/decisions.md +0 -55
  300. package/docs/manual-testing.md +0 -91
  301. package/docs/next-steps.md +0 -12
  302. package/recipes/README.md +0 -156
  303. package/recipes/audio-recorder-supabase/packages/backend/src/services/recordings.ts +0 -369
  304. package/recipes/chatbot/apps/native/src/api-client/chatbot.ts +0 -83
  305. package/recipes/chatbot/packages/backend/convex/agents.ts +0 -115
  306. package/recipes/chatbot/packages/backend/convex/tools/index.ts +0 -18
  307. package/recipes/chatbot/packages/backend/convex/tools/knowledgeRetrieval.ts +0 -97
  308. package/recipes/chatbot/packages/backend/convex/tools/tavilySearch.ts +0 -83
  309. package/recipes/chatbot/packages/backend/convex/tools/userProfile.ts +0 -72
  310. package/recipes/chatbot-supabase/apps/native/src/api-client/supabase/chatbot.ts +0 -515
  311. package/recipes/chatbot-supabase/packages/backend/src/services/conversations.ts +0 -243
  312. package/recipes/chatbot-supabase/packages/backend/src/services/messages.ts +0 -327
  313. package/recipes/image-analysis/apps/native/src/api-client/image-analyzer.ts +0 -62
  314. package/recipes/image-analysis-supabase/packages/backend/src/services/image-analyses.ts +0 -132
  315. package/recipes/image-generator/apps/native/src/api-client/image-generator.ts +0 -34
  316. package/recipes/payments/apps/native/src/api-client/payments.ts +0 -44
  317. package/recipes/payments-supabase/packages/backend/src/services/payments.ts +0 -201
  318. package/recipes/posthog.json +0 -47
  319. package/recipes/revenuecat.json +0 -43
  320. package/recipes/sentry.json +0 -47
  321. package/recipes/wake-word/apps/native/assets/vosk-model/README.md +0 -103
  322. package/recipes/wake-word/apps/native/scripts/download-vosk-model.mjs +0 -127
  323. /package/recipes/{audio-recorder/apps/native/src/app/(root)/(protected) → audio-recorder-supabase/apps/native/src/app}/audio-recorder/index.tsx +0 -0
  324. /package/recipes/ios-widget/{targets → apps/native/targets}/widget/AppIntent.swift +0 -0
  325. /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-20x20@1x.png +0 -0
  326. /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-20x20@2x.png +0 -0
  327. /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-20x20@3x.png +0 -0
  328. /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-29x29@1x.png +0 -0
  329. /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-29x29@2x.png +0 -0
  330. /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-29x29@3x.png +0 -0
  331. /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-40x40@1x.png +0 -0
  332. /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-40x40@2x.png +0 -0
  333. /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-40x40@3x.png +0 -0
  334. /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-60x60@2x.png +0 -0
  335. /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-60x60@3x.png +0 -0
  336. /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-76x76@1x.png +0 -0
  337. /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-76x76@2x.png +0 -0
  338. /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-83.5x83.5@2x.png +0 -0
  339. /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Assets.xcassets/AppIcon.appiconset/Contents.json +0 -0
  340. /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png +0 -0
  341. /package/recipes/ios-widget/{targets → apps/native/targets}/widget/CalorieTrackerWidget.swift +0 -0
  342. /package/recipes/ios-widget/{targets → apps/native/targets}/widget/HabitTrackerWidget.swift +0 -0
  343. /package/recipes/ios-widget/{targets → apps/native/targets}/widget/Info.plist +0 -0
  344. /package/recipes/ios-widget/{targets → apps/native/targets}/widget/WidgetLiveActivity.swift +0 -0
  345. /package/recipes/ios-widget/{targets → apps/native/targets}/widget/expo-target.config.js +0 -0
  346. /package/recipes/ios-widget/{targets → apps/native/targets}/widget/generated.entitlements +0 -0
  347. /package/recipes/ios-widget/{targets → apps/native/targets}/widget/index.swift +0 -0
  348. /package/recipes/ios-widget/{targets → apps/native/targets}/widget/widgets.swift +0 -0
@@ -1,19 +1,17 @@
1
1
  import * as ImagePicker from 'expo-image-picker';
2
2
  import { Stack, useRouter } from 'expo-router';
3
- import React, { useCallback, useEffect, useState } from 'react';
3
+ import React, { useCallback, useEffect } from 'react';
4
4
  import { Alert, ScrollView, TouchableOpacity, View } from 'react-native';
5
5
  import { useSafeAreaInsets } from 'react-native-safe-area-context';
6
6
 
7
7
  import { Button, Image, Text } from '@/components/ui';
8
8
  import type { AnalysisFlowConfig } from '@/features/image-analyzer/config/master-analysis-config';
9
- import { useCameraCaptureStore } from '@/features/image-analyzer/hooks/use-image-analysis';
9
+ import {
10
+ useCameraCaptureStore,
11
+ useImageCaptureFlowStore,
12
+ } from '@/features/image-analyzer/hooks/use-image-analysis';
10
13
  import { translate } from '@/lib';
11
14
 
12
- export type CapturedImage = {
13
- uri: string;
14
- stepId: string;
15
- };
16
-
17
15
  type ImageCaptureScreenProps = {
18
16
  config: AnalysisFlowConfig;
19
17
  };
@@ -26,13 +24,27 @@ export function ImageCaptureScreen({ config }: ImageCaptureScreenProps) {
26
24
  const router = useRouter();
27
25
  const { capturedImageUri, stepId, clearCapturedImage } =
28
26
  useCameraCaptureStore();
29
- const [currentStepIndex, setCurrentStepIndex] = useState(0);
30
- const [capturedImages, setCapturedImages] = useState<
31
- (CapturedImage | null)[]
32
- >(new Array(config.photoSteps.length).fill(null));
27
+ const {
28
+ currentStepIndex,
29
+ capturedImages,
30
+ initFlow,
31
+ setCurrentStepIndex,
32
+ setCapturedImageForStep,
33
+ } = useImageCaptureFlowStore();
34
+
35
+ useEffect(() => {
36
+ initFlow(config.id, config.photoSteps.length);
37
+ }, [config.id, config.photoSteps.length, initFlow]);
33
38
 
34
- const currentStep = config.photoSteps[currentStepIndex];
35
- const allImagesCaptured = capturedImages.every((img) => img !== null);
39
+ const stepImages =
40
+ capturedImages.length === config.photoSteps.length
41
+ ? capturedImages
42
+ : new Array(config.photoSteps.length).fill(null);
43
+ const safeStepIndex =
44
+ currentStepIndex < config.photoSteps.length ? currentStepIndex : 0;
45
+ const currentStep = config.photoSteps[safeStepIndex];
46
+ const activeStepIndex = safeStepIndex;
47
+ const allImagesCaptured = stepImages.every((img) => img !== null);
36
48
 
37
49
  // Handle incoming captured image from camera screen via Zustand store
38
50
  useEffect(() => {
@@ -49,13 +61,9 @@ export function ImageCaptureScreen({ config }: ImageCaptureScreenProps) {
49
61
  }
50
62
 
51
63
  // Update the captured images array
52
- setCapturedImages((prevImages) => {
53
- const newCapturedImages = [...prevImages];
54
- newCapturedImages[capturedStepIndex] = {
55
- uri: capturedImageUri,
56
- stepId: stepId,
57
- };
58
- return newCapturedImages;
64
+ setCapturedImageForStep(capturedStepIndex, {
65
+ uri: capturedImageUri,
66
+ stepId: stepId,
59
67
  });
60
68
 
61
69
  // Only auto-advance if we're currently viewing the step we just captured
@@ -72,7 +80,14 @@ export function ImageCaptureScreen({ config }: ImageCaptureScreenProps) {
72
80
 
73
81
  clearCapturedImage();
74
82
  }
75
- }, [capturedImageUri, stepId, clearCapturedImage, config.photoSteps]);
83
+ }, [
84
+ capturedImageUri,
85
+ stepId,
86
+ clearCapturedImage,
87
+ config.photoSteps,
88
+ setCapturedImageForStep,
89
+ setCurrentStepIndex,
90
+ ]);
76
91
 
77
92
  // Handle camera launch
78
93
  const handleOpenCamera = useCallback(async () => {
@@ -124,35 +139,40 @@ export function ImageCaptureScreen({ config }: ImageCaptureScreenProps) {
124
139
  if (!result.canceled && result.assets[0]) {
125
140
  const imageUri = result.assets[0].uri;
126
141
 
127
- setCapturedImages((prevImages) => {
128
- const newCapturedImages = [...prevImages];
129
- newCapturedImages[currentStepIndex] = {
130
- uri: imageUri,
131
- stepId: currentStep.id,
132
- };
133
- return newCapturedImages;
142
+ setCapturedImageForStep(activeStepIndex, {
143
+ uri: imageUri,
144
+ stepId: currentStep.id,
134
145
  });
135
146
 
136
147
  // Auto-advance to next step if not last
137
- const isLastStep = currentStepIndex === config.photoSteps.length - 1;
148
+ const isLastStep = activeStepIndex === config.photoSteps.length - 1;
138
149
  if (!isLastStep) {
139
- setCurrentStepIndex(currentStepIndex + 1);
150
+ setCurrentStepIndex(activeStepIndex + 1);
140
151
  }
141
152
  }
142
153
  } catch (error) {
143
154
  console.error('Error selecting from gallery:', error);
144
155
  Alert.alert('Error', 'Failed to select image. Please try again.');
145
156
  }
146
- }, [currentStepIndex, currentStep.id, config.photoSteps.length]);
157
+ }, [
158
+ activeStepIndex,
159
+ currentStep.id,
160
+ config.photoSteps.length,
161
+ setCapturedImageForStep,
162
+ setCurrentStepIndex,
163
+ ]);
147
164
 
148
165
  // Handle step switching - allow clicking any step freely
149
- const handleStepSwitch = useCallback((targetIndex: number) => {
150
- setCurrentStepIndex(targetIndex);
151
- }, []);
166
+ const handleStepSwitch = useCallback(
167
+ (targetIndex: number) => {
168
+ setCurrentStepIndex(targetIndex);
169
+ },
170
+ [setCurrentStepIndex],
171
+ );
152
172
 
153
173
  // Handle continue - either to options or directly to loading based on config
154
174
  const handleContinue = useCallback(() => {
155
- const imageUris = capturedImages.map((img) => img!.uri);
175
+ const imageUris = stepImages.map((img) => img!.uri);
156
176
 
157
177
  if (config.skipOptionsScreen) {
158
178
  // Go directly to loading with default preferences
@@ -176,7 +196,7 @@ export function ImageCaptureScreen({ config }: ImageCaptureScreenProps) {
176
196
  },
177
197
  });
178
198
  }
179
- }, [capturedImages, router, config]);
199
+ }, [stepImages, router, config]);
180
200
 
181
201
  const insets = useSafeAreaInsets();
182
202
 
@@ -203,16 +223,16 @@ export function ImageCaptureScreen({ config }: ImageCaptureScreenProps) {
203
223
  <TouchableOpacity
204
224
  onPress={() => handleStepSwitch(index)}
205
225
  className={`relative z-10 size-16 items-center justify-center rounded-full border-2 ${
206
- capturedImages[index] !== null
226
+ stepImages[index] !== null
207
227
  ? 'border-green-500 bg-green-500'
208
- : index === currentStepIndex
228
+ : index === activeStepIndex
209
229
  ? 'border-primary-600 bg-primary-600'
210
230
  : 'border-neutral-300 bg-white dark:border-neutral-600 dark:bg-neutral-800'
211
231
  }`}
212
232
  >
213
- {capturedImages[index] !== null ? (
233
+ {stepImages[index] !== null ? (
214
234
  <Text className="text-2xl font-bold text-white">✓</Text>
215
- ) : index === currentStepIndex ? (
235
+ ) : index === activeStepIndex ? (
216
236
  <Text className="text-xl font-semibold text-white">
217
237
  {index + 1}
218
238
  </Text>
@@ -227,9 +247,9 @@ export function ImageCaptureScreen({ config }: ImageCaptureScreenProps) {
227
247
  {index < config.photoSteps.length - 1 && (
228
248
  <View
229
249
  className={`mx-3 h-0.5 flex-1 ${
230
- capturedImages[index] !== null
250
+ stepImages[index] !== null
231
251
  ? 'bg-green-500'
232
- : index < currentStepIndex
252
+ : index < activeStepIndex
233
253
  ? 'bg-primary-600'
234
254
  : 'bg-neutral-300 dark:bg-neutral-600'
235
255
  }`}
@@ -274,8 +294,8 @@ export function ImageCaptureScreen({ config }: ImageCaptureScreenProps) {
274
294
  {/* Guide Image or Captured Image */}
275
295
  <Image
276
296
  source={
277
- capturedImages[currentStepIndex]
278
- ? { uri: capturedImages[currentStepIndex]!.uri }
297
+ stepImages[activeStepIndex]
298
+ ? { uri: stepImages[activeStepIndex]!.uri }
279
299
  : currentStep.guideImage
280
300
  }
281
301
  className="aspect-square w-full rounded-3xl"
@@ -294,9 +314,7 @@ export function ImageCaptureScreen({ config }: ImageCaptureScreenProps) {
294
314
  <View className="mb-1 flex-row gap-x-3">
295
315
  <Button
296
316
  onPress={handleOpenCamera}
297
- label={
298
- capturedImages[currentStepIndex] ? 'Retake Photo' : 'Take Photo'
299
- }
317
+ label={stepImages[activeStepIndex] ? 'Retake Photo' : 'Take Photo'}
300
318
  className="flex-1 bg-primary-600"
301
319
  textClassName="text-white font-semibold"
302
320
  />
@@ -1,6 +1,12 @@
1
1
  import type { Id } from '@vibefast/backend/_generated/dataModel';
2
2
  import { Stack, useLocalSearchParams, useRouter } from 'expo-router';
3
- import React, { useCallback, useEffect, useMemo, useState } from 'react';
3
+ import React, {
4
+ useCallback,
5
+ useEffect,
6
+ useMemo,
7
+ useRef,
8
+ useState,
9
+ } from 'react';
4
10
  import { ActivityIndicator, Alert, Animated, View } from 'react-native';
5
11
 
6
12
  import { Button, Text } from '@/components/ui';
@@ -42,6 +48,10 @@ export function LoadingScreen({ config }: LoadingScreenProps) {
42
48
  const [analysisId, setAnalysisId] = useState<Id<'imageAnalyses'> | undefined>(
43
49
  undefined,
44
50
  );
51
+ const hasStartedRef = useRef(false);
52
+ const isRunningRef = useRef(false);
53
+ const hasHandledFailureRef = useRef(false);
54
+ const hasNavigatedRef = useRef(false);
45
55
 
46
56
  const analyzeImages = useAnalyzeImages();
47
57
 
@@ -50,6 +60,12 @@ export function LoadingScreen({ config }: LoadingScreenProps) {
50
60
 
51
61
  // Handle analysis creation
52
62
  const handleCreateAnalysis = useCallback(async () => {
63
+ if (isRunningRef.current) {
64
+ return;
65
+ }
66
+
67
+ isRunningRef.current = true;
68
+ hasHandledFailureRef.current = false;
53
69
  try {
54
70
  setCurrentStep('Converting images...');
55
71
  setProgress(10);
@@ -60,6 +76,7 @@ export function LoadingScreen({ config }: LoadingScreenProps) {
60
76
  goal: analysisGoal,
61
77
  feedbackStyle,
62
78
  },
79
+ analysisConfigId: config.convexAnalysisConfigId,
63
80
  onProgress: (step: string, progressValue: number) => {
64
81
  setCurrentStep(step);
65
82
  setProgress(progressValue);
@@ -90,11 +107,25 @@ export function LoadingScreen({ config }: LoadingScreenProps) {
90
107
  },
91
108
  ],
92
109
  );
110
+ } finally {
111
+ isRunningRef.current = false;
93
112
  }
94
- }, [imageUris, analysisGoal, feedbackStyle, analyzeImages, router]);
113
+ }, [
114
+ imageUris,
115
+ analysisGoal,
116
+ feedbackStyle,
117
+ analyzeImages,
118
+ router,
119
+ config.convexAnalysisConfigId,
120
+ ]);
95
121
 
96
122
  // Start analysis on mount
97
123
  useEffect(() => {
124
+ if (hasStartedRef.current) {
125
+ return;
126
+ }
127
+
128
+ hasStartedRef.current = true;
98
129
  handleCreateAnalysis();
99
130
  }, [handleCreateAnalysis]);
100
131
 
@@ -107,6 +138,11 @@ export function LoadingScreen({ config }: LoadingScreenProps) {
107
138
  setProgress(80);
108
139
  break;
109
140
  case 'completed':
141
+ if (hasNavigatedRef.current) {
142
+ return;
143
+ }
144
+
145
+ hasNavigatedRef.current = true;
110
146
  setCurrentStep('Analysis complete!');
111
147
  setProgress(100);
112
148
  // Navigate to results after a brief delay
@@ -123,6 +159,11 @@ export function LoadingScreen({ config }: LoadingScreenProps) {
123
159
  }, 1000);
124
160
  break;
125
161
  case 'failed':
162
+ if (hasHandledFailureRef.current) {
163
+ return;
164
+ }
165
+
166
+ hasHandledFailureRef.current = true;
126
167
  setCurrentStep('Analysis failed');
127
168
  setProgress(0);
128
169
  Alert.alert(
@@ -1,6 +1,12 @@
1
1
  import type { Id } from '@vibefast/backend/_generated/dataModel';
2
2
  import { Stack, useLocalSearchParams, useRouter } from 'expo-router';
3
- import React, { useCallback, useEffect, useMemo, useState } from 'react';
3
+ import React, {
4
+ useCallback,
5
+ useEffect,
6
+ useMemo,
7
+ useRef,
8
+ useState,
9
+ } from 'react';
4
10
  import { ActivityIndicator, Alert, View } from 'react-native';
5
11
 
6
12
  import { Button, Text } from '@/components/ui';
@@ -40,6 +46,10 @@ export default function LoadingScreen() {
40
46
  const [analysisId, setAnalysisId] = useState<Id<'imageAnalyses'> | undefined>(
41
47
  undefined,
42
48
  );
49
+ const hasStartedRef = useRef(false);
50
+ const isRunningRef = useRef(false);
51
+ const hasHandledFailureRef = useRef(false);
52
+ const hasNavigatedRef = useRef(false);
43
53
 
44
54
  const analyzeImages = useAnalyzeImages();
45
55
 
@@ -48,6 +58,12 @@ export default function LoadingScreen() {
48
58
 
49
59
  // Handle analysis creation
50
60
  const handleCreateAnalysis = useCallback(async () => {
61
+ if (isRunningRef.current) {
62
+ return;
63
+ }
64
+
65
+ isRunningRef.current = true;
66
+ hasHandledFailureRef.current = false;
51
67
  try {
52
68
  setCurrentStep('Converting images...');
53
69
  setProgress(10);
@@ -88,11 +104,18 @@ export default function LoadingScreen() {
88
104
  },
89
105
  ],
90
106
  );
107
+ } finally {
108
+ isRunningRef.current = false;
91
109
  }
92
110
  }, [imageUris, analysisGoal, feedbackStyle, analyzeImages, router]);
93
111
 
94
112
  // Start analysis on mount
95
113
  useEffect(() => {
114
+ if (hasStartedRef.current) {
115
+ return;
116
+ }
117
+
118
+ hasStartedRef.current = true;
96
119
  handleCreateAnalysis();
97
120
  }, [handleCreateAnalysis]);
98
121
 
@@ -105,6 +128,11 @@ export default function LoadingScreen() {
105
128
  setProgress(80);
106
129
  break;
107
130
  case 'completed':
131
+ if (hasNavigatedRef.current) {
132
+ return;
133
+ }
134
+
135
+ hasNavigatedRef.current = true;
108
136
  setCurrentStep('Analysis complete!');
109
137
  setProgress(100);
110
138
  // Navigate to results after a brief delay
@@ -120,6 +148,11 @@ export default function LoadingScreen() {
120
148
  }, 1000);
121
149
  break;
122
150
  case 'failed':
151
+ if (hasHandledFailureRef.current) {
152
+ return;
153
+ }
154
+
155
+ hasHandledFailureRef.current = true;
123
156
  setCurrentStep('Analysis failed');
124
157
  setProgress(0);
125
158
  Alert.alert(
@@ -1,5 +1,5 @@
1
1
  import type { Id } from '@vibefast/backend/_generated/dataModel';
2
- import * as FileSystem from 'expo-file-system';
2
+ import * as FileSystem from 'expo-file-system/legacy';
3
3
  import * as ImageManipulator from 'expo-image-manipulator';
4
4
  import { useCallback } from 'react';
5
5
  import { create } from 'zustand';
@@ -28,6 +28,7 @@ export type AnalyzeImagesParams = {
28
28
  goal: string;
29
29
  feedbackStyle: string;
30
30
  };
31
+ analysisConfigId?: string;
31
32
  onProgress?: (step: string, progress: number) => void;
32
33
  };
33
34
 
@@ -40,6 +41,85 @@ export type CreateImageAnalysisParams = {
40
41
  onProgress?: (step: string, progress: number) => void;
41
42
  };
42
43
 
44
+ export type CapturedImage = {
45
+ uri: string;
46
+ stepId: string;
47
+ };
48
+
49
+ type StepIndexUpdater = number | ((current: number) => number);
50
+
51
+ type ImageCaptureFlowState = {
52
+ flowId: string | null;
53
+ stepsCount: number;
54
+ currentStepIndex: number;
55
+ capturedImages: (CapturedImage | null)[];
56
+ initFlow: (flowId: string, stepsCount: number) => void;
57
+ setCurrentStepIndex: (updater: StepIndexUpdater) => void;
58
+ setCapturedImageForStep: (stepIndex: number, image: CapturedImage) => void;
59
+ clearFlow: () => void;
60
+ };
61
+
62
+ export const useImageCaptureFlowStore = create<ImageCaptureFlowState>(
63
+ (set) => ({
64
+ flowId: null,
65
+ stepsCount: 0,
66
+ currentStepIndex: 0,
67
+ capturedImages: [],
68
+ initFlow: (flowId, stepsCount) =>
69
+ set((state) => {
70
+ if (
71
+ state.flowId === flowId &&
72
+ state.stepsCount === stepsCount &&
73
+ state.capturedImages.length === stepsCount
74
+ ) {
75
+ return state;
76
+ }
77
+
78
+ return {
79
+ flowId,
80
+ stepsCount,
81
+ currentStepIndex: 0,
82
+ capturedImages: new Array(stepsCount).fill(null),
83
+ };
84
+ }),
85
+ setCurrentStepIndex: (updater) =>
86
+ set((state) => {
87
+ const nextIndex =
88
+ typeof updater === 'function'
89
+ ? updater(state.currentStepIndex)
90
+ : updater;
91
+ if (state.stepsCount <= 0) {
92
+ return { currentStepIndex: nextIndex };
93
+ }
94
+
95
+ const maxIndex = state.stepsCount - 1;
96
+ const clampedIndex = Math.max(0, Math.min(nextIndex, maxIndex));
97
+ return { currentStepIndex: clampedIndex };
98
+ }),
99
+ setCapturedImageForStep: (stepIndex, image) =>
100
+ set((state) => {
101
+ const nextImages =
102
+ state.stepsCount > 0 ? new Array(state.stepsCount).fill(null) : [];
103
+
104
+ if (state.capturedImages.length === state.stepsCount) {
105
+ state.capturedImages.forEach((item, index) => {
106
+ nextImages[index] = item;
107
+ });
108
+ }
109
+
110
+ nextImages[stepIndex] = image;
111
+ return { capturedImages: nextImages };
112
+ }),
113
+ clearFlow: () =>
114
+ set({
115
+ flowId: null,
116
+ stepsCount: 0,
117
+ currentStepIndex: 0,
118
+ capturedImages: [],
119
+ }),
120
+ }),
121
+ );
122
+
43
123
  /**
44
124
  * Hook for analyzing images using the new background processing flow.
45
125
  */
@@ -50,7 +130,7 @@ export function useAnalyzeImages() {
50
130
  async (
51
131
  params: AnalyzeImagesParams,
52
132
  ): Promise<Id<'imageAnalyses'> | null> => {
53
- const { imageUris, preferences, onProgress } = params;
133
+ const { imageUris, preferences, analysisConfigId, onProgress } = params;
54
134
 
55
135
  try {
56
136
  onProgress?.('Optimizing images...', 15);
@@ -92,6 +172,7 @@ export function useAnalyzeImages() {
92
172
  const analysisId = await analyzeAction({
93
173
  imagesAsBase64: base64Images,
94
174
  preferences,
175
+ analysisConfigId,
95
176
  });
96
177
 
97
178
  onProgress?.('Analysis in progress...', 50);
@@ -1,38 +1,30 @@
1
1
  {
2
2
  "name": "image-analysis",
3
3
  "version": "1.0.0",
4
- "description": "AI-powered image analysis",
4
+ "description": "AI image analysis",
5
5
  "copy": [
6
+ {
7
+ "from": "apps/native/src/app/analysis",
8
+ "to": "apps/native/src/app/(root)/(protected)/analysis"
9
+ },
6
10
  {
7
11
  "from": "apps/native/src/features/image-analyzer",
8
12
  "to": "apps/native/src/features/image-analyzer"
9
13
  },
10
14
  {
11
- "from": "apps/native/src/api-client/image-analyzer.ts",
12
- "to": "apps/native/src/api-client/image-analyzer.ts"
15
+ "from": "packages/backend/convex/imageAnalysis",
16
+ "to": "packages/backend/convex/imageAnalysis"
13
17
  },
14
18
  {
15
19
  "from": "packages/backend/convex/imageAnalysis.ts",
16
20
  "to": "packages/backend/convex/imageAnalysis.ts"
17
- },
18
- {
19
- "from": "packages/backend/convex/imageAnalysis",
20
- "to": "packages/backend/convex/imageAnalysis"
21
21
  }
22
22
  ],
23
- "configuration": {
24
- "apiClient": {
25
- "exports": [
26
- "export { type ImageAnalyzerApi, imageAnalyzerApi } from './image-analyzer';"
27
- ]
28
- }
29
- },
30
23
  "nav": {
31
- "href": "/(root)/(protected)/image-analyzer",
32
- "label": "Image Analyzer",
24
+ "href": "/(root)/(protected)/analysis/face-analysis",
25
+ "label": "Image Analysis",
33
26
  "icon": "🔍",
34
- "color": "#3B82F6"
27
+ "color": "#22C55E"
35
28
  },
36
- "target": "native",
37
- "dependencies": { "expo": ["expo-camera", "expo-image-picker"] }
29
+ "target": "native"
38
30
  }
@@ -0,0 +1,5 @@
1
+ import { Slot } from 'expo-router';
2
+
3
+ export default function AnalysisLayout() {
4
+ return <Slot />;
5
+ }
@@ -0,0 +1,50 @@
1
+ import { Redirect, useLocalSearchParams } from 'expo-router';
2
+ import React from 'react';
3
+ import { View } from 'react-native';
4
+
5
+ import { FocusAwareStatusBar, Text } from '@/components/ui';
6
+ import { DynamicAnalysisOptionsScreen } from '@/features/image-analyzer/app/analysis-options-screen';
7
+ import { getAnalysisFlowConfigSafe } from '@/features/image-analyzer/config/master-analysis-config';
8
+ import { translate } from '@/lib';
9
+ import { useThemeConfig } from '@/lib/use-theme-config';
10
+
11
+ /**
12
+ * Dynamic analysis options route that loads the appropriate configuration
13
+ * based on the type parameter
14
+ */
15
+ export default function DynamicAnalysisOptionsRoute() {
16
+ const { type } = useLocalSearchParams<{ type: string }>();
17
+ const theme = useThemeConfig();
18
+
19
+ if (!type || typeof type !== 'string') {
20
+ return <Redirect href="/(protected)/(tabs)" />;
21
+ }
22
+
23
+ const config = getAnalysisFlowConfigSafe(type);
24
+
25
+ if (!config) {
26
+ // Invalid analysis type, redirect to home with a user-friendly message
27
+ return (
28
+ <View
29
+ style={{
30
+ flex: 1,
31
+ backgroundColor: theme.colors.background,
32
+ justifyContent: 'center',
33
+ alignItems: 'center',
34
+ padding: 20,
35
+ }}
36
+ >
37
+ <FocusAwareStatusBar />
38
+ <Text className="mb-4 text-center text-lg font-medium">
39
+ {translate('common.invalid_analysis_type')}
40
+ </Text>
41
+ <Text className="mb-6 text-center text-muted-foreground">
42
+ {translate('common.redirecting_home')}
43
+ </Text>
44
+ <Redirect href="/(protected)/(tabs)" />
45
+ </View>
46
+ );
47
+ }
48
+
49
+ return <DynamicAnalysisOptionsScreen config={config} />;
50
+ }
@@ -0,0 +1,2 @@
1
+ // Re-export the camera screen
2
+ export { default } from '@/features/image-analyzer/app/camera';
@@ -0,0 +1,50 @@
1
+ import { Redirect, useLocalSearchParams } from 'expo-router';
2
+ import React from 'react';
3
+ import { View } from 'react-native';
4
+
5
+ import { FocusAwareStatusBar, Text } from '@/components/ui';
6
+ import { ImageCaptureScreen } from '@/features/image-analyzer/app/image-capture-screen';
7
+ import { getAnalysisFlowConfigSafe } from '@/features/image-analyzer/config/master-analysis-config';
8
+ import { translate } from '@/lib';
9
+ import { useThemeConfig } from '@/lib/use-theme-config';
10
+
11
+ /**
12
+ * Dynamic analysis route that loads the appropriate configuration
13
+ * based on the type parameter
14
+ */
15
+ export default function DynamicAnalysisScreen() {
16
+ const { type } = useLocalSearchParams<{ type: string }>();
17
+ const theme = useThemeConfig();
18
+
19
+ if (!type || typeof type !== 'string') {
20
+ return <Redirect href="/(protected)/(tabs)" />;
21
+ }
22
+
23
+ const config = getAnalysisFlowConfigSafe(type);
24
+
25
+ if (!config) {
26
+ // Invalid analysis type, redirect to home with a user-friendly message
27
+ return (
28
+ <View
29
+ style={{
30
+ flex: 1,
31
+ backgroundColor: theme.colors.background,
32
+ justifyContent: 'center',
33
+ alignItems: 'center',
34
+ padding: 20,
35
+ }}
36
+ >
37
+ <FocusAwareStatusBar />
38
+ <Text className="mb-4 text-center text-lg font-medium">
39
+ {translate('common.invalid_analysis_type')}
40
+ </Text>
41
+ <Text className="mb-6 text-center text-muted-foreground">
42
+ {translate('common.redirecting_home')}
43
+ </Text>
44
+ <Redirect href="/(protected)/(tabs)" />
45
+ </View>
46
+ );
47
+ }
48
+
49
+ return <ImageCaptureScreen config={config} />;
50
+ }