@pylonsync/create-pylon 0.19.1 → 0.21.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 (696) hide show
  1. package/bin/create-pylon.js +30 -7
  2. package/package.json +1 -1
  3. package/templates/_root/AGENTS.md +1 -1
  4. package/templates/agency/.env.example +7 -0
  5. package/templates/agency/AGENTS.md +1 -1
  6. package/templates/agency/README.md +28 -3
  7. package/templates/agency/app/(marketing)/contact-form.tsx +20 -18
  8. package/templates/agency/app/(marketing)/page.tsx +3 -3
  9. package/templates/agency/app/(marketing)/work/page.tsx +3 -3
  10. package/templates/agency/app/auth-form.tsx +4 -4
  11. package/templates/agency/app/dashboard/dashboard-client.tsx +110 -64
  12. package/templates/agency/app/dashboard/page.tsx +14 -16
  13. package/templates/agency/app/icon.svg +3 -0
  14. package/templates/agency/app/login/page.tsx +12 -14
  15. package/templates/agency/components/marketing.tsx +12 -5
  16. package/templates/agency/functions/seedStudioBackoffice.ts +42 -11
  17. package/templates/agency/lib/agency.ts +22 -0
  18. package/templates/agency/lib/demo.ts +118 -0
  19. package/templates/agency/lib/site.config.ts +19 -13
  20. package/templates/agency/public/favicon.ico +0 -0
  21. package/templates/agency/tests/agency.test.ts +57 -0
  22. package/templates/ai-chat/.env.example +13 -27
  23. package/templates/ai-chat/AGENTS.md +1 -1
  24. package/templates/ai-chat/README.md +53 -60
  25. package/templates/ai-chat/app/(chat)/c/[id]/page.tsx +19 -0
  26. package/templates/ai-chat/app/(chat)/chat-client.tsx +657 -622
  27. package/templates/ai-chat/app/(chat)/layout.tsx +6 -43
  28. package/templates/ai-chat/app/(chat)/not-found.tsx +6 -10
  29. package/templates/ai-chat/app/(chat)/page.tsx +7 -11
  30. package/templates/ai-chat/app/auth-form.tsx +45 -38
  31. package/templates/ai-chat/app/error.tsx +9 -12
  32. package/templates/ai-chat/app/globals.css +464 -102
  33. package/templates/ai-chat/app/layout.tsx +11 -19
  34. package/templates/ai-chat/app/login/page.tsx +39 -24
  35. package/templates/ai-chat/app.ts +118 -43
  36. package/templates/ai-chat/components/chat/composer.tsx +193 -0
  37. package/templates/ai-chat/components/chat/empty-state.tsx +65 -0
  38. package/templates/ai-chat/components/chat/icons.tsx +198 -0
  39. package/templates/ai-chat/components/chat/markdown.tsx +164 -0
  40. package/templates/ai-chat/components/chat/message.tsx +183 -0
  41. package/templates/ai-chat/components/chat/setup.tsx +71 -0
  42. package/templates/ai-chat/components/chat/sidebar.tsx +288 -0
  43. package/templates/ai-chat/functions/_beginTurn.ts +145 -0
  44. package/templates/ai-chat/functions/_finishTurn.ts +55 -0
  45. package/templates/ai-chat/functions/assistantStatus.ts +13 -0
  46. package/templates/ai-chat/functions/claimGuestHistory.ts +39 -0
  47. package/templates/ai-chat/functions/createConversation.ts +28 -0
  48. package/templates/ai-chat/functions/deleteConversation.ts +15 -20
  49. package/templates/ai-chat/functions/prepareGuestClaim.ts +32 -0
  50. package/templates/ai-chat/functions/renameConversation.ts +19 -0
  51. package/templates/ai-chat/functions/respond.ts +88 -0
  52. package/templates/ai-chat/functions/seedExamples.ts +46 -0
  53. package/templates/ai-chat/functions/stopResponse.ts +30 -0
  54. package/templates/ai-chat/lib/brand-icon.ts +7 -0
  55. package/templates/ai-chat/lib/chat.ts +277 -0
  56. package/templates/ai-chat/lib/claim.ts +15 -0
  57. package/templates/ai-chat/lib/examples.ts +179 -0
  58. package/templates/ai-chat/lib/highlight.ts +107 -0
  59. package/templates/ai-chat/lib/markdown.ts +317 -0
  60. package/templates/ai-chat/lib/provider.ts +38 -0
  61. package/templates/ai-chat/lib/site.config.ts +55 -39
  62. package/templates/ai-chat/tests/chat.test.ts +174 -0
  63. package/templates/ai-chat/tests/components.test.tsx +57 -0
  64. package/templates/ai-chat/tests/markdown.test.ts +88 -0
  65. package/templates/ai-chat/tests/provider.test.ts +32 -0
  66. package/templates/ai-studio/AGENTS.md +1 -1
  67. package/templates/backend/consumer/apps/api/app/layout.tsx +33 -0
  68. package/templates/backend/consumer/apps/api/app/page.tsx +54 -0
  69. package/templates/backend/consumer/apps/api/app.ts +166 -0
  70. package/templates/backend/consumer/apps/api/functions/addComment.ts +24 -0
  71. package/templates/backend/consumer/apps/api/functions/createPost.ts +19 -36
  72. package/templates/backend/consumer/apps/api/functions/deletePost.ts +15 -13
  73. package/templates/backend/consumer/apps/api/functions/myProfile.ts +5 -8
  74. package/templates/backend/consumer/apps/api/functions/seedDemo.ts +95 -0
  75. package/templates/backend/consumer/apps/api/functions/setFollow.ts +36 -0
  76. package/templates/backend/consumer/apps/api/functions/setLike.ts +36 -0
  77. package/templates/backend/consumer/apps/api/functions/upsertProfile.ts +43 -37
  78. package/templates/backend/consumer/apps/api/lib/seed.ts +89 -0
  79. package/templates/backend/consumer/apps/api/lib/social.ts +50 -0
  80. package/templates/backend/consumer/apps/api/package.json +8 -2
  81. package/templates/backend/consumer/apps/api/public/images/posts/arch.jpg +0 -0
  82. package/templates/backend/consumer/apps/api/public/images/posts/coffee.jpg +0 -0
  83. package/templates/backend/consumer/apps/api/public/images/posts/dog.jpg +0 -0
  84. package/templates/backend/consumer/apps/api/public/images/posts/lisbon.jpg +0 -0
  85. package/templates/backend/consumer/apps/api/public/images/posts/ramen.jpg +0 -0
  86. package/templates/backend/consumer/apps/api/public/images/posts/run.jpg +0 -0
  87. package/templates/backend/consumer/apps/api/public/images/posts/sunset.jpg +0 -0
  88. package/templates/backend/consumer/apps/api/tsconfig.json +12 -2
  89. package/templates/barebones/AGENTS.md +1 -1
  90. package/templates/chat/AGENTS.md +1 -1
  91. package/templates/chat/README.md +43 -21
  92. package/templates/chat/app/chat-client.tsx +495 -85
  93. package/templates/chat/app/error.tsx +1 -1
  94. package/templates/chat/app/globals.css +170 -54
  95. package/templates/chat/app/layout.tsx +12 -9
  96. package/templates/chat/app/not-found.tsx +4 -2
  97. package/templates/chat/app/page.tsx +9 -19
  98. package/templates/chat/app.ts +96 -17
  99. package/templates/chat/components/chat/avatar.tsx +45 -0
  100. package/templates/chat/components/chat/composer.tsx +110 -0
  101. package/templates/chat/components/chat/message-list.tsx +285 -0
  102. package/templates/chat/components/chat/sidebar.tsx +190 -0
  103. package/templates/chat/functions/joinChat.ts +83 -0
  104. package/templates/chat/functions/renameMember.ts +25 -0
  105. package/templates/chat/lib/chat.ts +188 -0
  106. package/templates/chat/lib/seed.ts +78 -0
  107. package/templates/chat/tests/chat.test.ts +159 -0
  108. package/templates/chat/tests/composer.test.tsx +54 -0
  109. package/templates/consumer/AGENTS.md +1 -1
  110. package/templates/consumer/README.md +3 -3
  111. package/templates/consumer/app/globals.css +10 -3
  112. package/templates/consumer/app/icon.svg +1 -0
  113. package/templates/consumer/components/social/avatar.tsx +25 -11
  114. package/templates/consumer/functions/seedFeed.ts +3 -3
  115. package/templates/consumer/lib/seed.ts +43 -85
  116. package/templates/consumer/public/favicon.ico +0 -0
  117. package/templates/consumer/public/images/posts/arch.jpg +0 -0
  118. package/templates/consumer/public/images/posts/coffee.jpg +0 -0
  119. package/templates/consumer/public/images/posts/dog.jpg +0 -0
  120. package/templates/consumer/public/images/posts/lisbon.jpg +0 -0
  121. package/templates/consumer/public/images/posts/ramen.jpg +0 -0
  122. package/templates/consumer/public/images/posts/run.jpg +0 -0
  123. package/templates/consumer/public/images/posts/sunset.jpg +0 -0
  124. package/templates/consumer/tests/seed.test.ts +39 -0
  125. package/templates/creator/.env.example +7 -0
  126. package/templates/creator/AGENTS.md +1 -1
  127. package/templates/creator/README.md +24 -5
  128. package/templates/creator/app/(marketing)/newsletter-signup.tsx +33 -12
  129. package/templates/creator/app/auth-form.tsx +7 -7
  130. package/templates/creator/app/dashboard/dashboard-client.tsx +137 -100
  131. package/templates/creator/app/dashboard/page.tsx +18 -18
  132. package/templates/creator/app/icon.svg +2 -0
  133. package/templates/creator/app/login/page.tsx +14 -14
  134. package/templates/creator/app.ts +4 -4
  135. package/templates/creator/functions/seedDemo.ts +32 -0
  136. package/templates/creator/functions/subscribe.ts +4 -15
  137. package/templates/creator/lib/demo.ts +94 -0
  138. package/templates/creator/lib/site.config.ts +15 -10
  139. package/templates/creator/lib/stats.ts +35 -0
  140. package/templates/creator/public/favicon.ico +0 -0
  141. package/templates/creator/tests/counter.test.ts +92 -0
  142. package/templates/creator/tests/live-count.test.tsx +45 -0
  143. package/templates/crm/AGENTS.md +1 -1
  144. package/templates/crm/README.md +22 -1
  145. package/templates/crm/app/apple-icon.png +0 -0
  146. package/templates/crm/app/companies/companies-view.tsx +32 -14
  147. package/templates/crm/app/companies/page.tsx +2 -1
  148. package/templates/crm/app/contacts/contacts-view.tsx +34 -10
  149. package/templates/crm/app/contacts/page.tsx +2 -1
  150. package/templates/crm/app/deals/[id]/deal-view.tsx +137 -98
  151. package/templates/crm/app/deals/[id]/page.tsx +2 -1
  152. package/templates/crm/app/globals.css +29 -14
  153. package/templates/crm/app/icon.svg +1 -0
  154. package/templates/crm/app/layout.tsx +7 -5
  155. package/templates/crm/app/page.tsx +2 -1
  156. package/templates/crm/app/pipeline-view.tsx +5 -5
  157. package/templates/crm/app/workspace.tsx +29 -17
  158. package/templates/crm/app.ts +5 -5
  159. package/templates/crm/components/activity-timeline.tsx +14 -4
  160. package/templates/crm/components/app-shell.tsx +94 -0
  161. package/templates/crm/components/auth-form.tsx +35 -5
  162. package/templates/crm/components/avatar.tsx +5 -1
  163. package/templates/crm/components/board-skeleton.tsx +5 -5
  164. package/templates/crm/components/brand-mark.tsx +25 -0
  165. package/templates/crm/components/command-palette.tsx +17 -17
  166. package/templates/crm/components/data-table.tsx +97 -36
  167. package/templates/crm/components/deal-card.tsx +23 -27
  168. package/templates/crm/components/filter-tabs.tsx +101 -0
  169. package/templates/crm/components/metrics-bar.tsx +6 -6
  170. package/templates/crm/components/nav.tsx +15 -0
  171. package/templates/crm/components/page-header.tsx +43 -7
  172. package/templates/crm/components/pipeline-board.tsx +16 -10
  173. package/templates/crm/components/require-auth.tsx +24 -26
  174. package/templates/crm/components/sidebar.tsx +54 -49
  175. package/templates/crm/components/stage-badge.tsx +4 -2
  176. package/templates/crm/components/ui/button.tsx +5 -4
  177. package/templates/crm/components/ui/dialog.tsx +1 -1
  178. package/templates/crm/components/ui/input.tsx +1 -1
  179. package/templates/crm/components/ui/select.tsx +1 -1
  180. package/templates/crm/components/ui/textarea.tsx +1 -1
  181. package/templates/crm/functions/seedWorkspace.ts +32 -17
  182. package/templates/crm/lib/brand.ts +9 -0
  183. package/templates/crm/lib/demo-team.ts +51 -0
  184. package/templates/crm/lib/pipeline.ts +37 -0
  185. package/templates/crm/lib/search.ts +4 -3
  186. package/templates/crm/lib/seed.ts +53 -8
  187. package/templates/crm/public/favicon.ico +0 -0
  188. package/templates/crm/tests/components.test.tsx +5 -5
  189. package/templates/crm/tests/demo-team.test.ts +18 -0
  190. package/templates/crm/tests/pipeline.test.ts +59 -3
  191. package/templates/directory/.env.example +7 -0
  192. package/templates/directory/AGENTS.md +1 -1
  193. package/templates/directory/README.md +22 -3
  194. package/templates/directory/app/(marketing)/directory-browse.tsx +30 -12
  195. package/templates/directory/app/(marketing)/page.tsx +6 -4
  196. package/templates/directory/app/auth-form.tsx +4 -4
  197. package/templates/directory/app/dashboard/dashboard-client.tsx +177 -90
  198. package/templates/directory/app/dashboard/page.tsx +16 -14
  199. package/templates/directory/app/icon.svg +4 -0
  200. package/templates/directory/app/login/page.tsx +16 -15
  201. package/templates/directory/functions/seedDemo.ts +43 -0
  202. package/templates/directory/lib/demo.ts +145 -0
  203. package/templates/directory/lib/directory.ts +27 -0
  204. package/templates/directory/lib/site.config.ts +21 -16
  205. package/templates/directory/public/favicon.ico +0 -0
  206. package/templates/directory/tests/directory.test.ts +46 -0
  207. package/templates/expo/consumer/apps/expo/App.tsx +360 -288
  208. package/templates/expo/consumer/apps/expo/app.json +9 -1
  209. package/templates/expo/consumer/apps/expo/package.json +2 -1
  210. package/templates/helpdesk/AGENTS.md +1 -1
  211. package/templates/helpdesk/README.md +21 -0
  212. package/templates/helpdesk/app/apple-icon.png +0 -0
  213. package/templates/helpdesk/app/customers/customers-view.tsx +23 -7
  214. package/templates/helpdesk/app/customers/page.tsx +2 -1
  215. package/templates/helpdesk/app/globals.css +29 -14
  216. package/templates/helpdesk/app/icon.svg +1 -0
  217. package/templates/helpdesk/app/inbox-view.tsx +66 -47
  218. package/templates/helpdesk/app/layout.tsx +7 -5
  219. package/templates/helpdesk/app/page.tsx +4 -7
  220. package/templates/helpdesk/app/tickets/[id]/page.tsx +2 -1
  221. package/templates/helpdesk/app/tickets/[id]/ticket-view.tsx +134 -97
  222. package/templates/helpdesk/app/workspace.tsx +33 -22
  223. package/templates/helpdesk/app.ts +2 -2
  224. package/templates/helpdesk/components/app-shell.tsx +94 -0
  225. package/templates/helpdesk/components/auth-form.tsx +35 -5
  226. package/templates/helpdesk/components/avatar.tsx +5 -1
  227. package/templates/helpdesk/components/brand-mark.tsx +28 -0
  228. package/templates/helpdesk/components/command-palette.tsx +17 -17
  229. package/templates/helpdesk/components/data-table.tsx +97 -36
  230. package/templates/helpdesk/components/filter-tabs.tsx +101 -0
  231. package/templates/helpdesk/components/nav.tsx +14 -0
  232. package/templates/helpdesk/components/page-header.tsx +43 -7
  233. package/templates/helpdesk/components/priority-badge.tsx +49 -6
  234. package/templates/helpdesk/components/require-auth.tsx +24 -26
  235. package/templates/helpdesk/components/sidebar.tsx +54 -48
  236. package/templates/helpdesk/components/sla-indicator.tsx +5 -8
  237. package/templates/helpdesk/components/thread.tsx +7 -7
  238. package/templates/helpdesk/components/ticket-list.tsx +79 -19
  239. package/templates/helpdesk/components/ui/button.tsx +5 -4
  240. package/templates/helpdesk/components/ui/dialog.tsx +1 -1
  241. package/templates/helpdesk/components/ui/input.tsx +1 -1
  242. package/templates/helpdesk/components/ui/select.tsx +1 -1
  243. package/templates/helpdesk/components/ui/textarea.tsx +1 -1
  244. package/templates/helpdesk/functions/seedWorkspace.ts +25 -15
  245. package/templates/helpdesk/lib/brand.ts +9 -0
  246. package/templates/helpdesk/lib/demo-team.ts +51 -0
  247. package/templates/helpdesk/lib/search.ts +4 -3
  248. package/templates/helpdesk/lib/seed.ts +43 -2
  249. package/templates/helpdesk/lib/tickets.ts +68 -20
  250. package/templates/helpdesk/public/favicon.ico +0 -0
  251. package/templates/helpdesk/tests/components.test.tsx +10 -0
  252. package/templates/helpdesk/tests/demo-team.test.ts +18 -0
  253. package/templates/helpdesk/tests/tickets.test.ts +70 -0
  254. package/templates/imessage/.env.example +49 -0
  255. package/templates/imessage/AGENTS.md +153 -0
  256. package/templates/imessage/CLAUDE.md +7 -0
  257. package/templates/imessage/README.md +187 -0
  258. package/templates/imessage/app/console.tsx +382 -0
  259. package/templates/imessage/app/contacts/page.tsx +13 -0
  260. package/templates/imessage/app/error.tsx +16 -0
  261. package/templates/imessage/app/gate.tsx +31 -0
  262. package/templates/imessage/app/globals.css +183 -0
  263. package/templates/imessage/app/layout.tsx +17 -0
  264. package/templates/imessage/app/login/login-form.tsx +115 -0
  265. package/templates/imessage/app/login/page.tsx +30 -0
  266. package/templates/imessage/app/not-found.tsx +13 -0
  267. package/templates/imessage/app/page.tsx +14 -0
  268. package/templates/imessage/app/robots.ts +6 -0
  269. package/templates/imessage/app/setup/page.tsx +13 -0
  270. package/templates/imessage/app/sign-out.tsx +20 -0
  271. package/templates/imessage/app.ts +299 -0
  272. package/templates/imessage/bunfig +4 -0
  273. package/templates/imessage/components/avatar.tsx +46 -0
  274. package/templates/imessage/components/contact-panel.tsx +263 -0
  275. package/templates/imessage/components/contacts-view.tsx +172 -0
  276. package/templates/imessage/components/conversation-list.tsx +129 -0
  277. package/templates/imessage/components/nav-rail.tsx +67 -0
  278. package/templates/imessage/components/setup-view.tsx +408 -0
  279. package/templates/imessage/components/thread.tsx +229 -0
  280. package/templates/imessage/components/ui/badge.tsx +37 -0
  281. package/templates/imessage/components/ui/button.tsx +56 -0
  282. package/templates/imessage/components/ui/card.tsx +90 -0
  283. package/templates/imessage/components/ui/dialog.tsx +110 -0
  284. package/templates/imessage/components/ui/input.tsx +30 -0
  285. package/templates/imessage/components/ui/label.tsx +21 -0
  286. package/templates/imessage/components/ui/select.tsx +39 -0
  287. package/templates/imessage/components/ui/table.tsx +124 -0
  288. package/templates/imessage/components/ui/textarea.tsx +24 -0
  289. package/templates/imessage/components.json +20 -0
  290. package/templates/imessage/functions/answerNow.ts +34 -0
  291. package/templates/imessage/functions/assistant.ts +96 -0
  292. package/templates/imessage/functions/beginTurn.ts +101 -0
  293. package/templates/imessage/functions/cancelReminder.ts +13 -0
  294. package/templates/imessage/functions/claimOutbound.ts +44 -0
  295. package/templates/imessage/functions/clearDemo.ts +18 -0
  296. package/templates/imessage/functions/deleteNote.ts +11 -0
  297. package/templates/imessage/functions/deliverOutbound.ts +55 -0
  298. package/templates/imessage/functions/finishTurn.ts +84 -0
  299. package/templates/imessage/functions/fireReminder.ts +42 -0
  300. package/templates/imessage/functions/ingestInbound.ts +22 -0
  301. package/templates/imessage/functions/ownerAccount.ts +13 -0
  302. package/templates/imessage/functions/processTurn.ts +74 -0
  303. package/templates/imessage/functions/relayApply.ts +102 -0
  304. package/templates/imessage/functions/relaySync.ts +63 -0
  305. package/templates/imessage/functions/releaseOutbound.ts +18 -0
  306. package/templates/imessage/functions/resolveTurn.ts +25 -0
  307. package/templates/imessage/functions/saveContact.ts +27 -0
  308. package/templates/imessage/functions/seedDemo.ts +70 -0
  309. package/templates/imessage/functions/sendOwnerMessage.ts +28 -0
  310. package/templates/imessage/functions/sendblueWebhook.ts +49 -0
  311. package/templates/imessage/functions/setContactStatus.ts +17 -0
  312. package/templates/imessage/functions/settleOutbound.ts +35 -0
  313. package/templates/imessage/functions/setupStatus.ts +33 -0
  314. package/templates/imessage/functions/sweep.ts +58 -0
  315. package/templates/imessage/functions/toolCancelReminder.ts +17 -0
  316. package/templates/imessage/functions/toolForgetNote.ts +15 -0
  317. package/templates/imessage/functions/toolListNotes.ts +16 -0
  318. package/templates/imessage/functions/toolListReminders.ts +17 -0
  319. package/templates/imessage/functions/toolSaveNote.ts +22 -0
  320. package/templates/imessage/functions/toolSetReminder.ts +55 -0
  321. package/templates/imessage/functions/updateSettings.ts +48 -0
  322. package/templates/imessage/gitignore +11 -0
  323. package/templates/imessage/lib/agent-call.ts +251 -0
  324. package/templates/imessage/lib/assistant.ts +74 -0
  325. package/templates/imessage/lib/chunk.ts +135 -0
  326. package/templates/imessage/lib/demo.ts +68 -0
  327. package/templates/imessage/lib/format.ts +170 -0
  328. package/templates/imessage/lib/gate.ts +104 -0
  329. package/templates/imessage/lib/handles.ts +67 -0
  330. package/templates/imessage/lib/provider.ts +38 -0
  331. package/templates/imessage/lib/relay-protocol.ts +95 -0
  332. package/templates/imessage/lib/reminders.ts +41 -0
  333. package/templates/imessage/lib/secrets.ts +59 -0
  334. package/templates/imessage/lib/sendblue.ts +173 -0
  335. package/templates/imessage/lib/store.ts +407 -0
  336. package/templates/imessage/lib/transport.ts +135 -0
  337. package/templates/imessage/lib/utils.ts +10 -0
  338. package/templates/imessage/package.json +40 -0
  339. package/templates/imessage/relay/.env.example +14 -0
  340. package/templates/imessage/relay/applescript.ts +70 -0
  341. package/templates/imessage/relay/chatdb.ts +165 -0
  342. package/templates/imessage/relay/client.ts +41 -0
  343. package/templates/imessage/relay/main.ts +137 -0
  344. package/templates/imessage/relay/state.ts +35 -0
  345. package/templates/imessage/tests/agent-call.test.ts +112 -0
  346. package/templates/imessage/tests/applescript.test.ts +88 -0
  347. package/templates/imessage/tests/chatdb.test.ts +139 -0
  348. package/templates/imessage/tests/chunk.test.ts +61 -0
  349. package/templates/imessage/tests/fake-ctx.ts +88 -0
  350. package/templates/imessage/tests/ingest.test.ts +141 -0
  351. package/templates/imessage/tests/lib.test.ts +99 -0
  352. package/templates/imessage/tests/review-fixes.test.ts +138 -0
  353. package/templates/imessage/tests/secrets.test.ts +36 -0
  354. package/templates/imessage/tests/sendblue.test.ts +143 -0
  355. package/templates/imessage/tests/setup.ts +5 -0
  356. package/templates/imessage/tsconfig.json +18 -0
  357. package/templates/inventory/AGENTS.md +1 -1
  358. package/templates/inventory/README.md +21 -0
  359. package/templates/inventory/app/apple-icon.png +0 -0
  360. package/templates/inventory/app/globals.css +29 -14
  361. package/templates/inventory/app/icon.svg +1 -0
  362. package/templates/inventory/app/layout.tsx +7 -5
  363. package/templates/inventory/app/movements/movements-view.tsx +32 -10
  364. package/templates/inventory/app/movements/page.tsx +2 -1
  365. package/templates/inventory/app/page.tsx +4 -6
  366. package/templates/inventory/app/products/[id]/page.tsx +2 -1
  367. package/templates/inventory/app/products/[id]/product-view.tsx +55 -25
  368. package/templates/inventory/app/products-view.tsx +74 -33
  369. package/templates/inventory/app/workspace.tsx +34 -16
  370. package/templates/inventory/app.ts +10 -2
  371. package/templates/inventory/components/app-shell.tsx +94 -0
  372. package/templates/inventory/components/auth-form.tsx +35 -5
  373. package/templates/inventory/components/avatar.tsx +5 -1
  374. package/templates/inventory/components/brand-mark.tsx +25 -0
  375. package/templates/inventory/components/category-tile.tsx +49 -0
  376. package/templates/inventory/components/command-palette.tsx +17 -17
  377. package/templates/inventory/components/data-table.tsx +97 -36
  378. package/templates/inventory/components/filter-tabs.tsx +101 -0
  379. package/templates/inventory/components/nav.tsx +14 -0
  380. package/templates/inventory/components/page-header.tsx +43 -7
  381. package/templates/inventory/components/require-auth.tsx +24 -26
  382. package/templates/inventory/components/sidebar.tsx +54 -48
  383. package/templates/inventory/components/stock-level.tsx +30 -14
  384. package/templates/inventory/components/summary-bar.tsx +8 -15
  385. package/templates/inventory/components/ui/button.tsx +5 -4
  386. package/templates/inventory/components/ui/dialog.tsx +1 -1
  387. package/templates/inventory/components/ui/input.tsx +1 -1
  388. package/templates/inventory/components/ui/select.tsx +1 -1
  389. package/templates/inventory/components/ui/textarea.tsx +1 -1
  390. package/templates/inventory/functions/seedWorkspace.ts +18 -8
  391. package/templates/inventory/lib/brand.ts +9 -0
  392. package/templates/inventory/lib/demo-team.ts +51 -0
  393. package/templates/inventory/lib/search.ts +4 -3
  394. package/templates/inventory/lib/seed.ts +29 -2
  395. package/templates/inventory/lib/stock.ts +32 -0
  396. package/templates/inventory/public/favicon.ico +0 -0
  397. package/templates/inventory/tests/demo-team.test.ts +18 -0
  398. package/templates/inventory/tests/stock.test.ts +29 -0
  399. package/templates/invoices/AGENTS.md +1 -1
  400. package/templates/invoices/README.md +16 -0
  401. package/templates/invoices/app/apple-icon.png +0 -0
  402. package/templates/invoices/app/clients/clients-view.tsx +18 -6
  403. package/templates/invoices/app/clients/page.tsx +2 -1
  404. package/templates/invoices/app/globals.css +29 -14
  405. package/templates/invoices/app/icon.svg +1 -0
  406. package/templates/invoices/app/invoices/[id]/invoice-view.tsx +72 -48
  407. package/templates/invoices/app/invoices/[id]/page.tsx +2 -1
  408. package/templates/invoices/app/invoices-view.tsx +81 -32
  409. package/templates/invoices/app/layout.tsx +7 -5
  410. package/templates/invoices/app/page.tsx +4 -6
  411. package/templates/invoices/app/workspace.tsx +26 -16
  412. package/templates/invoices/app.ts +2 -2
  413. package/templates/invoices/components/app-shell.tsx +94 -0
  414. package/templates/invoices/components/auth-form.tsx +35 -5
  415. package/templates/invoices/components/avatar.tsx +5 -1
  416. package/templates/invoices/components/brand-mark.tsx +26 -0
  417. package/templates/invoices/components/command-palette.tsx +17 -17
  418. package/templates/invoices/components/data-table.tsx +97 -36
  419. package/templates/invoices/components/filter-tabs.tsx +101 -0
  420. package/templates/invoices/components/line-items.tsx +19 -14
  421. package/templates/invoices/components/nav.tsx +14 -0
  422. package/templates/invoices/components/page-header.tsx +43 -7
  423. package/templates/invoices/components/require-auth.tsx +24 -26
  424. package/templates/invoices/components/sidebar.tsx +54 -48
  425. package/templates/invoices/components/status-badge.tsx +7 -7
  426. package/templates/invoices/components/summary-bar.tsx +5 -5
  427. package/templates/invoices/components/ui/button.tsx +5 -4
  428. package/templates/invoices/components/ui/dialog.tsx +1 -1
  429. package/templates/invoices/components/ui/input.tsx +1 -1
  430. package/templates/invoices/components/ui/select.tsx +1 -1
  431. package/templates/invoices/components/ui/textarea.tsx +1 -1
  432. package/templates/invoices/functions/recordPayment.ts +3 -0
  433. package/templates/invoices/lib/billing.ts +40 -0
  434. package/templates/invoices/lib/brand.ts +9 -0
  435. package/templates/invoices/lib/search.ts +4 -3
  436. package/templates/invoices/public/favicon.ico +0 -0
  437. package/templates/invoices/tests/billing.test.ts +27 -0
  438. package/templates/ios/consumer/apps/ios/Package.swift +4 -3
  439. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ActivityView.swift +107 -0
  440. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/AppModel.swift +171 -0
  441. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/AuthView.swift +182 -0
  442. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/CommentsView.swift +136 -0
  443. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ComposeView.swift +152 -0
  444. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ExploreView.swift +117 -0
  445. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/FeedView.swift +175 -159
  446. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/MainTabView.swift +54 -0
  447. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Media.swift +271 -0
  448. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Models.swift +101 -4
  449. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/PostCard.swift +245 -0
  450. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileEditorView.swift +208 -0
  451. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileView.swift +183 -0
  452. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/RootView.swift +25 -31
  453. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/SocialStore.swift +285 -0
  454. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/StoryViewer.swift +155 -0
  455. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Theme.swift +82 -0
  456. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/__APP_NAME_PASCAL__App.swift +4 -47
  457. package/templates/ios/consumer/apps/ios/project.yml +13 -9
  458. package/templates/local-service/.env.example +7 -0
  459. package/templates/local-service/AGENTS.md +1 -1
  460. package/templates/local-service/README.md +26 -2
  461. package/templates/local-service/app/(marketing)/booking-widget.tsx +48 -9
  462. package/templates/local-service/app/(marketing)/page.tsx +66 -42
  463. package/templates/local-service/app/auth-form.tsx +8 -8
  464. package/templates/local-service/app/dashboard/dashboard-client.tsx +119 -92
  465. package/templates/local-service/app/dashboard/page.tsx +14 -14
  466. package/templates/local-service/app/icon.svg +2 -0
  467. package/templates/local-service/app/login/page.tsx +15 -15
  468. package/templates/local-service/functions/seedDemo.ts +49 -0
  469. package/templates/local-service/lib/demo.ts +135 -0
  470. package/templates/local-service/lib/hours.ts +44 -0
  471. package/templates/local-service/lib/site.config.ts +8 -3
  472. package/templates/local-service/lib/slots.ts +3 -0
  473. package/templates/local-service/public/favicon.ico +0 -0
  474. package/templates/local-service/tests/booking.test.ts +104 -0
  475. package/templates/marketplace/AGENTS.md +1 -1
  476. package/templates/marketplace/README.md +23 -15
  477. package/templates/marketplace/app/globals.css +172 -114
  478. package/templates/marketplace/app/layout.tsx +75 -30
  479. package/templates/marketplace/app/listing/[id]/page.tsx +210 -123
  480. package/templates/marketplace/app/me/page.tsx +4 -4
  481. package/templates/marketplace/app/page.tsx +221 -298
  482. package/templates/marketplace/app/sell/page.tsx +8 -11
  483. package/templates/marketplace/app.ts +32 -7
  484. package/templates/marketplace/client/ActivityFeed.tsx +311 -0
  485. package/templates/marketplace/client/AuthNav.tsx +24 -16
  486. package/templates/marketplace/client/ListingCard.tsx +118 -0
  487. package/templates/marketplace/client/LiveGrid.tsx +92 -0
  488. package/templates/marketplace/client/LiveListingStatus.tsx +32 -24
  489. package/templates/marketplace/client/LoginCard.tsx +114 -136
  490. package/templates/marketplace/client/MarketProvider.tsx +3 -5
  491. package/templates/marketplace/client/MyMarket.tsx +371 -185
  492. package/templates/marketplace/client/OfferPanel.tsx +421 -287
  493. package/templates/marketplace/client/SellForm.tsx +271 -192
  494. package/templates/marketplace/client/ThemeToggle.tsx +1 -0
  495. package/templates/marketplace/client/WatchButton.tsx +16 -16
  496. package/templates/marketplace/client/market.ts +61 -14
  497. package/templates/marketplace/functions/seedMarket.ts +143 -36
  498. package/templates/marketplace/functions/seedOffers.ts +101 -0
  499. package/templates/marketplace/lib/catalog.ts +72 -1
  500. package/templates/marketplace/public/images/listings/ceramic-lamp-640.webp +0 -0
  501. package/templates/marketplace/public/images/listings/ceramic-lamp.webp +0 -0
  502. package/templates/marketplace/public/images/listings/duffel-640.webp +0 -0
  503. package/templates/marketplace/public/images/listings/duffel.webp +0 -0
  504. package/templates/marketplace/public/images/listings/film-camera-640.webp +0 -0
  505. package/templates/marketplace/public/images/listings/film-camera.webp +0 -0
  506. package/templates/marketplace/public/images/listings/lounge-chair-640.webp +0 -0
  507. package/templates/marketplace/public/images/listings/lounge-chair.webp +0 -0
  508. package/templates/marketplace/public/images/listings/road-bike-640.webp +0 -0
  509. package/templates/marketplace/public/images/listings/road-bike.webp +0 -0
  510. package/templates/marketplace/public/images/listings/slipper-chair-640.webp +0 -0
  511. package/templates/marketplace/public/images/listings/slipper-chair.webp +0 -0
  512. package/templates/marketplace/public/images/listings/turntable-640.webp +0 -0
  513. package/templates/marketplace/public/images/listings/turntable.webp +0 -0
  514. package/templates/marketplace/public/images/listings/wardrobe-640.webp +0 -0
  515. package/templates/marketplace/public/images/listings/wardrobe.webp +0 -0
  516. package/templates/marketplace/public/images/listings/watch-640.webp +0 -0
  517. package/templates/marketplace/public/images/listings/watch.webp +0 -0
  518. package/templates/marketplace/public/images/listings/weekender-bag-640.webp +0 -0
  519. package/templates/marketplace/public/images/listings/weekender-bag.webp +0 -0
  520. package/templates/marketplace/tests/example.test.ts +64 -2
  521. package/templates/marketplace/tests/listing-card.test.tsx +61 -0
  522. package/templates/projects/AGENTS.md +1 -1
  523. package/templates/projects/README.md +24 -3
  524. package/templates/projects/app/apple-icon.png +0 -0
  525. package/templates/projects/app/clients/clients-view.tsx +20 -6
  526. package/templates/projects/app/clients/page.tsx +2 -1
  527. package/templates/projects/app/globals.css +29 -14
  528. package/templates/projects/app/icon.svg +1 -0
  529. package/templates/projects/app/layout.tsx +7 -5
  530. package/templates/projects/app/page.tsx +3 -5
  531. package/templates/projects/app/projects/[id]/page.tsx +2 -1
  532. package/templates/projects/app/projects/[id]/project-view.tsx +45 -30
  533. package/templates/projects/app/projects-view.tsx +105 -52
  534. package/templates/projects/app/workspace.tsx +41 -20
  535. package/templates/projects/app.ts +2 -2
  536. package/templates/projects/components/app-shell.tsx +94 -0
  537. package/templates/projects/components/auth-form.tsx +35 -5
  538. package/templates/projects/components/avatar.tsx +5 -1
  539. package/templates/projects/components/board-skeleton.tsx +3 -3
  540. package/templates/projects/components/brand-mark.tsx +25 -0
  541. package/templates/projects/components/command-palette.tsx +17 -17
  542. package/templates/projects/components/data-table.tsx +97 -36
  543. package/templates/projects/components/filter-tabs.tsx +101 -0
  544. package/templates/projects/components/meter.tsx +48 -0
  545. package/templates/projects/components/nav.tsx +14 -0
  546. package/templates/projects/components/page-header.tsx +43 -7
  547. package/templates/projects/components/require-auth.tsx +24 -26
  548. package/templates/projects/components/sidebar.tsx +54 -48
  549. package/templates/projects/components/summary-bar.tsx +49 -0
  550. package/templates/projects/components/task-board.tsx +10 -9
  551. package/templates/projects/components/time-dialog.tsx +29 -5
  552. package/templates/projects/components/ui/button.tsx +5 -4
  553. package/templates/projects/components/ui/dialog.tsx +1 -1
  554. package/templates/projects/components/ui/input.tsx +1 -1
  555. package/templates/projects/components/ui/select.tsx +1 -1
  556. package/templates/projects/components/ui/textarea.tsx +1 -1
  557. package/templates/projects/functions/seedWorkspace.ts +26 -12
  558. package/templates/projects/lib/brand.ts +9 -0
  559. package/templates/projects/lib/demo-team.ts +51 -0
  560. package/templates/projects/lib/search.ts +4 -3
  561. package/templates/projects/lib/seed.ts +35 -1
  562. package/templates/projects/lib/work.ts +63 -0
  563. package/templates/projects/public/favicon.ico +0 -0
  564. package/templates/projects/tests/demo-team.test.ts +18 -0
  565. package/templates/projects/tests/work.test.ts +65 -0
  566. package/templates/restaurant/.env.example +7 -0
  567. package/templates/restaurant/AGENTS.md +1 -1
  568. package/templates/restaurant/README.md +29 -3
  569. package/templates/restaurant/app/(marketing)/layout.tsx +4 -2
  570. package/templates/restaurant/app/(marketing)/page.tsx +47 -23
  571. package/templates/restaurant/app/(marketing)/reservation-widget.tsx +37 -7
  572. package/templates/restaurant/app/auth-form.tsx +8 -8
  573. package/templates/restaurant/app/dashboard/dashboard-client.tsx +241 -109
  574. package/templates/restaurant/app/dashboard/page.tsx +14 -14
  575. package/templates/restaurant/app/icon.svg +4 -0
  576. package/templates/restaurant/app/login/page.tsx +15 -15
  577. package/templates/restaurant/functions/seedDemo.ts +44 -0
  578. package/templates/restaurant/lib/demo.ts +144 -0
  579. package/templates/restaurant/lib/hours.ts +45 -0
  580. package/templates/restaurant/lib/site.config.ts +3 -3
  581. package/templates/restaurant/public/favicon.ico +0 -0
  582. package/templates/restaurant/tests/reservations.test.ts +67 -0
  583. package/templates/saas/AGENTS.md +1 -1
  584. package/templates/saas/README.md +54 -35
  585. package/templates/saas/app/(auth)/auth-form.tsx +25 -23
  586. package/templates/saas/app/(auth)/forgot-password/page.tsx +1 -1
  587. package/templates/saas/app/(auth)/layout.tsx +27 -33
  588. package/templates/saas/app/(auth)/login/page.tsx +1 -1
  589. package/templates/saas/app/(auth)/reset-password/page.tsx +1 -1
  590. package/templates/saas/app/(auth)/signup/page.tsx +1 -1
  591. package/templates/saas/app/(marketing)/layout.tsx +127 -253
  592. package/templates/saas/app/(marketing)/page.tsx +133 -372
  593. package/templates/saas/app/(marketing)/pricing/page.tsx +21 -19
  594. package/templates/saas/app/(marketing)/products/[slug]/page.tsx +40 -95
  595. package/templates/saas/app/dashboard/board-client.tsx +416 -0
  596. package/templates/saas/app/dashboard/dashboard-client.tsx +30 -615
  597. package/templates/saas/app/dashboard/layout.tsx +27 -13
  598. package/templates/saas/app/dashboard/overview-client.tsx +332 -0
  599. package/templates/saas/app/dashboard/page.tsx +29 -35
  600. package/templates/saas/app/dashboard/projects/[id]/page.tsx +29 -0
  601. package/templates/saas/app/dashboard/projects/page.tsx +11 -7
  602. package/templates/saas/app/dashboard/projects-client.tsx +398 -0
  603. package/templates/saas/app/globals.css +5 -4
  604. package/templates/saas/app/icon.svg +1 -0
  605. package/templates/saas/app/onboarding/onboarding-client.tsx +93 -26
  606. package/templates/saas/app/onboarding/page.tsx +6 -2
  607. package/templates/saas/app.ts +68 -13
  608. package/templates/saas/components/dashboard-shell.tsx +212 -132
  609. package/templates/saas/components/marketing.tsx +137 -274
  610. package/templates/saas/components/pricing-table.tsx +12 -11
  611. package/templates/saas/components/setup-checklist.tsx +31 -29
  612. package/templates/saas/components/task-ui.tsx +199 -0
  613. package/templates/saas/functions/createProject.ts +7 -13
  614. package/templates/saas/functions/createTask.ts +67 -0
  615. package/templates/saas/functions/deleteProject.ts +17 -0
  616. package/templates/saas/functions/deleteTask.ts +14 -0
  617. package/templates/saas/functions/seedWorkspace.ts +46 -0
  618. package/templates/saas/functions/setProjectStatus.ts +41 -0
  619. package/templates/saas/functions/updateProfile.ts +13 -0
  620. package/templates/saas/functions/updateProject.ts +4 -5
  621. package/templates/saas/functions/updateTask.ts +88 -0
  622. package/templates/saas/lib/members.ts +53 -0
  623. package/templates/saas/lib/names.ts +53 -0
  624. package/templates/saas/lib/plans.ts +11 -0
  625. package/templates/saas/lib/sample-workspace.ts +117 -0
  626. package/templates/saas/lib/site.config.ts +200 -463
  627. package/templates/saas/lib/tasks.ts +101 -0
  628. package/templates/saas/lib/use-live.ts +25 -0
  629. package/templates/saas/public/favicon.ico +0 -0
  630. package/templates/saas/public/screenshots/board.png +0 -0
  631. package/templates/saas/public/screenshots/members.png +0 -0
  632. package/templates/saas/public/screenshots/overview.png +0 -0
  633. package/templates/saas/public/screenshots/projects.png +0 -0
  634. package/templates/saas/scripts/capture-screenshots.mjs +156 -0
  635. package/templates/saas/tests/names.test.ts +35 -0
  636. package/templates/saas/tests/plans.test.ts +12 -0
  637. package/templates/saas/tests/sample-workspace.test.ts +46 -0
  638. package/templates/saas/tests/tasks.test.ts +81 -0
  639. package/templates/shop/AGENTS.md +1 -1
  640. package/templates/shop/README.md +25 -13
  641. package/templates/shop/app/(marketing)/layout.tsx +94 -67
  642. package/templates/shop/app/(marketing)/not-found.tsx +13 -12
  643. package/templates/shop/app/(marketing)/page.tsx +120 -59
  644. package/templates/shop/app/(marketing)/shop-client.tsx +364 -237
  645. package/templates/shop/app/(marketing)/success/page.tsx +75 -22
  646. package/templates/shop/app/globals.css +94 -1
  647. package/templates/shop/app/layout.tsx +4 -4
  648. package/templates/shop/app/login/page.tsx +4 -4
  649. package/templates/shop/app.ts +18 -6
  650. package/templates/shop/components/cart-button.tsx +33 -0
  651. package/templates/shop/components/marketing.tsx +17 -87
  652. package/templates/shop/components/shelf-count.tsx +36 -0
  653. package/templates/shop/functions/checkout.ts +17 -1
  654. package/templates/shop/lib/cart-store.ts +57 -0
  655. package/templates/shop/lib/shelf-store.ts +35 -0
  656. package/templates/shop/lib/shop.ts +27 -0
  657. package/templates/shop/lib/site.config.ts +108 -98
  658. package/templates/shop/public/images/hero.jpg +0 -0
  659. package/templates/shop/public/images/products/cedar-smoke.jpg +0 -0
  660. package/templates/shop/public/images/products/fig-leaf.jpg +0 -0
  661. package/templates/shop/public/images/products/linen.jpg +0 -0
  662. package/templates/shop/public/images/products/sea-salt.jpg +0 -0
  663. package/templates/shop/public/images/products/tobacco-oak.jpg +0 -0
  664. package/templates/shop/tests/shop.test.ts +64 -0
  665. package/templates/todo/AGENTS.md +1 -1
  666. package/templates/waitlist/.env.example +7 -0
  667. package/templates/waitlist/AGENTS.md +1 -1
  668. package/templates/waitlist/README.md +29 -13
  669. package/templates/waitlist/app/(marketing)/page.tsx +9 -7
  670. package/templates/waitlist/app/(marketing)/waitlist-hero.tsx +64 -31
  671. package/templates/waitlist/app/auth-form.tsx +8 -8
  672. package/templates/waitlist/app/dashboard/dashboard-client.tsx +132 -95
  673. package/templates/waitlist/app/dashboard/page.tsx +17 -16
  674. package/templates/waitlist/app/icon.svg +2 -0
  675. package/templates/waitlist/app/login/page.tsx +14 -14
  676. package/templates/waitlist/app.ts +6 -6
  677. package/templates/waitlist/functions/joinWaitlist.ts +6 -18
  678. package/templates/waitlist/functions/seedDemo.ts +32 -0
  679. package/templates/waitlist/lib/demo.ts +94 -0
  680. package/templates/waitlist/lib/launch.ts +19 -0
  681. package/templates/waitlist/lib/site.config.ts +14 -9
  682. package/templates/waitlist/lib/stats.ts +35 -0
  683. package/templates/waitlist/public/favicon.ico +0 -0
  684. package/templates/waitlist/tests/counter.test.ts +107 -0
  685. package/templates/waitlist/tests/live-count.test.tsx +48 -0
  686. package/templates/ai-chat/tests/example.test.ts +0 -12
  687. package/templates/backend/consumer/apps/api/functions/feed.ts +0 -57
  688. package/templates/backend/consumer/apps/api/functions/profilePosts.ts +0 -17
  689. package/templates/backend/consumer/apps/api/functions/toggleLike.ts +0 -48
  690. package/templates/backend/consumer/apps/api/schema.ts +0 -143
  691. package/templates/chat/functions/_keep.ts +0 -13
  692. package/templates/chat/tests/example.test.ts +0 -12
  693. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileSetupView.swift +0 -68
  694. package/templates/marketplace/client/LiveTicker.tsx +0 -116
  695. package/templates/marketplace/client/ScrollToListingsLink.tsx +0 -29
  696. package/templates/marketplace/public/images/marketplace-hero.webp +0 -0
