vibefast-cli 1.3.0 → 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 +8 -9
  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 +271 -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 -33
  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 -69
  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 -70
  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 -62
  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 -44
  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
@@ -2,7 +2,13 @@ import { MaterialIcons } from '@expo/vector-icons';
2
2
  import { type CameraType, CameraView, useCameraPermissions } from 'expo-camera';
3
3
  import { Stack, useLocalSearchParams, useRouter } from 'expo-router';
4
4
  import React, { useCallback, useRef, useState } from 'react';
5
- import { ActivityIndicator, Alert, TouchableOpacity, View } from 'react-native';
5
+ import {
6
+ ActivityIndicator,
7
+ Alert,
8
+ StyleSheet,
9
+ TouchableOpacity,
10
+ View,
11
+ } from 'react-native';
6
12
 
7
13
  import { Button, Image, Text } from '@/components/ui';
8
14
  import { useCameraCaptureStore } from '@/features/image-analyzer/hooks/use-image-analysis';
@@ -11,7 +17,6 @@ type CameraScreenParams = {
11
17
  stepId: string;
12
18
  stepLabel: string;
13
19
  stepDescription: string;
14
- guideText: string;
15
20
  };
16
21
 
17
22
  /**
@@ -28,11 +33,12 @@ export default function CameraScreen() {
28
33
  const [facing, setFacing] = useState<CameraType>('back');
29
34
  const [capturedImageUri, setCapturedImageUri] = useState<string | null>(null);
30
35
  const [isCapturing, setIsCapturing] = useState(false);
36
+ const [isCameraReady, setIsCameraReady] = useState(false);
31
37
  const cameraRef = useRef<CameraView>(null);
32
38
 
33
39
  // Handle photo capture
34
40
  const handleTakePhoto = useCallback(async () => {
35
- if (!cameraRef.current || isCapturing) return;
41
+ if (!cameraRef.current || isCapturing || !isCameraReady) return;
36
42
 
37
43
  try {
38
44
  setIsCapturing(true);
@@ -49,7 +55,7 @@ export default function CameraScreen() {
49
55
  } finally {
50
56
  setIsCapturing(false);
51
57
  }
52
- }, [isCapturing]);
58
+ }, [isCapturing, isCameraReady]);
53
59
 
54
60
  // Handle retake
55
61
  const handleRetake = useCallback(() => {
@@ -147,74 +153,75 @@ export default function CameraScreen() {
147
153
  <View className="flex-1 bg-black">
148
154
  <CameraView
149
155
  ref={cameraRef}
150
- style={{ flex: 1 }}
156
+ style={StyleSheet.absoluteFill}
151
157
  facing={facing}
152
158
  mode="picture"
153
- >
154
- {/* Header with step info */}
155
- <View className="absolute inset-x-0 top-12 px-6">
156
- <View className="rounded-xl bg-black/70 p-4">
157
- <Text className="text-center text-lg font-bold text-white">
158
- {params.stepLabel}
159
- </Text>
160
- <Text className="text-center text-sm text-white/80">
161
- 📸 {params.guideText}
162
- </Text>
163
- </View>
159
+ onCameraReady={() => setIsCameraReady(true)}
160
+ />
161
+
162
+ {/* Header with step info */}
163
+ <View className="absolute inset-x-0 top-12 px-6">
164
+ <View className="rounded-xl bg-black/70 p-4">
165
+ <Text className="text-center text-lg font-bold text-white">
166
+ {params.stepLabel}
167
+ </Text>
168
+ <Text className="text-center text-sm text-white/80">
169
+ 📸 {params.stepDescription}
170
+ </Text>
164
171
  </View>
172
+ </View>
165
173
 
