vibefast-cli 1.1.5 → 1.2.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 (299) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +63 -169
  3. package/dist/commands/add.d.ts +1 -1
  4. package/dist/commands/add.d.ts.map +1 -1
  5. package/dist/commands/add.js +547 -589
  6. package/dist/commands/add.js.map +1 -1
  7. package/dist/commands/checklist.d.ts +1 -1
  8. package/dist/commands/checklist.d.ts.map +1 -1
  9. package/dist/commands/checklist.js +40 -39
  10. package/dist/commands/checklist.js.map +1 -1
  11. package/dist/commands/doctor.d.ts +1 -1
  12. package/dist/commands/doctor.js +22 -22
  13. package/dist/commands/doctor.js.map +1 -1
  14. package/dist/commands/env.d.ts +1 -1
  15. package/dist/commands/env.d.ts.map +1 -1
  16. package/dist/commands/env.js +58 -53
  17. package/dist/commands/env.js.map +1 -1
  18. package/dist/commands/health.d.ts +1 -1
  19. package/dist/commands/health.d.ts.map +1 -1
  20. package/dist/commands/health.js +101 -93
  21. package/dist/commands/health.js.map +1 -1
  22. package/dist/commands/init.d.ts +1 -1
  23. package/dist/commands/init.d.ts.map +1 -1
  24. package/dist/commands/init.js +416 -296
  25. package/dist/commands/init.js.map +1 -1
  26. package/dist/commands/remove.d.ts +1 -1
  27. package/dist/commands/remove.d.ts.map +1 -1
  28. package/dist/commands/remove.js +77 -64
  29. package/dist/commands/remove.js.map +1 -1
  30. package/dist/commands/status.d.ts +1 -1
  31. package/dist/commands/status.d.ts.map +1 -1
  32. package/dist/commands/status.js +15 -14
  33. package/dist/commands/status.js.map +1 -1
  34. package/dist/core/__tests__/detect.test.js +68 -34
  35. package/dist/core/__tests__/detect.test.js.map +1 -1
  36. package/dist/core/ast.d.ts +14 -0
  37. package/dist/core/ast.d.ts.map +1 -0
  38. package/dist/core/ast.js +239 -0
  39. package/dist/core/ast.js.map +1 -0
  40. package/dist/core/codemod.d.ts.map +1 -1
  41. package/dist/core/codemod.js +62 -44
  42. package/dist/core/codemod.js.map +1 -1
  43. package/dist/core/config.d.ts +10 -0
  44. package/dist/core/config.d.ts.map +1 -0
  45. package/dist/core/config.js +51 -0
  46. package/dist/core/config.js.map +1 -0
  47. package/dist/core/detect.d.ts +8 -2
  48. package/dist/core/detect.d.ts.map +1 -1
  49. package/dist/core/detect.js +52 -21
  50. package/dist/core/detect.js.map +1 -1
  51. package/dist/core/errors.d.ts.map +1 -1
  52. package/dist/core/errors.js +9 -8
  53. package/dist/core/errors.js.map +1 -1
  54. package/dist/core/exec.d.ts +16 -0
  55. package/dist/core/exec.d.ts.map +1 -0
  56. package/dist/core/exec.js +48 -0
  57. package/dist/core/exec.js.map +1 -0
  58. package/dist/core/manualSteps.d.ts +7 -0
  59. package/dist/core/manualSteps.d.ts.map +1 -0
  60. package/dist/core/manualSteps.js +59 -0
  61. package/dist/core/manualSteps.js.map +1 -0
  62. package/dist/core/paths.d.ts +3 -1
  63. package/dist/core/paths.d.ts.map +1 -1
  64. package/dist/core/paths.js +14 -10
  65. package/dist/core/paths.js.map +1 -1
  66. package/dist/core/spinner.d.ts +1 -1
  67. package/dist/core/spinner.d.ts.map +1 -1
  68. package/dist/core/spinner.js +38 -8
  69. package/dist/core/spinner.js.map +1 -1
  70. package/dist/core/vosk.d.ts.map +1 -1
  71. package/dist/core/vosk.js +50 -39
  72. package/dist/core/vosk.js.map +1 -1
  73. package/docs/manual-testing.md +91 -0
  74. package/package.json +6 -3
  75. package/recipes/audio-recorder/apps/native/src/app/audio-recorder/index.tsx +5 -0
  76. package/recipes/audio-recorder/recipe.json +3 -3
  77. package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/components/audio-player.tsx +301 -0
  78. package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/components/audio-recorder.tsx +373 -0
  79. package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/components/audio-waveform.tsx +270 -0
  80. package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/components/index.ts +4 -0
  81. package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/components/recording-list.tsx +89 -0
  82. package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/audio-player-demo.tsx +66 -0
  83. package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/audio-recorder-cloud.tsx +68 -0
  84. package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/audio-recorder-interview.tsx +102 -0
  85. package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/basic.tsx +27 -0
  86. package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/index.ts +5 -0
  87. package/recipes/audio-recorder-supabase/apps/native/src/features/audio-recorder/demo/with-recording-list-demo.tsx +82 -0
  88. package/recipes/audio-recorder-supabase/packages/backend/src/services/recordings.ts +369 -0
  89. package/recipes/audio-recorder-supabase/packages/backend/supabase/migrations/recordings.sql +70 -0
  90. package/recipes/audio-recorder-supabase/recipe.json +35 -0
  91. package/recipes/audio-recorder-supabase@latest.zip +0 -0
  92. package/recipes/audio-recorder@latest.zip +0 -0
  93. package/recipes/charts/apps/native/src/features/charts/components/bar-chart.tsx +3 -3
  94. package/recipes/charts/apps/native/src/features/charts/components/candlestick-chart.tsx +2 -2
  95. package/recipes/charts/apps/native/src/features/charts/components/chart-card.tsx +5 -5
  96. package/recipes/charts/apps/native/src/features/charts/components/column-chart.tsx +3 -3
  97. package/recipes/charts/apps/native/src/features/charts/components/doughnut-chart.tsx +20 -4
  98. package/recipes/charts/apps/native/src/features/charts/components/line-chart.tsx +7 -6
  99. package/recipes/charts/apps/native/src/features/charts/components/radar-chart.tsx +6 -4
  100. package/recipes/charts/apps/native/src/features/charts/components/radial-bar-chart.tsx +1 -1
  101. package/recipes/charts/apps/native/src/features/charts/components/stacked-bar-chart.tsx +5 -4
  102. package/recipes/charts/recipe.json +4 -13
  103. package/recipes/charts@latest.zip +0 -0
  104. package/recipes/chatbot/apps/native/src/app/chatbot/index.tsx +1 -0
  105. package/recipes/chatbot/apps/native/src/features/chatbot/components/chat-markdown.tsx +86 -86
  106. package/recipes/chatbot/apps/native/src/features/chatbot/components/markdown/code-block.tsx +86 -53
  107. package/recipes/chatbot/recipe.json +26 -92
  108. package/recipes/chatbot-supabase/apps/native/src/api-client/supabase/chatbot.ts +515 -0
  109. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/app/index.tsx +257 -0
  110. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/chat-header-buttons.tsx +59 -0
  111. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/chat-input-bar.tsx +485 -0
  112. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/chat-markdown.tsx +575 -0
  113. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/chat-message-bubble.tsx +223 -0
  114. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/chat-settings-modal.tsx +161 -0
  115. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/image-preview-list.tsx +116 -0
  116. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/markdown/code-block.tsx +165 -0
  117. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/markdown/index.ts +10 -0
  118. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/markdown/table-renderer.tsx +129 -0
  119. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/message-error-boundary.tsx +78 -0
  120. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/message-list.tsx +170 -0
  121. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/model-selector.tsx +283 -0
  122. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/report-content-modal.tsx +188 -0
  123. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/components/suggested-messages.tsx +67 -0
  124. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/constants/models.ts +20 -0
  125. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/constants/report-reasons.ts +9 -0
  126. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-attachment-cache.ts +142 -0
  127. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-chat-config.ts +458 -0
  128. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-chat-handlers.ts +429 -0
  129. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-chatbot-settings.ts +89 -0
  130. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-conversation.ts +90 -0
  131. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-image-picker.ts +122 -0
  132. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-keyboard-coordinator.ts +161 -0
  133. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/hooks/use-smart-scroll-manager.ts +213 -0
  134. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/models/index.ts +86 -0
  135. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/models/models.ts +162 -0
  136. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/models/providers.ts +62 -0
  137. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/models/types.ts +40 -0
  138. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/services/file-uploader.ts +287 -0
  139. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/services/message-handler-service.ts +189 -0
  140. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/types/index.ts +70 -0
  141. package/recipes/chatbot-supabase/apps/native/src/features/chatbot/utils/chat-telemetry.ts +91 -0
  142. package/recipes/chatbot-supabase/packages/backend/src/services/conversations.ts +243 -0
  143. package/recipes/chatbot-supabase/packages/backend/src/services/messages.ts +327 -0
  144. package/recipes/chatbot-supabase/packages/backend/supabase/functions/chat-stream/index.ts +347 -0
  145. package/recipes/chatbot-supabase/packages/backend/supabase/migrations/chatbot.sql +104 -0
  146. package/recipes/chatbot-supabase/recipe.json +79 -0
  147. package/recipes/chatbot-supabase@latest.zip +0 -0
  148. package/recipes/chatbot.zip +0 -0
  149. package/recipes/chatbot@latest.zip +0 -0
  150. package/recipes/image-analysis/packages/backend/convex/imageAnalysis/index.ts +2 -2
  151. package/recipes/image-analysis/packages/backend/convex/imageAnalysis.ts +0 -1
  152. package/recipes/image-analysis/recipe.json +15 -55
  153. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/analysis-options-screen.tsx +304 -0
  154. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/camera.tsx +221 -0
  155. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/image-capture-screen.tsx +333 -0
  156. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/loading-screen.tsx +214 -0
  157. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/loading.tsx +191 -0
  158. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/results.tsx +137 -0
  159. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/trait-details.tsx +172 -0
  160. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/use-analysis-data.ts +160 -0
  161. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/app/use-results-screen.ts +151 -0
  162. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/achievement-badge.tsx +77 -0
  163. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/achievement-card.tsx +75 -0
  164. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/achievement-unlocked-modal.tsx +162 -0
  165. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/achievements-section.tsx +44 -0
  166. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/advice-list.tsx +42 -0
  167. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/circular-progress.tsx +233 -0
  168. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/content-card.tsx +38 -0
  169. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/error-state.tsx +42 -0
  170. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/index.ts +9 -0
  171. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/loading-state.tsx +26 -0
  172. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/profile-image.tsx +60 -0
  173. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/results-header.tsx +62 -0
  174. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/score-display.tsx +54 -0
  175. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/share-options-modal.tsx +110 -0
  176. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/components/results/traits-grid.tsx +74 -0
  177. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/config/analysis-config.ts +80 -0
  178. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/config/master-analysis-config.ts +157 -0
  179. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/hooks/index.ts +1 -0
  180. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/hooks/use-analysis.ts +38 -0
  181. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/hooks/use-image-analysis.ts +208 -0
  182. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/services/analysis-service.ts +262 -0
  183. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/services/share-service.ts +176 -0
  184. package/recipes/image-analysis-supabase/apps/native/src/features/image-analyzer/services/trait-details-service.ts +289 -0
  185. package/recipes/image-analysis-supabase/packages/backend/src/services/image-analyses.ts +132 -0
  186. package/recipes/image-analysis-supabase/packages/backend/supabase/functions/analyze-image/index.ts +312 -0
  187. package/recipes/image-analysis-supabase/packages/backend/supabase/migrations/image_analysis.sql +42 -0
  188. package/recipes/image-analysis-supabase/recipe.json +57 -0
  189. package/recipes/image-analysis-supabase@latest.zip +0 -0
  190. package/recipes/image-analysis@latest.zip +0 -0
  191. package/recipes/image-generator/apps/native/src/features/image-generator/app/index.tsx +16 -2
  192. package/recipes/image-generator/apps/native/src/features/image-generator/components/image-model-selector.tsx +11 -5
  193. package/recipes/image-generator/apps/native/src/features/image-generator/hooks/use-image-generator.ts +11 -5
  194. package/recipes/image-generator/packages/backend/convex/imageGeneration/index.ts +2 -2
  195. package/recipes/image-generator/recipe.json +16 -39
  196. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/app/_layout.tsx +26 -0
  197. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/app/gallery.tsx +217 -0
  198. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/app/index.tsx +251 -0
  199. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/components/gallery-image.tsx +25 -0
  200. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/components/image-detail-modal.tsx +215 -0
  201. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/components/image-model-selector.tsx +216 -0
  202. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/components/image-placeholder.tsx +26 -0
  203. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/hooks/use-image-gallery.ts +71 -0
  204. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/hooks/use-image-generator-settings.ts +152 -0
  205. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/hooks/use-image-generator.ts +103 -0
  206. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/models/models.ts +66 -0
  207. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/services/image-gallery-service.ts +96 -0
  208. package/recipes/image-generator-supabase/apps/native/src/features/image-generator/services/image-save-service.ts +120 -0
  209. package/recipes/image-generator-supabase/packages/backend/supabase/functions/generate-image/index.ts +291 -0
  210. package/recipes/image-generator-supabase/packages/backend/supabase/migrations/image_generator.sql +71 -0
  211. package/recipes/image-generator-supabase/recipe.json +59 -0
  212. package/recipes/image-generator-supabase@latest.zip +0 -0
  213. package/recipes/image-generator@latest.zip +0 -0
  214. package/recipes/ios-widget/recipe.json +15 -24
  215. package/recipes/ios-widget@latest.zip +0 -0
  216. package/recipes/onboarding/apps/native/src/features/onboarding/analytics/index.ts +9 -0
  217. package/recipes/onboarding/apps/native/src/features/onboarding/components/onboarding-with-analytics.tsx +141 -0
  218. package/recipes/onboarding/apps/native/src/features/onboarding/components/onboarding.tsx +173 -0
  219. package/recipes/onboarding/apps/native/src/features/onboarding/config/onboarding-flow-config.ts +189 -0
  220. package/recipes/onboarding/apps/native/src/features/onboarding/demo-one/app/index.tsx +42 -0
  221. package/recipes/onboarding/apps/native/src/features/onboarding/demo-one/data.ts +32 -0
  222. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/app/index.tsx +43 -0
  223. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/interactive-onboarding.tsx +222 -0
  224. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/ai-tone-step.tsx +133 -0
  225. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/currency-step.tsx +165 -0
  226. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/feature-ai-step.tsx +199 -0
  227. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/feature-chatbot-step.tsx +154 -0
  228. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/feature-manual-step.tsx +156 -0
  229. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/feature-scan-step.tsx +158 -0
  230. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/main-reason-step.tsx +139 -0
  231. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/notification-step.tsx +129 -0
  232. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/overspend-step.tsx +138 -0
  233. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/personalizing-step.tsx +190 -0
  234. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/rating-step.tsx +98 -0
  235. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/reminder-step.tsx +181 -0
  236. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/safety-step.tsx +110 -0
  237. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/struggle-step.tsx +139 -0
  238. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/steps/welcome-step.tsx +217 -0
  239. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/components/ui/onboarding-header.tsx +58 -0
  240. package/recipes/onboarding/apps/native/src/features/onboarding/expense-tracker/constants.ts +179 -0
  241. package/recipes/onboarding/apps/native/src/features/onboarding/hooks/use-onboarding-analytics.ts +323 -0
  242. package/recipes/onboarding/apps/native/src/features/onboarding/services/onboarding-analytics.ts +432 -0
  243. package/recipes/onboarding/recipe.json +15 -0
  244. package/recipes/onboarding@latest.zip +0 -0
  245. package/recipes/payments/recipe.json +28 -61
  246. package/recipes/payments-supabase/apps/native/src/features/payments/README.md +200 -0
  247. package/recipes/payments-supabase/apps/native/src/features/payments/app/local-paywall.tsx +194 -0
  248. package/recipes/payments-supabase/apps/native/src/features/payments/app/remote-paywall.tsx +79 -0
  249. package/recipes/payments-supabase/apps/native/src/features/payments/components/payment-initializer.tsx +95 -0
  250. package/recipes/payments-supabase/apps/native/src/features/payments/components/paywall-error-state.tsx +60 -0
  251. package/recipes/payments-supabase/apps/native/src/features/payments/components/paywall-local-mode.tsx +116 -0
  252. package/recipes/payments-supabase/apps/native/src/features/payments/components/paywall-product-card.tsx +133 -0
  253. package/recipes/payments-supabase/apps/native/src/features/payments/components/paywall-remote-mode.tsx +146 -0
  254. package/recipes/payments-supabase/apps/native/src/features/payments/hooks/use-entitlement.ts +63 -0
  255. package/recipes/payments-supabase/apps/native/src/features/payments/index.ts +8 -0
  256. package/recipes/payments-supabase/apps/native/src/features/payments/services/revenuecat-adapter.ts +407 -0
  257. package/recipes/payments-supabase/packages/backend/src/services/payments.ts +201 -0
  258. package/recipes/payments-supabase/packages/backend/supabase/migrations/payments.sql +35 -0
  259. package/recipes/payments-supabase/recipe.json +51 -0
  260. package/recipes/payments-supabase@latest.zip +0 -0
  261. package/recipes/payments@latest.zip +0 -0
  262. package/recipes/quiz/apps/native/src/features/quiz/index.tsx +1 -2
  263. package/recipes/quiz/recipe.json +6 -9
  264. package/recipes/quiz@latest.zip +0 -0
  265. package/recipes/tracker-app/apps/native/src/features/tracker-app/app/index.tsx +1 -2
  266. package/recipes/tracker-app/recipe.json +7 -10
  267. package/recipes/tracker-app@latest.zip +0 -0
  268. package/recipes/voice-bot/recipe.json +8 -68
  269. package/recipes/voice-bot.zip +0 -0
  270. package/recipes/voice-bot@latest.zip +0 -0
  271. package/recipes/wake-word/recipe.json +10 -9
  272. package/recipes/wake-word.zip +0 -0
  273. package/recipes/wake-word@latest.zip +0 -0
  274. package/recipes/charts/apps/native/src/app/(root)/(protected)/charts/index.tsx +0 -3
  275. package/recipes/chatbot/packages/backend/convex/lib/rateLimit.ts +0 -100
  276. package/recipes/chatbot/packages/backend/convex/lib/telemetry.ts +0 -29
  277. package/recipes/chatbot/packages/backend/convex/ragKnowledge.ts +0 -0
  278. package/recipes/image-analysis/apps/native/assets/features/image-analyzer/front.jpg +0 -0
  279. package/recipes/image-analysis/apps/native/assets/features/image-analyzer/side.jpg +0 -0
  280. package/recipes/image-analysis/apps/native/assets/features/image-analyzer/threeQuarter.jpg +0 -0
  281. package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/_layout.tsx +0 -5
  282. package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/analysis-options.tsx +0 -50
  283. package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/camera.tsx +0 -2
  284. package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/index.tsx +0 -50
  285. package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/loading.tsx +0 -50
  286. package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/results.tsx +0 -2
  287. package/recipes/image-analysis/apps/native/src/app/(root)/(protected)/analysis/[type]/trait-details.tsx +0 -3
  288. package/recipes/image-analysis/packages/backend/convex/imageAnalysisFunctions.ts +0 -325
  289. package/recipes/image-analysis/packages/backend/convex/lib/ai/imageAnalysisAdapter.ts +0 -200
  290. package/recipes/payments/apps/native/src/app/(root)/(protected)/paywall/index.tsx +0 -74
  291. package/recipes/payments/apps/native/src/app/(root)/(protected)/paywall/local.tsx +0 -25
  292. package/recipes/payments/apps/native/src/app/(root)/(protected)/paywall/remote.tsx +0 -23
  293. package/recipes/quiz/apps/native/src/app/(root)/(protected)/quiz/index.tsx +0 -47
  294. package/recipes/tracker-app/apps/native/src/app/(root)/(protected)/tracker-app/index.tsx +0 -1
  295. package/recipes/voice-bot/apps/native/src/app/(root)/(protected)/voice-bot/index.tsx +0 -27
  296. package/recipes/voice-bot/packages/backend/convex/router.ts +0 -81
  297. /package/recipes/{chatbot/apps/native/src/app/(root)/(protected) → chatbot-supabase/apps/native/src/app}/chatbot/index.tsx +0 -0
  298. /package/recipes/{image-generator/apps/native/src/app/(root)/(protected) → image-generator-supabase/apps/native/src/app}/image-generator/gallery.tsx +0 -0
  299. /package/recipes/{image-generator/apps/native/src/app/(root)/(protected) → image-generator-supabase/apps/native/src/app}/image-generator/index.tsx +0 -0