@@ -0,0 +1,285 @@
1
+ import Combine
2
+ import Foundation
3
+ import PylonClient
4
+ import PylonSync
5
+ import PylonSwiftUI
6
+
7
+ /// The live social graph. Five subscriptions (Profile, Post, Like, Comment,
8
+ /// Follow) on the sync engine, joined in memory. A row written by any
9
+ /// client reaches every open app over the engine's WebSocket, and every
10
+ /// view that reads this store redraws.
11
+ ///
12
+ /// Writes go through server functions. The server writes the row, the
13
+ /// change comes back through sync, and the view updates. Likes and follows
14
+ /// also show at once through a pending state that clears when the synced
15
+ /// row arrives.
16
+ @MainActor
17
+ final class SocialStore: ObservableObject {
18
+ let engine: SyncEngine
19
+ let client: PylonClient
20
+ let media: MediaService
21
+
22
+ @Published var myProfileId: String?
23
+ @Published private(set) var profiles: [String: Profile] = [:]
24
+ /// Newest first.
25
+ @Published private(set) var posts: [Post] = []
26
+ @Published private(set) var likesByPost: [String: [Like]] = [:]
27
+ /// Oldest first within a post.
28
+ @Published private(set) var commentsByPost: [String: [Comment]] = [:]
29
+ @Published private(set) var follows: [Follow] = []
30
+ @Published private(set) var isLoading = true
31
+ /// Post ids the user liked or unliked whose synced row has not arrived yet.
32
+ @Published private(set) var pendingLikes: [String: Bool] = [:]
33
+ @Published private(set) var pendingFollows: [String: Bool] = [:]
34
+
35
+ private let profileQuery: PylonQuery<Profile>
36
+ private let postQuery: PylonQuery<Post>
37
+ private let likeQuery: PylonQuery<Like>
38
+ private let commentQuery: PylonQuery<Comment>
39
+ private let followQuery: PylonQuery<Follow>
40
+ private var subscriptions: Set<AnyCancellable> = []
41
+
42
+ init(engine: SyncEngine, client: PylonClient, media: MediaService) {
43
+ self.engine = engine
44
+ self.client = client
45
+ self.media = media
46
+ profileQuery = PylonQuery(engine: engine, entity: "Profile")
47
+ postQuery = PylonQuery(engine: engine, entity: "Post")
48
+ likeQuery = PylonQuery(engine: engine, entity: "Like")
49
+ commentQuery = PylonQuery(engine: engine, entity: "Comment")
50
+ followQuery = PylonQuery(engine: engine, entity: "Follow")
51
+
52
+ profileQuery.$rows
53
+ .sink { [weak self] rows in
54
+ self?.profiles = Dictionary(rows.map { ($0.id, $0) }, uniquingKeysWith: { _, new in new })
55
+ }
56
+ .store(in: &subscriptions)
57
+ postQuery.$rows
58
+ .sink { [weak self] rows in
59
+ self?.posts = rows.sorted { $0.createdAt > $1.createdAt }
60
+ }
61
+ .store(in: &subscriptions)
62
+ likeQuery.$rows
63
+ .sink { [weak self] rows in
64
+ guard let self else { return }
65
+ likesByPost = Dictionary(grouping: rows, by: \.postId)
66
+ pendingLikes = pendingLikes.filter { postId, liked in
67
+ self.syncedLike(postId) != liked
68
+ }
69
+ }
70
+ .store(in: &subscriptions)
71
+ commentQuery.$rows
72
+ .sink { [weak self] rows in
73
+ self?.commentsByPost = Dictionary(grouping: rows, by: \.postId)
74
+ .mapValues { $0.sorted { $0.createdAt < $1.createdAt } }
75
+ }
76
+ .store(in: &subscriptions)
77
+ followQuery.$rows
78
+ .sink { [weak self] rows in
79
+ guard let self else { return }
80
+ follows = rows
81
+ pendingFollows = pendingFollows.filter { profileId, following in
82
+ self.syncedFollow(profileId) != following
83
+ }
84
+ }
85
+ .store(in: &subscriptions)
86
+ postQuery.$loading
87
+ .sink { [weak self] loading in self?.isLoading = loading }
88
+ .store(in: &subscriptions)
89
+ }
90
+
91
+ // MARK: - Reads
92
+
93
+ var me: Profile? { myProfileId.flatMap { profiles[$0] } }
94
+
95
+ func profile(_ id: String) -> Profile? { profiles[id] }
96
+
97
+ func posts(by profileId: String) -> [Post] {
98
+ posts.filter { $0.authorId == profileId }
99
+ }
100
+
101
+ func post(_ id: String) -> Post? { posts.first { $0.id == id } }
102
+
103
+ func isLiked(_ postId: String) -> Bool {
104
+ pendingLikes[postId] ?? syncedLike(postId)
105
+ }
106
+
107
+ func likeCount(_ postId: String) -> Int {
108
+ let synced = likesByPost[postId]?.count ?? 0
109
+ guard let pending = pendingLikes[postId], pending != syncedLike(postId) else { return synced }
110
+ return synced + (pending ? 1 : -1)
111
+ }
112
+
113
+ /// A liker to name under the photo: someone the user follows first, else the latest.
114
+ func featuredLiker(_ postId: String) -> Profile? {
115
+ let likes = (likesByPost[postId] ?? []).filter { $0.profileId != myProfileId }
116
+ let following = Set(followingIds(of: myProfileId))
117
+ let pick = likes.first { following.contains($0.profileId) } ?? likes.max { $0.createdAt < $1.createdAt }
118
+ return pick.flatMap { profiles[$0.profileId] }
119
+ }
120
+
121
+ func comments(_ postId: String) -> [Comment] { commentsByPost[postId] ?? [] }
122
+
123
+ func followerIds(of profileId: String) -> [String] {
124
+ follows.filter { $0.followingId == profileId }.map(\.followerId)
125
+ }
126
+
127
+ func followingIds(of profileId: String?) -> [String] {
128
+ guard let profileId else { return [] }
129
+ return follows.filter { $0.followerId == profileId }.map(\.followingId)
130
+ }
131
+
132
+ func isFollowing(_ profileId: String) -> Bool {
133
+ pendingFollows[profileId] ?? syncedFollow(profileId)
134
+ }
135
+
136
+ func followerCount(_ profileId: String) -> Int {
137
+ let synced = followerIds(of: profileId).count
138
+ guard let pending = pendingFollows[profileId], pending != syncedFollow(profileId) else { return synced }
139
+ return synced + (pending ? 1 : -1)
140
+ }
141
+
142
+ /// People with a post in the last 24 hours, the user first, then newest.
143
+ var storyAuthors: [Profile] {
144
+ let cutoff = Date.now.addingTimeInterval(-86_400)
145
+ var seen = Set<String>()
146
+ var out: [Profile] = []
147
+ for post in posts where post.date > cutoff {
148
+ guard !seen.contains(post.authorId), post.authorId != myProfileId,
149
+ let author = profiles[post.authorId] else { continue }
150
+ seen.insert(post.authorId)
151
+ out.append(author)
152
+ }
153
+ return out
154
+ }
155
+
156
+ func hasRecentPost(_ profileId: String) -> Bool {
157
+ let cutoff = Date.now.addingTimeInterval(-86_400)
158
+ return posts.contains { $0.authorId == profileId && $0.date > cutoff }
159
+ }
160
+
161
+ func recentPosts(by profileId: String) -> [Post] {
162
+ let cutoff = Date.now.addingTimeInterval(-86_400)
163
+ return posts.filter { $0.authorId == profileId && $0.date > cutoff }.reversed()
164
+ }
165
+
166
+ /// Likes, comments, and follows aimed at the user, newest first.
167
+ var activity: [ActivityItem] {
168
+ guard let me = myProfileId else { return [] }
169
+ let mine = Set(posts.filter { $0.authorId == me }.map(\.id))
170
+ var items: [ActivityItem] = []
171
+ for postId in mine {
172
+ for like in likesByPost[postId] ?? [] where like.profileId != me {
173
+ items.append(ActivityItem(id: "like-\(like.id)", kind: .like, actorId: like.profileId, postId: postId, text: nil, createdAt: like.createdAt))
174
+ }
175
+ for comment in commentsByPost[postId] ?? [] where comment.profileId != me {
176
+ items.append(ActivityItem(id: "comment-\(comment.id)", kind: .comment, actorId: comment.profileId, postId: postId, text: comment.text, createdAt: comment.createdAt))
177
+ }
178
+ }
179
+ for follow in follows where follow.followingId == me {
180
+ items.append(ActivityItem(id: "follow-\(follow.id)", kind: .follow, actorId: follow.followerId, postId: nil, text: nil, createdAt: follow.createdAt))
181
+ }
182
+ return items.sorted { $0.createdAt > $1.createdAt }
183
+ }
184
+
185
+ private func syncedLike(_ postId: String) -> Bool {
186
+ guard let me = myProfileId else { return false }
187
+ return likesByPost[postId]?.contains { $0.profileId == me } ?? false
188
+ }
189
+
190
+ private func syncedFollow(_ profileId: String) -> Bool {
191
+ guard let me = myProfileId else { return false }
192
+ return follows.contains { $0.followerId == me && $0.followingId == profileId }
193
+ }
194
+
195
+ // MARK: - Writes
196
+
197
+ func toggleLike(_ postId: String) {
198
+ let next = !isLiked(postId)
199
+ setLike(postId, liked: next)
200
+ }
201
+
202
+ /// Double tap only ever likes.
203
+ func like(_ postId: String) {
204
+ if !isLiked(postId) { setLike(postId, liked: true) }
205
+ }
206
+
207
+ /// Records the tap at once, then sends the wanted state. The server call
208
+ /// sets the state rather than flipping it, so taps that arrive out of
209
+ /// order still end in the last tap's state.
210
+ private func setLike(_ postId: String, liked: Bool) {
211
+ pendingLikes[postId] = liked
212
+ Task {
213
+ do {
214
+ _ = try await client.callFn("setLike", args: SetLikeArgs(postId: postId, liked: liked), as: SetLikeResult.self)
215
+ } catch {
216
+ if pendingLikes[postId] == liked { pendingLikes[postId] = nil }
217
+ }
218
+ }
219
+ }
220
+
221
+ func toggleFollow(_ profileId: String) {
222
+ let following = !isFollowing(profileId)
223
+ pendingFollows[profileId] = following
224
+ Task {
225
+ do {
226
+ _ = try await client.callFn("setFollow", args: SetFollowArgs(profileId: profileId, following: following), as: SetFollowResult.self)
227
+ } catch {
228
+ if pendingFollows[profileId] == following { pendingFollows[profileId] = nil }
229
+ }
230
+ }
231
+ }
232
+
233
+ func addComment(_ postId: String, text: String) async throws {
234
+ _ = try await client.callFn("addComment", args: AddCommentArgs(postId: postId, text: text), as: IdResult.self)
235
+ }
236
+
237
+ func deletePost(_ postId: String) async throws {
238
+ _ = try await client.callFn("deletePost", args: DeletePostArgs(id: postId), as: IdResult.self)
239
+ }
240
+
241
+ func createPost(jpeg: Data, caption: String) async throws {
242
+ let imageUrl = try await media.uploadPublicJPEG(jpeg)
243
+ _ = try await client.callFn("createPost", args: CreatePostArgs(imageUrl: imageUrl, caption: caption), as: IdResult.self)
244
+ }
245
+
246
+ func saveProfile(handle: String, displayName: String, bio: String, avatarJPEG: Data?) async throws -> Profile {
247
+ var avatarUrl: String?
248
+ if let avatarJPEG {
249
+ avatarUrl = try await media.uploadPublicJPEG(avatarJPEG)
250
+ }
251
+ return try await client.callFn(
252
+ "upsertProfile",
253
+ args: UpsertProfileArgs(handle: handle, displayName: displayName, bio: bio, avatarUrl: avatarUrl),
254
+ as: Profile.self
255
+ )
256
+ }
257
+ }
258
+
259
+ struct ActivityItem: Identifiable, Hashable {
260
+ enum Kind { case like, comment, follow }
261
+ let id: String
262
+ let kind: Kind
263
+ let actorId: String
264
+ let postId: String?
265
+ let text: String?
266
+ let createdAt: String
267
+
268
+ var date: Date { Timestamp.parse(createdAt) }
269
+ }
270
+
271
+ /// A readable sentence for an error from a server function or the network.
272
+ func friendlyMessage(_ error: Error) -> String {
273
+ if let pylon = error as? PylonError {
274
+ switch pylon {
275
+ case .http(_, _, let message?) where !message.isEmpty:
276
+ return message.prefix(1).uppercased() + message.dropFirst()
277
+ case .transport:
278
+ return "Cannot reach the server. Check your connection and try again."
279
+ default:
280
+ return "Something went wrong. Try again."
281
+ }
282
+ }
283
+ if let local = error as? LocalizedError, let text = local.errorDescription { return text }
284
+ return "Something went wrong. Try again."
285
+ }
@@ -0,0 +1,155 @@
1
+ import SwiftUI
2
+
3
+ /// Full-screen playback of each person's posts from the last 24 hours.
4
+ /// Every photo shows for five seconds. Tap the right side for the next
5
+ /// photo, the left side for the previous one, or swipe down to close.
6
+ struct StoryViewer: View {
7
+ @EnvironmentObject private var social: SocialStore
8
+ @Environment(\.dismiss) private var dismiss
9
+
10
+ let authors: [Profile]
11
+ @State var authorIndex: Int
12
+ @State private var postIndex = 0
13
+ @State private var progress: Double = 0
14
+ @State private var dragOffset: CGFloat = 0
15
+ /// Bumped to replay the current photo from the start.
16
+ @State private var restarts = 0
17
+
18
+ private let duration: Double = 5
19
+ private let tick: Double = 0.05
20
+
21
+ init(authors: [Profile], startIndex: Int) {
22
+ self.authors = authors
23
+ _authorIndex = State(initialValue: startIndex)
24
+ }
25
+
26
+ private var author: Profile? { authors.indices.contains(authorIndex) ? authors[authorIndex] : nil }
27
+ private var posts: [Post] { author.map { social.recentPosts(by: $0.id) } ?? [] }
28
+ private var post: Post? { posts.indices.contains(postIndex) ? posts[postIndex] : nil }
29
+
30
+ var body: some View {
31
+ ZStack {
32
+ Color.black.ignoresSafeArea()
33
+ if let post {
34
+ let url = social.media.url(for: post.imageUrl)
35
+ RemoteImage(url: url)
36
+ .blur(radius: 40)
37
+ .opacity(0.6)
38
+ .ignoresSafeArea()
39
+ Color.clear
40
+ .aspectRatio(4 / 5, contentMode: .fit)
41
+ .overlay { RemoteImage(url: url) }
42
+ .clipShape(RoundedRectangle(cornerRadius: 12, style: .continuous))
43
+ .id(post.id)
44
+ }
45
+ HStack(spacing: 0) {
46
+ Color.clear.contentShape(Rectangle()).onTapGesture(perform: previous)
47
+ Color.clear.contentShape(Rectangle()).onTapGesture(perform: next)
48
+ }
49
+ VStack(spacing: 10) {
50
+ progressBars
51
+ topBar
52
+ Spacer()
53
+ if let caption = post?.caption, !caption.isEmpty {
54
+ Text(caption)
55
+ .font(.subheadline)
56
+ .foregroundStyle(.white)
57
+ .multilineTextAlignment(.leading)
58
+ .frame(maxWidth: .infinity, alignment: .leading)
59
+ .padding(14)
60
+ .background(.black.opacity(0.35), in: RoundedRectangle(cornerRadius: 14, style: .continuous))
61
+ }
62
+ }
63
+ .padding(.horizontal, 12)
64
+ .padding(.top, 8)
65
+ }
66
+ .offset(y: dragOffset)
67
+ .gesture(
68
+ DragGesture()
69
+ .onChanged { value in dragOffset = max(0, value.translation.height) }
70
+ .onEnded { value in
71
+ if value.translation.height > 120 { dismiss() } else {
72
+ withAnimation(.spring) { dragOffset = 0 }
73
+ }
74
+ }
75
+ )
76
+ .statusBarHidden()
77
+ .task(id: "\(authorIndex)-\(postIndex)-\(restarts)") { await play() }
78
+ }
79
+
80
+ private var progressBars: some View {
81
+ HStack(spacing: 4) {
82
+ ForEach(posts.indices, id: \.self) { index in
83
+ GeometryReader { geo in
84
+ Capsule().fill(.white.opacity(0.35))
85
+ .overlay(alignment: .leading) {
86
+ Capsule().fill(.white)
87
+ .frame(width: geo.size.width * fill(for: index))
88
+ }
89
+ }
90
+ .frame(height: 2.5)
91
+ }
92
+ }
93
+ }
94
+
95
+ private var topBar: some View {
96
+ HStack(spacing: 10) {
97
+ AvatarView(profile: author, size: 32)
98
+ Text(author?.handle ?? "")
99
+ .font(.subheadline.weight(.semibold))
100
+ if let post {
101
+ Text(Timestamp.short(post.date))
102
+ .font(.subheadline)
103
+ .opacity(0.7)
104
+ }
105
+ Spacer()
106
+ Button { dismiss() } label: {
107
+ Image(systemName: "xmark")
108
+ .font(.system(size: 20, weight: .semibold))
109
+ .frame(width: 40, height: 40)
110
+ .contentShape(Rectangle())
111
+ }
112
+ .accessibilityLabel("Close")
113
+ }
114
+ .foregroundStyle(.white)
115
+ }
116
+
117
+ private func fill(for index: Int) -> Double {
118
+ if index < postIndex { return 1 }
119
+ if index == postIndex { return progress }
120
+ return 0
121
+ }
122
+
123
+ private func play() async {
124
+ progress = 0
125
+ let steps = Int(duration / tick)
126
+ for _ in 0..<steps {
127
+ try? await Task.sleep(for: .seconds(tick))
128
+ if Task.isCancelled { return }
129
+ progress += tick / duration
130
+ }
131
+ next()
132
+ }
133
+
134
+ private func next() {
135
+ if postIndex + 1 < posts.count {
136
+ postIndex += 1
137
+ } else if authorIndex + 1 < authors.count {
138
+ authorIndex += 1
139
+ postIndex = 0
140
+ } else {
141
+ dismiss()
142
+ }
143
+ }
144
+
145
+ private func previous() {
146
+ if postIndex > 0 {
147
+ postIndex -= 1
148
+ } else if authorIndex > 0 {
149
+ authorIndex -= 1
150
+ postIndex = 0
151
+ } else {
152
+ restarts += 1
153
+ }
154
+ }
155
+ }
@@ -0,0 +1,82 @@
1
+ import SwiftUI
2
+
3
+ enum Theme {
4
+ /// Filled buttons, links, and badges. Fixed so the black tab bar tint
5
+ /// does not change it.
6
+ static let accent = Color(red: 0.0, green: 0.584, blue: 0.965)
7
+ /// The like color.
8
+ static let heart = Color(red: 1.0, green: 0.19, blue: 0.25)
9
+ /// The ring around an avatar with a post from the last 24 hours.
10
+ static let storyRing = AngularGradient(
11
+ colors: [
12
+ Color(red: 0.99, green: 0.73, blue: 0.27),
13
+ Color(red: 0.98, green: 0.38, blue: 0.20),
14
+ Color(red: 0.87, green: 0.15, blue: 0.47),
15
+ Color(red: 0.55, green: 0.22, blue: 0.85),
16
+ Color(red: 0.99, green: 0.73, blue: 0.27),
17
+ ],
18
+ center: .center
19
+ )
20
+ static let hairline = Color(uiColor: .separator)
21
+ static let fieldFill = Color(uiColor: .secondarySystemBackground)
22
+ }
23
+
24
+ /// The app name in the feed header and on the sign-in screen.
25
+ struct Wordmark: View {
26
+ var size: CGFloat = 30
27
+
28
+ var body: some View {
29
+ Text("__APP_NAME__")
30
+ .font(.system(size: size, weight: .semibold, design: .serif))
31
+ .italic()
32
+ .tracking(-0.5)
33
+ }
34
+ }
35
+
36
+ /// A full-width filled button for the main action on a screen.
37
+ struct PrimaryButtonStyle: ButtonStyle {
38
+ var isLoading = false
39
+
40
+ func makeBody(configuration: Configuration) -> some View {
41
+ ZStack {
42
+ configuration.label.opacity(isLoading ? 0 : 1)
43
+ if isLoading { ProgressView().tint(.white) }
44
+ }
45
+ .font(.body.weight(.semibold))
46
+ .foregroundStyle(.white)
47
+ .frame(maxWidth: .infinity, minHeight: 50)
48
+ .background(Theme.accent, in: RoundedRectangle(cornerRadius: 12, style: .continuous))
49
+ .opacity(configuration.isPressed ? 0.8 : 1)
50
+ }
51
+ }
52
+
53
+ /// A compact button for profile actions: Edit profile, Follow, Following.
54
+ struct ProfileButtonStyle: ButtonStyle {
55
+ var prominent = false
56
+
57
+ func makeBody(configuration: Configuration) -> some View {
58
+ configuration.label
59
+ .font(.subheadline.weight(.semibold))
60
+ .foregroundStyle(prominent ? Color.white : Color.primary)
61
+ .frame(maxWidth: .infinity, minHeight: 34)
62
+ .background(
63
+ prominent ? Theme.accent : Theme.fieldFill,
64
+ in: RoundedRectangle(cornerRadius: 9, style: .continuous)
65
+ )
66
+ .opacity(configuration.isPressed ? 0.7 : 1)
67
+ }
68
+ }
69
+
70
+ /// A rounded text field on the sign-in and profile screens.
71
+ struct FieldStyle: ViewModifier {
72
+ func body(content: Content) -> some View {
73
+ content
74
+ .padding(.horizontal, 14)
75
+ .frame(minHeight: 50)
76
+ .background(Theme.fieldFill, in: RoundedRectangle(cornerRadius: 12, style: .continuous))
77
+ }
78
+ }
79
+
80
+ extension View {
81
+ func fieldStyle() -> some View { modifier(FieldStyle()) }
82
+ }
@@ -1,57 +1,14 @@
1
1
  import SwiftUI