166
- {/* Frame guide overlay */}
167
- <View className="absolute inset-0 items-center justify-center">
168
- <View className="relative size-80">
169
- {/* Frame */}
170
- <View className="absolute inset-0 rounded-2xl border-2 border-white/60" />
171
-
172
- {/* Corner guides */}
173
- <View className="absolute left-2 top-2 size-6 border-l-4 border-t-4 border-white" />
174
- <View className="absolute right-2 top-2 size-6 border-r-4 border-t-4 border-white" />
175
- <View className="absolute bottom-2 left-2 size-6 border-b-4 border-l-4 border-white" />
176
- <View className="absolute bottom-2 right-2 size-6 border-b-4 border-r-4 border-white" />
177
-
178
- {/* Center guide */}
179
- <View className="absolute inset-0 items-center justify-center">
180
- <View className="h-1 w-8 bg-white/60" />
181
- <View className="absolute h-8 w-1 bg-white/60" />
182
- </View>
174
+ {/* Frame guide overlay */}
175
+ <View className="absolute inset-0 items-center justify-center">
176
+ <View className="relative size-80">
177
+ {/* Frame */}
178
+ <View className="absolute inset-0 rounded-2xl border-2 border-white/60" />
179
+
180
+ {/* Corner guides */}
181
+ <View className="absolute left-2 top-2 size-6 border-l-4 border-t-4 border-white" />
182
+ <View className="absolute right-2 top-2 size-6 border-r-4 border-t-4 border-white" />
183
+ <View className="absolute bottom-2 left-2 size-6 border-b-4 border-l-4 border-white" />
184
+ <View className="absolute bottom-2 right-2 size-6 border-b-4 border-r-4 border-white" />
185
+
186
+ {/* Center guide */}
187
+ <View className="absolute inset-0 items-center justify-center">
188
+ <View className="h-1 w-8 bg-white/60" />
189
+ <View className="absolute h-8 w-1 bg-white/60" />
183
190
  </View>
184
191
  </View>
192
+ </View>
185
193
 
186
- {/* Controls */}
187
- <View className="absolute inset-x-0 bottom-12 flex-row items-center justify-center px-6">
188
- {/* Close button */}
189
- <TouchableOpacity
190
- onPress={() => router.back()}
191
- className="absolute left-6 size-12 items-center justify-center rounded-full bg-black/50"
192
- >
193
- <Text className="text-xl text-white">✕</Text>
194
- </TouchableOpacity>
195
-
196
- {/* Shutter button */}
197
- <TouchableOpacity
198
- onPress={handleTakePhoto}
199
- disabled={isCapturing}
200
- className="size-20 items-center justify-center rounded-full border-4 border-white bg-white/20"
201
- >
202
- {isCapturing ? (
203
- <ActivityIndicator size="large" color="white" />
204
- ) : (
205
- <View className="size-16 rounded-full bg-white" />
206
- )}
207
- </TouchableOpacity>
208
-
209
- {/* Flip camera button */}
210
- <TouchableOpacity
211
- onPress={toggleCameraFacing}
212
- className="absolute right-6 size-12 items-center justify-center rounded-full bg-black/50"
213
- >
214
- <MaterialIcons name="cameraswitch" size={24} color="white" />
215
- </TouchableOpacity>
216
- </View>
217
- </CameraView>
194
+ {/* Controls */}
195
+ <View className="absolute inset-x-0 bottom-12 flex-row items-center justify-center px-6">
196
+ {/* Close button */}
197
+ <TouchableOpacity
198
+ onPress={() => router.back()}
199
+ className="absolute left-6 size-12 items-center justify-center rounded-full bg-black/50"
200
+ >
201
+ <Text className="text-xl text-white">✕</Text>
202
+ </TouchableOpacity>
203
+
204
+ {/* Shutter button */}
205
+ <TouchableOpacity
206
+ onPress={handleTakePhoto}
207
+ disabled={isCapturing || !isCameraReady}
208
+ className="size-20 items-center justify-center rounded-full border-4 border-white bg-white/20"
209
+ >
210
+ {isCapturing ? (
211
+ <ActivityIndicator size="large" color="white" />
212
+ ) : (
213
+ <View className="size-16 rounded-full bg-white" />
214
+ )}
215
+ </TouchableOpacity>
216
+
217
+ {/* Flip camera button */}
218
+ <TouchableOpacity
219
+ onPress={toggleCameraFacing}
220
+ className="absolute right-6 size-12 items-center justify-center rounded-full bg-black/50"
221
+ >
222
+ <MaterialIcons name="cameraswitch" size={24} color="white" />
223
+ </TouchableOpacity>
224
+ </View>
218
225
  </View>
219
226
  </>
220
227
  );
@@ -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);