@pylonsync/create-pylon 0.19.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (675) hide show
  1. package/bin/create-pylon.js +30 -7
  2. package/package.json +1 -1
  3. package/templates/agency/.env.example +7 -0
  4. package/templates/agency/README.md +28 -3
  5. package/templates/agency/app/(marketing)/contact-form.tsx +20 -18
  6. package/templates/agency/app/(marketing)/page.tsx +3 -3
  7. package/templates/agency/app/(marketing)/work/page.tsx +3 -3
  8. package/templates/agency/app/auth-form.tsx +4 -4
  9. package/templates/agency/app/dashboard/dashboard-client.tsx +110 -64
  10. package/templates/agency/app/dashboard/page.tsx +14 -16
  11. package/templates/agency/app/icon.svg +3 -0
  12. package/templates/agency/app/login/page.tsx +12 -14
  13. package/templates/agency/components/marketing.tsx +12 -5
  14. package/templates/agency/functions/seedStudioBackoffice.ts +42 -11
  15. package/templates/agency/lib/agency.ts +22 -0
  16. package/templates/agency/lib/demo.ts +118 -0
  17. package/templates/agency/lib/site.config.ts +19 -13
  18. package/templates/agency/public/favicon.ico +0 -0
  19. package/templates/agency/tests/agency.test.ts +57 -0
  20. package/templates/ai-chat/.env.example +13 -27
  21. package/templates/ai-chat/README.md +53 -60
  22. package/templates/ai-chat/app/(chat)/c/[id]/page.tsx +19 -0
  23. package/templates/ai-chat/app/(chat)/chat-client.tsx +657 -622
  24. package/templates/ai-chat/app/(chat)/layout.tsx +6 -43
  25. package/templates/ai-chat/app/(chat)/not-found.tsx +6 -10
  26. package/templates/ai-chat/app/(chat)/page.tsx +7 -11
  27. package/templates/ai-chat/app/auth-form.tsx +45 -38
  28. package/templates/ai-chat/app/error.tsx +9 -12
  29. package/templates/ai-chat/app/globals.css +464 -102
  30. package/templates/ai-chat/app/layout.tsx +11 -19
  31. package/templates/ai-chat/app/login/page.tsx +39 -24
  32. package/templates/ai-chat/app.ts +118 -43
  33. package/templates/ai-chat/components/chat/composer.tsx +193 -0
  34. package/templates/ai-chat/components/chat/empty-state.tsx +65 -0
  35. package/templates/ai-chat/components/chat/icons.tsx +198 -0
  36. package/templates/ai-chat/components/chat/markdown.tsx +164 -0
  37. package/templates/ai-chat/components/chat/message.tsx +183 -0
  38. package/templates/ai-chat/components/chat/setup.tsx +71 -0
  39. package/templates/ai-chat/components/chat/sidebar.tsx +288 -0
  40. package/templates/ai-chat/functions/_beginTurn.ts +145 -0
  41. package/templates/ai-chat/functions/_finishTurn.ts +55 -0
  42. package/templates/ai-chat/functions/assistantStatus.ts +13 -0
  43. package/templates/ai-chat/functions/claimGuestHistory.ts +39 -0
  44. package/templates/ai-chat/functions/createConversation.ts +28 -0
  45. package/templates/ai-chat/functions/deleteConversation.ts +15 -20
  46. package/templates/ai-chat/functions/prepareGuestClaim.ts +32 -0
  47. package/templates/ai-chat/functions/renameConversation.ts +19 -0
  48. package/templates/ai-chat/functions/respond.ts +88 -0
  49. package/templates/ai-chat/functions/seedExamples.ts +46 -0
  50. package/templates/ai-chat/functions/stopResponse.ts +30 -0
  51. package/templates/ai-chat/lib/brand-icon.ts +7 -0
  52. package/templates/ai-chat/lib/chat.ts +277 -0
  53. package/templates/ai-chat/lib/claim.ts +15 -0
  54. package/templates/ai-chat/lib/examples.ts +179 -0
  55. package/templates/ai-chat/lib/highlight.ts +107 -0
  56. package/templates/ai-chat/lib/markdown.ts +317 -0
  57. package/templates/ai-chat/lib/provider.ts +38 -0
  58. package/templates/ai-chat/lib/site.config.ts +55 -39
  59. package/templates/ai-chat/tests/chat.test.ts +174 -0
  60. package/templates/ai-chat/tests/components.test.tsx +57 -0
  61. package/templates/ai-chat/tests/markdown.test.ts +88 -0
  62. package/templates/ai-chat/tests/provider.test.ts +32 -0
  63. package/templates/backend/consumer/apps/api/app/layout.tsx +33 -0
  64. package/templates/backend/consumer/apps/api/app/page.tsx +54 -0
  65. package/templates/backend/consumer/apps/api/app.ts +166 -0
  66. package/templates/backend/consumer/apps/api/functions/addComment.ts +24 -0
  67. package/templates/backend/consumer/apps/api/functions/createPost.ts +19 -36
  68. package/templates/backend/consumer/apps/api/functions/deletePost.ts +15 -13
  69. package/templates/backend/consumer/apps/api/functions/myProfile.ts +5 -8
  70. package/templates/backend/consumer/apps/api/functions/seedDemo.ts +95 -0
  71. package/templates/backend/consumer/apps/api/functions/setFollow.ts +36 -0
  72. package/templates/backend/consumer/apps/api/functions/setLike.ts +36 -0
  73. package/templates/backend/consumer/apps/api/functions/upsertProfile.ts +43 -37
  74. package/templates/backend/consumer/apps/api/lib/seed.ts +89 -0
  75. package/templates/backend/consumer/apps/api/lib/social.ts +50 -0
  76. package/templates/backend/consumer/apps/api/package.json +8 -2
  77. package/templates/backend/consumer/apps/api/public/images/posts/arch.jpg +0 -0
  78. package/templates/backend/consumer/apps/api/public/images/posts/coffee.jpg +0 -0
  79. package/templates/backend/consumer/apps/api/public/images/posts/dog.jpg +0 -0
  80. package/templates/backend/consumer/apps/api/public/images/posts/lisbon.jpg +0 -0
  81. package/templates/backend/consumer/apps/api/public/images/posts/ramen.jpg +0 -0
  82. package/templates/backend/consumer/apps/api/public/images/posts/run.jpg +0 -0
  83. package/templates/backend/consumer/apps/api/public/images/posts/sunset.jpg +0 -0
  84. package/templates/backend/consumer/apps/api/tsconfig.json +12 -2
  85. package/templates/chat/README.md +43 -21
  86. package/templates/chat/app/chat-client.tsx +495 -85
  87. package/templates/chat/app/error.tsx +1 -1
  88. package/templates/chat/app/globals.css +170 -54
  89. package/templates/chat/app/layout.tsx +12 -9
  90. package/templates/chat/app/not-found.tsx +4 -2
  91. package/templates/chat/app/page.tsx +9 -19
  92. package/templates/chat/app.ts +96 -17
  93. package/templates/chat/components/chat/avatar.tsx +45 -0
  94. package/templates/chat/components/chat/composer.tsx +110 -0
  95. package/templates/chat/components/chat/message-list.tsx +285 -0
  96. package/templates/chat/components/chat/sidebar.tsx +190 -0
  97. package/templates/chat/functions/joinChat.ts +83 -0
  98. package/templates/chat/functions/renameMember.ts +25 -0
  99. package/templates/chat/lib/chat.ts +188 -0
  100. package/templates/chat/lib/seed.ts +78 -0
  101. package/templates/chat/tests/chat.test.ts +159 -0
  102. package/templates/chat/tests/composer.test.tsx +54 -0
  103. package/templates/consumer/README.md +3 -3
  104. package/templates/consumer/app/globals.css +10 -3
  105. package/templates/consumer/app/icon.svg +1 -0
  106. package/templates/consumer/components/social/avatar.tsx +25 -11
  107. package/templates/consumer/functions/seedFeed.ts +3 -3
  108. package/templates/consumer/lib/seed.ts +43 -85
  109. package/templates/consumer/public/favicon.ico +0 -0
  110. package/templates/consumer/public/images/posts/arch.jpg +0 -0
  111. package/templates/consumer/public/images/posts/coffee.jpg +0 -0
  112. package/templates/consumer/public/images/posts/dog.jpg +0 -0
  113. package/templates/consumer/public/images/posts/lisbon.jpg +0 -0
  114. package/templates/consumer/public/images/posts/ramen.jpg +0 -0
  115. package/templates/consumer/public/images/posts/run.jpg +0 -0
  116. package/templates/consumer/public/images/posts/sunset.jpg +0 -0
  117. package/templates/consumer/tests/seed.test.ts +39 -0
  118. package/templates/creator/.env.example +7 -0
  119. package/templates/creator/README.md +24 -5
  120. package/templates/creator/app/(marketing)/newsletter-signup.tsx +33 -12
  121. package/templates/creator/app/auth-form.tsx +7 -7
  122. package/templates/creator/app/dashboard/dashboard-client.tsx +137 -100
  123. package/templates/creator/app/dashboard/page.tsx +18 -18
  124. package/templates/creator/app/icon.svg +2 -0
  125. package/templates/creator/app/login/page.tsx +14 -14
  126. package/templates/creator/app.ts +4 -4
  127. package/templates/creator/functions/seedDemo.ts +32 -0
  128. package/templates/creator/functions/subscribe.ts +4 -15
  129. package/templates/creator/lib/demo.ts +94 -0
  130. package/templates/creator/lib/site.config.ts +15 -10
  131. package/templates/creator/lib/stats.ts +35 -0
  132. package/templates/creator/public/favicon.ico +0 -0
  133. package/templates/creator/tests/counter.test.ts +92 -0
  134. package/templates/creator/tests/live-count.test.tsx +45 -0
  135. package/templates/crm/README.md +22 -1
  136. package/templates/crm/app/apple-icon.png +0 -0
  137. package/templates/crm/app/companies/companies-view.tsx +32 -14
  138. package/templates/crm/app/companies/page.tsx +2 -1
  139. package/templates/crm/app/contacts/contacts-view.tsx +34 -10
  140. package/templates/crm/app/contacts/page.tsx +2 -1
  141. package/templates/crm/app/deals/[id]/deal-view.tsx +137 -98
  142. package/templates/crm/app/deals/[id]/page.tsx +2 -1
  143. package/templates/crm/app/globals.css +29 -14
  144. package/templates/crm/app/icon.svg +1 -0
  145. package/templates/crm/app/layout.tsx +7 -5
  146. package/templates/crm/app/page.tsx +2 -1
  147. package/templates/crm/app/pipeline-view.tsx +5 -5
  148. package/templates/crm/app/workspace.tsx +29 -17
  149. package/templates/crm/app.ts +5 -5
  150. package/templates/crm/components/activity-timeline.tsx +14 -4
  151. package/templates/crm/components/app-shell.tsx +94 -0
  152. package/templates/crm/components/auth-form.tsx +35 -5
  153. package/templates/crm/components/avatar.tsx +5 -1
  154. package/templates/crm/components/board-skeleton.tsx +5 -5
  155. package/templates/crm/components/brand-mark.tsx +25 -0
  156. package/templates/crm/components/command-palette.tsx +17 -17
  157. package/templates/crm/components/data-table.tsx +97 -36
  158. package/templates/crm/components/deal-card.tsx +23 -27
  159. package/templates/crm/components/filter-tabs.tsx +101 -0
  160. package/templates/crm/components/metrics-bar.tsx +6 -6
  161. package/templates/crm/components/nav.tsx +15 -0
  162. package/templates/crm/components/page-header.tsx +43 -7
  163. package/templates/crm/components/pipeline-board.tsx +16 -10
  164. package/templates/crm/components/require-auth.tsx +24 -26
  165. package/templates/crm/components/sidebar.tsx +54 -49
  166. package/templates/crm/components/stage-badge.tsx +4 -2
  167. package/templates/crm/components/ui/button.tsx +5 -4
  168. package/templates/crm/components/ui/dialog.tsx +1 -1
  169. package/templates/crm/components/ui/input.tsx +1 -1
  170. package/templates/crm/components/ui/select.tsx +1 -1
  171. package/templates/crm/components/ui/textarea.tsx +1 -1
  172. package/templates/crm/functions/seedWorkspace.ts +32 -17
  173. package/templates/crm/lib/brand.ts +9 -0
  174. package/templates/crm/lib/demo-team.ts +51 -0
  175. package/templates/crm/lib/pipeline.ts +37 -0
  176. package/templates/crm/lib/search.ts +4 -3
  177. package/templates/crm/lib/seed.ts +53 -8
  178. package/templates/crm/public/favicon.ico +0 -0
  179. package/templates/crm/tests/components.test.tsx +5 -5
  180. package/templates/crm/tests/demo-team.test.ts +18 -0
  181. package/templates/crm/tests/pipeline.test.ts +59 -3
  182. package/templates/directory/.env.example +7 -0
  183. package/templates/directory/README.md +22 -3
  184. package/templates/directory/app/(marketing)/directory-browse.tsx +30 -12
  185. package/templates/directory/app/(marketing)/page.tsx +6 -4
  186. package/templates/directory/app/auth-form.tsx +4 -4
  187. package/templates/directory/app/dashboard/dashboard-client.tsx +177 -90
  188. package/templates/directory/app/dashboard/page.tsx +16 -14
  189. package/templates/directory/app/icon.svg +4 -0
  190. package/templates/directory/app/login/page.tsx +16 -15
  191. package/templates/directory/functions/seedDemo.ts +43 -0
  192. package/templates/directory/lib/demo.ts +145 -0
  193. package/templates/directory/lib/directory.ts +27 -0
  194. package/templates/directory/lib/site.config.ts +21 -16
  195. package/templates/directory/public/favicon.ico +0 -0
  196. package/templates/directory/tests/directory.test.ts +46 -0
  197. package/templates/expo/consumer/apps/expo/App.tsx +360 -288
  198. package/templates/expo/consumer/apps/expo/app.json +9 -1
  199. package/templates/expo/consumer/apps/expo/package.json +2 -1
  200. package/templates/helpdesk/README.md +21 -0
  201. package/templates/helpdesk/app/apple-icon.png +0 -0
  202. package/templates/helpdesk/app/customers/customers-view.tsx +23 -7
  203. package/templates/helpdesk/app/customers/page.tsx +2 -1
  204. package/templates/helpdesk/app/globals.css +29 -14
  205. package/templates/helpdesk/app/icon.svg +1 -0
  206. package/templates/helpdesk/app/inbox-view.tsx +66 -47
  207. package/templates/helpdesk/app/layout.tsx +7 -5
  208. package/templates/helpdesk/app/page.tsx +4 -7
  209. package/templates/helpdesk/app/tickets/[id]/page.tsx +2 -1
  210. package/templates/helpdesk/app/tickets/[id]/ticket-view.tsx +134 -97
  211. package/templates/helpdesk/app/workspace.tsx +33 -22
  212. package/templates/helpdesk/app.ts +2 -2
  213. package/templates/helpdesk/components/app-shell.tsx +94 -0
  214. package/templates/helpdesk/components/auth-form.tsx +35 -5
  215. package/templates/helpdesk/components/avatar.tsx +5 -1
  216. package/templates/helpdesk/components/brand-mark.tsx +28 -0
  217. package/templates/helpdesk/components/command-palette.tsx +17 -17
  218. package/templates/helpdesk/components/data-table.tsx +97 -36
  219. package/templates/helpdesk/components/filter-tabs.tsx +101 -0
  220. package/templates/helpdesk/components/nav.tsx +14 -0
  221. package/templates/helpdesk/components/page-header.tsx +43 -7
  222. package/templates/helpdesk/components/priority-badge.tsx +49 -6
  223. package/templates/helpdesk/components/require-auth.tsx +24 -26
  224. package/templates/helpdesk/components/sidebar.tsx +54 -48
  225. package/templates/helpdesk/components/sla-indicator.tsx +5 -8
  226. package/templates/helpdesk/components/thread.tsx +7 -7
  227. package/templates/helpdesk/components/ticket-list.tsx +79 -19
  228. package/templates/helpdesk/components/ui/button.tsx +5 -4
  229. package/templates/helpdesk/components/ui/dialog.tsx +1 -1
  230. package/templates/helpdesk/components/ui/input.tsx +1 -1
  231. package/templates/helpdesk/components/ui/select.tsx +1 -1
  232. package/templates/helpdesk/components/ui/textarea.tsx +1 -1
  233. package/templates/helpdesk/functions/seedWorkspace.ts +25 -15
  234. package/templates/helpdesk/lib/brand.ts +9 -0
  235. package/templates/helpdesk/lib/demo-team.ts +51 -0
  236. package/templates/helpdesk/lib/search.ts +4 -3
  237. package/templates/helpdesk/lib/seed.ts +43 -2
  238. package/templates/helpdesk/lib/tickets.ts +68 -20
  239. package/templates/helpdesk/public/favicon.ico +0 -0
  240. package/templates/helpdesk/tests/components.test.tsx +10 -0
  241. package/templates/helpdesk/tests/demo-team.test.ts +18 -0
  242. package/templates/helpdesk/tests/tickets.test.ts +70 -0
  243. package/templates/imessage/.env.example +49 -0
  244. package/templates/imessage/AGENTS.md +153 -0
  245. package/templates/imessage/CLAUDE.md +7 -0
  246. package/templates/imessage/README.md +187 -0
  247. package/templates/imessage/app/console.tsx +382 -0
  248. package/templates/imessage/app/contacts/page.tsx +13 -0
  249. package/templates/imessage/app/error.tsx +16 -0
  250. package/templates/imessage/app/gate.tsx +31 -0
  251. package/templates/imessage/app/globals.css +183 -0
  252. package/templates/imessage/app/layout.tsx +17 -0
  253. package/templates/imessage/app/login/login-form.tsx +115 -0
  254. package/templates/imessage/app/login/page.tsx +30 -0
  255. package/templates/imessage/app/not-found.tsx +13 -0
  256. package/templates/imessage/app/page.tsx +14 -0
  257. package/templates/imessage/app/robots.ts +6 -0
  258. package/templates/imessage/app/setup/page.tsx +13 -0
  259. package/templates/imessage/app/sign-out.tsx +20 -0
  260. package/templates/imessage/app.ts +299 -0
  261. package/templates/imessage/bunfig +4 -0
  262. package/templates/imessage/components/avatar.tsx +46 -0
  263. package/templates/imessage/components/contact-panel.tsx +263 -0
  264. package/templates/imessage/components/contacts-view.tsx +172 -0
  265. package/templates/imessage/components/conversation-list.tsx +129 -0
  266. package/templates/imessage/components/nav-rail.tsx +67 -0
  267. package/templates/imessage/components/setup-view.tsx +408 -0
  268. package/templates/imessage/components/thread.tsx +229 -0
  269. package/templates/imessage/components/ui/badge.tsx +37 -0
  270. package/templates/imessage/components/ui/button.tsx +56 -0
  271. package/templates/imessage/components/ui/card.tsx +90 -0
  272. package/templates/imessage/components/ui/dialog.tsx +110 -0
  273. package/templates/imessage/components/ui/input.tsx +30 -0
  274. package/templates/imessage/components/ui/label.tsx +21 -0
  275. package/templates/imessage/components/ui/select.tsx +39 -0
  276. package/templates/imessage/components/ui/table.tsx +124 -0
  277. package/templates/imessage/components/ui/textarea.tsx +24 -0
  278. package/templates/imessage/components.json +20 -0
  279. package/templates/imessage/functions/answerNow.ts +34 -0
  280. package/templates/imessage/functions/assistant.ts +96 -0
  281. package/templates/imessage/functions/beginTurn.ts +101 -0
  282. package/templates/imessage/functions/cancelReminder.ts +13 -0
  283. package/templates/imessage/functions/claimOutbound.ts +44 -0
  284. package/templates/imessage/functions/clearDemo.ts +18 -0
  285. package/templates/imessage/functions/deleteNote.ts +11 -0
  286. package/templates/imessage/functions/deliverOutbound.ts +55 -0
  287. package/templates/imessage/functions/finishTurn.ts +84 -0
  288. package/templates/imessage/functions/fireReminder.ts +42 -0
  289. package/templates/imessage/functions/ingestInbound.ts +22 -0
  290. package/templates/imessage/functions/ownerAccount.ts +13 -0
  291. package/templates/imessage/functions/processTurn.ts +74 -0
  292. package/templates/imessage/functions/relayApply.ts +102 -0
  293. package/templates/imessage/functions/relaySync.ts +63 -0
  294. package/templates/imessage/functions/releaseOutbound.ts +18 -0
  295. package/templates/imessage/functions/resolveTurn.ts +25 -0
  296. package/templates/imessage/functions/saveContact.ts +27 -0
  297. package/templates/imessage/functions/seedDemo.ts +70 -0
  298. package/templates/imessage/functions/sendOwnerMessage.ts +28 -0
  299. package/templates/imessage/functions/sendblueWebhook.ts +49 -0
  300. package/templates/imessage/functions/setContactStatus.ts +17 -0
  301. package/templates/imessage/functions/settleOutbound.ts +35 -0
  302. package/templates/imessage/functions/setupStatus.ts +33 -0
  303. package/templates/imessage/functions/sweep.ts +58 -0
  304. package/templates/imessage/functions/toolCancelReminder.ts +17 -0
  305. package/templates/imessage/functions/toolForgetNote.ts +15 -0
  306. package/templates/imessage/functions/toolListNotes.ts +16 -0
  307. package/templates/imessage/functions/toolListReminders.ts +17 -0
  308. package/templates/imessage/functions/toolSaveNote.ts +22 -0
  309. package/templates/imessage/functions/toolSetReminder.ts +55 -0
  310. package/templates/imessage/functions/updateSettings.ts +48 -0
  311. package/templates/imessage/gitignore +11 -0
  312. package/templates/imessage/lib/agent-call.ts +251 -0
  313. package/templates/imessage/lib/assistant.ts +74 -0
  314. package/templates/imessage/lib/chunk.ts +135 -0
  315. package/templates/imessage/lib/demo.ts +68 -0
  316. package/templates/imessage/lib/format.ts +170 -0
  317. package/templates/imessage/lib/gate.ts +104 -0
  318. package/templates/imessage/lib/handles.ts +67 -0
  319. package/templates/imessage/lib/provider.ts +38 -0
  320. package/templates/imessage/lib/relay-protocol.ts +95 -0
  321. package/templates/imessage/lib/reminders.ts +41 -0
  322. package/templates/imessage/lib/secrets.ts +59 -0
  323. package/templates/imessage/lib/sendblue.ts +173 -0
  324. package/templates/imessage/lib/store.ts +407 -0
  325. package/templates/imessage/lib/transport.ts +135 -0
  326. package/templates/imessage/lib/utils.ts +10 -0
  327. package/templates/imessage/package.json +40 -0
  328. package/templates/imessage/relay/.env.example +14 -0
  329. package/templates/imessage/relay/applescript.ts +70 -0
  330. package/templates/imessage/relay/chatdb.ts +165 -0
  331. package/templates/imessage/relay/client.ts +41 -0
  332. package/templates/imessage/relay/main.ts +137 -0
  333. package/templates/imessage/relay/state.ts +35 -0
  334. package/templates/imessage/tests/agent-call.test.ts +112 -0
  335. package/templates/imessage/tests/applescript.test.ts +88 -0
  336. package/templates/imessage/tests/chatdb.test.ts +139 -0
  337. package/templates/imessage/tests/chunk.test.ts +61 -0
  338. package/templates/imessage/tests/fake-ctx.ts +88 -0
  339. package/templates/imessage/tests/ingest.test.ts +141 -0
  340. package/templates/imessage/tests/lib.test.ts +99 -0
  341. package/templates/imessage/tests/review-fixes.test.ts +138 -0
  342. package/templates/imessage/tests/secrets.test.ts +36 -0
  343. package/templates/imessage/tests/sendblue.test.ts +143 -0
  344. package/templates/imessage/tests/setup.ts +5 -0
  345. package/templates/imessage/tsconfig.json +18 -0
  346. package/templates/inventory/README.md +21 -0
  347. package/templates/inventory/app/apple-icon.png +0 -0
  348. package/templates/inventory/app/globals.css +29 -14
  349. package/templates/inventory/app/icon.svg +1 -0
  350. package/templates/inventory/app/layout.tsx +7 -5
  351. package/templates/inventory/app/movements/movements-view.tsx +32 -10
  352. package/templates/inventory/app/movements/page.tsx +2 -1
  353. package/templates/inventory/app/page.tsx +4 -6
  354. package/templates/inventory/app/products/[id]/page.tsx +2 -1
  355. package/templates/inventory/app/products/[id]/product-view.tsx +55 -25
  356. package/templates/inventory/app/products-view.tsx +74 -33
  357. package/templates/inventory/app/workspace.tsx +34 -16
  358. package/templates/inventory/app.ts +10 -2
  359. package/templates/inventory/components/app-shell.tsx +94 -0
  360. package/templates/inventory/components/auth-form.tsx +35 -5
  361. package/templates/inventory/components/avatar.tsx +5 -1
  362. package/templates/inventory/components/brand-mark.tsx +25 -0
  363. package/templates/inventory/components/category-tile.tsx +49 -0
  364. package/templates/inventory/components/command-palette.tsx +17 -17
  365. package/templates/inventory/components/data-table.tsx +97 -36
  366. package/templates/inventory/components/filter-tabs.tsx +101 -0
  367. package/templates/inventory/components/nav.tsx +14 -0
  368. package/templates/inventory/components/page-header.tsx +43 -7
  369. package/templates/inventory/components/require-auth.tsx +24 -26
  370. package/templates/inventory/components/sidebar.tsx +54 -48
  371. package/templates/inventory/components/stock-level.tsx +30 -14
  372. package/templates/inventory/components/summary-bar.tsx +8 -15
  373. package/templates/inventory/components/ui/button.tsx +5 -4
  374. package/templates/inventory/components/ui/dialog.tsx +1 -1
  375. package/templates/inventory/components/ui/input.tsx +1 -1
  376. package/templates/inventory/components/ui/select.tsx +1 -1
  377. package/templates/inventory/components/ui/textarea.tsx +1 -1
  378. package/templates/inventory/functions/seedWorkspace.ts +18 -8
  379. package/templates/inventory/lib/brand.ts +9 -0
  380. package/templates/inventory/lib/demo-team.ts +51 -0
  381. package/templates/inventory/lib/search.ts +4 -3
  382. package/templates/inventory/lib/seed.ts +29 -2
  383. package/templates/inventory/lib/stock.ts +32 -0
  384. package/templates/inventory/public/favicon.ico +0 -0
  385. package/templates/inventory/tests/demo-team.test.ts +18 -0
  386. package/templates/inventory/tests/stock.test.ts +29 -0
  387. package/templates/invoices/README.md +16 -0
  388. package/templates/invoices/app/apple-icon.png +0 -0
  389. package/templates/invoices/app/clients/clients-view.tsx +18 -6
  390. package/templates/invoices/app/clients/page.tsx +2 -1
  391. package/templates/invoices/app/globals.css +29 -14
  392. package/templates/invoices/app/icon.svg +1 -0
  393. package/templates/invoices/app/invoices/[id]/invoice-view.tsx +72 -48
  394. package/templates/invoices/app/invoices/[id]/page.tsx +2 -1
  395. package/templates/invoices/app/invoices-view.tsx +81 -32
  396. package/templates/invoices/app/layout.tsx +7 -5
  397. package/templates/invoices/app/page.tsx +4 -6
  398. package/templates/invoices/app/workspace.tsx +26 -16
  399. package/templates/invoices/app.ts +2 -2
  400. package/templates/invoices/components/app-shell.tsx +94 -0
  401. package/templates/invoices/components/auth-form.tsx +35 -5
  402. package/templates/invoices/components/avatar.tsx +5 -1
  403. package/templates/invoices/components/brand-mark.tsx +26 -0
  404. package/templates/invoices/components/command-palette.tsx +17 -17
  405. package/templates/invoices/components/data-table.tsx +97 -36
  406. package/templates/invoices/components/filter-tabs.tsx +101 -0
  407. package/templates/invoices/components/line-items.tsx +19 -14
  408. package/templates/invoices/components/nav.tsx +14 -0
  409. package/templates/invoices/components/page-header.tsx +43 -7
  410. package/templates/invoices/components/require-auth.tsx +24 -26
  411. package/templates/invoices/components/sidebar.tsx +54 -48
  412. package/templates/invoices/components/status-badge.tsx +7 -7
  413. package/templates/invoices/components/summary-bar.tsx +5 -5
  414. package/templates/invoices/components/ui/button.tsx +5 -4
  415. package/templates/invoices/components/ui/dialog.tsx +1 -1
  416. package/templates/invoices/components/ui/input.tsx +1 -1
  417. package/templates/invoices/components/ui/select.tsx +1 -1
  418. package/templates/invoices/components/ui/textarea.tsx +1 -1
  419. package/templates/invoices/functions/recordPayment.ts +3 -0
  420. package/templates/invoices/lib/billing.ts +40 -0
  421. package/templates/invoices/lib/brand.ts +9 -0
  422. package/templates/invoices/lib/search.ts +4 -3
  423. package/templates/invoices/public/favicon.ico +0 -0
  424. package/templates/invoices/tests/billing.test.ts +27 -0
  425. package/templates/ios/consumer/apps/ios/Package.swift +4 -3
  426. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ActivityView.swift +107 -0
  427. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/AppModel.swift +171 -0
  428. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/AuthView.swift +182 -0
  429. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/CommentsView.swift +136 -0
  430. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ComposeView.swift +152 -0
  431. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ExploreView.swift +117 -0
  432. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/FeedView.swift +175 -159
  433. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/MainTabView.swift +54 -0
  434. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Media.swift +271 -0
  435. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Models.swift +101 -4
  436. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/PostCard.swift +245 -0
  437. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileEditorView.swift +208 -0
  438. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileView.swift +183 -0
  439. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/RootView.swift +25 -31
  440. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/SocialStore.swift +285 -0
  441. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/StoryViewer.swift +155 -0
  442. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Theme.swift +82 -0
  443. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/__APP_NAME_PASCAL__App.swift +4 -47
  444. package/templates/ios/consumer/apps/ios/project.yml +13 -9
  445. package/templates/local-service/.env.example +7 -0
  446. package/templates/local-service/README.md +26 -2
  447. package/templates/local-service/app/(marketing)/booking-widget.tsx +48 -9
  448. package/templates/local-service/app/(marketing)/page.tsx +66 -42
  449. package/templates/local-service/app/auth-form.tsx +8 -8
  450. package/templates/local-service/app/dashboard/dashboard-client.tsx +119 -92
  451. package/templates/local-service/app/dashboard/page.tsx +14 -14
  452. package/templates/local-service/app/icon.svg +2 -0
  453. package/templates/local-service/app/login/page.tsx +15 -15
  454. package/templates/local-service/functions/seedDemo.ts +49 -0
  455. package/templates/local-service/lib/demo.ts +135 -0
  456. package/templates/local-service/lib/hours.ts +44 -0
  457. package/templates/local-service/lib/site.config.ts +8 -3
  458. package/templates/local-service/lib/slots.ts +3 -0
  459. package/templates/local-service/public/favicon.ico +0 -0
  460. package/templates/local-service/tests/booking.test.ts +104 -0
  461. package/templates/marketplace/README.md +23 -15
  462. package/templates/marketplace/app/globals.css +172 -114
  463. package/templates/marketplace/app/layout.tsx +75 -30
  464. package/templates/marketplace/app/listing/[id]/page.tsx +210 -123
  465. package/templates/marketplace/app/me/page.tsx +4 -4
  466. package/templates/marketplace/app/page.tsx +221 -298
  467. package/templates/marketplace/app/sell/page.tsx +8 -11
  468. package/templates/marketplace/app.ts +32 -7
  469. package/templates/marketplace/client/ActivityFeed.tsx +311 -0
  470. package/templates/marketplace/client/AuthNav.tsx +24 -16
  471. package/templates/marketplace/client/ListingCard.tsx +118 -0
  472. package/templates/marketplace/client/LiveGrid.tsx +92 -0
  473. package/templates/marketplace/client/LiveListingStatus.tsx +32 -24
  474. package/templates/marketplace/client/LoginCard.tsx +114 -136
  475. package/templates/marketplace/client/MarketProvider.tsx +3 -5
  476. package/templates/marketplace/client/MyMarket.tsx +371 -185
  477. package/templates/marketplace/client/OfferPanel.tsx +421 -287
  478. package/templates/marketplace/client/SellForm.tsx +271 -192
  479. package/templates/marketplace/client/ThemeToggle.tsx +1 -0
  480. package/templates/marketplace/client/WatchButton.tsx +16 -16
  481. package/templates/marketplace/client/market.ts +61 -14
  482. package/templates/marketplace/functions/seedMarket.ts +143 -36
  483. package/templates/marketplace/functions/seedOffers.ts +101 -0
  484. package/templates/marketplace/lib/catalog.ts +72 -1
  485. package/templates/marketplace/public/images/listings/ceramic-lamp-640.webp +0 -0
  486. package/templates/marketplace/public/images/listings/ceramic-lamp.webp +0 -0
  487. package/templates/marketplace/public/images/listings/duffel-640.webp +0 -0
  488. package/templates/marketplace/public/images/listings/duffel.webp +0 -0
  489. package/templates/marketplace/public/images/listings/film-camera-640.webp +0 -0
  490. package/templates/marketplace/public/images/listings/film-camera.webp +0 -0
  491. package/templates/marketplace/public/images/listings/lounge-chair-640.webp +0 -0
  492. package/templates/marketplace/public/images/listings/lounge-chair.webp +0 -0
  493. package/templates/marketplace/public/images/listings/road-bike-640.webp +0 -0
  494. package/templates/marketplace/public/images/listings/road-bike.webp +0 -0
  495. package/templates/marketplace/public/images/listings/slipper-chair-640.webp +0 -0
  496. package/templates/marketplace/public/images/listings/slipper-chair.webp +0 -0
  497. package/templates/marketplace/public/images/listings/turntable-640.webp +0 -0
  498. package/templates/marketplace/public/images/listings/turntable.webp +0 -0
  499. package/templates/marketplace/public/images/listings/wardrobe-640.webp +0 -0
  500. package/templates/marketplace/public/images/listings/wardrobe.webp +0 -0
  501. package/templates/marketplace/public/images/listings/watch-640.webp +0 -0
  502. package/templates/marketplace/public/images/listings/watch.webp +0 -0
  503. package/templates/marketplace/public/images/listings/weekender-bag-640.webp +0 -0
  504. package/templates/marketplace/public/images/listings/weekender-bag.webp +0 -0
  505. package/templates/marketplace/tests/example.test.ts +64 -2
  506. package/templates/marketplace/tests/listing-card.test.tsx +61 -0
  507. package/templates/projects/README.md +24 -3
  508. package/templates/projects/app/apple-icon.png +0 -0
  509. package/templates/projects/app/clients/clients-view.tsx +20 -6
  510. package/templates/projects/app/clients/page.tsx +2 -1
  511. package/templates/projects/app/globals.css +29 -14
  512. package/templates/projects/app/icon.svg +1 -0
  513. package/templates/projects/app/layout.tsx +7 -5
  514. package/templates/projects/app/page.tsx +3 -5
  515. package/templates/projects/app/projects/[id]/page.tsx +2 -1
  516. package/templates/projects/app/projects/[id]/project-view.tsx +45 -30
  517. package/templates/projects/app/projects-view.tsx +105 -52
  518. package/templates/projects/app/workspace.tsx +41 -20
  519. package/templates/projects/app.ts +2 -2
  520. package/templates/projects/components/app-shell.tsx +94 -0
  521. package/templates/projects/components/auth-form.tsx +35 -5
  522. package/templates/projects/components/avatar.tsx +5 -1
  523. package/templates/projects/components/board-skeleton.tsx +3 -3
  524. package/templates/projects/components/brand-mark.tsx +25 -0
  525. package/templates/projects/components/command-palette.tsx +17 -17
  526. package/templates/projects/components/data-table.tsx +97 -36
  527. package/templates/projects/components/filter-tabs.tsx +101 -0
  528. package/templates/projects/components/meter.tsx +48 -0
  529. package/templates/projects/components/nav.tsx +14 -0
  530. package/templates/projects/components/page-header.tsx +43 -7
  531. package/templates/projects/components/require-auth.tsx +24 -26
  532. package/templates/projects/components/sidebar.tsx +54 -48
  533. package/templates/projects/components/summary-bar.tsx +49 -0
  534. package/templates/projects/components/task-board.tsx +10 -9
  535. package/templates/projects/components/time-dialog.tsx +29 -5
  536. package/templates/projects/components/ui/button.tsx +5 -4
  537. package/templates/projects/components/ui/dialog.tsx +1 -1
  538. package/templates/projects/components/ui/input.tsx +1 -1
  539. package/templates/projects/components/ui/select.tsx +1 -1
  540. package/templates/projects/components/ui/textarea.tsx +1 -1
  541. package/templates/projects/functions/seedWorkspace.ts +26 -12
  542. package/templates/projects/lib/brand.ts +9 -0
  543. package/templates/projects/lib/demo-team.ts +51 -0
  544. package/templates/projects/lib/search.ts +4 -3
  545. package/templates/projects/lib/seed.ts +35 -1
  546. package/templates/projects/lib/work.ts +63 -0
  547. package/templates/projects/public/favicon.ico +0 -0
  548. package/templates/projects/tests/demo-team.test.ts +18 -0
  549. package/templates/projects/tests/work.test.ts +65 -0
  550. package/templates/restaurant/.env.example +7 -0
  551. package/templates/restaurant/README.md +29 -3
  552. package/templates/restaurant/app/(marketing)/layout.tsx +4 -2
  553. package/templates/restaurant/app/(marketing)/page.tsx +47 -23
  554. package/templates/restaurant/app/(marketing)/reservation-widget.tsx +37 -7
  555. package/templates/restaurant/app/auth-form.tsx +8 -8
  556. package/templates/restaurant/app/dashboard/dashboard-client.tsx +241 -109
  557. package/templates/restaurant/app/dashboard/page.tsx +14 -14
  558. package/templates/restaurant/app/icon.svg +4 -0
  559. package/templates/restaurant/app/login/page.tsx +15 -15
  560. package/templates/restaurant/functions/seedDemo.ts +44 -0
  561. package/templates/restaurant/lib/demo.ts +144 -0
  562. package/templates/restaurant/lib/hours.ts +45 -0
  563. package/templates/restaurant/lib/site.config.ts +3 -3
  564. package/templates/restaurant/public/favicon.ico +0 -0
  565. package/templates/restaurant/tests/reservations.test.ts +67 -0
  566. package/templates/saas/README.md +54 -35
  567. package/templates/saas/app/(auth)/auth-form.tsx +25 -23
  568. package/templates/saas/app/(auth)/forgot-password/page.tsx +1 -1
  569. package/templates/saas/app/(auth)/layout.tsx +27 -33
  570. package/templates/saas/app/(auth)/login/page.tsx +1 -1
  571. package/templates/saas/app/(auth)/reset-password/page.tsx +1 -1
  572. package/templates/saas/app/(auth)/signup/page.tsx +1 -1
  573. package/templates/saas/app/(marketing)/layout.tsx +127 -253
  574. package/templates/saas/app/(marketing)/page.tsx +133 -372
  575. package/templates/saas/app/(marketing)/pricing/page.tsx +21 -19
  576. package/templates/saas/app/(marketing)/products/[slug]/page.tsx +40 -95
  577. package/templates/saas/app/dashboard/board-client.tsx +416 -0
  578. package/templates/saas/app/dashboard/dashboard-client.tsx +30 -615
  579. package/templates/saas/app/dashboard/layout.tsx +27 -13
  580. package/templates/saas/app/dashboard/overview-client.tsx +332 -0
  581. package/templates/saas/app/dashboard/page.tsx +29 -35
  582. package/templates/saas/app/dashboard/projects/[id]/page.tsx +29 -0
  583. package/templates/saas/app/dashboard/projects/page.tsx +11 -7
  584. package/templates/saas/app/dashboard/projects-client.tsx +398 -0
  585. package/templates/saas/app/globals.css +5 -4
  586. package/templates/saas/app/icon.svg +1 -0
  587. package/templates/saas/app/onboarding/onboarding-client.tsx +93 -26
  588. package/templates/saas/app/onboarding/page.tsx +6 -2
  589. package/templates/saas/app.ts +68 -13
  590. package/templates/saas/components/dashboard-shell.tsx +212 -132
  591. package/templates/saas/components/marketing.tsx +137 -274
  592. package/templates/saas/components/pricing-table.tsx +12 -11
  593. package/templates/saas/components/setup-checklist.tsx +31 -29
  594. package/templates/saas/components/task-ui.tsx +199 -0
  595. package/templates/saas/functions/createProject.ts +7 -13
  596. package/templates/saas/functions/createTask.ts +67 -0
  597. package/templates/saas/functions/deleteProject.ts +17 -0
  598. package/templates/saas/functions/deleteTask.ts +14 -0
  599. package/templates/saas/functions/seedWorkspace.ts +46 -0
  600. package/templates/saas/functions/setProjectStatus.ts +41 -0
  601. package/templates/saas/functions/updateProfile.ts +13 -0
  602. package/templates/saas/functions/updateProject.ts +4 -5
  603. package/templates/saas/functions/updateTask.ts +88 -0
  604. package/templates/saas/lib/members.ts +53 -0
  605. package/templates/saas/lib/names.ts +53 -0
  606. package/templates/saas/lib/plans.ts +11 -0
  607. package/templates/saas/lib/sample-workspace.ts +117 -0
  608. package/templates/saas/lib/site.config.ts +200 -463
  609. package/templates/saas/lib/tasks.ts +101 -0
  610. package/templates/saas/lib/use-live.ts +25 -0
  611. package/templates/saas/public/favicon.ico +0 -0
  612. package/templates/saas/public/screenshots/board.png +0 -0
  613. package/templates/saas/public/screenshots/members.png +0 -0
  614. package/templates/saas/public/screenshots/overview.png +0 -0
  615. package/templates/saas/public/screenshots/projects.png +0 -0
  616. package/templates/saas/scripts/capture-screenshots.mjs +156 -0
  617. package/templates/saas/tests/names.test.ts +35 -0
  618. package/templates/saas/tests/plans.test.ts +12 -0
  619. package/templates/saas/tests/sample-workspace.test.ts +46 -0
  620. package/templates/saas/tests/tasks.test.ts +81 -0
  621. package/templates/shop/README.md +25 -13
  622. package/templates/shop/app/(marketing)/layout.tsx +94 -67
  623. package/templates/shop/app/(marketing)/not-found.tsx +13 -12
  624. package/templates/shop/app/(marketing)/page.tsx +120 -59
  625. package/templates/shop/app/(marketing)/shop-client.tsx +364 -237
  626. package/templates/shop/app/(marketing)/success/page.tsx +75 -22
  627. package/templates/shop/app/globals.css +94 -1
  628. package/templates/shop/app/layout.tsx +4 -4
  629. package/templates/shop/app/login/page.tsx +4 -4
  630. package/templates/shop/app.ts +18 -6
  631. package/templates/shop/components/cart-button.tsx +33 -0
  632. package/templates/shop/components/marketing.tsx +17 -87
  633. package/templates/shop/components/shelf-count.tsx +36 -0
  634. package/templates/shop/functions/checkout.ts +17 -1
  635. package/templates/shop/lib/cart-store.ts +57 -0
  636. package/templates/shop/lib/shelf-store.ts +35 -0
  637. package/templates/shop/lib/shop.ts +27 -0
  638. package/templates/shop/lib/site.config.ts +108 -98
  639. package/templates/shop/public/images/hero.jpg +0 -0
  640. package/templates/shop/public/images/products/cedar-smoke.jpg +0 -0
  641. package/templates/shop/public/images/products/fig-leaf.jpg +0 -0
  642. package/templates/shop/public/images/products/linen.jpg +0 -0
  643. package/templates/shop/public/images/products/sea-salt.jpg +0 -0
  644. package/templates/shop/public/images/products/tobacco-oak.jpg +0 -0
  645. package/templates/shop/tests/shop.test.ts +64 -0
  646. package/templates/waitlist/.env.example +7 -0
  647. package/templates/waitlist/README.md +29 -13
  648. package/templates/waitlist/app/(marketing)/page.tsx +9 -7
  649. package/templates/waitlist/app/(marketing)/waitlist-hero.tsx +64 -31
  650. package/templates/waitlist/app/auth-form.tsx +8 -8
  651. package/templates/waitlist/app/dashboard/dashboard-client.tsx +132 -95
  652. package/templates/waitlist/app/dashboard/page.tsx +17 -16
  653. package/templates/waitlist/app/icon.svg +2 -0
  654. package/templates/waitlist/app/login/page.tsx +14 -14
  655. package/templates/waitlist/app.ts +6 -6
  656. package/templates/waitlist/functions/joinWaitlist.ts +6 -18
  657. package/templates/waitlist/functions/seedDemo.ts +32 -0
  658. package/templates/waitlist/lib/demo.ts +94 -0
  659. package/templates/waitlist/lib/launch.ts +19 -0
  660. package/templates/waitlist/lib/site.config.ts +14 -9
  661. package/templates/waitlist/lib/stats.ts +35 -0
  662. package/templates/waitlist/public/favicon.ico +0 -0
  663. package/templates/waitlist/tests/counter.test.ts +107 -0
  664. package/templates/waitlist/tests/live-count.test.tsx +48 -0
  665. package/templates/ai-chat/tests/example.test.ts +0 -12
  666. package/templates/backend/consumer/apps/api/functions/feed.ts +0 -57
  667. package/templates/backend/consumer/apps/api/functions/profilePosts.ts +0 -17
  668. package/templates/backend/consumer/apps/api/functions/toggleLike.ts +0 -48
  669. package/templates/backend/consumer/apps/api/schema.ts +0 -143
  670. package/templates/chat/functions/_keep.ts +0 -13
  671. package/templates/chat/tests/example.test.ts +0 -12
  672. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileSetupView.swift +0 -68
  673. package/templates/marketplace/client/LiveTicker.tsx +0 -116
  674. package/templates/marketplace/client/ScrollToListingsLink.tsx +0 -29
  675. package/templates/marketplace/public/images/marketplace-hero.webp +0 -0