2
- import PylonClient
3
- import PylonSync
4
2
 
5
3
  @main
6
4
  struct __APP_NAME_PASCAL__App: App {
7
- @StateObject private var session = AppSession()
5
+ @StateObject private var app = AppModel()
8
6
 
9
7
  var body: some Scene {
10
8
  WindowGroup {
11
- Group {
12
- if let engine = session.engine {
13
- RootView(engine: engine)
14
- .environmentObject(session)
15
- } else {
16
- ProgressView()
17
- .frame(maxWidth: .infinity, maxHeight: .infinity)
18
- }
19
- }
20
- .task { await session.bootIfNeeded() }
9
+ RootView()
10
+ .environmentObject(app)
11
+ .task { await app.boot() }
21
12
  }
22
13
  }
23
14
  }
24
-
25
- @MainActor
26
- final class AppSession: ObservableObject {
27
- let client: PylonClient
28
- @Published private(set) var engine: SyncEngine?
29
- @Published var myProfileId: String?
30
-
31
- init() {
32
- let baseURLString = ProcessInfo.processInfo.environment["PYLON_BASE_URL"]
33
- ?? "http://localhost:4321"
34
- guard let url = URL(string: baseURLString) else {
35
- fatalError("Invalid PYLON_BASE_URL: \(baseURLString)")
36
- }
37
- self.client = PylonClient(baseURL: url, appName: "__APP_NAME_SNAKE__")
38
- self.myProfileId = UserDefaults.standard.string(forKey: "myProfileId")
39
- }
40
-
41
- func bootIfNeeded() async {
42
- guard engine == nil else { return }
43
- let baseURLString = ProcessInfo.processInfo.environment["PYLON_BASE_URL"]
44
- ?? "http://localhost:4321"
45
- guard let url = URL(string: baseURLString) else { return }
46
- let config = SyncEngineConfig(baseURL: url, appName: "__APP_NAME_SNAKE__")
47
- let engine = await SyncEngine(config: config, client: client)
48
- await engine.start()
49
- self.engine = engine
50
- }
51
-
52
- func setMyProfileId(_ id: String?) {
53
- myProfileId = id
54
- if let id { UserDefaults.standard.set(id, forKey: "myProfileId") }
55
- else { UserDefaults.standard.removeObject(forKey: "myProfileId") }
56
- }
57
- }
@@ -3,18 +3,24 @@
3
3
  # Materialize the Xcode project once with:
