@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
@@ -1,8 +1,10 @@
1
- // Demo data for a new app: twelve people who post, follow each other, like,
2
- // and comment, so the first visit opens on a feed that looks lived in.
1
+ // Demo data for a new app: eight people who post, follow each other, like,
2
+ // and comment, so the first visit opens on a feed with content in it.
3
3
  //
4
- // Photos live in public/images (avatars/ and posts/). Demo user ids start
5
- // with "demo_" so they never collide with a real or guest account.
4
+ // Post photos live in public/images/posts and the server serves them at
5
+ // /images/posts/<key>.jpg. Demo people have no profile photo, so the app
6
+ // draws their initial. Demo user ids start with "demo_" so they never
7
+ // collide with a real or guest account.
6
8
  // Pure data + pure shaping; functions/seedFeed.ts is a thin wrapper.
7
9
 
8
10
  export interface SeedProfile {
@@ -12,6 +14,7 @@ export interface SeedProfile {
12
14
  }
13
15
 
14
16
  export interface SeedPost {
17
+ /** File name under public/images/posts, without `.jpg`. */
15
18
  key: string;
16
19
  author: string;
17
20
  caption: string;
@@ -29,99 +32,54 @@ export interface SeedComment {
29
32
  }
30
33
 
31
34
  export const SEED_PROFILES: SeedProfile[] = [
32
- { username: "mara.bakes", displayName: "Mara Lindqvist", bio: "Sourdough, mostly. Stockholm." },
33
- { username: "theo.climbs", displayName: "Theo Adeyemi", bio: "Bouldering most evenings. Route setter at Blocworks." },
34
- { username: "ines.clay", displayName: "Inés Moreno", bio: "Wheel-thrown stoneware from a small studio in Valencia." },
35
- { username: "kenji.rides", displayName: "Kenji Watanabe", bio: "Gravel on weekends, commuting on weekdays." },
36
- { username: "lou.streets", displayName: "Louise Carter", bio: "35mm film, mostly Brooklyn." },
37
- { username: "sam.grows", displayName: "Sam Okafor", bio: "Allotment 14B. Tomatoes, beans, too many courgettes." },
38
- { username: "rosa.roasts", displayName: "Rosa Delgado", bio: "Head roaster at a small shop in Oakland." },
39
- { username: "biscuit.corgi", displayName: "Biscuit", bio: "Corgi. Professional napper. Managed by @nora.h" },
40
- { username: "nora.h", displayName: "Nora Hughes", bio: "Product designer. Biscuit's person." },
41
- { username: "ari.wanders", displayName: "Ari Cohen", bio: "Trains over planes." },
42
- { username: "dev.draws", displayName: "Dev Patel", bio: "Architect. A sketch every morning before work." },
43
- { username: "june.surfs", displayName: "June Tanaka", bio: "Cold water surfer on the Oregon coast." },
35
+ { username: "maya.rivera", displayName: "Maya Rivera", bio: "Travel photographer. Currently in Lisbon." },
36
+ { username: "jordan.eats", displayName: "Jordan Brooks", bio: "Cooking at home, eating out, writing it all down." },
37
+ { username: "priya.home", displayName: "Priya Shah", bio: "Interior designer. Small rooms, good light." },
38
+ { username: "kenji.mori", displayName: "Kenji Mori", bio: "Architecture and night walks. Tokyo." },
39
+ { username: "elena.runs", displayName: "Elena Berg", bio: "Trail runner. Mountains most weekends." },
40
+ { username: "sam.and.juno", displayName: "Sam Whitaker", bio: "Juno is the golden one." },
41
+ { username: "amara.o", displayName: "Amara Okafor", bio: "Coffee, markets, and early mornings." },
42
+ { username: "leo.haddad", displayName: "Leo Haddad", bio: "Road trips and sunsets. Film when I can." },
44
43
  ];
45
44
 
46
45
  export const SEED_POSTS: SeedPost[] = [
47
- { key: "mara-loaf", author: "mara.bakes", caption: "Sunday loaf. 78% hydration and finally an ear I am happy with.", shape: "portrait", age: 3 },
48
- { key: "june-dawn", author: "june.surfs", caption: "Glassy at 6am and nobody else out. Worth the cold hands.", shape: "portrait", age: 5 },
49
- { key: "biscuit-couch", author: "biscuit.corgi", caption: "Guarding the couch from nothing in particular.", shape: "square", age: 8 },
50
- { key: "rosa-roaster", author: "rosa.roasts", caption: "New Ethiopia lot on the roaster today. Blueberry and jasmine in the cup.", shape: "square", age: 11 },
51
- { key: "ines-glaze", author: "ines.clay", caption: "Out of the kiln: the speckled oat glaze on a batch of mugs. Shop update Friday.", shape: "portrait", age: 14 },
52
- { key: "lou-crosswalk", author: "lou.streets", caption: "Bedford Ave, late afternoon. Portra 400.", shape: "portrait", age: 20 },
53
- { key: "dev-sketch", author: "dev.draws", caption: "Morning sketch of the old library stairs. Twenty minutes, one pen.", shape: "square", age: 26 },
54
- { key: "kenji-gravel", author: "kenji.rides", caption: "82 km, 1,400 m of climbing, one flat. Good day.", shape: "portrait", age: 30 },
55
- { key: "sam-tomatoes", author: "sam.grows", caption: "First proper harvest of the year. The yellow ones are Sungold.", shape: "square", age: 34 },
56
- { key: "theo-boulder", author: "theo.climbs", caption: "Set a new blue circuit this week. This one is the crux of number 6.", shape: "portrait", age: 40 },
57
- { key: "ari-train", author: "ari.wanders", caption: "Night train from Vienna to Venice. Woke up to this.", shape: "portrait", age: 46 },
58
- { key: "nora-desk", author: "nora.h", caption: "New desk setup. @biscuit.corgi approved the rug.", shape: "square", age: 52 },
59
- { key: "mara-buns", author: "mara.bakes", caption: "Cardamom buns for the neighbours. Kept two.", shape: "square", age: 60 },
60
- { key: "june-board", author: "june.surfs", caption: "Waxed and ready for tomorrow.", shape: "square", age: 70 },
61
- { key: "rosa-latte", author: "rosa.roasts", caption: "Tuesday latte art practice. Getting there.", shape: "square", age: 78 },
62
- { key: "ines-wheel", author: "ines.clay", caption: "Trimming feet on a batch of bowls. The quiet part of the week.", shape: "portrait", age: 90 },
63
- { key: "lou-diner", author: "lou.streets", caption: "Counter seats at the diner on Graham. Coffee refills forever.", shape: "square", age: 100 },
64
- { key: "biscuit-beach", author: "biscuit.corgi", caption: "First time at the beach. Sand everywhere. No regrets.", shape: "portrait", age: 110 },
65
- { key: "dev-model", author: "dev.draws", caption: "Card model for the community centre competition entry.", shape: "square", age: 124 },
66
- { key: "kenji-coffee", author: "kenji.rides", caption: "Mid-ride coffee stop. The bike rests too.", shape: "square", age: 140 },
67
- { key: "sam-shed", author: "sam.grows", caption: "Seed trays in the shed. Kale, chard, and far too much basil.", shape: "portrait", age: 155 },
68
- { key: "theo-chalk", author: "theo.climbs", caption: "Chalk bag, tape, and a very tired pair of shoes.", shape: "square", age: 170 },
69
- { key: "ari-market", author: "ari.wanders", caption: "Morning market in Palermo. I bought too many lemons.", shape: "square", age: 190 },
70
- { key: "nora-sketches", author: "nora.h", caption: "Paper first, screens later. Wireframes for the booking flow.", shape: "square", age: 210 },
71
- { key: "mara-starter", author: "mara.bakes", caption: "Meet Olof, the starter. Eleven years old this month.", shape: "portrait", age: 240 },
72
- { key: "june-van", author: "june.surfs", caption: "Home for the weekend.", shape: "square", age: 280 },
73
- { key: "ines-studio", author: "ines.clay", caption: "The studio shelves on a good week.", shape: "portrait", age: 320 },
74
- { key: "lou-rain", author: "lou.streets", caption: "Rain on the J train platform.", shape: "portrait", age: 360 },
46
+ { key: "lisbon", author: "maya.rivera", caption: "Tram 28 at golden hour. Waited twenty minutes for this one and it was worth it.", shape: "square", age: 1.5 },
47
+ { key: "coffee", author: "amara.o", caption: "Flat white and a croissant before the market opens.", shape: "square", age: 3 },
48
+ { key: "dog", author: "sam.and.juno", caption: "Juno found the tall grass again.", shape: "square", age: 5 },
49
+ { key: "run", author: "elena.runs", caption: "Sunrise on the ridge. 18 km, 900 m up, legs gone.", shape: "square", age: 8 },
50
+ { key: "ramen", author: "jordan.eats", caption: "Tonkotsu from the new place on 5th. Broth cooked for 14 hours and you can tell.", shape: "square", age: 11 },
51
+ { key: "arch", author: "kenji.mori", caption: "Curves and shadows. The museum extension at noon.", shape: "square", age: 14 },
52
+ { key: "sunset", author: "leo.haddad", caption: "Big Sur, last light. 30 second exposure.", shape: "square", age: 20 },
75
53
  ];
76
54
 
77
55
  export const SEED_COMMENTS: SeedComment[] = [
78
- { post: "mara-loaf", author: "rosa.roasts", text: "That crumb. Trading you a bag of the new Ethiopia for one.", after: 0.5 },
79
- { post: "mara-loaf", author: "sam.grows", text: "What flour are you using?", after: 1 },
80
- { post: "mara-loaf", author: "mara.bakes", text: "@sam.grows 80% bread flour, 20% whole wheat from the mill down the road.", after: 1.5 },
81
- { post: "june-dawn", author: "kenji.rides", text: "Unreal colours.", after: 1 },
82
- { post: "june-dawn", author: "ari.wanders", text: "Which beach is this?", after: 2 },
83
- { post: "biscuit-couch", author: "nora.h", text: "He has not moved in three hours.", after: 0.2 },
84
- { post: "biscuit-couch", author: "lou.streets", text: "A very serious job.", after: 2 },
85
- { post: "biscuit-couch", author: "theo.climbs", text: "Those ears.", after: 3 },
86
- { post: "rosa-roaster", author: "mara.bakes", text: "Saving me a bag?", after: 1 },
87
- { post: "rosa-roaster", author: "rosa.roasts", text: "@mara.bakes already set aside.", after: 1.2 },
88
- { post: "ines-glaze", author: "dev.draws", text: "The speckle on that glaze is lovely.", after: 2 },
89
- { post: "ines-glaze", author: "nora.h", text: "Setting an alarm for Friday.", after: 3 },
90
- { post: "lou-crosswalk", author: "ari.wanders", text: "The light in this one.", after: 4 },
91
- { post: "dev-sketch", author: "ines.clay", text: "Twenty minutes? Show-off.", after: 1 },
92
- { post: "kenji-gravel", author: "june.surfs", text: "Where was the flat?", after: 2 },
93
- { post: "kenji-gravel", author: "kenji.rides", text: "@june.surfs about 5 km from the end, of course.", after: 3 },
94
- { post: "sam-tomatoes", author: "mara.bakes", text: "Tomato and bread season.", after: 1 },
95
- { post: "theo-boulder", author: "kenji.rides", text: "Looks sandbagged.", after: 2 },
96
- { post: "theo-boulder", author: "theo.climbs", text: "@kenji.rides it is a fair blue. Mostly.", after: 2.5 },
97
- { post: "ari-train", author: "lou.streets", text: "Adding this to the list.", after: 5 },
98
- { post: "nora-desk", author: "dev.draws", text: "Where is the lamp from?", after: 1 },
99
- { post: "mara-buns", author: "rosa.roasts", text: "Recipe please.", after: 2 },
100
- { post: "biscuit-beach", author: "june.surfs", text: "Surf lessons next.", after: 1 },
101
- { post: "dev-model", author: "ines.clay", text: "Good luck with the entry.", after: 3 },
102
- { post: "sam-shed", author: "mara.bakes", text: "Pesto for the whole street then.", after: 4 },
103
- { post: "mara-starter", author: "biscuit.corgi", text: "Can I eat Olof.", after: 2 },
104
- { post: "mara-starter", author: "mara.bakes", text: "@biscuit.corgi no.", after: 2.2 },
56
+ { post: "lisbon", author: "leo.haddad", text: "The light on those tiles. Which street is this?", after: 0.3 },
57
+ { post: "lisbon", author: "maya.rivera", text: "@leo.haddad Rua da Conceição, just before it turns up to the cathedral.", after: 0.6 },
58
+ { post: "lisbon", author: "amara.o", text: "Adding this to the list for May.", after: 0.9 },
59
+ { post: "coffee", author: "jordan.eats", text: "That rosetta is perfect.", after: 0.5 },
60
+ { post: "coffee", author: "priya.home", text: "Where is this? I need that table.", after: 1 },
61
+ { post: "dog", author: "elena.runs", text: "Juno looks so happy.", after: 0.4 },
62
+ { post: "dog", author: "amara.o", text: "Best dog on this app.", after: 1.2 },
63
+ { post: "run", author: "kenji.mori", text: "Incredible view. How early did you start?", after: 1 },
64
+ { post: "run", author: "elena.runs", text: "@kenji.mori headlamps on at 4:30.", after: 1.5 },
65
+ { post: "ramen", author: "maya.rivera", text: "Going this week.", after: 2 },
66
+ { post: "arch", author: "priya.home", text: "Those shadows are unreal.", after: 1 },
67
+ { post: "sunset", author: "sam.and.juno", text: "Colors like this make me want to drive up the coast tonight.", after: 2 },
105
68
  ];
106
69
 
107
70
  /** Who each demo user follows, as username → usernames. */
108
71
  export const SEED_FOLLOWS: Record<string, string[]> = {
109
- "mara.bakes": ["rosa.roasts", "sam.grows", "ines.clay", "biscuit.corgi", "lou.streets"],
110
- "theo.climbs": ["kenji.rides", "june.surfs", "ari.wanders", "biscuit.corgi"],
111
- "ines.clay": ["dev.draws", "mara.bakes", "nora.h", "lou.streets"],
112
- "kenji.rides": ["theo.climbs", "june.surfs", "rosa.roasts", "ari.wanders"],
113
- "lou.streets": ["ari.wanders", "dev.draws", "biscuit.corgi", "ines.clay"],
114
- "sam.grows": ["mara.bakes", "rosa.roasts", "biscuit.corgi"],
115
- "rosa.roasts": ["mara.bakes", "kenji.rides", "ines.clay", "sam.grows"],
116
- "biscuit.corgi": ["nora.h", "june.surfs"],
117
- "nora.h": ["biscuit.corgi", "dev.draws", "ines.clay", "lou.streets", "mara.bakes"],
118
- "ari.wanders": ["lou.streets", "june.surfs", "kenji.rides", "theo.climbs"],
119
- "dev.draws": ["ines.clay", "nora.h", "lou.streets"],
120
- "june.surfs": ["kenji.rides", "biscuit.corgi", "ari.wanders", "theo.climbs"],
72
+ "maya.rivera": ["leo.haddad", "amara.o", "priya.home", "kenji.mori"],
73
+ "jordan.eats": ["amara.o", "maya.rivera", "sam.and.juno"],
74
+ "priya.home": ["kenji.mori", "maya.rivera", "amara.o", "elena.runs"],
75
+ "kenji.mori": ["priya.home", "leo.haddad", "elena.runs"],
76
+ "elena.runs": ["sam.and.juno", "leo.haddad", "maya.rivera", "kenji.mori"],
77
+ "sam.and.juno": ["elena.runs", "jordan.eats", "amara.o"],
78
+ "amara.o": ["jordan.eats", "maya.rivera", "priya.home", "sam.and.juno", "elena.runs"],
79
+ "leo.haddad": ["maya.rivera", "elena.runs", "kenji.mori"],
121
80
  };
122
81
 
123
82
  export const demoUserId = (username: string) => `demo_${username.replace(/\./g, "_")}`;
124
- export const avatarPath = (username: string) => `/images/avatars/${username}.jpg`;
125
83
  export const postImagePath = (key: string) => `/images/posts/${key}.jpg`;
126
84
 
127
85
  /**
@@ -158,7 +116,7 @@ export function shapeSeed(now: number = Date.now()): ShapedSeed {
158
116
  username: p.username,
159
117
  displayName: p.displayName,
160
118
  bio: p.bio,
161
- avatarUrl: avatarPath(p.username),
119
+ avatarUrl: null,
162
120
  createdAt: hoursAgo(2000 - i * 40),
163
121
  })),
164
122
  posts: SEED_POSTS.map((p) => ({
@@ -0,0 +1,39 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { existsSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { SEED_COMMENTS, SEED_FOLLOWS, SEED_POSTS, SEED_PROFILES, shapeSeed } from "../lib/seed";
5
+
6
+ const PUBLIC = join(import.meta.dir, "..", "public");
7
+
8
+ /** Every image path the seed writes that the server must serve from public/. */
9
+ function localImages(): string[] {
10
+ const seed = shapeSeed(0);
11
+ const urls = [
12
+ ...seed.posts.map((p) => p.row.imageUrl),
13
+ ...seed.profiles.map((p) => p.avatarUrl),
14
+ ];
15
+ return urls.filter((u): u is string => typeof u === "string" && u.startsWith("/"));
16
+ }
17
+
18
+ describe("demo seed", () => {
19
+ test("every local image it references exists in public/", () => {
20
+ const images = localImages();
21
+ expect(images.length).toBeGreaterThan(0);
22
+ const missing = images.filter((url) => !existsSync(join(PUBLIC, url)));
23
+ expect(missing).toEqual([]);
24
+ });
25
+
26
+ test("posts, comments, and follows only name demo people", () => {
27
+ const people = new Set(SEED_PROFILES.map((p) => p.username));
28
+ const posts = new Set(SEED_POSTS.map((p) => p.key));
29
+ for (const p of SEED_POSTS) expect(people.has(p.author)).toBe(true);
30
+ for (const c of SEED_COMMENTS) {
31
+ expect(people.has(c.author)).toBe(true);
32
+ expect(posts.has(c.post)).toBe(true);
33
+ }
34
+ for (const [from, tos] of Object.entries(SEED_FOLLOWS)) {
35
+ expect(people.has(from)).toBe(true);
36
+ for (const to of tos) expect(people.has(to)).toBe(true);
37
+ }
38
+ });
39
+ });
@@ -10,3 +10,10 @@ PYLON_OWNER_EMAIL=you@yourbusiness.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://yourbusiness.com
13
+
14
+ # ── Demo data ────────────────────────────────────────────────────────────────
15
+ # `pylon dev` fills an empty subscriber list with fictional rows. Seeding runs
16
+ # only under `pylon dev` (it checks PYLON_DEV_MODE and PYLON_DEV_WATCH_DIR), so
17
+ # `pylon start`, Docker, and Pylon Cloud deploys never seed. Set 0 to start
18
+ # 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`.
@@ -18,11 +18,23 @@ increment in another without refreshing.
18
18
  ## How the realtime works
19
19
 
20
20
  - `functions/subscribe.ts`: a public **mutation** that validates, lowercases,
21
- and dedupes the email, inserts one `Subscriber` row, and bumps a public,
22
- PII-free `SubscriberCount` row.
23
- - `app/newsletter-signup.tsx` subscribes to `SubscriberCount` with
21
+ and dedupes the email, inserts one `Subscriber` row, and recounts into the
22
+ public, PII-free `SubscriberCount` row.
23
+ - `app/(marketing)/newsletter-signup.tsx` reads `SubscriberCount` with
24
24
  `db.useQuery`, so the live count syncs to every open tab. No polling.
25
25
 
26
+ The counter is the `Subscriber` table's row count, and it is hidden while the
27
+ list is empty. `newsletter.importedCount` in `lib/site.config.ts` adds
28
+ subscribers you are moving over from another tool. It ships as 0.
29
+
30
+ ## Demo data
31
+
32
+ In `pylon dev`, an empty list gets about 180 fictional subscribers from the last
33
+ 30 days, so the counter, chart, and list have data (`functions/seedDemo.ts`,
34
+ `lib/demo.ts`). Seeding runs only when `PYLON_DEMO_DATA` is on, or when it is unset and the app runs under `pylon dev`, and only while the `Subscriber` table is
35
+ empty. `pylon start`, Docker, and Pylon Cloud deploys do not seed, because only `pylon dev` sets `PYLON_DEV_WATCH_DIR`. Set `PYLON_DEMO_DATA=0`
36
+ to start empty in development. Delete both files once you have real subscribers.
37
+
26
38
  ## Privacy
27
39
 
28
40
  The `Subscriber` entity holds reader emails (PII), so its policy in `app.ts`
@@ -44,6 +56,10 @@ Your name, colors, bio, offerings, testimonials, newsletter copy, and links
44
56
  live in **`lib/site.config.ts`**. Edit that file, or generate it, to re-theme
45
57
  the page.
46
58
 
59
+ The tab icon is `app/icon.svg` (with `public/favicon.ico` for browsers that
60
+ request `/favicon.ico`). Both carry this brand's letter and colors; replace
61
+ them when you rename the brand.
62
+
47
63
  ## Layout
48
64
 
49
65
  ```
@@ -51,8 +67,11 @@ app.ts Subscriber + SubscriberCount + User + policies
51
67
  lib/site.config.ts ALL copy + brand + offerings + newsletter (edit this)
52
68
  functions/subscribe.ts public mutation: validate + dedupe + count
53
69
  functions/subscriberStats.ts owner-only query: subscribers + emails
54
- app/page.tsx the landing page (server-rendered)
55
- app/newsletter-signup.tsx client island: signup form + live counter
70
+ functions/seedDemo.ts dev-only demo subscribers (see Demo data)
71
+ lib/stats.ts dashboard types + the public count
72
+ lib/demo.ts demo subscribers + the dev-only gate
73
+ app/(marketing)/page.tsx the landing page (server-rendered)
74
+ app/(marketing)/newsletter-signup.tsx client island: signup form + live counter
56
75
  app/dashboard/ owner dashboard (auth-gated, live)
57
76
  ```
58
77
 
@@ -4,6 +4,7 @@ import React, { useEffect, useRef, useState } from "react";
4
4
  import { db, callFn } from "@pylonsync/react";
5
5
  import { EnsureGuest } from "@pylonsync/client";
6
6
  import type { CreatorConfig } from "@/lib/site.config";
7
+ import { publicCount } from "@/lib/stats";
7
8
 
8
9
  // The newsletter signup — email capture plus a LIVE subscriber counter. This is
9
10
  // the realtime proof: the counter is a live `db.useQuery("SubscriberCount")`
@@ -23,7 +24,11 @@ export function NewsletterSignup({ newsletter }: Props) {
23
24
  <div>
24
25
  <SubscribeForm newsletter={newsletter} />
25
26
  <div className="mt-3">
26
- <LiveCounter seed={newsletter.seedCount ?? 0} label={newsletter.counterLabel} />
27
+ <LiveCounter
28
+ importedCount={newsletter.importedCount}
29
+ label={newsletter.counterLabel}
30
+ labelOne={newsletter.counterLabelOne}
31
+ />
27
32
  </div>
28
33
  </div>
29
34
  );
@@ -100,27 +105,43 @@ interface SubscriberCountRow {
100
105
  count: number;
101
106
  }
102
107
 
103
- function LiveCounter({ seed, label }: { seed: number; label: string }) {
108
+ type CounterProps = { importedCount: number; label: string; labelOne: string };
109
+
110
+ export function LiveCounter(props: CounterProps) {
104
111
  return (
105
- <EnsureGuest fallback={<CounterView value={seed} label={label} />}>
106
- <LiveCounterInner seed={seed} label={label} />
112
+ <EnsureGuest fallback={null}>
113
+ <LiveCounterInner {...props} />
107
114
  </EnsureGuest>
108
115
  );
109
116
  }
110
117
 
111
- function LiveCounterInner({ seed, label }: { seed: number; label: string }) {
118
+ function LiveCounterInner({ importedCount, label, labelOne }: CounterProps) {
112
119
  const { data, loading } = db.useQuery<SubscriberCountRow>("SubscriberCount");
113
- const real = data.length > 0 ? data[0].count : 0;
114
- return <CounterView value={seed + real} label={label} live={!loading} />;
120
+
121
+ // In `pylon dev` an empty list gets demo subscribers (functions/seedDemo.ts).
122
+ // The call goes out only when the synced count is empty, so a live site with
123
+ // subscribers never makes it.
124
+ const empty = !loading && data.length === 0;
125
+ const sent = useRef(false);
126
+ useEffect(() => {
127
+ if (!empty || sent.current) return;
128
+ sent.current = true;
129
+ void callFn("seedDemo", {}).catch(() => {});
130
+ }, [empty]);
131
+
132
+ if (loading) return null;
133
+ const value = publicCount(data, importedCount);
134
+ // A new newsletter has no subscribers yet; show nothing rather than "0".
135
+ if (value === 0) return null;
136
+ return <CounterView value={value} label={value === 1 ? labelOne : label} />;
115
137
  }
116
138
 
117
- // Plain text under the form. The fallback shows the seed until the guest
118
- // session connects; `live` is set once the count is synced.
119
- function CounterView({ value, label, live }: { value: number; label: string; live?: boolean }) {
139
+ // Plain text under the form, shown once the count has synced.
140
+ function CounterView({ value, label }: { value: number; label: string }) {
120
141
  const shown = useCountUp(value);
121
142
  return (
122
- <p className="text-[15px] text-ink-2" data-live={live ? "true" : undefined}>
123
- <span className="tabular-nums text-ink">{shown.toLocaleString()}</span> {label}
143
+ <p className="text-[15px] text-ink-2" data-testid="live-count">
144
+ <span className="tabular-nums text-ink">{shown.toLocaleString("en-US")}</span> {label}
124
145
  </p>
125
146
  );
126
147
  }
@@ -51,7 +51,7 @@ export function AuthForm() {
51
51
  <div className="space-y-5">
52
52
  <form onSubmit={onSubmit} className="space-y-4">
53
53
  <label className="block">
54
- <span className="mb-1.5 block text-[13px] font-medium text-zinc-700">Email</span>
54
+ <span className="mb-1.5 block text-[15px] text-ink-2">Email</span>
55
55
  <input
56
56
  type="email"
57
57
  value={email}
@@ -59,11 +59,11 @@ export function AuthForm() {
59
59
  required
60
60
  autoComplete="email"
61
61
  placeholder="you@yourbusiness.com"
62
- className="h-10 w-full rounded-lg border border-zinc-300 bg-white px-3 text-sm text-zinc-900 outline-none transition placeholder:text-zinc-400 focus:border-brand focus:ring-2 focus:ring-brand/20"
62
+ className="h-11 w-full rounded-md border border-rule bg-white px-3.5 text-[16px] text-ink outline-none transition placeholder:text-ink-2/60 focus:border-brand focus:ring-2 focus:ring-brand/20"
63
63
  />
64
64
  </label>
65
65
  <label className="block">
66
- <span className="mb-1.5 block text-[13px] font-medium text-zinc-700">Password</span>
66
+ <span className="mb-1.5 block text-[15px] text-ink-2">Password</span>
67
67
  <input
68
68
  type="password"
69
69
  value={password}
@@ -71,7 +71,7 @@ export function AuthForm() {
71
71
  required
72
72
  autoComplete={mode === "login" ? "current-password" : "new-password"}
73
73
  placeholder={mode === "login" ? "Your password" : "At least 10 characters"}
74
- className="h-10 w-full rounded-lg border border-zinc-300 bg-white px-3 text-sm text-zinc-900 outline-none transition placeholder:text-zinc-400 focus:border-brand focus:ring-2 focus:ring-brand/20"
74
+ className="h-11 w-full rounded-md border border-rule bg-white px-3.5 text-[16px] text-ink outline-none transition placeholder:text-ink-2/60 focus:border-brand focus:ring-2 focus:ring-brand/20"
75
75
  />
76
76
  </label>
77
77
  {error ? (
@@ -82,13 +82,13 @@ export function AuthForm() {
82
82
  <button
83
83
  type="submit"
84
84
  disabled={pending}
85
- className="inline-flex h-10 w-full items-center justify-center rounded-lg bg-zinc-900 text-sm font-medium text-white transition-colors hover:bg-zinc-700 disabled:opacity-60"
85
+ className="inline-flex h-11 w-full items-center justify-center rounded-md bg-brand text-[16px] font-medium text-white transition-opacity hover:opacity-90 disabled:opacity-60"
86
86
  >
87
87
  {pending ? "…" : mode === "login" ? "Sign in" : "Create account"}
88
88
  </button>
89
89
  </form>
90
90
 
91
- <p className="text-center text-[13px] text-zinc-500">
91
+ <p className="text-center text-[15px] text-ink-2">
92
92
  {mode === "login" ? "First time here?" : "Already have an account?"}{" "}
93
93
  <button
94
94
  type="button"
@@ -96,7 +96,7 @@ export function AuthForm() {
96
96
  setMode(mode === "login" ? "subscriber" : "login");
97
97
  setError(null);
98
98
  }}
99
- className="font-medium text-zinc-900 underline underline-offset-2"
99
+ className="font-medium text-ink underline decoration-brand/50 underline-offset-4"
100
100
  >
101
101
  {mode === "login" ? "Create the owner account" : "Sign in"}
102
102
  </button>