@@ -0,0 +1,369 @@
1
+ /**
2
+ * Recordings Service
3
+ *
4
+ * Handles recording operations including createRecordingDraft, finalizeRecording,
5
+ * listRecordings, updateRecordingName, and deleteRecording.
6
+ *
7
+ * Requirements: 12.1, 12.2, 12.3, 12.4, 12.5
8
+ */
9
+
10
+ import type { SupabaseClientType } from '../lib/supabase';
11
+ import type { Recording } from '../types';
12
+ import type { Json } from '../types/database';
13
+
14
+ export interface CreateRecordingDraftInput {
15
+ name?: string | null;
16
+ fileUri: string;
17
+ }
18
+
19
+ export interface CreateRecordingDraftResult {
20
+ success: boolean;
21
+ recording?: Recording;
22
+ error?: string;
23
+ }
24
+
25
+ export interface FinalizeRecordingInput {
26
+ recordingId: string;
27
+ duration: number;
28
+ fileUri: string;
29
+ metering?: Json | null;
30
+ }
31
+
32
+ export interface FinalizeRecordingResult {
33
+ success: boolean;
34
+ recording?: Recording;
35
+ error?: string;
36
+ }
37
+
38
+ export interface ListRecordingsResult {
39
+ success: boolean;
40
+ recordings?: Recording[];
41
+ hasMore?: boolean;
42
+ error?: string;
43
+ }
44
+
45
+ export interface UpdateRecordingNameResult {
46
+ success: boolean;
47
+ recording?: Recording;
48
+ error?: string;
49
+ }
50
+
51
+ export interface DeleteRecordingResult {
52
+ success: boolean;
53
+ error?: string;
54
+ }
55
+
56
+ const DEFAULT_PAGE_SIZE = 50;
57
+
58
+ /**
59
+ * Create a recording draft
60
+ *
61
+ * Creates a new recording with status "draft". The recording can be finalized
62
+ * later with duration and metering data.
63
+ *
64
+ * @param supabase - Supabase client (authenticated)
65
+ * @param input - Recording draft data
66
+ * @returns The created recording draft
67
+ *
68
+ * Requirements: 12.1
69
+ */
70
+ export async function createRecordingDraft(
71
+ supabase: SupabaseClientType,
72
+ input: CreateRecordingDraftInput
73
+ ): Promise<CreateRecordingDraftResult> {
74
+ // Get the current auth user
75
+ const { data: authData, error: authError } = await supabase.auth.getUser();
76
+
77
+ if (authError || !authData.user) {
78
+ return { success: false, error: 'Not authenticated' };
79
+ }
80
+
81
+ if (!input.fileUri) {
82
+ return { success: false, error: 'File URI is required' };
83
+ }
84
+
85
+ // Create the recording draft
86
+ const { data: recording, error: createError } = await supabase
87
+ .from('recordings')
88
+ .insert({
89
+ user_id: authData.user.id,
90
+ name: input.name || null,
91
+ file_uri: input.fileUri,
92
+ duration: 0, // Will be updated when finalized
93
+ status: 'draft',
94
+ metering: null,
95
+ })
96
+ .select()
97
+ .single();
98
+
99
+ if (createError) {
100
+ return { success: false, error: `Failed to create recording draft: ${createError.message}` };
101
+ }
102
+
103
+ return { success: true, recording };
104
+ }
105
+
106
+
107
+ /**
108
+ * Finalize a recording
109
+ *
110
+ * Updates a draft recording with duration, file URI, and metering data,
111
+ * then sets status to "completed".
112
+ *
113
+ * @param supabase - Supabase client (authenticated)
114
+ * @param input - Finalization data
115
+ * @returns The finalized recording
116
+ *
117
+ * Requirements: 12.2
118
+ */
119
+ export async function finalizeRecording(
120
+ supabase: SupabaseClientType,
121
+ input: FinalizeRecordingInput
122
+ ): Promise<FinalizeRecordingResult> {
123
+ // Get the current auth user
124
+ const { data: authData, error: authError } = await supabase.auth.getUser();
125
+
126
+ if (authError || !authData.user) {
127
+ return { success: false, error: 'Not authenticated' };
128
+ }
129
+
130
+ if (!input.recordingId) {
131
+ return { success: false, error: 'Recording ID is required' };
132
+ }
133
+
134
+ if (input.duration < 0) {
135
+ return { success: false, error: 'Duration must be non-negative' };
136
+ }
137
+
138
+ if (!input.fileUri) {
139
+ return { success: false, error: 'File URI is required' };
140
+ }
141
+
142
+ // Update the recording (RLS + user_id check ensures ownership)
143
+ const { data: recording, error: updateError } = await supabase
144
+ .from('recordings')
145
+ .update({
146
+ duration: input.duration,
147
+ file_uri: input.fileUri,
148
+ metering: input.metering || null,
149
+ status: 'completed',
150
+ })
151
+ .eq('id', input.recordingId)
152
+ .eq('user_id', authData.user.id)
153
+ .eq('status', 'draft') // Can only finalize drafts
154
+ .select()
155
+ .single();
156
+
157
+ if (updateError) {
158
+ if (updateError.code === 'PGRST116') {
159
+ return { success: false, error: 'Recording not found, not owned by user, or already finalized' };
160
+ }
161
+ return { success: false, error: `Failed to finalize recording: ${updateError.message}` };
162
+ }
163
+
164
+ return { success: true, recording };
165
+ }
166
+
167
+ /**
168
+ * List recordings for the current user
169
+ *
170
+ * Returns only completed recordings, ordered by creation time (newest first).
171
+ *
172
+ * @param supabase - Supabase client (authenticated)
173
+ * @param options - Pagination options
174
+ * @returns Paginated list of completed recordings
175
+ *
176
+ * Requirements: 12.3
177
+ */
178
+ export async function listRecordings(
179
+ supabase: SupabaseClientType,
180
+ options?: {
181
+ limit?: number;
182
+ cursor?: string; // created_at timestamp for cursor-based pagination
183
+ }
184
+ ): Promise<ListRecordingsResult> {
185
+ // Get the current auth user
186
+ const { data: authData, error: authError } = await supabase.auth.getUser();
187
+
188
+ if (authError || !authData.user) {
189
+ return { success: false, error: 'Not authenticated' };
190
+ }
191
+
192
+ const limit = options?.limit || DEFAULT_PAGE_SIZE;
193
+
194
+ // Build query - only return completed recordings
195
+ let query = supabase
196
+ .from('recordings')
197
+ .select('*')
198
+ .eq('user_id', authData.user.id)
199
+ .eq('status', 'completed')
200
+ .order('created_at', { ascending: false })
201
+ .limit(limit + 1); // Fetch one extra to check if there are more
202
+
203
+ // Apply cursor if provided
204
+ if (options?.cursor) {
205
+ query = query.lt('created_at', options.cursor);
206
+ }
207
+
208
+ const { data: recordings, error: listError } = await query;
209
+
210
+ if (listError) {
211
+ return { success: false, error: `Failed to list recordings: ${listError.message}` };
212
+ }
213
+
214
+ // Check if there are more results
215
+ const hasMore = recordings && recordings.length > limit;
216
+ const resultRecordings = hasMore ? recordings.slice(0, limit) : (recordings || []);
217
+
218
+ return {
219
+ success: true,
220
+ recordings: resultRecordings,
221
+ hasMore,
222
+ };
223
+ }
224
+
225
+
226
+ /**
227
+ * Update a recording's name
228
+ *
229
+ * Only allows update if the user owns the recording.
230
+ *
231
+ * @param supabase - Supabase client (authenticated)
232
+ * @param recordingId - The ID of the recording to update
233
+ * @param name - The new name for the recording
234
+ * @returns The updated recording
235
+ *
236
+ * Requirements: 12.4
237
+ */
238
+ export async function updateRecordingName(
239
+ supabase: SupabaseClientType,
240
+ recordingId: string,
241
+ name: string | null
242
+ ): Promise<UpdateRecordingNameResult> {
243
+ // Get the current auth user
244
+ const { data: authData, error: authError } = await supabase.auth.getUser();
245
+
246
+ if (authError || !authData.user) {
247
+ return { success: false, error: 'Not authenticated' };
248
+ }
249
+
250
+ if (!recordingId) {
251
+ return { success: false, error: 'Recording ID is required' };
252
+ }
253
+
254
+ // Update the recording name (RLS + user_id check ensures ownership)
255
+ const { data: recording, error: updateError } = await supabase
256
+ .from('recordings')
257
+ .update({ name })
258
+ .eq('id', recordingId)
259
+ .eq('user_id', authData.user.id)
260
+ .select()
261
+ .single();
262
+
263
+ if (updateError) {
264
+ if (updateError.code === 'PGRST116') {
265
+ return { success: false, error: 'Recording not found or access denied' };
266
+ }
267
+ return { success: false, error: `Failed to update recording: ${updateError.message}` };
268
+ }
269
+
270
+ return { success: true, recording };
271
+ }
272
+
273
+ /**
274
+ * Delete a recording
275
+ *
276
+ * Removes the recording record. Note: This does not delete the associated
277
+ * file from storage - that should be handled separately.
278
+ *
279
+ * @param supabase - Supabase client (authenticated)
280
+ * @param recordingId - The ID of the recording to delete
281
+ * @returns Success or error
282
+ *
283
+ * Requirements: 12.5
284
+ */
285
+ export async function deleteRecording(
286
+ supabase: SupabaseClientType,
287
+ recordingId: string
288
+ ): Promise<DeleteRecordingResult> {
289
+ // Get the current auth user
290
+ const { data: authData, error: authError } = await supabase.auth.getUser();
291
+
292
+ if (authError || !authData.user) {
293
+ return { success: false, error: 'Not authenticated' };
294
+ }
295
+
296
+ if (!recordingId) {
297
+ return { success: false, error: 'Recording ID is required' };
298
+ }
299
+
300
+ // Delete the recording (RLS + user_id check ensures ownership)
301
+ const { data, error: deleteError } = await supabase
302
+ .from('recordings')
303
+ .delete()
304
+ .eq('id', recordingId)
305
+ .eq('user_id', authData.user.id)
306
+ .select('id')
307
+ .single();
308
+
309
+ if (deleteError) {
310
+ if (deleteError.code === 'PGRST116') {
311
+ return { success: false, error: 'Recording not found or access denied' };
312
+ }
313
+ return { success: false, error: `Failed to delete recording: ${deleteError.message}` };
314
+ }
315
+
316
+ if (!data) {
317
+ return { success: false, error: 'Recording not found or access denied' };
318
+ }
319
+
320
+ return { success: true };
321
+ }
322
+
323
+ /**
324
+ * Get a recording by ID
325
+ *
326
+ * @param supabase - Supabase client (authenticated)
327
+ * @param recordingId - The ID of the recording
328
+ * @returns The recording or error
329
+ */
330
+ export async function getRecordingById(
331
+ supabase: SupabaseClientType,
332
+ recordingId: string
333
+ ): Promise<{ success: boolean; recording?: Recording; error?: string }> {
334
+ // Get the current auth user
335
+ const { data: authData, error: authError } = await supabase.auth.getUser();
336
+
337
+ if (authError || !authData.user) {
338
+ return { success: false, error: 'Not authenticated' };
339
+ }
340
+
341
+ if (!recordingId) {
342
+ return { success: false, error: 'Recording ID is required' };
343
+ }
344
+
345
+ const { data: recording, error: getError } = await supabase
346
+ .from('recordings')
347
+ .select('*')
348
+ .eq('id', recordingId)
349
+ .eq('user_id', authData.user.id)
350
+ .single();
351
+
352
+ if (getError) {
353
+ if (getError.code === 'PGRST116') {
354
+ return { success: false, error: 'Recording not found' };
355
+ }
356
+ return { success: false, error: `Failed to get recording: ${getError.message}` };
357
+ }
358
+
359
+ return { success: true, recording };
360
+ }
361
+
362
+ export const recordingsService = {
363
+ createRecordingDraft,
364
+ finalizeRecording,
365
+ listRecordings,
366
+ updateRecordingName,
367
+ deleteRecording,
368
+ getRecordingById,
369
+ };
@@ -0,0 +1,70 @@
1
+ -- Audio Recorder Feature Migration
2
+ -- Creates tables for recording metadata and storage
3
+ -- Run this migration when adding the audio-recorder feature via CLI
4
+
5
+ -- ============================================================================
6
+ -- RECORDINGS TABLE
7
+ -- Stores audio recording metadata
8
+ -- ============================================================================
9
+ CREATE TABLE IF NOT EXISTS recordings (
10
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
11
+ user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
12
+ name TEXT,
13
+ file_uri TEXT NOT NULL,
14
+ duration INTEGER NOT NULL,
15
+ status TEXT NOT NULL CHECK (status IN ('draft', 'completed')),
16
+ metering JSONB,
17
+ created_at TIMESTAMPTZ DEFAULT now()
18
+ );
19
+
20
+ -- ============================================================================
21
+ -- INDEXES
22
+ -- ============================================================================
23
+ CREATE INDEX IF NOT EXISTS idx_recordings_user_id_status ON recordings(user_id, status);
24
+
25
+ -- ============================================================================
26
+ -- RLS POLICIES
27
+ -- ============================================================================
28
+ ALTER TABLE recordings ENABLE ROW LEVEL SECURITY;
29
+
30
+ CREATE POLICY "recordings_select_own" ON recordings
31
+ FOR SELECT USING (auth.uid() = user_id);
32
+
33
+ CREATE POLICY "recordings_insert_own" ON recordings
34
+ FOR INSERT WITH CHECK (auth.uid() = user_id);
35
+
36
+ CREATE POLICY "recordings_update_own" ON recordings
37
+ FOR UPDATE USING (auth.uid() = user_id);
38
+
39
+ CREATE POLICY "recordings_delete_own" ON recordings
40
+ FOR DELETE USING (auth.uid() = user_id);
41
+
42
+ -- ============================================================================
43
+ -- STORAGE BUCKET - recordings
44
+ -- ============================================================================
45
+ INSERT INTO storage.buckets (id, name, public, file_size_limit, allowed_mime_types)
46
+ VALUES (
47
+ 'recordings',
48
+ 'recordings',
49
+ false,
50
+ 104857600, -- 100MB limit
51
+ ARRAY['audio/mpeg', 'audio/wav', 'audio/m4a', 'audio/aac', 'audio/ogg']
52
+ ) ON CONFLICT (id) DO NOTHING;
53
+
54
+ -- Storage RLS for recordings
55
+ CREATE POLICY "Users can view their own recordings"
56
+ ON storage.objects FOR SELECT TO authenticated
57
+ USING (bucket_id = 'recordings' AND (storage.foldername(name))[1] = auth.uid()::text);
58
+
59
+ CREATE POLICY "Users can upload their own recordings"
60
+ ON storage.objects FOR INSERT TO authenticated
61
+ WITH CHECK (bucket_id = 'recordings' AND (storage.foldername(name))[1] = auth.uid()::text);
62
+
63
+ CREATE POLICY "Users can update their own recordings"
64
+ ON storage.objects FOR UPDATE TO authenticated
65
+ USING (bucket_id = 'recordings' AND (storage.foldername(name))[1] = auth.uid()::text)
66
+ WITH CHECK (bucket_id = 'recordings' AND (storage.foldername(name))[1] = auth.uid()::text);
67
+
68
+ CREATE POLICY "Users can delete their own recordings"
69
+ ON storage.objects FOR DELETE TO authenticated
70
+ USING (bucket_id = 'recordings' AND (storage.foldername(name))[1] = auth.uid()::text);
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "audio-recorder",
3
+ "version": "1.0.0",
4
+ "description": "Audio recording with Supabase storage",
5
+ "copy": [
6
+ {
7
+ "from": "apps/native/src/features/audio-recorder",
8
+ "to": "apps/native/src/features/audio-recorder"
9
+ },
10
+ {
11
+ "from": "packages/backend/supabase/migrations/recordings.sql",
12
+ "to": "packages/backend/supabase/migrations/recordings.sql"
13
+ },
14
+ {
15
+ "from": "packages/backend/src/services/recordings.ts",
16
+ "to": "packages/backend/src/services/recordings.ts"
17
+ }
18
+ ],
19
+ "nav": {
20
+ "href": "/(root)/(protected)/audio-recorder",
21
+ "label": "Audio Recorder",
22
+ "icon": "🎙️",
23
+ "color": "#EF4444"
24
+ },
25
+ "target": "native",
26
+ "dependencies": {
27
+ "expo": ["@supabase/supabase-js", "expo-av", "expo-file-system"]
28
+ },
29
+ "manualSteps": [
30
+ {
31
+ "title": "Apply Supabase migration",
32
+ "description": "Run: cd packages/backend && supabase db push"
33
+ }
34
+ ]
35
+ }
Binary file
@@ -27,7 +27,7 @@ type Props = {
27
27
  };