4
4
  # brew install xcodegen
5
5
  # xcodegen generate
6
- # Then open __APP_NAME_PASCAL__.xcodeproj.
6
+ # Then open __APP_NAME_PASCAL__.xcodeproj. Run xcodegen again after you add
7
+ # or remove Swift files.
8
+ #
9
+ # The app talks to the Pylon backend in apps/api. Start it with `pylon dev`
10
+ # there. The simulator reaches http://localhost:4321; on a device, set
11
+ # PYLON_BASE_URL in the scheme's environment to your Mac's LAN address or
12
+ # your deployed API.
7
13
 
8
14
  name: __APP_NAME_PASCAL__
9
15
  options:
10
16
  bundleIdPrefix: com.example
11
17
  deploymentTarget:
12
- iOS: "16.0"
18
+ iOS: "17.0"
13
19
 
14
20
  packages:
15
21
  pylon:
16
22
  url: https://github.com/pylonsync/pylon.git
17
- from: "0.3.0"
23
+ from: "__PYLON_VERSION__"
18
24
 
19
25
  targets:
20
26
  __APP_NAME_PASCAL__:
@@ -25,15 +31,13 @@ targets:
25
31
  settings:
26
32
  base:
27
33
  GENERATE_INFOPLIST_FILE: YES
34
+ INFOPLIST_KEY_CFBundleDisplayName: "__APP_NAME__"
28
35
  INFOPLIST_KEY_UIApplicationSceneManifest_Generation: YES