@@ -1,48 +1,31 @@
1
1
  import { mutation, v } from "@pylonsync/functions";
2
+ import { CAPTION_MAX, isImageUrl, requireProfile } from "../lib/social";
2
3
 
3
4
  /**
4
- * Post something. Resolves the caller's Profile, then writes a Post
5
- * with `authorId = profile.id`. Returns the inserted row + author
6
- * card so the client can render it without a follow-up read.
5
+ * Publish a photo. The client uploads the file first (`/api/files/init`
6
+ * with visibility "public", so every viewer can load it) and sends the
7
+ * file URL here with the caption.
7
8
  */
8
9
  export default mutation({
9
- args: { body: v.string() },
10
- async handler(ctx, args: { body: string }) {
11
- if (!ctx.auth.userId) {
12
- throw ctx.error("UNAUTHENTICATED", "log in first");
10
+ args: { imageUrl: v.string(), caption: v.optional(v.string()) },
11
+ async handler(ctx, args: { imageUrl: string; caption?: string }) {
12
+ const profile = await requireProfile(ctx, ctx.auth.userId);
13
+ const imageUrl = args.imageUrl.trim();
14
+ if (!isImageUrl(imageUrl)) {
15
+ throw ctx.error("INVALID_IMAGE", "Upload the photo again.");
13
16
  }
14
- const body = args.body.trim();
15
- if (!body) throw ctx.error("EMPTY_BODY", "post body cannot be empty");
16
- if (body.length > 1000) {
17
- throw ctx.error("TOO_LONG", "post body capped at 1000 chars");
17
+ const caption = (args.caption ?? "").trim();
18
+ if (caption.length > CAPTION_MAX) {
19
+ throw ctx.error("CAPTION_TOO_LONG", `Use at most ${CAPTION_MAX} characters.`);
18
20
  }
19
- const profiles = (await ctx.db.query("Profile", {
20
- userId: ctx.auth.userId,
21
- })) as any[];
22
- if (profiles.length === 0) {
23
- throw ctx.error(
24
- "NO_PROFILE",
25
- "create a profile first via upsertProfile",
26
- );
27
- }
28
- const profile = profiles[0];
29
- const id = await ctx.db.insert("Post", {
21
+ // The Post policy refuses client writes. This function is the checked
22
+ // write path, so it writes past the policy.
23
+ const id = await ctx.db.unsafe.insert("Post", {
30
24
  authorId: profile.id,
31
- body,
25
+ imageUrl,
26
+ caption: caption || null,
32
27
  createdAt: new Date().toISOString(),
33
28
  });
34
- const post = (await ctx.db.get("Post", id)) as any;
35
- return {
36
- id: post.id,
37
- body: post.body,
38
- createdAt: post.createdAt,
39
- author: {
40
- id: profile.id,
41
- handle: profile.handle,
42
- displayName: profile.displayName,
43
- },
44
- likeCount: 0,
45
- likedByMe: false,
46
- };
29
+ return { id };
47
30
  },
48
31
  });
@@ -1,21 +1,23 @@
1
1
  import { mutation, v } from "@pylonsync/functions";
2
+ import { requireProfile } from "../lib/social";
2
3
 
3
- /**
4
- * Delete a Post and any associated Likes. Post policy already gates
5
- * deletes to the author's Profile, so a non-author can't reach the
6
- * actual delete call — but we double-check so a future policy
7
- * loosening doesn't accidentally orphan Like rows.
8
- */
4
+ /** Delete one of the caller's posts with its likes and comments. */
9
5
  export default mutation({
10
6
  args: { id: v.id("Post") },
11
7
  async handler(ctx, args: { id: string }) {
12
- const snapshot = (await ctx.db.get("Post", args.id)) as any;
13
- if (!snapshot) throw ctx.error("NOT_FOUND", "post not found");
14
- const likes = (await ctx.db.query("Like", { postId: args.id })) as any[];
15
- for (const like of likes) {
16
- await ctx.db.delete("Like", like.id);
8
+ const profile = await requireProfile(ctx, ctx.auth.userId);
9
+ const post = await ctx.db.get("Post", args.id);
10
+ if (!post) throw ctx.error("NOT_FOUND", "This post no longer exists.");
11
+ if (post.authorId !== profile.id) {
12
+ throw ctx.error("FORBIDDEN", "You can only delete your own posts.");
17
13
  }
18
- await ctx.db.delete("Post", args.id);
19
- return snapshot;
14
+ for (const like of await ctx.db.query("Like", { postId: args.id })) {
15
+ await ctx.db.unsafe.delete("Like", String(like.id));
16
+ }
17
+ for (const comment of await ctx.db.query("Comment", { postId: args.id })) {
18
+ await ctx.db.unsafe.delete("Comment", String(comment.id));
19
+ }
20
+ await ctx.db.unsafe.delete("Post", args.id);
21
+ return { id: args.id };
20
22
  },
21
23
  });
@@ -1,17 +1,14 @@
1
1
  import { query } from "@pylonsync/functions";
2
+ import { findProfile } from "../lib/social";
2
3
 
3
4
  /**
4
- * Resolve the caller's own Profile (or null if they haven't created
5
- * one yet). Used by the client to decide whether to show the
6
- * profile-setup screen on first launch.
5
+ * The caller's Profile, or null when they have not made one yet. The
6
+ * clients call this after sign-in to decide between the profile setup
7
+ * screen and the feed.
7
8
  */
8
9
  export default query({
9
10
  args: {},
10
11
  async handler(ctx) {
11
- if (!ctx.auth.userId) return null;
12
- const rows = (await ctx.db.query("Profile", {
13
- userId: ctx.auth.userId,
14
- })) as any[];
15
- return rows[0] ?? null;
12
+ return await findProfile(ctx, ctx.auth.userId);
16
13
  },
17
14
  });
@@ -0,0 +1,95 @@
1
+ import { mutation } from "@pylonsync/functions";
2
+ import {
3
+ SEED_COMMENTS,
4
+ SEED_PEOPLE,
5
+ SEED_POSTS,
6
+ photoPath,
7
+ seedFollows,
8
+ seedLikers,
9
+ } from "../lib/seed";
10
+
11
+ /**
12
+ * Write the demo people, posts, likes, comments, and follows once. The
13
+ * clients call this on launch. It returns at once when the demo data
14
+ * exists, and a lock keeps two first launches from seeding twice.
15
+ *
16
+ * Public so the first launch can seed before anyone signs in. It writes
17
+ * only the fixed data in lib/seed.ts. When you launch, delete this function,
18
+ * lib/seed.ts, public/images, the reserved-handle check in upsertProfile.ts,
19
+ * and the client calls to `seedDemo`.
20
+ */
21
+ export default mutation<Record<string, never>, { seeded: boolean }>({
22
+ auth: "public",
23
+ args: {},
24
+ async handler(ctx) {
25
+ // The first demo account's email marks a seeded database. Check before
26
+ // the lock so launches after the first skip it, then again under it.
27
+ const marker = { email: `${SEED_PEOPLE[0].handle}@demo.invalid`, $limit: 1 };
28
+ if ((await ctx.db.unsafe.query("User", marker)).length > 0) return { seeded: false };
29
+ await ctx.db.advisoryLock("consumer_seed_demo");
30
+ if ((await ctx.db.unsafe.query("User", marker)).length > 0) return { seeded: false };
31
+
32
+ const now = Date.now();
33
+ const hoursAgo = (h: number) => new Date(now - h * 3_600_000).toISOString();
34
+
35
+ const profileIds = new Map<string, string>();
36
+ for (const person of SEED_PEOPLE) {
37
+ // A demo account has no password, so nobody can sign in as it.
38
+ const userId = await ctx.db.unsafe.insert("User", {
39
+ email: `${person.handle}@demo.invalid`,
40
+ displayName: person.displayName,
41
+ createdAt: hoursAgo(24 * 60),
42
+ });
43
+ const profileId = await ctx.db.unsafe.insert("Profile", {
44
+ userId,
45
+ handle: person.handle,
46
+ displayName: person.displayName,
47
+ bio: person.bio,
48
+ createdAt: hoursAgo(24 * 60),
49
+ });
50
+ profileIds.set(person.handle, profileId);
51
+ }
52
+
53
+ const postIds = new Map<string, string>();
54
+ const postAges = new Map<string, number>();
55
+ for (const post of SEED_POSTS) {
56
+ const id = await ctx.db.unsafe.insert("Post", {
57
+ authorId: profileIds.get(post.author),
58
+ imageUrl: photoPath(post.photo),
59
+ caption: post.caption,
60
+ createdAt: hoursAgo(post.age),
61
+ });
62
+ postIds.set(post.photo, id);
63
+ postAges.set(post.photo, post.age);
64
+ for (const [i, liker] of seedLikers(post.photo).entries()) {
65
+ if (liker === post.author) continue;
66
+ await ctx.db.unsafe.insert("Like", {
67
+ postId: id,
68
+ profileId: profileIds.get(liker),
69
+ createdAt: hoursAgo(Math.max(post.age - 0.2 * (i + 1), 0.05)),
70
+ });
71
+ }
72
+ }
73
+
74
+ for (const comment of SEED_COMMENTS) {
75
+ const postId = postIds.get(comment.photo);
76
+ const age = postAges.get(comment.photo);
77
+ if (!postId || age === undefined) continue;
78
+ await ctx.db.unsafe.insert("Comment", {
79
+ postId,
80
+ profileId: profileIds.get(comment.author),
81
+ text: comment.text,
82
+ createdAt: hoursAgo(Math.max(age - comment.after, 0.05)),
83
+ });
84
+ }
85
+
86
+ for (const [follower, following] of seedFollows()) {
87
+ await ctx.db.unsafe.insert("Follow", {
88
+ followerId: profileIds.get(follower),
89
+ followingId: profileIds.get(following),
90
+ createdAt: hoursAgo(24 * 30),
91
+ });
92
+ }
93
+ return { seeded: true };
94
+ },
95
+ });
@@ -0,0 +1,36 @@
1
+ import { mutation, v } from "@pylonsync/functions";
2
+ import { requireProfile } from "../lib/social";
3
+
4
+ /**
5
+ * Follow or unfollow a profile as the caller. Idempotent, like setLike.
6
+ */
7
+ export default mutation({
8
+ args: { profileId: v.id("Profile"), following: v.boolean() },
9
+ async handler(ctx, args: { profileId: string; following: boolean }) {
10
+ const me = await requireProfile(ctx, ctx.auth.userId);
11
+ if (me.id === args.profileId) {
12
+ throw ctx.error("SELF_FOLLOW", "You cannot follow yourself.");
13
+ }
14
+ const target = await ctx.db.get("Profile", args.profileId);
15
+ if (!target) throw ctx.error("NOT_FOUND", "This profile no longer exists.");
16
+
17
+ await ctx.db.advisoryLock(`follow:${me.id}:${args.profileId}`);
18
+ const existing = await ctx.db.query("Follow", {
19
+ followerId: me.id,
20
+ followingId: args.profileId,
21
+ });
22
+ if (args.following && existing.length === 0) {
23
+ await ctx.db.unsafe.insert("Follow", {
24
+ followerId: me.id,
25
+ followingId: args.profileId,
26
+ createdAt: new Date().toISOString(),
27
+ });
28
+ }
29
+ if (!args.following) {
30
+ for (const row of existing) {
31
+ await ctx.db.unsafe.delete("Follow", String(row.id));
32
+ }
33
+ }
34
+ return { following: args.following };
35
+ },
36
+ });
@@ -0,0 +1,36 @@
1
+ import { mutation, v } from "@pylonsync/functions";
2
+ import { requireProfile } from "../lib/social";
3
+
4
+ /**
5
+ * Like or unlike a post as the caller. Idempotent: `liked: true` twice
6
+ * leaves one like, so taps that reach the server out of order still end
7
+ * in the state of the last tap. Returns the post's like count.
8
+ */
9
+ export default mutation({
10
+ args: { postId: v.id("Post"), liked: v.boolean() },
11
+ async handler(ctx, args: { postId: string; liked: boolean }) {
12
+ const profile = await requireProfile(ctx, ctx.auth.userId);
13
+ const post = await ctx.db.get("Post", args.postId);
14
+ if (!post) throw ctx.error("NOT_FOUND", "This post no longer exists.");
15
+
16
+ await ctx.db.advisoryLock(`like:${profile.id}:${args.postId}`);
17
+ const existing = await ctx.db.query("Like", {
18
+ postId: args.postId,
19
+ profileId: profile.id,
20
+ });
21
+ if (args.liked && existing.length === 0) {
22
+ await ctx.db.unsafe.insert("Like", {
23
+ postId: args.postId,
24
+ profileId: profile.id,
25
+ createdAt: new Date().toISOString(),
26
+ });
27
+ }
28
+ if (!args.liked) {
29
+ for (const like of existing) {
30
+ await ctx.db.unsafe.delete("Like", String(like.id));
31
+ }
32
+ }
33
+ const likes = await ctx.db.query("Like", { postId: args.postId });
34
+ return { liked: args.liked, likeCount: likes.length };
35
+ },
36
+ });
@@ -1,70 +1,76 @@
1
1
  import { mutation, v } from "@pylonsync/functions";
2
+ import { SEED_PEOPLE } from "../lib/seed";
3
+ import { BIO_MAX, HANDLE_PATTERN, findProfile, isImageUrl } from "../lib/social";
2
4
 
3
5
  /**
4
- * Create-or-update the caller's Profile. Handle is unique (case-
5
- * insensitive); we lowercase + check before inserting. On update
6
- * we don't re-check the handle if it didn't change.
6
+ * Create or update the caller's Profile. Handles are lowercase and unique.
7
+ * `avatarUrl` is optional; an empty string clears it.
7
8
  */
8
9
  export default mutation({
9
10
  args: {
10
11
  handle: v.string(),
11
12
  displayName: v.string(),
12
13
  bio: v.string(),
14
+ avatarUrl: v.optional(v.string()),
13
15
  },
14
16
  async handler(
15
17
  ctx,
16
- args: { handle: string; displayName: string; bio: string },
18
+ args: { handle: string; displayName: string; bio: string; avatarUrl?: string },
17
19
  ) {
18
- if (!ctx.auth.userId) {
19
- throw ctx.error("UNAUTHENTICATED", "log in first");
20
- }
20
+ const userId = ctx.auth.userId;
21
21
  const handle = args.handle.trim().toLowerCase();
22
- if (!/^[a-z0-9_]{2,20}$/.test(handle)) {
22
+ if (!HANDLE_PATTERN.test(handle)) {
23
23
  throw ctx.error(
24
24
  "INVALID_HANDLE",
25
- "handle must be 2–20 chars: lowercase letters, digits, underscore",
25
+ "Use 2 to 24 characters: lowercase letters, digits, periods, and underscores.",
26
26
  );
27
27
  }
28
+ // The demo people in lib/seed.ts own these handles, even before the
29
+ // seed runs. Delete this check with lib/seed.ts.
30
+ if (SEED_PEOPLE.some((p) => p.handle === handle)) {
31
+ throw ctx.error("HANDLE_TAKEN", `@${handle} is taken.`);
32
+ }
28
33
  const displayName = args.displayName.trim();
29
- if (!displayName) {
30
- throw ctx.error("EMPTY_NAME", "displayName cannot be empty");
34
+ if (!displayName) throw ctx.error("EMPTY_NAME", "Enter your name.");
35
+ if (displayName.length > 40) {
36
+ throw ctx.error("NAME_TOO_LONG", "Use at most 40 characters for your name.");
37
+ }
38
+ const bio = args.bio.trim();
39
+ if (bio.length > BIO_MAX) {
40
+ throw ctx.error("BIO_TOO_LONG", `Use at most ${BIO_MAX} characters for your bio.`);
41
+ }
42
+ const avatarUrl = args.avatarUrl?.trim();
43
+ if (avatarUrl && !isImageUrl(avatarUrl)) {
44
+ throw ctx.error("INVALID_IMAGE", "Upload the photo again.");
31
45
  }
32
46
 
33
- const existing = (await ctx.db.query("Profile", {
34
- userId: ctx.auth.userId,
35
- })) as any[];
47
+ // Two devices saving at once must not create two profiles.
48
+ await ctx.db.advisoryLock(`profile:${userId}`);
49
+ const existing = await findProfile(ctx, userId);
50
+ const [taken] = await ctx.db.query("Profile", { handle, $limit: 1 });
51
+ if (taken && taken.id !== existing?.id) {
52
+ throw ctx.error("HANDLE_TAKEN", `@${handle} is taken.`);
53
+ }
36
54
 
37
- if (existing.length === 0) {
38
- // New profile — handle must be unique
39
- const dup = (await ctx.db.query("Profile", { handle })) as any[];
40
- if (dup.length > 0) {
41
- throw ctx.error("HANDLE_TAKEN", `@${handle} is taken`);
42
- }
43
- const id = await ctx.db.insert("Profile", {
44
- userId: ctx.auth.userId,
55
+ // The Profile policy refuses client writes. This function is the
56
+ // checked write path, so it writes past the policy.
57
+ if (!existing) {
58
+ const id = await ctx.db.unsafe.insert("Profile", {
59
+ userId,
45
60
  handle,
46
61
  displayName,
47
- bio: args.bio.trim() || null,
62
+ bio: bio || null,
63
+ avatarUrl: avatarUrl || null,
48
64
  createdAt: new Date().toISOString(),
49
65
  });
50
66
  return await ctx.db.get("Profile", id);
51
67
  }
52
-
53
- const profile = existing[0];
54
- // Update — re-check handle uniqueness only if changing
55
- if (profile.handle !== handle) {
56
- const dup = ((await ctx.db.query("Profile", { handle })) as any[]).filter(
57
- (p) => p.id !== profile.id,
58
- );
59
- if (dup.length > 0) {
60
- throw ctx.error("HANDLE_TAKEN", `@${handle} is taken`);
61
- }
62
- }
63
- await ctx.db.update("Profile", profile.id, {
68
+ await ctx.db.unsafe.update("Profile", existing.id, {
64
69
  handle,
65
70
  displayName,
66
- bio: args.bio.trim() || null,
71
+ bio: bio || null,
72
+ ...(args.avatarUrl !== undefined ? { avatarUrl: avatarUrl || null } : {}),
67
73
  });
68
- return await ctx.db.get("Profile", profile.id);
74
+ return await ctx.db.get("Profile", existing.id);
69
75
  },
70
76
  });
@@ -0,0 +1,89 @@
1
+ // Demo data for a new app: eight people who post, follow each other, like,
2
+ // and comment, so the first launch opens on a feed with content in it.
3
+ //
4
+ // Photos live in public/images/posts and the server serves them at
5
+ // /images/posts/... Demo people have no avatar photo, so the clients draw
6
+ // their initial. Demo accounts have no password, so nobody can sign in as
7
+ // them. functions/seedDemo.ts writes this data once.
8
+
9
+ export interface SeedPerson {
10
+ handle: string;
11
+ displayName: string;
12
+ bio: string;
13
+ }
14
+
15
+ export interface SeedPost {
16
+ /** File name under public/images/posts, without `.jpg`. */
17
+ photo: string;
18
+ author: string;
19
+ caption: string;
20
+ /** Hours before the seed runs. */
21
+ age: number;
22
+ }
23
+
24
+ export interface SeedComment {
25
+ photo: string;
26
+ author: string;
27
+ text: string;
28
+ /** Hours after the post. */
29
+ after: number;
30
+ }
31
+
32
+ export const SEED_PEOPLE: SeedPerson[] = [
33
+ { handle: "maya.rivera", displayName: "Maya Rivera", bio: "Travel photographer. Currently in Lisbon." },
34
+ { handle: "jordan.eats", displayName: "Jordan Brooks", bio: "Cooking at home, eating out, writing it all down." },
35
+ { handle: "priya.home", displayName: "Priya Shah", bio: "Interior designer. Small rooms, good light." },
36
+ { handle: "kenji.mori", displayName: "Kenji Mori", bio: "Architecture and night walks. Tokyo." },
37
+ { handle: "elena.runs", displayName: "Elena Berg", bio: "Trail runner. Mountains most weekends." },
38
+ { handle: "sam.and.juno", displayName: "Sam Whitaker", bio: "Juno is the golden one." },
39
+ { handle: "amara.o", displayName: "Amara Okafor", bio: "Coffee, markets, and early mornings." },
40
+ { handle: "leo.haddad", displayName: "Leo Haddad", bio: "Road trips and sunsets. Film when I can." },
41
+ ];
42
+
43
+ export const SEED_POSTS: SeedPost[] = [
44
+ { photo: "lisbon", author: "maya.rivera", caption: "Tram 28 at golden hour. Waited twenty minutes for this one and it was worth it.", age: 1.5 },
45
+ { photo: "coffee", author: "amara.o", caption: "Flat white and a croissant before the market opens.", age: 3 },
46
+ { photo: "dog", author: "sam.and.juno", caption: "Juno found the tall grass again.", age: 5 },
47
+ { photo: "run", author: "elena.runs", caption: "Sunrise on the ridge. 18 km, 900 m up, legs gone.", age: 8 },
48
+ { photo: "ramen", author: "jordan.eats", caption: "Tonkotsu from the new place on 5th. Broth cooked for 14 hours and you can tell.", age: 11 },
49
+ { photo: "arch", author: "kenji.mori", caption: "Curves and shadows. The museum extension at noon.", age: 14 },
50
+ { photo: "sunset", author: "leo.haddad", caption: "Big Sur, last light. 30 second exposure.", age: 20 },
51
+ ];
52
+
53
+ export const SEED_COMMENTS: SeedComment[] = [
54
+ { photo: "lisbon", author: "leo.haddad", text: "The light on those tiles. Which street is this?", after: 0.3 },
55
+ { photo: "lisbon", author: "maya.rivera", text: "Rua da Conceição, just before it turns up to the cathedral.", after: 0.6 },
56
+ { photo: "lisbon", author: "amara.o", text: "Adding this to the list for May.", after: 0.9 },
57
+ { photo: "coffee", author: "jordan.eats", text: "That rosetta is perfect.", after: 0.5 },
58
+ { photo: "coffee", author: "priya.home", text: "Where is this? I need that table.", after: 1 },
59
+ { photo: "dog", author: "elena.runs", text: "Juno looks so happy.", after: 0.4 },
60
+ { photo: "dog", author: "amara.o", text: "Best dog on this app.", after: 1.2 },
61
+ { photo: "run", author: "kenji.mori", text: "Incredible view. How early did you start?", after: 1 },
62
+ { photo: "run", author: "elena.runs", text: "Headlamps on at 4:30.", after: 1.5 },
63
+ { photo: "ramen", author: "maya.rivera", text: "Going this week.", after: 2 },
64
+ { photo: "arch", author: "priya.home", text: "Those shadows are unreal.", after: 1 },
65
+ { photo: "sunset", author: "sam.and.juno", text: "Colors like this make me want to drive up the coast tonight.", after: 2 },
66
+ ];
67
+
68
+ /** Which demo people like which photo. Deterministic, so every seed looks the same. */
69
+ export function seedLikers(photo: string): string[] {
70
+ const people = SEED_PEOPLE.map((p) => p.handle);
71
+ let hash = 0;
72
+ for (const ch of photo) hash = (hash * 31 + ch.charCodeAt(0)) >>> 0;
73
+ return people.filter((_, i) => ((hash >> i) & 3) !== 0);
74
+ }
75
+
76
+ /** Everyone follows everyone else, except a few pairs. */
77
+ export function seedFollows(): Array<[string, string]> {
78
+ const out: Array<[string, string]> = [];
79
+ for (const a of SEED_PEOPLE) {
80
+ for (const b of SEED_PEOPLE) {
81
+ if (a.handle === b.handle) continue;
82
+ if ((a.handle.length + b.handle.length) % 5 === 0) continue;
83
+ out.push([a.handle, b.handle]);
84
+ }
85
+ }
86
+ return out;
87
+ }
88
+
89
+ export const photoPath = (photo: string) => `/images/posts/${photo}.jpg`;
@@ -0,0 +1,50 @@
1
+ import type { MutationCtx, QueryCtx } from "@pylonsync/functions";
2
+
3
+ // Shared checks for the functions in functions/.
4
+
5
+ export const CAPTION_MAX = 2200;
6
+ export const COMMENT_MAX = 500;
7
+ export const BIO_MAX = 150;
8
+ export const HANDLE_PATTERN = /^[a-z0-9._]{2,24}$/;
9
+
10
+ /**
11
+ * A post or avatar image must be a demo photo served from public/images or
12
+ * a file uploaded to this server (`/api/files/<id>`). Anything else is
13
+ * refused, so a client cannot point the feed at an arbitrary host.
14
+ */
15
+ export function isImageUrl(url: string): boolean {
16
+ if (url.includes("..") || url.includes("//")) return false;
17
+ return /^\/images\/[a-z0-9/_.-]+\.(jpg|jpeg|png|webp)$/i.test(url) ||
18
+ /^\/api\/files\/[A-Za-z0-9_.-]+$/.test(url);
19
+ }
20
+
21
+ /** The caller's Profile row, or null when they have not made one yet. */
22
+ export async function findProfile(
23
+ ctx: Pick<QueryCtx, "db"> | Pick<MutationCtx, "db">,
24
+ userId: string,
25
+ ): Promise<ProfileRow | null> {
26
+ const rows = await ctx.db.query("Profile", { userId, $limit: 1 });
27
+ return (rows[0] as unknown as ProfileRow | undefined) ?? null;
28
+ }
29
+
30
+ /** The caller's Profile row. Throws NO_PROFILE when there is none. */
31
+ export async function requireProfile(
32
+ ctx: Pick<MutationCtx, "db" | "error">,
33
+ userId: string,
34
+ ): Promise<ProfileRow> {
35
+ const profile = await findProfile(ctx, userId);
36
+ if (!profile) {
37
+ throw ctx.error("NO_PROFILE", "Create your profile first.");
38
+ }
39
+ return profile;
40
+ }
41
+
42
+ export interface ProfileRow {
43
+ id: string;
44
+ userId: string;
45
+ handle: string;
46
+ displayName: string;
47
+ bio?: string | null;
48
+ avatarUrl?: string | null;
49
+ createdAt: string;
50
+ }
@@ -6,15 +6,21 @@
6
6
  "scripts": {
7
7
  "dev": "pylon dev",
8
8
  "build": "pylon build",
9
+ "check": "tsc --noEmit",
9
10
  "schema:push": "pylon schema push --sqlite .pylon/dev.db",
10
11
  "schema:inspect": "pylon schema inspect --sqlite .pylon/dev.db"
11
12
  },
12
13
  "dependencies": {
13
14
  "@pylonsync/sdk": "^__PYLON_VERSION__",
14
- "@pylonsync/functions": "^__PYLON_VERSION__"
15
+ "@pylonsync/functions": "^__PYLON_VERSION__",
16
+ "@pylonsync/react": "^__PYLON_VERSION__",
17
+ "react": "^19.0.0",
18
+ "react-dom": "^19.0.0"
15
19
  },
16
20
  "devDependencies": {
17
21
  "@pylonsync/cli": "^__PYLON_VERSION__",
18
- "typescript": "^5.5.0"
22
+ "typescript": "^5.5.0",
23
+ "@types/react": "^19.0.0",
24
+ "@types/react-dom": "^19.0.0"
19
25
  }
20
26
  }
@@ -7,7 +7,17 @@
7
7
  "skipLibCheck": true,
8
8
  "noEmit": true,
9
9
  "esModuleInterop": true,
10
- "allowSyntheticDefaultImports": true
10
+ "allowSyntheticDefaultImports": true,
11
+ "jsx": "react",
12
+ "lib": [
13
+ "ES2022",
14
+ "DOM"
15
+ ]
11
16
  },
12
- "include": ["schema.ts", "functions/**/*.ts"]
17
+ "include": [
18
+ "app.ts",
19
+ "app/**/*",
20
+ "functions/**/*",
21
+ "lib/**/*"
22
+ ]
13
23
  }