28
28
 
29
29
  export const BarChart = ({ data, config = {}, style }: Props) => {
30
- const [containerWidth, setContainerWidth] = useState(300);
30
+ const [containerWidth, setContainerWidth] = useState(() => config.width ?? 300);
31
31
  const {
32
32
  height = 200,
33
33
  padding = 20,
@@ -36,7 +36,7 @@ export const BarChart = ({ data, config = {}, style }: Props) => {
36
36
  showLabels = true,
37
37
  } = config;
38
38
 
39
- const chartWidth = containerWidth || config.width || 300;
39
+ const chartWidth = config.width ?? containerWidth;
40
40
 
41
41
  const theme = useThemeConfig();
42
42
  const primaryColor = theme.colors.primary as string;
@@ -71,7 +71,7 @@ export const BarChart = ({ data, config = {}, style }: Props) => {
71
71
 
72
72
  if (!data.length) return null;
73
73
 
74
- const maxValue = Math.max(...data.map((d) => d.value));
74
+ const maxValue = Math.max(1, ...data.map((d) => d.value));
75
75
  const innerChartWidth = chartWidth - padding * 2;
76
76
  const chartHeight = height - padding * 2;
77
77
  const barWidth = (innerChartWidth / data.length) * 0.8;
@@ -36,7 +36,7 @@ type Props = {
36
36
  };
37
37
 
38
38
  export const CandlestickChart = ({ data, config = {}, style }: Props) => {
39
- const [containerWidth, setContainerWidth] = useState(300);
39
+ const [containerWidth, setContainerWidth] = useState(() => config.width ?? 300);
40
40
 
41
41
  const {
42
42
  height = 200,
@@ -47,7 +47,7 @@ export const CandlestickChart = ({ data, config = {}, style }: Props) => {
47
47
  duration = 800,
48
48
  } = config;
49
49
 
50
- const chartWidth = containerWidth || config.width || 300;
50
+ const chartWidth = config.width ?? containerWidth;
51
51
 
52
52
  const theme = useThemeConfig();
53
53
  const bullishColor = '#10B981';
@@ -1,11 +1,9 @@
1
1
  import type React from 'react';
2
- import { Dimensions, View } from 'react-native';
2
+ import { useWindowDimensions, View } from 'react-native';
3
3
 
4
4
  import { Text } from '@/components/ui';
5
5
  import { useThemeConfig } from '@/lib/use-theme-config';
6
6
 
7
- const { width: screenWidth } = Dimensions.get('window');
8
-
9
7
  interface ChartCardProps {
10
8
  title: string;
11
9
  subtitle?: string;
@@ -17,17 +15,19 @@ interface ChartCardProps {
17
15
  const ChartCard: React.FC<ChartCardProps> = ({
18
16
  title,
19
17
  subtitle,
20
- width = screenWidth - 32,
18
+ width,
21
19
  height: _height,
22
20
  children,
23
21
  }) => {
24
22
  const theme = useThemeConfig();
23
+ const { width: screenWidth } = useWindowDimensions();
24
+ const cardWidth = width ?? screenWidth - 32;
25
25
 
26
26
  return (
27
27
  <View
28
28
  className="mb-4 overflow-hidden rounded-xl"
29
29
  style={{
30
- width,
30
+ width: cardWidth,
31
31
  backgroundColor: theme.colors.card,
32
32
  borderColor: theme.colors.outline,
33
33
  borderWidth: 1,
@@ -27,7 +27,7 @@ type Props = {
27
27
  };
28
28
 
29
29
  export const ColumnChart = ({ data, config = {}, style }: Props) => {
30
- const [containerWidth, setContainerWidth] = useState(300);
30
+ const [containerWidth, setContainerWidth] = useState(() => config.width ?? 300);
31
31
  const {
32
32
  height = 200,
33
33
  padding = 20,
@@ -35,7 +35,7 @@ export const ColumnChart = ({ data, config = {}, style }: Props) => {
35
35
  animated = true,
36
36
  duration = 800,
37
37
  } = config;
38
- const chartWidth = containerWidth || config.width || 300;
38
+ const chartWidth = config.width ?? containerWidth;
39
39
 
40
40
  const theme = useThemeConfig();
41
41
  const primaryColor = theme.colors.primary as string;
@@ -70,7 +70,7 @@ export const ColumnChart = ({ data, config = {}, style }: Props) => {
70
70
 
71
71
  if (!data.length) return null;
72
72
 
73
- const maxValue = Math.max(...data.map((d) => d.value));
73
+ const maxValue = Math.max(1, ...data.map((d) => d.value));
74
74
  const innerChartWidth = chartWidth - padding * 2;
75
75
  const chartHeight = height - padding * 2;
76
76
  const barHeight = (chartHeight / data.length) * 0.8;
@@ -40,6 +40,21 @@ const makeDonutSlicePath = (
40
40
  startAngle: number,
41
41
  endAngle: number,
42
42
  ) => {
43
+ if (innerR <= 0.5) {
44
+ const largeArcFlag = endAngle - startAngle > Math.PI ? 1 : 0;
45
+ const x1 = cx + outerR * Math.cos(startAngle);
46
+ const y1 = cy + outerR * Math.sin(startAngle);
47
+ const x2 = cx + outerR * Math.cos(endAngle);
48
+ const y2 = cy + outerR * Math.sin(endAngle);
49
+ const pathData = [
50
+ `M ${cx} ${cy}`,
51
+ `L ${x1} ${y1}`,
52
+ `A ${outerR} ${outerR} 0 ${largeArcFlag} 1 ${x2} ${y2}`,
53
+ 'Z',
54
+ ].join(' ');
55
+ return Skia.Path.MakeFromSVGString(pathData) || Skia.Path.Make();
56
+ }
57
+
43
58
  const largeArcFlag = endAngle - startAngle > Math.PI ? 1 : 0;
44
59
 
45
60
  const x1 = cx + outerR * Math.cos(startAngle);
@@ -64,7 +79,7 @@ const makeDonutSlicePath = (
64
79
  };
65
80
 
66
81
  export const DoughnutChart = ({ data, config = {}, style }: Props) => {
67
- const [containerWidth, setContainerWidth] = useState(300);
82
+ const [containerWidth, setContainerWidth] = useState(() => config.width ?? 300);
68
83
  const {
69
84
  height = 200,
70
85
  animated = true,
@@ -75,7 +90,7 @@ export const DoughnutChart = ({ data, config = {}, style }: Props) => {
75
90
 
76
91
  const theme = useThemeConfig();
77
92
  const primaryColor = theme.colors.primary as string;
78
- const chartWidth = containerWidth || config.width || 300;
93
+ const chartWidth = config.width ?? containerWidth;
79
94
  const centerX = chartWidth / 2;
80
95
  const centerY = height / 2;
81
96
 
@@ -83,8 +98,9 @@ export const DoughnutChart = ({ data, config = {}, style }: Props) => {
83
98
  1,
84
99
  data.reduce((s, d) => s + d.value, 0),
85
100
  );
86
- const outerR = Math.min(chartWidth, height) / 2 - 20;
87
- const innerR = outerR * innerRadius;
101
+ const outerR = Math.max(1, Math.min(chartWidth, height) / 2 - 20);
102
+ const safeInnerRadius = Math.min(0.95, Math.max(0, innerRadius));
103
+ const innerR = outerR * safeInnerRadius;
88
104
 
89
105
  const [progress, setProgress] = useState(1);
90
106
 
@@ -89,12 +89,13 @@ const formatNumber = (num: number): string => {
89
89
 
90
90
  export const LineChart = ({ data, config = {}, style }: Props) => {
91
91
  const hasData = data.length > 0;
92
- const [containerWidth, setContainerWidth] = useState(300);
92
+ const [containerWidth, setContainerWidth] = useState(() => config.width ?? 300);
93
93
  const [progress, setProgress] = useState(1);
94
94
  const {
95
95
  height = 200,
96
96
  padding = 20,
97
97
  showGrid = true,
98
+ showLabels = false,
98
99
  animated = true,
99
100
  duration = 1000,
100
101
  gradient = false,
@@ -103,7 +104,7 @@ export const LineChart = ({ data, config = {}, style }: Props) => {
103
104
  yAxisWidth = 20,
104
105
  } = config;
105
106
 
106
- const chartWidth = containerWidth || config.width || 300;
107
+ const chartWidth = config.width ?? containerWidth;
107
108
  const theme = useThemeConfig();
108
109
  const primaryColor = theme.colors.primary as string;
109
110
  const mutedColor = theme.colors.mutedForeground as string;
@@ -167,7 +168,7 @@ export const LineChart = ({ data, config = {}, style }: Props) => {
167
168
  // Simple horizontal grid lines
168
169
  const yAxisLabels = useMemo(() => {
169
170
  const labels: { value: number; y: number }[] = [];
170
- const count = yLabelCount;
171
+ const count = Math.max(2, yLabelCount);
171
172
  for (let i = 0; i < count; i++) {
172
173
  const ratio = i / (count - 1);
173
174
  const value = maxValue - ratio * valueRange;
@@ -263,13 +264,13 @@ export const LineChart = ({ data, config = {}, style }: Props) => {
263
264
  ))}
264
265
  </Canvas>
265
266
  {/* Labels overlays for parity with SVG */}
266
- {(config.showYLabels || config.showLabels) && (
267
+ {(showYLabels || showLabels) && (
267
268
  <Svg
268
269
  width={chartWidth}
269
270
  height={height}
270
271
  style={{ position: 'absolute', left: 0, top: 0 }}
271
272
  >
272
- {config.showYLabels && (
273
+ {showYLabels && (
273
274
  <G>
274
275
  {yAxisLabels.map((label, index) => (
275
276
  <SvgText
@@ -285,7 +286,7 @@ export const LineChart = ({ data, config = {}, style }: Props) => {
285
286
  ))}
286
287
  </G>
287
288
  )}
288
- {config.showLabels && (
289
+ {showLabels && (
289
290
  <G>
290
291
  {data.map((point, index) => (
291
292
  <SvgText