29
36
  INFOPLIST_KEY_UILaunchScreen_Generation: YES
30
- INFOPLIST_KEY_UISupportedInterfaceOrientations: "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"
31
- TARGETED_DEVICE_FAMILY: "1,2"
32
- SWIFT_VERSION: "5.9"
37
+ INFOPLIST_KEY_UISupportedInterfaceOrientations: "UIInterfaceOrientationPortrait"
38
+ TARGETED_DEVICE_FAMILY: "1"
39
+ SWIFT_VERSION: "5.0"
33
40
  ENABLE_PREVIEWS: YES
34
- # __APP_NAME__ talks to a Pylon backend on localhost during dev.
35
- # The simulator can reach localhost directly; on a physical device
36
- # set PYLON_BASE_URL to your machine's LAN IP.
37
41
  INFOPLIST_KEY_NSAppTransportSecurity: "{NSAllowsLocalNetworking = YES;}"
38
42
  dependencies:
39
43
  - package: pylon
@@ -10,3 +10,10 @@ PYLON_OWNER_EMAIL=you@yourshop.com
10
10
  # ── Site URL (optional) ──────────────────────────────────────────────────────
11
11
  # Used by robots.txt + sitemap.xml. Point it at your real domain in production.
12
12
  # SITE_URL=https://yourshop.com
13
+
14
+ # ── Demo data ────────────────────────────────────────────────────────────────
15
+ # `pylon dev` fills an empty booking book with fictional appointments. Seeding
16
+ # runs only under `pylon dev` (it checks PYLON_DEV_MODE and
17
+ # PYLON_DEV_WATCH_DIR), so `pylon start`, Docker, and Pylon Cloud deploys never
18
+ # seed. Set 0 to start empty in development. Never set 1 on a deploy.
19
+ # PYLON_DEMO_DATA=0
@@ -62,7 +62,7 @@ this pass before the user has to ask.
62
62
  - **`serverData` (SSR) is READ-ONLY.** No write methods; the runtime rejects write frames (`SSR_WRITE_FORBIDDEN`). Mutations belong in actions/functions, never in a page render.
63
63
  - **`response.*` / `response.redirect()` / `response.notFound()` must fire in the synchronous shell render**, before any `await` / `<Suspense>`. The HTTP head commits when the shell is ready; status/headers/cookies set from a suspended subtree are lost, and `redirect`/`notFound` thrown below a Suspense boundary are swallowed.
64
64
  - **`ctx.llm`, `ctx.rooms`, and `ctx.connections` are on mutation + action only, NOT query** (reactive purity). `action` has no direct `ctx.db`; use `ctx.runQuery` / `ctx.runMutation`.
65
- - **`ctx.llm.stream(request, onEvent)` streams tokens as they generate** and still resolves with the full response, so `stop_reason === "tool_use"` drives an agent tool loop. Events are `text_delta` / `tool_use_start` / `tool_input_delta` / `done`. Same auth + model-allowlist gating as `ctx.llm.complete`. Streaming does NOT extend the call deadline (`PYLON_FN_CALL_TIMEOUT`, 30s default) — set `timeout: <secs>` on the def for a long run.
65
+ - **`ctx.llm.stream(request, onEvent)` streams tokens as they generate** and still resolves with the full response, so `stop_reason === "tool_use"` drives an agent tool loop. Events are `text_delta` / `tool_use_start` / `tool_input_delta` / `done`. Same auth + model-allowlist gating as `ctx.llm.complete`. The call timeout is an idle timeout (`PYLON_FN_CALL_TIMEOUT`, 30s default; Stack0 Cloud 300s): each stream event resets it, and the total run is capped at 10× the timeout. Set `timeout: <secs>` on the def when a step (a slow tool call) can be silent longer than that.
66
66
  - **`ctx.stream.write(text)` streams to the caller — and every fn stream is RESUMABLE**: the server buffers frames under a stream id (`streamFn`'s `onStreamId`), so a dropped connection or closed tab catches up via `resumeStream(id)`, including the final result after the handler finished. `ctx.rooms.broadcast(room, topic, data)` fans out to every CURRENTLY-CONNECTED subscriber (second device, second tab) but does not replay missed messages — use the stream id for anything that must survive a gap. Broadcast resolves `{ delivered: false }` when the room has no members — a no-op, not an error. Clients receive with `useRoomMessages(room, cb)` from `@pylonsync/react`; `useRoom` is the send side.
67
67
  - **It's `db.useQueryOne`, not `useOne`.** Validators and field types have aliases: `v.bool`/`v.boolean`, `v.float`/`v.number`.
68
68
  - **Use the supported file and scheduling APIs.** Files go through `<FileUpload>` and `/api/files/*`; an upload is readable by its uploader only, so pass `visibility: "public"` to `uploadFile` or `<FileUpload>` for files every visitor sees; there is no `ctx.files`. One-shot work uses `ctx.scheduler.runAfter`, `runAt`, or `cancel`; there is no `defineWorkflow` or `defineJob`. Recurring work uses `cron("0 * * * *", "fnName")` in `buildManifest({ crons: [...] })`, imported from `@pylonsync/sdk`. Make the target function `internal: true`. It runs with anonymous auth, but its own `ctx.db.*` calls are server-side and bypass policies. Use `ctx.auth.elevate({ admin: true, reason: "..." })`, with a mandatory reason, only when chaining another internal function through `ctx.scheduler`.