@pylonsync/create-pylon 0.19.1 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (675) hide show
  1. package/bin/create-pylon.js +30 -7
  2. package/package.json +1 -1
  3. package/templates/agency/.env.example +7 -0
  4. package/templates/agency/README.md +28 -3
  5. package/templates/agency/app/(marketing)/contact-form.tsx +20 -18
  6. package/templates/agency/app/(marketing)/page.tsx +3 -3
  7. package/templates/agency/app/(marketing)/work/page.tsx +3 -3
  8. package/templates/agency/app/auth-form.tsx +4 -4
  9. package/templates/agency/app/dashboard/dashboard-client.tsx +110 -64
  10. package/templates/agency/app/dashboard/page.tsx +14 -16
  11. package/templates/agency/app/icon.svg +3 -0
  12. package/templates/agency/app/login/page.tsx +12 -14
  13. package/templates/agency/components/marketing.tsx +12 -5
  14. package/templates/agency/functions/seedStudioBackoffice.ts +42 -11
  15. package/templates/agency/lib/agency.ts +22 -0
  16. package/templates/agency/lib/demo.ts +118 -0
  17. package/templates/agency/lib/site.config.ts +19 -13
  18. package/templates/agency/public/favicon.ico +0 -0
  19. package/templates/agency/tests/agency.test.ts +57 -0
  20. package/templates/ai-chat/.env.example +13 -27
  21. package/templates/ai-chat/README.md +53 -60
  22. package/templates/ai-chat/app/(chat)/c/[id]/page.tsx +19 -0
  23. package/templates/ai-chat/app/(chat)/chat-client.tsx +657 -622
  24. package/templates/ai-chat/app/(chat)/layout.tsx +6 -43
  25. package/templates/ai-chat/app/(chat)/not-found.tsx +6 -10
  26. package/templates/ai-chat/app/(chat)/page.tsx +7 -11
  27. package/templates/ai-chat/app/auth-form.tsx +45 -38
  28. package/templates/ai-chat/app/error.tsx +9 -12
  29. package/templates/ai-chat/app/globals.css +464 -102
  30. package/templates/ai-chat/app/layout.tsx +11 -19
  31. package/templates/ai-chat/app/login/page.tsx +39 -24
  32. package/templates/ai-chat/app.ts +118 -43
  33. package/templates/ai-chat/components/chat/composer.tsx +193 -0
  34. package/templates/ai-chat/components/chat/empty-state.tsx +65 -0
  35. package/templates/ai-chat/components/chat/icons.tsx +198 -0
  36. package/templates/ai-chat/components/chat/markdown.tsx +164 -0
  37. package/templates/ai-chat/components/chat/message.tsx +183 -0
  38. package/templates/ai-chat/components/chat/setup.tsx +71 -0
  39. package/templates/ai-chat/components/chat/sidebar.tsx +288 -0
  40. package/templates/ai-chat/functions/_beginTurn.ts +145 -0
  41. package/templates/ai-chat/functions/_finishTurn.ts +55 -0
  42. package/templates/ai-chat/functions/assistantStatus.ts +13 -0
  43. package/templates/ai-chat/functions/claimGuestHistory.ts +39 -0
  44. package/templates/ai-chat/functions/createConversation.ts +28 -0
  45. package/templates/ai-chat/functions/deleteConversation.ts +15 -20
  46. package/templates/ai-chat/functions/prepareGuestClaim.ts +32 -0
  47. package/templates/ai-chat/functions/renameConversation.ts +19 -0
  48. package/templates/ai-chat/functions/respond.ts +88 -0
  49. package/templates/ai-chat/functions/seedExamples.ts +46 -0
  50. package/templates/ai-chat/functions/stopResponse.ts +30 -0
  51. package/templates/ai-chat/lib/brand-icon.ts +7 -0
  52. package/templates/ai-chat/lib/chat.ts +277 -0
  53. package/templates/ai-chat/lib/claim.ts +15 -0
  54. package/templates/ai-chat/lib/examples.ts +179 -0
  55. package/templates/ai-chat/lib/highlight.ts +107 -0
  56. package/templates/ai-chat/lib/markdown.ts +317 -0
  57. package/templates/ai-chat/lib/provider.ts +38 -0
  58. package/templates/ai-chat/lib/site.config.ts +55 -39
  59. package/templates/ai-chat/tests/chat.test.ts +174 -0
  60. package/templates/ai-chat/tests/components.test.tsx +57 -0
  61. package/templates/ai-chat/tests/markdown.test.ts +88 -0
  62. package/templates/ai-chat/tests/provider.test.ts +32 -0
  63. package/templates/backend/consumer/apps/api/app/layout.tsx +33 -0
  64. package/templates/backend/consumer/apps/api/app/page.tsx +54 -0
  65. package/templates/backend/consumer/apps/api/app.ts +166 -0
  66. package/templates/backend/consumer/apps/api/functions/addComment.ts +24 -0
  67. package/templates/backend/consumer/apps/api/functions/createPost.ts +19 -36
  68. package/templates/backend/consumer/apps/api/functions/deletePost.ts +15 -13
  69. package/templates/backend/consumer/apps/api/functions/myProfile.ts +5 -8
  70. package/templates/backend/consumer/apps/api/functions/seedDemo.ts +95 -0
  71. package/templates/backend/consumer/apps/api/functions/setFollow.ts +36 -0
  72. package/templates/backend/consumer/apps/api/functions/setLike.ts +36 -0
  73. package/templates/backend/consumer/apps/api/functions/upsertProfile.ts +43 -37
  74. package/templates/backend/consumer/apps/api/lib/seed.ts +89 -0
  75. package/templates/backend/consumer/apps/api/lib/social.ts +50 -0
  76. package/templates/backend/consumer/apps/api/package.json +8 -2
  77. package/templates/backend/consumer/apps/api/public/images/posts/arch.jpg +0 -0
  78. package/templates/backend/consumer/apps/api/public/images/posts/coffee.jpg +0 -0
  79. package/templates/backend/consumer/apps/api/public/images/posts/dog.jpg +0 -0
  80. package/templates/backend/consumer/apps/api/public/images/posts/lisbon.jpg +0 -0
  81. package/templates/backend/consumer/apps/api/public/images/posts/ramen.jpg +0 -0
  82. package/templates/backend/consumer/apps/api/public/images/posts/run.jpg +0 -0
  83. package/templates/backend/consumer/apps/api/public/images/posts/sunset.jpg +0 -0
  84. package/templates/backend/consumer/apps/api/tsconfig.json +12 -2
  85. package/templates/chat/README.md +43 -21
  86. package/templates/chat/app/chat-client.tsx +495 -85
  87. package/templates/chat/app/error.tsx +1 -1
  88. package/templates/chat/app/globals.css +170 -54
  89. package/templates/chat/app/layout.tsx +12 -9
  90. package/templates/chat/app/not-found.tsx +4 -2
  91. package/templates/chat/app/page.tsx +9 -19
  92. package/templates/chat/app.ts +96 -17
  93. package/templates/chat/components/chat/avatar.tsx +45 -0
  94. package/templates/chat/components/chat/composer.tsx +110 -0
  95. package/templates/chat/components/chat/message-list.tsx +285 -0
  96. package/templates/chat/components/chat/sidebar.tsx +190 -0
  97. package/templates/chat/functions/joinChat.ts +83 -0
  98. package/templates/chat/functions/renameMember.ts +25 -0
  99. package/templates/chat/lib/chat.ts +188 -0
  100. package/templates/chat/lib/seed.ts +78 -0
  101. package/templates/chat/tests/chat.test.ts +159 -0
  102. package/templates/chat/tests/composer.test.tsx +54 -0
  103. package/templates/consumer/README.md +3 -3
  104. package/templates/consumer/app/globals.css +10 -3
  105. package/templates/consumer/app/icon.svg +1 -0
  106. package/templates/consumer/components/social/avatar.tsx +25 -11
  107. package/templates/consumer/functions/seedFeed.ts +3 -3
  108. package/templates/consumer/lib/seed.ts +43 -85
  109. package/templates/consumer/public/favicon.ico +0 -0
  110. package/templates/consumer/public/images/posts/arch.jpg +0 -0
  111. package/templates/consumer/public/images/posts/coffee.jpg +0 -0
  112. package/templates/consumer/public/images/posts/dog.jpg +0 -0
  113. package/templates/consumer/public/images/posts/lisbon.jpg +0 -0
  114. package/templates/consumer/public/images/posts/ramen.jpg +0 -0
  115. package/templates/consumer/public/images/posts/run.jpg +0 -0
  116. package/templates/consumer/public/images/posts/sunset.jpg +0 -0
  117. package/templates/consumer/tests/seed.test.ts +39 -0
  118. package/templates/creator/.env.example +7 -0
  119. package/templates/creator/README.md +24 -5
  120. package/templates/creator/app/(marketing)/newsletter-signup.tsx +33 -12
  121. package/templates/creator/app/auth-form.tsx +7 -7
  122. package/templates/creator/app/dashboard/dashboard-client.tsx +137 -100
  123. package/templates/creator/app/dashboard/page.tsx +18 -18
  124. package/templates/creator/app/icon.svg +2 -0
  125. package/templates/creator/app/login/page.tsx +14 -14
  126. package/templates/creator/app.ts +4 -4
  127. package/templates/creator/functions/seedDemo.ts +32 -0
  128. package/templates/creator/functions/subscribe.ts +4 -15
  129. package/templates/creator/lib/demo.ts +94 -0
  130. package/templates/creator/lib/site.config.ts +15 -10
  131. package/templates/creator/lib/stats.ts +35 -0
  132. package/templates/creator/public/favicon.ico +0 -0
  133. package/templates/creator/tests/counter.test.ts +92 -0
  134. package/templates/creator/tests/live-count.test.tsx +45 -0
  135. package/templates/crm/README.md +22 -1
  136. package/templates/crm/app/apple-icon.png +0 -0
  137. package/templates/crm/app/companies/companies-view.tsx +32 -14
  138. package/templates/crm/app/companies/page.tsx +2 -1
  139. package/templates/crm/app/contacts/contacts-view.tsx +34 -10
  140. package/templates/crm/app/contacts/page.tsx +2 -1
  141. package/templates/crm/app/deals/[id]/deal-view.tsx +137 -98
  142. package/templates/crm/app/deals/[id]/page.tsx +2 -1
  143. package/templates/crm/app/globals.css +29 -14
  144. package/templates/crm/app/icon.svg +1 -0
  145. package/templates/crm/app/layout.tsx +7 -5
  146. package/templates/crm/app/page.tsx +2 -1
  147. package/templates/crm/app/pipeline-view.tsx +5 -5
  148. package/templates/crm/app/workspace.tsx +29 -17
  149. package/templates/crm/app.ts +5 -5
  150. package/templates/crm/components/activity-timeline.tsx +14 -4
  151. package/templates/crm/components/app-shell.tsx +94 -0
  152. package/templates/crm/components/auth-form.tsx +35 -5
  153. package/templates/crm/components/avatar.tsx +5 -1
  154. package/templates/crm/components/board-skeleton.tsx +5 -5
  155. package/templates/crm/components/brand-mark.tsx +25 -0
  156. package/templates/crm/components/command-palette.tsx +17 -17
  157. package/templates/crm/components/data-table.tsx +97 -36
  158. package/templates/crm/components/deal-card.tsx +23 -27
  159. package/templates/crm/components/filter-tabs.tsx +101 -0
  160. package/templates/crm/components/metrics-bar.tsx +6 -6
  161. package/templates/crm/components/nav.tsx +15 -0
  162. package/templates/crm/components/page-header.tsx +43 -7
  163. package/templates/crm/components/pipeline-board.tsx +16 -10
  164. package/templates/crm/components/require-auth.tsx +24 -26
  165. package/templates/crm/components/sidebar.tsx +54 -49
  166. package/templates/crm/components/stage-badge.tsx +4 -2
  167. package/templates/crm/components/ui/button.tsx +5 -4
  168. package/templates/crm/components/ui/dialog.tsx +1 -1
  169. package/templates/crm/components/ui/input.tsx +1 -1
  170. package/templates/crm/components/ui/select.tsx +1 -1
  171. package/templates/crm/components/ui/textarea.tsx +1 -1
  172. package/templates/crm/functions/seedWorkspace.ts +32 -17
  173. package/templates/crm/lib/brand.ts +9 -0
  174. package/templates/crm/lib/demo-team.ts +51 -0
  175. package/templates/crm/lib/pipeline.ts +37 -0
  176. package/templates/crm/lib/search.ts +4 -3
  177. package/templates/crm/lib/seed.ts +53 -8
  178. package/templates/crm/public/favicon.ico +0 -0
  179. package/templates/crm/tests/components.test.tsx +5 -5
  180. package/templates/crm/tests/demo-team.test.ts +18 -0
  181. package/templates/crm/tests/pipeline.test.ts +59 -3
  182. package/templates/directory/.env.example +7 -0
  183. package/templates/directory/README.md +22 -3
  184. package/templates/directory/app/(marketing)/directory-browse.tsx +30 -12
  185. package/templates/directory/app/(marketing)/page.tsx +6 -4
  186. package/templates/directory/app/auth-form.tsx +4 -4
  187. package/templates/directory/app/dashboard/dashboard-client.tsx +177 -90
  188. package/templates/directory/app/dashboard/page.tsx +16 -14
  189. package/templates/directory/app/icon.svg +4 -0
  190. package/templates/directory/app/login/page.tsx +16 -15
  191. package/templates/directory/functions/seedDemo.ts +43 -0
  192. package/templates/directory/lib/demo.ts +145 -0
  193. package/templates/directory/lib/directory.ts +27 -0
  194. package/templates/directory/lib/site.config.ts +21 -16
  195. package/templates/directory/public/favicon.ico +0 -0
  196. package/templates/directory/tests/directory.test.ts +46 -0
  197. package/templates/expo/consumer/apps/expo/App.tsx +360 -288
  198. package/templates/expo/consumer/apps/expo/app.json +9 -1
  199. package/templates/expo/consumer/apps/expo/package.json +2 -1
  200. package/templates/helpdesk/README.md +21 -0
  201. package/templates/helpdesk/app/apple-icon.png +0 -0
  202. package/templates/helpdesk/app/customers/customers-view.tsx +23 -7
  203. package/templates/helpdesk/app/customers/page.tsx +2 -1
  204. package/templates/helpdesk/app/globals.css +29 -14
  205. package/templates/helpdesk/app/icon.svg +1 -0
  206. package/templates/helpdesk/app/inbox-view.tsx +66 -47
  207. package/templates/helpdesk/app/layout.tsx +7 -5
  208. package/templates/helpdesk/app/page.tsx +4 -7
  209. package/templates/helpdesk/app/tickets/[id]/page.tsx +2 -1
  210. package/templates/helpdesk/app/tickets/[id]/ticket-view.tsx +134 -97
  211. package/templates/helpdesk/app/workspace.tsx +33 -22
  212. package/templates/helpdesk/app.ts +2 -2
  213. package/templates/helpdesk/components/app-shell.tsx +94 -0
  214. package/templates/helpdesk/components/auth-form.tsx +35 -5
  215. package/templates/helpdesk/components/avatar.tsx +5 -1
  216. package/templates/helpdesk/components/brand-mark.tsx +28 -0
  217. package/templates/helpdesk/components/command-palette.tsx +17 -17
  218. package/templates/helpdesk/components/data-table.tsx +97 -36
  219. package/templates/helpdesk/components/filter-tabs.tsx +101 -0
  220. package/templates/helpdesk/components/nav.tsx +14 -0
  221. package/templates/helpdesk/components/page-header.tsx +43 -7
  222. package/templates/helpdesk/components/priority-badge.tsx +49 -6
  223. package/templates/helpdesk/components/require-auth.tsx +24 -26
  224. package/templates/helpdesk/components/sidebar.tsx +54 -48
  225. package/templates/helpdesk/components/sla-indicator.tsx +5 -8
  226. package/templates/helpdesk/components/thread.tsx +7 -7
  227. package/templates/helpdesk/components/ticket-list.tsx +79 -19
  228. package/templates/helpdesk/components/ui/button.tsx +5 -4
  229. package/templates/helpdesk/components/ui/dialog.tsx +1 -1
  230. package/templates/helpdesk/components/ui/input.tsx +1 -1
  231. package/templates/helpdesk/components/ui/select.tsx +1 -1
  232. package/templates/helpdesk/components/ui/textarea.tsx +1 -1
  233. package/templates/helpdesk/functions/seedWorkspace.ts +25 -15
  234. package/templates/helpdesk/lib/brand.ts +9 -0
  235. package/templates/helpdesk/lib/demo-team.ts +51 -0
  236. package/templates/helpdesk/lib/search.ts +4 -3
  237. package/templates/helpdesk/lib/seed.ts +43 -2
  238. package/templates/helpdesk/lib/tickets.ts +68 -20
  239. package/templates/helpdesk/public/favicon.ico +0 -0
  240. package/templates/helpdesk/tests/components.test.tsx +10 -0
  241. package/templates/helpdesk/tests/demo-team.test.ts +18 -0
  242. package/templates/helpdesk/tests/tickets.test.ts +70 -0
  243. package/templates/imessage/.env.example +49 -0
  244. package/templates/imessage/AGENTS.md +153 -0
  245. package/templates/imessage/CLAUDE.md +7 -0
  246. package/templates/imessage/README.md +187 -0
  247. package/templates/imessage/app/console.tsx +382 -0
  248. package/templates/imessage/app/contacts/page.tsx +13 -0
  249. package/templates/imessage/app/error.tsx +16 -0
  250. package/templates/imessage/app/gate.tsx +31 -0
  251. package/templates/imessage/app/globals.css +183 -0
  252. package/templates/imessage/app/layout.tsx +17 -0
  253. package/templates/imessage/app/login/login-form.tsx +115 -0
  254. package/templates/imessage/app/login/page.tsx +30 -0
  255. package/templates/imessage/app/not-found.tsx +13 -0
  256. package/templates/imessage/app/page.tsx +14 -0
  257. package/templates/imessage/app/robots.ts +6 -0
  258. package/templates/imessage/app/setup/page.tsx +13 -0
  259. package/templates/imessage/app/sign-out.tsx +20 -0
  260. package/templates/imessage/app.ts +299 -0
  261. package/templates/imessage/bunfig +4 -0
  262. package/templates/imessage/components/avatar.tsx +46 -0
  263. package/templates/imessage/components/contact-panel.tsx +263 -0
  264. package/templates/imessage/components/contacts-view.tsx +172 -0
  265. package/templates/imessage/components/conversation-list.tsx +129 -0
  266. package/templates/imessage/components/nav-rail.tsx +67 -0
  267. package/templates/imessage/components/setup-view.tsx +408 -0
  268. package/templates/imessage/components/thread.tsx +229 -0
  269. package/templates/imessage/components/ui/badge.tsx +37 -0
  270. package/templates/imessage/components/ui/button.tsx +56 -0
  271. package/templates/imessage/components/ui/card.tsx +90 -0
  272. package/templates/imessage/components/ui/dialog.tsx +110 -0
  273. package/templates/imessage/components/ui/input.tsx +30 -0
  274. package/templates/imessage/components/ui/label.tsx +21 -0
  275. package/templates/imessage/components/ui/select.tsx +39 -0
  276. package/templates/imessage/components/ui/table.tsx +124 -0
  277. package/templates/imessage/components/ui/textarea.tsx +24 -0
  278. package/templates/imessage/components.json +20 -0
  279. package/templates/imessage/functions/answerNow.ts +34 -0
  280. package/templates/imessage/functions/assistant.ts +96 -0
  281. package/templates/imessage/functions/beginTurn.ts +101 -0
  282. package/templates/imessage/functions/cancelReminder.ts +13 -0
  283. package/templates/imessage/functions/claimOutbound.ts +44 -0
  284. package/templates/imessage/functions/clearDemo.ts +18 -0
  285. package/templates/imessage/functions/deleteNote.ts +11 -0
  286. package/templates/imessage/functions/deliverOutbound.ts +55 -0
  287. package/templates/imessage/functions/finishTurn.ts +84 -0
  288. package/templates/imessage/functions/fireReminder.ts +42 -0
  289. package/templates/imessage/functions/ingestInbound.ts +22 -0
  290. package/templates/imessage/functions/ownerAccount.ts +13 -0
  291. package/templates/imessage/functions/processTurn.ts +74 -0
  292. package/templates/imessage/functions/relayApply.ts +102 -0
  293. package/templates/imessage/functions/relaySync.ts +63 -0
  294. package/templates/imessage/functions/releaseOutbound.ts +18 -0
  295. package/templates/imessage/functions/resolveTurn.ts +25 -0
  296. package/templates/imessage/functions/saveContact.ts +27 -0
  297. package/templates/imessage/functions/seedDemo.ts +70 -0
  298. package/templates/imessage/functions/sendOwnerMessage.ts +28 -0
  299. package/templates/imessage/functions/sendblueWebhook.ts +49 -0
  300. package/templates/imessage/functions/setContactStatus.ts +17 -0
  301. package/templates/imessage/functions/settleOutbound.ts +35 -0
  302. package/templates/imessage/functions/setupStatus.ts +33 -0
  303. package/templates/imessage/functions/sweep.ts +58 -0
  304. package/templates/imessage/functions/toolCancelReminder.ts +17 -0
  305. package/templates/imessage/functions/toolForgetNote.ts +15 -0
  306. package/templates/imessage/functions/toolListNotes.ts +16 -0
  307. package/templates/imessage/functions/toolListReminders.ts +17 -0
  308. package/templates/imessage/functions/toolSaveNote.ts +22 -0
  309. package/templates/imessage/functions/toolSetReminder.ts +55 -0
  310. package/templates/imessage/functions/updateSettings.ts +48 -0
  311. package/templates/imessage/gitignore +11 -0
  312. package/templates/imessage/lib/agent-call.ts +251 -0
  313. package/templates/imessage/lib/assistant.ts +74 -0
  314. package/templates/imessage/lib/chunk.ts +135 -0
  315. package/templates/imessage/lib/demo.ts +68 -0
  316. package/templates/imessage/lib/format.ts +170 -0
  317. package/templates/imessage/lib/gate.ts +104 -0
  318. package/templates/imessage/lib/handles.ts +67 -0
  319. package/templates/imessage/lib/provider.ts +38 -0
  320. package/templates/imessage/lib/relay-protocol.ts +95 -0
  321. package/templates/imessage/lib/reminders.ts +41 -0
  322. package/templates/imessage/lib/secrets.ts +59 -0
  323. package/templates/imessage/lib/sendblue.ts +173 -0
  324. package/templates/imessage/lib/store.ts +407 -0
  325. package/templates/imessage/lib/transport.ts +135 -0
  326. package/templates/imessage/lib/utils.ts +10 -0
  327. package/templates/imessage/package.json +40 -0
  328. package/templates/imessage/relay/.env.example +14 -0
  329. package/templates/imessage/relay/applescript.ts +70 -0
  330. package/templates/imessage/relay/chatdb.ts +165 -0
  331. package/templates/imessage/relay/client.ts +41 -0
  332. package/templates/imessage/relay/main.ts +137 -0
  333. package/templates/imessage/relay/state.ts +35 -0
  334. package/templates/imessage/tests/agent-call.test.ts +112 -0
  335. package/templates/imessage/tests/applescript.test.ts +88 -0
  336. package/templates/imessage/tests/chatdb.test.ts +139 -0
  337. package/templates/imessage/tests/chunk.test.ts +61 -0
  338. package/templates/imessage/tests/fake-ctx.ts +88 -0
  339. package/templates/imessage/tests/ingest.test.ts +141 -0
  340. package/templates/imessage/tests/lib.test.ts +99 -0
  341. package/templates/imessage/tests/review-fixes.test.ts +138 -0
  342. package/templates/imessage/tests/secrets.test.ts +36 -0
  343. package/templates/imessage/tests/sendblue.test.ts +143 -0
  344. package/templates/imessage/tests/setup.ts +5 -0
  345. package/templates/imessage/tsconfig.json +18 -0
  346. package/templates/inventory/README.md +21 -0
  347. package/templates/inventory/app/apple-icon.png +0 -0
  348. package/templates/inventory/app/globals.css +29 -14
  349. package/templates/inventory/app/icon.svg +1 -0
  350. package/templates/inventory/app/layout.tsx +7 -5
  351. package/templates/inventory/app/movements/movements-view.tsx +32 -10
  352. package/templates/inventory/app/movements/page.tsx +2 -1
  353. package/templates/inventory/app/page.tsx +4 -6
  354. package/templates/inventory/app/products/[id]/page.tsx +2 -1
  355. package/templates/inventory/app/products/[id]/product-view.tsx +55 -25
  356. package/templates/inventory/app/products-view.tsx +74 -33
  357. package/templates/inventory/app/workspace.tsx +34 -16
  358. package/templates/inventory/app.ts +10 -2
  359. package/templates/inventory/components/app-shell.tsx +94 -0
  360. package/templates/inventory/components/auth-form.tsx +35 -5
  361. package/templates/inventory/components/avatar.tsx +5 -1
  362. package/templates/inventory/components/brand-mark.tsx +25 -0
  363. package/templates/inventory/components/category-tile.tsx +49 -0
  364. package/templates/inventory/components/command-palette.tsx +17 -17
  365. package/templates/inventory/components/data-table.tsx +97 -36
  366. package/templates/inventory/components/filter-tabs.tsx +101 -0
  367. package/templates/inventory/components/nav.tsx +14 -0
  368. package/templates/inventory/components/page-header.tsx +43 -7
  369. package/templates/inventory/components/require-auth.tsx +24 -26
  370. package/templates/inventory/components/sidebar.tsx +54 -48
  371. package/templates/inventory/components/stock-level.tsx +30 -14
  372. package/templates/inventory/components/summary-bar.tsx +8 -15
  373. package/templates/inventory/components/ui/button.tsx +5 -4
  374. package/templates/inventory/components/ui/dialog.tsx +1 -1
  375. package/templates/inventory/components/ui/input.tsx +1 -1
  376. package/templates/inventory/components/ui/select.tsx +1 -1
  377. package/templates/inventory/components/ui/textarea.tsx +1 -1
  378. package/templates/inventory/functions/seedWorkspace.ts +18 -8
  379. package/templates/inventory/lib/brand.ts +9 -0
  380. package/templates/inventory/lib/demo-team.ts +51 -0
  381. package/templates/inventory/lib/search.ts +4 -3
  382. package/templates/inventory/lib/seed.ts +29 -2
  383. package/templates/inventory/lib/stock.ts +32 -0
  384. package/templates/inventory/public/favicon.ico +0 -0
  385. package/templates/inventory/tests/demo-team.test.ts +18 -0
  386. package/templates/inventory/tests/stock.test.ts +29 -0
  387. package/templates/invoices/README.md +16 -0
  388. package/templates/invoices/app/apple-icon.png +0 -0
  389. package/templates/invoices/app/clients/clients-view.tsx +18 -6
  390. package/templates/invoices/app/clients/page.tsx +2 -1
  391. package/templates/invoices/app/globals.css +29 -14
  392. package/templates/invoices/app/icon.svg +1 -0
  393. package/templates/invoices/app/invoices/[id]/invoice-view.tsx +72 -48
  394. package/templates/invoices/app/invoices/[id]/page.tsx +2 -1
  395. package/templates/invoices/app/invoices-view.tsx +81 -32
  396. package/templates/invoices/app/layout.tsx +7 -5
  397. package/templates/invoices/app/page.tsx +4 -6
  398. package/templates/invoices/app/workspace.tsx +26 -16
  399. package/templates/invoices/app.ts +2 -2
  400. package/templates/invoices/components/app-shell.tsx +94 -0
  401. package/templates/invoices/components/auth-form.tsx +35 -5
  402. package/templates/invoices/components/avatar.tsx +5 -1
  403. package/templates/invoices/components/brand-mark.tsx +26 -0
  404. package/templates/invoices/components/command-palette.tsx +17 -17
  405. package/templates/invoices/components/data-table.tsx +97 -36
  406. package/templates/invoices/components/filter-tabs.tsx +101 -0
  407. package/templates/invoices/components/line-items.tsx +19 -14
  408. package/templates/invoices/components/nav.tsx +14 -0
  409. package/templates/invoices/components/page-header.tsx +43 -7
  410. package/templates/invoices/components/require-auth.tsx +24 -26
  411. package/templates/invoices/components/sidebar.tsx +54 -48
  412. package/templates/invoices/components/status-badge.tsx +7 -7
  413. package/templates/invoices/components/summary-bar.tsx +5 -5
  414. package/templates/invoices/components/ui/button.tsx +5 -4
  415. package/templates/invoices/components/ui/dialog.tsx +1 -1
  416. package/templates/invoices/components/ui/input.tsx +1 -1
  417. package/templates/invoices/components/ui/select.tsx +1 -1
  418. package/templates/invoices/components/ui/textarea.tsx +1 -1
  419. package/templates/invoices/functions/recordPayment.ts +3 -0
  420. package/templates/invoices/lib/billing.ts +40 -0
  421. package/templates/invoices/lib/brand.ts +9 -0
  422. package/templates/invoices/lib/search.ts +4 -3
  423. package/templates/invoices/public/favicon.ico +0 -0
  424. package/templates/invoices/tests/billing.test.ts +27 -0
  425. package/templates/ios/consumer/apps/ios/Package.swift +4 -3
  426. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ActivityView.swift +107 -0
  427. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/AppModel.swift +171 -0
  428. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/AuthView.swift +182 -0
  429. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/CommentsView.swift +136 -0
  430. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ComposeView.swift +152 -0
  431. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ExploreView.swift +117 -0
  432. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/FeedView.swift +175 -159
  433. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/MainTabView.swift +54 -0
  434. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Media.swift +271 -0
  435. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Models.swift +101 -4
  436. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/PostCard.swift +245 -0
  437. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileEditorView.swift +208 -0
  438. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileView.swift +183 -0
  439. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/RootView.swift +25 -31
  440. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/SocialStore.swift +285 -0
  441. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/StoryViewer.swift +155 -0
  442. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Theme.swift +82 -0
  443. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/__APP_NAME_PASCAL__App.swift +4 -47
  444. package/templates/ios/consumer/apps/ios/project.yml +13 -9
  445. package/templates/local-service/.env.example +7 -0
  446. package/templates/local-service/README.md +26 -2
  447. package/templates/local-service/app/(marketing)/booking-widget.tsx +48 -9
  448. package/templates/local-service/app/(marketing)/page.tsx +66 -42
  449. package/templates/local-service/app/auth-form.tsx +8 -8
  450. package/templates/local-service/app/dashboard/dashboard-client.tsx +119 -92
  451. package/templates/local-service/app/dashboard/page.tsx +14 -14
  452. package/templates/local-service/app/icon.svg +2 -0
  453. package/templates/local-service/app/login/page.tsx +15 -15
  454. package/templates/local-service/functions/seedDemo.ts +49 -0
  455. package/templates/local-service/lib/demo.ts +135 -0
  456. package/templates/local-service/lib/hours.ts +44 -0
  457. package/templates/local-service/lib/site.config.ts +8 -3
  458. package/templates/local-service/lib/slots.ts +3 -0
  459. package/templates/local-service/public/favicon.ico +0 -0
  460. package/templates/local-service/tests/booking.test.ts +104 -0
  461. package/templates/marketplace/README.md +23 -15
  462. package/templates/marketplace/app/globals.css +172 -114
  463. package/templates/marketplace/app/layout.tsx +75 -30
  464. package/templates/marketplace/app/listing/[id]/page.tsx +210 -123
  465. package/templates/marketplace/app/me/page.tsx +4 -4
  466. package/templates/marketplace/app/page.tsx +221 -298
  467. package/templates/marketplace/app/sell/page.tsx +8 -11
  468. package/templates/marketplace/app.ts +32 -7
  469. package/templates/marketplace/client/ActivityFeed.tsx +311 -0
  470. package/templates/marketplace/client/AuthNav.tsx +24 -16
  471. package/templates/marketplace/client/ListingCard.tsx +118 -0
  472. package/templates/marketplace/client/LiveGrid.tsx +92 -0
  473. package/templates/marketplace/client/LiveListingStatus.tsx +32 -24
  474. package/templates/marketplace/client/LoginCard.tsx +114 -136
  475. package/templates/marketplace/client/MarketProvider.tsx +3 -5
  476. package/templates/marketplace/client/MyMarket.tsx +371 -185
  477. package/templates/marketplace/client/OfferPanel.tsx +421 -287
  478. package/templates/marketplace/client/SellForm.tsx +271 -192
  479. package/templates/marketplace/client/ThemeToggle.tsx +1 -0
  480. package/templates/marketplace/client/WatchButton.tsx +16 -16
  481. package/templates/marketplace/client/market.ts +61 -14
  482. package/templates/marketplace/functions/seedMarket.ts +143 -36
  483. package/templates/marketplace/functions/seedOffers.ts +101 -0
  484. package/templates/marketplace/lib/catalog.ts +72 -1
  485. package/templates/marketplace/public/images/listings/ceramic-lamp-640.webp +0 -0
  486. package/templates/marketplace/public/images/listings/ceramic-lamp.webp +0 -0
  487. package/templates/marketplace/public/images/listings/duffel-640.webp +0 -0
  488. package/templates/marketplace/public/images/listings/duffel.webp +0 -0
  489. package/templates/marketplace/public/images/listings/film-camera-640.webp +0 -0
  490. package/templates/marketplace/public/images/listings/film-camera.webp +0 -0
  491. package/templates/marketplace/public/images/listings/lounge-chair-640.webp +0 -0
  492. package/templates/marketplace/public/images/listings/lounge-chair.webp +0 -0
  493. package/templates/marketplace/public/images/listings/road-bike-640.webp +0 -0
  494. package/templates/marketplace/public/images/listings/road-bike.webp +0 -0
  495. package/templates/marketplace/public/images/listings/slipper-chair-640.webp +0 -0
  496. package/templates/marketplace/public/images/listings/slipper-chair.webp +0 -0
  497. package/templates/marketplace/public/images/listings/turntable-640.webp +0 -0
  498. package/templates/marketplace/public/images/listings/turntable.webp +0 -0
  499. package/templates/marketplace/public/images/listings/wardrobe-640.webp +0 -0
  500. package/templates/marketplace/public/images/listings/wardrobe.webp +0 -0
  501. package/templates/marketplace/public/images/listings/watch-640.webp +0 -0
  502. package/templates/marketplace/public/images/listings/watch.webp +0 -0
  503. package/templates/marketplace/public/images/listings/weekender-bag-640.webp +0 -0
  504. package/templates/marketplace/public/images/listings/weekender-bag.webp +0 -0
  505. package/templates/marketplace/tests/example.test.ts +64 -2
  506. package/templates/marketplace/tests/listing-card.test.tsx +61 -0
  507. package/templates/projects/README.md +24 -3
  508. package/templates/projects/app/apple-icon.png +0 -0
  509. package/templates/projects/app/clients/clients-view.tsx +20 -6
  510. package/templates/projects/app/clients/page.tsx +2 -1
  511. package/templates/projects/app/globals.css +29 -14
  512. package/templates/projects/app/icon.svg +1 -0
  513. package/templates/projects/app/layout.tsx +7 -5
  514. package/templates/projects/app/page.tsx +3 -5
  515. package/templates/projects/app/projects/[id]/page.tsx +2 -1
  516. package/templates/projects/app/projects/[id]/project-view.tsx +45 -30
  517. package/templates/projects/app/projects-view.tsx +105 -52
  518. package/templates/projects/app/workspace.tsx +41 -20
  519. package/templates/projects/app.ts +2 -2
  520. package/templates/projects/components/app-shell.tsx +94 -0
  521. package/templates/projects/components/auth-form.tsx +35 -5
  522. package/templates/projects/components/avatar.tsx +5 -1
  523. package/templates/projects/components/board-skeleton.tsx +3 -3
  524. package/templates/projects/components/brand-mark.tsx +25 -0
  525. package/templates/projects/components/command-palette.tsx +17 -17
  526. package/templates/projects/components/data-table.tsx +97 -36
  527. package/templates/projects/components/filter-tabs.tsx +101 -0
  528. package/templates/projects/components/meter.tsx +48 -0
  529. package/templates/projects/components/nav.tsx +14 -0
  530. package/templates/projects/components/page-header.tsx +43 -7
  531. package/templates/projects/components/require-auth.tsx +24 -26
  532. package/templates/projects/components/sidebar.tsx +54 -48
  533. package/templates/projects/components/summary-bar.tsx +49 -0
  534. package/templates/projects/components/task-board.tsx +10 -9
  535. package/templates/projects/components/time-dialog.tsx +29 -5
  536. package/templates/projects/components/ui/button.tsx +5 -4
  537. package/templates/projects/components/ui/dialog.tsx +1 -1
  538. package/templates/projects/components/ui/input.tsx +1 -1
  539. package/templates/projects/components/ui/select.tsx +1 -1
  540. package/templates/projects/components/ui/textarea.tsx +1 -1
  541. package/templates/projects/functions/seedWorkspace.ts +26 -12
  542. package/templates/projects/lib/brand.ts +9 -0
  543. package/templates/projects/lib/demo-team.ts +51 -0
  544. package/templates/projects/lib/search.ts +4 -3
  545. package/templates/projects/lib/seed.ts +35 -1
  546. package/templates/projects/lib/work.ts +63 -0
  547. package/templates/projects/public/favicon.ico +0 -0
  548. package/templates/projects/tests/demo-team.test.ts +18 -0
  549. package/templates/projects/tests/work.test.ts +65 -0
  550. package/templates/restaurant/.env.example +7 -0
  551. package/templates/restaurant/README.md +29 -3
  552. package/templates/restaurant/app/(marketing)/layout.tsx +4 -2
  553. package/templates/restaurant/app/(marketing)/page.tsx +47 -23
  554. package/templates/restaurant/app/(marketing)/reservation-widget.tsx +37 -7
  555. package/templates/restaurant/app/auth-form.tsx +8 -8
  556. package/templates/restaurant/app/dashboard/dashboard-client.tsx +241 -109
  557. package/templates/restaurant/app/dashboard/page.tsx +14 -14
  558. package/templates/restaurant/app/icon.svg +4 -0
  559. package/templates/restaurant/app/login/page.tsx +15 -15
  560. package/templates/restaurant/functions/seedDemo.ts +44 -0
  561. package/templates/restaurant/lib/demo.ts +144 -0
  562. package/templates/restaurant/lib/hours.ts +45 -0
  563. package/templates/restaurant/lib/site.config.ts +3 -3
  564. package/templates/restaurant/public/favicon.ico +0 -0
  565. package/templates/restaurant/tests/reservations.test.ts +67 -0
  566. package/templates/saas/README.md +54 -35
  567. package/templates/saas/app/(auth)/auth-form.tsx +25 -23
  568. package/templates/saas/app/(auth)/forgot-password/page.tsx +1 -1
  569. package/templates/saas/app/(auth)/layout.tsx +27 -33
  570. package/templates/saas/app/(auth)/login/page.tsx +1 -1
  571. package/templates/saas/app/(auth)/reset-password/page.tsx +1 -1
  572. package/templates/saas/app/(auth)/signup/page.tsx +1 -1
  573. package/templates/saas/app/(marketing)/layout.tsx +127 -253
  574. package/templates/saas/app/(marketing)/page.tsx +133 -372
  575. package/templates/saas/app/(marketing)/pricing/page.tsx +21 -19
  576. package/templates/saas/app/(marketing)/products/[slug]/page.tsx +40 -95
  577. package/templates/saas/app/dashboard/board-client.tsx +416 -0
  578. package/templates/saas/app/dashboard/dashboard-client.tsx +30 -615
  579. package/templates/saas/app/dashboard/layout.tsx +27 -13
  580. package/templates/saas/app/dashboard/overview-client.tsx +332 -0
  581. package/templates/saas/app/dashboard/page.tsx +29 -35
  582. package/templates/saas/app/dashboard/projects/[id]/page.tsx +29 -0
  583. package/templates/saas/app/dashboard/projects/page.tsx +11 -7
  584. package/templates/saas/app/dashboard/projects-client.tsx +398 -0
  585. package/templates/saas/app/globals.css +5 -4
  586. package/templates/saas/app/icon.svg +1 -0
  587. package/templates/saas/app/onboarding/onboarding-client.tsx +93 -26
  588. package/templates/saas/app/onboarding/page.tsx +6 -2
  589. package/templates/saas/app.ts +68 -13
  590. package/templates/saas/components/dashboard-shell.tsx +212 -132
  591. package/templates/saas/components/marketing.tsx +137 -274
  592. package/templates/saas/components/pricing-table.tsx +12 -11
  593. package/templates/saas/components/setup-checklist.tsx +31 -29
  594. package/templates/saas/components/task-ui.tsx +199 -0
  595. package/templates/saas/functions/createProject.ts +7 -13
  596. package/templates/saas/functions/createTask.ts +67 -0
  597. package/templates/saas/functions/deleteProject.ts +17 -0
  598. package/templates/saas/functions/deleteTask.ts +14 -0
  599. package/templates/saas/functions/seedWorkspace.ts +46 -0
  600. package/templates/saas/functions/setProjectStatus.ts +41 -0
  601. package/templates/saas/functions/updateProfile.ts +13 -0
  602. package/templates/saas/functions/updateProject.ts +4 -5
  603. package/templates/saas/functions/updateTask.ts +88 -0
  604. package/templates/saas/lib/members.ts +53 -0
  605. package/templates/saas/lib/names.ts +53 -0
  606. package/templates/saas/lib/plans.ts +11 -0
  607. package/templates/saas/lib/sample-workspace.ts +117 -0
  608. package/templates/saas/lib/site.config.ts +200 -463
  609. package/templates/saas/lib/tasks.ts +101 -0
  610. package/templates/saas/lib/use-live.ts +25 -0
  611. package/templates/saas/public/favicon.ico +0 -0
  612. package/templates/saas/public/screenshots/board.png +0 -0
  613. package/templates/saas/public/screenshots/members.png +0 -0
  614. package/templates/saas/public/screenshots/overview.png +0 -0
  615. package/templates/saas/public/screenshots/projects.png +0 -0
  616. package/templates/saas/scripts/capture-screenshots.mjs +156 -0
  617. package/templates/saas/tests/names.test.ts +35 -0
  618. package/templates/saas/tests/plans.test.ts +12 -0
  619. package/templates/saas/tests/sample-workspace.test.ts +46 -0
  620. package/templates/saas/tests/tasks.test.ts +81 -0
  621. package/templates/shop/README.md +25 -13
  622. package/templates/shop/app/(marketing)/layout.tsx +94 -67
  623. package/templates/shop/app/(marketing)/not-found.tsx +13 -12
  624. package/templates/shop/app/(marketing)/page.tsx +120 -59
  625. package/templates/shop/app/(marketing)/shop-client.tsx +364 -237
  626. package/templates/shop/app/(marketing)/success/page.tsx +75 -22
  627. package/templates/shop/app/globals.css +94 -1
  628. package/templates/shop/app/layout.tsx +4 -4
  629. package/templates/shop/app/login/page.tsx +4 -4
  630. package/templates/shop/app.ts +18 -6
  631. package/templates/shop/components/cart-button.tsx +33 -0
  632. package/templates/shop/components/marketing.tsx +17 -87
  633. package/templates/shop/components/shelf-count.tsx +36 -0
  634. package/templates/shop/functions/checkout.ts +17 -1
  635. package/templates/shop/lib/cart-store.ts +57 -0
  636. package/templates/shop/lib/shelf-store.ts +35 -0
  637. package/templates/shop/lib/shop.ts +27 -0
  638. package/templates/shop/lib/site.config.ts +108 -98
  639. package/templates/shop/public/images/hero.jpg +0 -0
  640. package/templates/shop/public/images/products/cedar-smoke.jpg +0 -0
  641. package/templates/shop/public/images/products/fig-leaf.jpg +0 -0
  642. package/templates/shop/public/images/products/linen.jpg +0 -0
  643. package/templates/shop/public/images/products/sea-salt.jpg +0 -0
  644. package/templates/shop/public/images/products/tobacco-oak.jpg +0 -0
  645. package/templates/shop/tests/shop.test.ts +64 -0
  646. package/templates/waitlist/.env.example +7 -0
  647. package/templates/waitlist/README.md +29 -13
  648. package/templates/waitlist/app/(marketing)/page.tsx +9 -7
  649. package/templates/waitlist/app/(marketing)/waitlist-hero.tsx +64 -31
  650. package/templates/waitlist/app/auth-form.tsx +8 -8
  651. package/templates/waitlist/app/dashboard/dashboard-client.tsx +132 -95
  652. package/templates/waitlist/app/dashboard/page.tsx +17 -16
  653. package/templates/waitlist/app/icon.svg +2 -0
  654. package/templates/waitlist/app/login/page.tsx +14 -14
  655. package/templates/waitlist/app.ts +6 -6
  656. package/templates/waitlist/functions/joinWaitlist.ts +6 -18
  657. package/templates/waitlist/functions/seedDemo.ts +32 -0
  658. package/templates/waitlist/lib/demo.ts +94 -0
  659. package/templates/waitlist/lib/launch.ts +19 -0
  660. package/templates/waitlist/lib/site.config.ts +14 -9
  661. package/templates/waitlist/lib/stats.ts +35 -0
  662. package/templates/waitlist/public/favicon.ico +0 -0
  663. package/templates/waitlist/tests/counter.test.ts +107 -0
  664. package/templates/waitlist/tests/live-count.test.tsx +48 -0
  665. package/templates/ai-chat/tests/example.test.ts +0 -12
  666. package/templates/backend/consumer/apps/api/functions/feed.ts +0 -57
  667. package/templates/backend/consumer/apps/api/functions/profilePosts.ts +0 -17
  668. package/templates/backend/consumer/apps/api/functions/toggleLike.ts +0 -48
  669. package/templates/backend/consumer/apps/api/schema.ts +0 -143
  670. package/templates/chat/functions/_keep.ts +0 -13
  671. package/templates/chat/tests/example.test.ts +0 -12
  672. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileSetupView.swift +0 -68
  673. package/templates/marketplace/client/LiveTicker.tsx +0 -116
  674. package/templates/marketplace/client/ScrollToListingsLink.tsx +0 -29
  675. package/templates/marketplace/public/images/marketplace-hero.webp +0 -0
@@ -2,17 +2,25 @@ import { mutation } from "@pylonsync/functions";
2
2
  import { emailMatchesOwner } from "../lib/owner";
3
3
  import { siteConfig } from "../lib/site.config";
4
4
  import { lineItemsTotal, slugify, type ProjectRow } from "../lib/agency";
5
+ import { DEMO_INQUIRIES, daysAgoDate, demoDataEnabled } from "../lib/demo";
5
6
 
6
- // seedStudioBackoffice — owner-only, idempotent. Fills the dashboard's CRM +
7
- // billing with the demo clients/invoices from config so it isn't an empty shell
8
- // on first sign-in. Private data (PII + money) is only ever seeded for the
9
- // signed-in owner — never by an anonymous visitor — which is why this is
10
- // owner-gated, unlike the public seedProjects.
7
+ // seedStudioBackoffice — owner-only, development-only, idempotent. Fills the
8
+ // dashboard's pipeline, CRM, and billing with demo leads (lib/demo.ts) and the
9
+ // demo clients/invoices from config, so the tabs are not empty on first
10
+ // sign-in.
11
+ //
12
+ // It writes nothing unless demo data is on (lib/demo.ts: PYLON_DEMO_DATA, else
13
+ // a `pylon dev` process), so `pylon start`, Docker, and Pylon Cloud deploys do
14
+ // not get demo rows. Each table is seeded only while it is empty, so deleting
15
+ // every lead in development seeds them again on the next load. Private data
16
+ // (PII + money) is only ever seeded for the signed-in owner, never by an
17
+ // anonymous visitor, which is why this is owner-gated, unlike the public
18
+ // seedProjects.
11
19
  //
12
20
  // Invoices link to clients by name and to projects by slug; we resolve those to
13
- // ids here. Projects may already be seeded (the public site calls seedProjects);
14
- // if a project isn't present yet, the invoice still carries its title and just
15
- // doesn't deep-link.
21
+ // ids here. Projects may already be seeded (the public site calls
22
+ // seedProjects); if a project isn't present yet, the invoice still carries its
23
+ // title and just doesn't deep-link.
16
24
  export default mutation<Record<string, never>, { seeded: boolean }>({
17
25
  auth: "user",
18
26
  async handler(ctx) {
@@ -21,9 +29,29 @@ export default mutation<Record<string, never>, { seeded: boolean }>({
21
29
  throw ctx.error("POLICY_DENIED", "Only the owner can seed the back-office.");
22
30
  }
23
31
 
32
+ if (!demoDataEnabled(ctx.env)) return { seeded: false };
33
+
24
34
  await ctx.db.advisoryLock("agency_seed_backoffice");
35
+ const nowMs = Date.now();
36
+
37
+ const existingInquiries = await ctx.db.unsafe.list("Inquiry");
38
+ if (existingInquiries.length === 0) {
39
+ for (const q of DEMO_INQUIRIES) {
40
+ await ctx.db.unsafe.insert("Inquiry", {
41
+ name: q.name,
42
+ email: q.email,
43
+ company: q.company,
44
+ projectType: q.projectType,
45
+ budget: q.budget,
46
+ message: q.message,
47
+ status: q.status,
48
+ createdAt: new Date(nowMs - q.hoursAgo * 3_600_000).toISOString(),
49
+ });
50
+ }
51
+ }
52
+
25
53
  const existingClients = await ctx.db.unsafe.list("Client");
26
- if (existingClients.length > 0) return { seeded: false };
54
+ if (existingClients.length > 0) return { seeded: existingInquiries.length === 0 };
27
55
 
28
56
  const now = new Date().toISOString();
29
57
 
@@ -62,8 +90,11 @@ export default mutation<Record<string, never>, { seeded: boolean }>({
62
90
  lineItems: JSON.stringify(inv.lineItems),
63
91
  amountCents: lineItemsTotal(inv.lineItems),
64
92
  status: inv.status ?? "draft",
65
- issuedAt: inv.issuedAt ?? null,
66
- dueAt: inv.dueAt ?? null,
93
+ issuedAt: inv.issuedDaysAgo !== undefined ? daysAgoDate(inv.issuedDaysAgo, nowMs) : null,
94
+ dueAt:
95
+ inv.issuedDaysAgo !== undefined
96
+ ? daysAgoDate(inv.issuedDaysAgo - (inv.termsDays ?? 30), nowMs)
97
+ : null,
67
98
  notes: null,
68
99
  createdAt: now,
69
100
  });
@@ -187,3 +187,25 @@ export function viewFromRow(r: ProjectRow): ProjectView {
187
187
  liveUrl: r.liveUrl,
188
188
  };
189
189
  }
190
+
191
+ // The booking window the site names next to the open-slot count. An empty
192
+ // label means the quarter a new project could start in: the quarter of the
193
+ // date three weeks from today. It is never a past quarter, and in the last
194
+ // three weeks of a quarter it names the next one. The owner can set a fixed
195
+ // label ("January", "after the holidays") from the dashboard.
196
+ const LEAD_DAYS = 21;
197
+ export function quarterLabel(nowMs: number): string {
198
+ const d = new Date(nowMs);
199
+ return `Q${Math.floor(d.getMonth() / 3) + 1} ${d.getFullYear()}`;
200
+ }
201
+
202
+ export function bookingWindow(label: string | null | undefined, nowMs: number): string {
203
+ const fixed = (label ?? "").trim();
204
+ return fixed || quarterLabel(nowMs + LEAD_DAYS * 86_400_000);
205
+ }
206
+
207
+ // The work grid is two columns. With an odd number of case studies the first
208
+ // one spans both columns, so the last row never holds a single card.
209
+ export function isWideCard(index: number, count: number): boolean {
210
+ return count > 1 && count % 2 === 1 && index === 0;
211
+ }
@@ -0,0 +1,118 @@
1
+ // Demo back-office data for local development.
2
+ //
3
+ // The owner's first dashboard load in `pylon dev` fills an empty studio with
4
+ // fictional leads, clients, and invoices (functions/seedStudioBackoffice.ts),
5
+ // so the pipeline, CRM, and billing tabs have rows. A deploy does not seed:
6
+ // `demoDataEnabled` is true only when PYLON_DEMO_DATA is on, or when it is
7
+ // unset and the process runs under `pylon dev`. Set PYLON_DEMO_DATA=0 to turn
8
+ // it off in development too.
9
+ //
10
+ // The public portfolio (functions/seedProjects.ts) is different: it copies the
11
+ // case studies from lib/site.config.ts, which is the studio's own content, so it
12
+ // runs in every environment.
13
+
14
+ const ON = new Set(["1", "true", "yes", "on"]);
15
+ const OFF = new Set(["0", "false", "no", "off"]);
16
+
17
+ /**
18
+ * True when demo rows may be written. An explicit PYLON_DEMO_DATA wins.
19
+ * Otherwise it needs both PYLON_DEV_MODE on and PYLON_DEV_WATCH_DIR set. Only
20
+ * `pylon dev` sets PYLON_DEV_WATCH_DIR; `pylon start` and the Docker image do
21
+ * not, and the image turns PYLON_DEV_MODE on by default, so dev mode alone is
22
+ * not proof of a local machine.
23
+ */
24
+ export function demoDataEnabled(env: Record<string, string | undefined>): boolean {
25
+ const explicit = env.PYLON_DEMO_DATA?.trim().toLowerCase();
26
+ if (explicit && ON.has(explicit)) return true;
27
+ if (explicit && OFF.has(explicit)) return false;
28
+ const dev = env.PYLON_DEV_MODE?.trim().toLowerCase();
29
+ const devMode = dev === "1" || dev === "true";
30
+ return devMode && Boolean(env.PYLON_DEV_WATCH_DIR?.trim());
31
+ }
32
+
33
+ /** Local "YYYY-MM-DD" for `daysAgo` days before `nowMs` (negative = ahead). */
34
+ export function daysAgoDate(daysAgo: number, nowMs: number): string {
35
+ const d = new Date(nowMs);
36
+ d.setDate(d.getDate() - daysAgo);
37
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
38
+ }
39
+
40
+ export interface DemoInquiry {
41
+ name: string;
42
+ email: string;
43
+ company: string;
44
+ projectType: string;
45
+ budget: string;
46
+ message: string;
47
+ status: "new" | "booked" | "declined";
48
+ /** Hours before the seed runs. */
49
+ hoursAgo: number;
50
+ }
51
+
52
+ // Fictional leads. Project types and budgets use the contact form's options.
53
+ export const DEMO_INQUIRIES: DemoInquiry[] = [
54
+ {
55
+ name: "Priya Raman",
56
+ email: "priya@fernhillvet.example",
57
+ company: "Fernhill Veterinary",
58
+ projectType: "New product (0→1)",
59
+ budget: "$50–100k",
60
+ message:
61
+ "We run six clinics and want an app for booking visits and refilling prescriptions. We have an API from our practice software. Hoping to launch in spring.",
62
+ status: "new",
63
+ hoursAgo: 3,
64
+ },
65
+ {
66
+ name: "Marcus Iyer",
67
+ email: "marcus@northwind.example",
68
+ company: "Northwind Logistics",
69
+ projectType: "Existing product",
70
+ budget: "$100k+",
71
+ message:
72
+ "Our dispatch team works out of spreadsheets. We need a web app for load planning with live driver locations. Two engineers in-house can take it over after launch.",
73
+ status: "new",
74
+ hoursAgo: 20,
75
+ },
76
+ {
77
+ name: "Lena Fischer",
78
+ email: "lena@beaconproperty.example",
79
+ company: "Beacon Property",
80
+ projectType: "Existing product",
81
+ budget: "$25–50k",
82
+ message:
83
+ "Three product teams, three sets of buttons. We want one component library in Figma and React, and help rolling it out.",
84
+ status: "new",
85
+ hoursAgo: 46,
86
+ },
87
+ {
88
+ name: "Ben Osei",
89
+ email: "ben@quarry.example",
90
+ company: "Quarry Design Co",
91
+ projectType: "Rebrand",
92
+ budget: "$25–50k",
93
+ message: "Looking for a partner to build our new marketing site in a headless CMS. Designs are about 70% done.",
94
+ status: "booked",
95
+ hoursAgo: 120,
96
+ },
97
+ {
98
+ name: "Tom Alvarez",
99
+ email: "tom@riverbed.example",
100
+ company: "Riverbed Coffee",
101
+ projectType: "New product (0→1)",
102
+ budget: "Let's talk",
103
+ message: "A loyalty app for our four cafes. Punch card, order ahead, and push offers.",
104
+ status: "declined",
105
+ hoursAgo: 200,
106
+ },
107
+ {
108
+ name: "Simone Clarke",
109
+ email: "simone@atlasfitness.example",
110
+ company: "Atlas Fitness",
111
+ projectType: "New product (0→1)",
112
+ budget: "$50–100k",
113
+ message:
114
+ "Members book classes through a third-party tool that charges per booking. We want our own booking and billing, with a coach view for check-ins.",
115
+ status: "new",
116
+ hoursAgo: 70,
117
+ },
118
+ ];
@@ -68,8 +68,10 @@ export type InvoiceSeed = {
68
68
  // Line items drive the total — `amountCents` is computed from them on seed.
69
69
  lineItems: { description: string; quantity: number; unitCents: number }[];
70
70
  status?: "draft" | "sent" | "paid" | "overdue";
71
- issuedAt?: string; // "2026-04-01"
72
- dueAt?: string;
71
+ // Days before the seed runs, so demo invoices stay current: an invoice
72
+ // issued 12 days ago with 30-day terms is always "due in 18 days".
73
+ issuedDaysAgo?: number;
74
+ termsDays?: number;
73
75
  };
74
76
 
75
77
  // The studio's billing identity — the "from" block + terms on every invoice and
@@ -89,7 +91,8 @@ export type AgencyConfig = BaseConfig & {
89
91
  };
90
92
  // Seeds the public Capacity row on first visit; after that it lives in the DB
91
93
  // and the owner manages it from the dashboard. `openSlots` is the number the
92
- // hero shows live; `label` is the booking window it refers to.
94
+ // hero shows live; `label` is the booking window it refers to ("" = the
95
+ // next bookable quarter).
93
96
  capacity: { label: string; openSlots: number };
94
97
  services: { headline: string; items: Service[] };
95
98
  work: { headline: string; items: CaseStudy[] };
@@ -151,7 +154,10 @@ export const siteConfig: AgencyConfig = {
151
154
  secondaryCtaLabel: "See our work",
152
155
  },
153
156
 
154
- capacity: { label: "Q3 2026", openSlots: 3 },
157
+ // `label` empty = the quarter a new project could start in, worked out from
158
+ // today's date (lib/agency.ts → bookingWindow). Set a fixed label only for a window with a
159
+ // real end date.
160
+ capacity: { label: "", openSlots: 3 },
155
161
 
156
162
  services: {
157
163
  headline: "What we do",
@@ -283,8 +289,8 @@ export const siteConfig: AgencyConfig = {
283
289
  { description: "Product design & iOS build — Ledger (12-week engagement)", quantity: 1, unitCents: 4800000 },
284
290
  ],
285
291
  status: "paid",
286
- issuedAt: "2026-01-15",
287
- dueAt: "2026-02-14",
292
+ issuedDaysAgo: 240,
293
+ termsDays: 30,
288
294
  },
289
295
  {
290
296
  number: "INV-002",
@@ -295,19 +301,19 @@ export const siteConfig: AgencyConfig = {
295
301
  { description: "Discovery & scoping sprint", quantity: 1, unitCents: 700000 },
296
302
  ],
297
303
  status: "paid",
298
- issuedAt: "2026-03-01",
299
- dueAt: "2026-03-31",
304
+ issuedDaysAgo: 150,
305
+ termsDays: 30,
300
306
  },
301
307
  {
302
308
  number: "INV-003",
303
309
  client: "Tom Reyes",
304
310
  projectSlug: "atlas-health",
305
311
  lineItems: [
306
- { description: "Maintenance retainer — June", quantity: 1, unitCents: 850000 },
312
+ { description: "Monthly maintenance retainer", quantity: 1, unitCents: 850000 },
307
313
  ],
308
314
  status: "sent",
309
- issuedAt: "2026-06-01",
310
- dueAt: "2026-07-01",
315
+ issuedDaysAgo: 12,
316
+ termsDays: 30,
311
317
  },
312
318
  {
313
319
  number: "INV-004",
@@ -318,8 +324,8 @@ export const siteConfig: AgencyConfig = {
318
324
  { description: "Marketing site design & build", quantity: 1, unitCents: 1000000 },
319
325
  ],
320
326
  status: "overdue",
321
- issuedAt: "2026-04-10",
322
- dueAt: "2026-05-10",
327
+ issuedDaysAgo: 64,
328
+ termsDays: 30,
323
329
  },
324
330
  ],
325
331
  },
@@ -0,0 +1,57 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { bookingWindow, isWideCard, quarterLabel } from "../lib/agency";
3
+ import { DEMO_INQUIRIES, daysAgoDate, demoDataEnabled } from "../lib/demo";
4
+ import { siteConfig } from "../lib/site.config";
5
+
6
+ describe("booking window", () => {
7
+ test("an empty label means the quarter a project could start in", () => {
8
+ expect(quarterLabel(new Date(2026, 8, 26).getTime())).toBe("Q3 2026");
9
+ expect(quarterLabel(new Date(2026, 9, 1).getTime())).toBe("Q4 2026");
10
+ expect(bookingWindow("", new Date(2027, 0, 5).getTime())).toBe("Q1 2027");
11
+ expect(bookingWindow(" ", new Date(2027, 0, 5).getTime())).toBe("Q1 2027");
12
+ // Three weeks from the end of a quarter, the window moves to the next one.
13
+ expect(bookingWindow("", new Date(2026, 8, 5).getTime())).toBe("Q3 2026");
14
+ expect(bookingWindow("", new Date(2026, 8, 26).getTime())).toBe("Q4 2026");
15
+ expect(bookingWindow("", new Date(2026, 11, 20).getTime())).toBe("Q1 2027");
16
+ });
17
+ test("a fixed label wins", () => {
18
+ expect(bookingWindow("January", Date.now())).toBe("January");
19
+ });
20
+ test("the shipped config does not pin a quarter", () => {
21
+ expect(siteConfig.capacity.label).toBe("");
22
+ });
23
+ });
24
+
25
+ describe("work grid", () => {
26
+ test("an odd count widens the first card so no card sits alone on the last row", () => {
27
+ expect([0, 1, 2].map((i) => isWideCard(i, 3))).toEqual([true, false, false]);
28
+ expect([0, 1, 2, 3].map((i) => isWideCard(i, 4))).toEqual([false, false, false, false]);
29
+ expect(isWideCard(0, 1)).toBe(false);
30
+ });
31
+ test("the shipped homepage has three selected case studies, so the rule applies", () => {
32
+ expect(siteConfig.work.items.filter((w) => w.selected).length % 2).toBe(1);
33
+ });
34
+ });
35
+
36
+ describe("demo data", () => {
37
+ test("only under `pylon dev` unless PYLON_DEMO_DATA says otherwise", () => {
38
+ expect(demoDataEnabled({ PYLON_DEV_MODE: "1", PYLON_DEV_WATCH_DIR: "/src/app" })).toBe(true);
39
+ // The Docker base image turns dev mode on by default; without `pylon dev` it must not seed.
40
+ expect(demoDataEnabled({ PYLON_DEV_MODE: "true" })).toBe(false);
41
+ expect(demoDataEnabled({ PYLON_DEV_MODE: "false" })).toBe(false);
42
+ expect(demoDataEnabled({})).toBe(false);
43
+ expect(demoDataEnabled({ PYLON_DEV_MODE: "1", PYLON_DEV_WATCH_DIR: "/src/app", PYLON_DEMO_DATA: "0" })).toBe(false);
44
+ });
45
+ test("demo leads use the contact form's own options", () => {
46
+ for (const q of DEMO_INQUIRIES) {
47
+ expect(siteConfig.contact.projectTypes).toContain(q.projectType);
48
+ expect(siteConfig.contact.budgets).toContain(q.budget);
49
+ }
50
+ });
51
+ test("demo invoice dates are relative to today", () => {
52
+ const now = new Date(2026, 8, 26).getTime();
53
+ expect(daysAgoDate(12, now)).toBe("2026-09-14");
54
+ expect(daysAgoDate(-18, now)).toBe("2026-10-14");
55
+ for (const inv of siteConfig.backoffice.invoices) expect(inv.issuedDaysAgo).toBeGreaterThan(0);
56
+ });
57
+ });
@@ -1,33 +1,19 @@
1
1
  # Copy to `.env` and fill in. `pylon dev` loads `.env` automatically.
2
2
  #
3
- # The app boots with no config — you can sign in and create chats — but the
4
- # assistant can't reply until you point it at an LLM provider. Streaming runs
5
- # through the built-in POST /api/ai/stream endpoint, so your API key stays on
6
- # the server and never reaches the browser.
3
+ # The app runs with no config: visitors can chat as guests and messages are
4
+ # saved. Replies need a model provider key. The key stays on the server.
7
5
 
8
- # ── AI provider (required for the assistant to reply) ────────────────────────
9
- # PYLON_AI_PROVIDER "anthropic" | "openai" | "custom"
10
- # PYLON_AI_API_KEY your provider key (sk-… / sk-ant-…)
11
- # PYLON_AI_MODEL the default model used when none is selected
12
- # PYLON_AI_PROVIDER=anthropic
13
- # PYLON_AI_API_KEY=sk-ant-...
14
- # PYLON_AI_MODEL=claude-sonnet-4-6
6
+ # Model provider (required for replies)
7
+ # PYLON_LLM_PROVIDER "anthropic" | "openai"
8
+ # ANTHROPIC_API_KEY or OPENAI_API_KEY
9
+ # PYLON_LLM_PROVIDER=anthropic
10
+ # ANTHROPIC_API_KEY=sk-ant-...
15
11
 
16
- # ── Model switching ──────────────────────────────────────────────────────────
17
- # The chat's model picker (lib/site.config.ts → chat.models) sends a `model` per
18
- # request. For security, client-chosen models must be allow-listed here
19
- # (comma-separated) — otherwise the override is refused and the chat shows a
20
- # notice. List exactly the model ids you expose in the picker:
21
- # PYLON_AI_MODELS_ALLOWED=claude-sonnet-4-6,claude-opus-4-8,claude-haiku-4-5
12
+ # OpenAI-compatible gateway (optional), such as OpenRouter:
13
+ # PYLON_LLM_PROVIDER=openai
14
+ # PYLON_LLM_BASE_URL=https://openrouter.ai/api
15
+ # OPENAI_API_KEY=<gateway key>
16
+ # Then list the gateway's model ids in lib/site.config.ts (chat.models).
22
17
 
23
- # ── Multiple PROVIDERS in one picker (optional) ──────────────────────────────
24
- # /api/ai/stream talks to ONE provider. To offer Anthropic + OpenAI + others in
25
- # the same picker, route through an OpenAI-compatible gateway like OpenRouter:
26
- # PYLON_AI_PROVIDER=custom
27
- # PYLON_AI_BASE_URL=https://openrouter.ai/api/v1
28
- # PYLON_AI_API_KEY=<openrouter key>
29
- # then set chat.models (config) + PYLON_AI_MODELS_ALLOWED to that gateway's slugs
30
- # (e.g. anthropic/claude-sonnet-4, openai/gpt-4o, google/gemini-2.5-pro).
31
-
32
- # ── Site URL (optional) ──────────────────────────────────────────────────────
18
+ # Site URL (optional)
33
19
  # SITE_URL=https://yourchat.com
@@ -1,11 +1,9 @@
1
1
  # __APP_NAME__
2
2
 
3
- A streaming AI chat app built with [Pylon](https://pylonsync.com). One server
4
- handles token streaming, conversation history, and cross-tab sync.
5
-
6
- Tokens stream from the built-in `POST /api/ai/stream` endpoint, so your provider
7
- API key never reaches the browser. Conversations are sync-backed and
8
- owner-scoped; a chat sent in one tab appears in the other.
3
+ Lumen, a streaming AI assistant built with [Pylon](https://pylonsync.com).
4
+ Visitors chat right away in a guest session. Signing in moves their chats to
5
+ the account. Conversations sync live across tabs and devices, and the provider
6
+ key stays on the server.
9
7
 
10
8
  ## Develop
11
9
 
@@ -13,80 +11,75 @@ owner-scoped; a chat sent in one tab appears in the other.
13
11
  __RUN_DEV__
14
12
  ```
15
13
 
16
- Open http://localhost:4321. You can create chats immediately. Configure an LLM
17
- provider to receive replies, then open a second tab to see conversations and
18
- messages sync.
19
-
20
- ## Configure the model
14
+ Open http://localhost:4321. With no provider key, the app shows the setup
15
+ steps, seeds three example conversations (labeled as examples and read-only),
16
+ and still stores the messages you send.
21
17
 
22
- The assistant replies after you configure a provider. Without one, the app
23
- still boots and displays a configuration notice:
18
+ ## Add a model provider
24
19
 
25
20
  ```bash
26
21
  # .env
27
- PYLON_AI_PROVIDER=anthropic # or "openai" / "custom"
28
- PYLON_AI_API_KEY=sk-ant-...
29
- PYLON_AI_MODEL=claude-sonnet-4-6 # default when none is picked
22
+ PYLON_LLM_PROVIDER=anthropic
23
+ ANTHROPIC_API_KEY=sk-ant-...
30
24
  ```
31
25
 
32
- `/api/ai/stream` is auth-gated (it uses your signed-in session) and rate-limited
33
- per user, so a drive-by caller can't burn your budget.
34
-
35
- ### Switching models / providers
36
-
37
- The composer has a **model picker** — its options live in `lib/site.config.ts`
38
- (`chat.models`, each with a provider label), and the chosen id is sent per
39
- request. Client-chosen models must be allow-listed server-side:
26
+ Restart `pylon dev`. For OpenAI, set `PYLON_LLM_PROVIDER=openai` and
27
+ `OPENAI_API_KEY`, then replace the ids in `chat.models` in
28
+ `lib/site.config.ts`. For an OpenAI-compatible gateway such as OpenRouter,
29
+ also set `PYLON_LLM_BASE_URL` (for example `https://openrouter.ai/api`) and use
30
+ the gateway's model ids.
40
31
 
41
- ```bash
42
- PYLON_AI_MODELS_ALLOWED=claude-sonnet-4-6,claude-opus-4-8,gpt-4o
43
- ```
44
-
45
- `/api/ai/stream` talks to **one** provider. To offer models from **multiple
46
- providers** in one picker, route through an OpenAI-compatible gateway like
47
- [OpenRouter](https://openrouter.ai):
48
-
49
- ```bash
50
- PYLON_AI_PROVIDER=custom
51
- PYLON_AI_BASE_URL=https://openrouter.ai/api/v1
52
- PYLON_AI_API_KEY=<openrouter key>
53
- ```
54
-
55
- then set `chat.models` + `PYLON_AI_MODELS_ALLOWED` to the gateway's slugs
56
- (`anthropic/claude-sonnet-4`, `openai/gpt-4o`, `google/gemini-2.5-pro`, …).
32
+ The model list in `lib/site.config.ts` is the allowlist. `app.ts` passes it to
33
+ `llm({ allowedModels })`, and `respond` refuses any other id.
57
34
 
58
35
  ## How it works
59
36
 
60
- - **Streaming.** `app/chat-client.tsx` POSTs the conversation to
61
- `/api/ai/stream` and reads the SSE response (`data: {choices:[{delta:…}]}` …
62
- `data: [DONE]`), appending tokens to the live assistant bubble.
63
- - **Realtime history.** `Conversation` + `Message` are owner-scoped entities
64
- read with `db.useQuery` — private to each user and synced across their tabs.
65
- Messages are written with optimistic `db.insert` (userId is stamped from the
66
- session via `field.owner()`), so no custom write functions are needed.
67
- - **Sign-in required.** Chats are tied to your account, so the home page
68
- redirects unauthenticated visitors to `/login`. Once signed in, your history
69
- follows you across tabs and devices.
37
+ - **Replies.** `functions/respond.ts` is a streaming action. It calls
38
+ `ctx.llm.stream` and writes each token to `ctx.stream`. The client reads it
39
+ with `streamFn("respond", …)`.
40
+ - **Other tabs.** `_beginTurn` stores the call's resumable stream id on the
41
+ assistant message. Another tab (or this one after a reload) attaches with
42
+ `resumeStream(streamId)` and shows the same tokens. The final text is written
43
+ to the message row, which syncs everywhere.
44
+ - **Stop and regenerate.** Stop stores the text shown so far with status
45
+ `stopped`. Regenerate removes the last reply and writes a new one.
46
+ - **Guests.** `<EnsureGuest>` creates a guest session on first visit. Every
47
+ function the chat calls accepts guests (`auth: "guest"`).
48
+ - **Sign-in keeps history.** Before signing in, the guest gets a one-time code
49
+ from `prepareGuestClaim`. After sign-in, `claimGuestHistory` uses it to move
50
+ the guest's conversations to the account.
51
+ - **Limits.** `repliesPerHour` in `lib/site.config.ts` caps replies per rolling
52
+ hour, lower for guests than for accounts.
70
53
 
71
54
  ## Privacy
72
55
 
73
- `Conversation` and `Message` policies are owner-scoped (`auth.userId ==
74
- data.userId`) — you can only ever read or write your own. `User.passwordHash` is
75
- `serverOnly`. The provider key lives only on the server.
56
+ `Conversation` and `Message` rows are readable only by their owner. Clients can
57
+ rename their own conversations; every other write goes through a server
58
+ function that checks ownership. `GuestClaim` is never readable by clients, and
59
+ only a hash of each claim code is stored. `User.passwordHash` is `serverOnly`.
76
60
 
77
61
  ## Rebrand it
78
62
 
79
- The name, colors, system prompt, empty-state copy, and starter prompts live in
80
- **`lib/site.config.ts`**.
63
+ The name, colors, system prompt, suggestions, models, and limits live in
64
+ `lib/site.config.ts`. Replace or delete the examples in `lib/examples.ts`.
81
65
 
82
66
  ## Layout
83
67
 
84
68
  ```
85
- app.ts Conversation + Message (owner-scoped) + User
86
- lib/site.config.ts brand + system prompt + suggestions (edit this)
87
- app/page.tsx renders the chat island
88
- app/chat-client.tsx sidebar + thread + streaming via /api/ai/stream
89
- app/login/ optional sign-in (carries history across devices)
69
+ app.ts entities, policies, llm() allowlist, fonts
70
+ lib/site.config.ts brand, prompt, suggestions, models, limits
71
+ lib/chat.ts history, titles, day groups, error copy (tested)
72
+ lib/markdown.ts Markdown parser for replies (tested)
73
+ functions/respond.ts streams a reply with ctx.llm.stream
74
+ app/(chat)/chat-client.tsx the only file that talks to Pylon on the client
75
+ components/chat/ sidebar, thread, composer, setup card
76
+ app/login/ optional sign-in
77
+ ```
78
+
79
+ ## Test
80
+
81
+ ```bash
82
+ pylon test
90
83
  ```
91
84
 
92
85
  ## Deploy
@@ -0,0 +1,19 @@
1
+ import React from "react";
2
+ import { type Metadata, type PageProps } from "@pylonsync/react";
3
+ import { ChatApp } from "../../chat-client";
4
+ import { siteConfig } from "@/lib/site.config";
5
+ import { brandIcon } from "@/lib/brand-icon";
6
+
7
+ export const metadata: Metadata = {
8
+ title: siteConfig.seo.title,
9
+ robots: "noindex",
10
+ icons: { icon: brandIcon },
11
+ };
12
+
13
+ // `/c/:id`: one conversation. The id is only a starting point; the client
14
+ // loads the conversation through the owner-scoped live query, so another
15
+ // visitor's id shows nothing.
16
+ export default function ConversationPage({ auth, params }: PageProps<{ id: string }>) {
17
+ const signedIn = Boolean(auth.user_id) && !auth.is_guest && !auth.user_id?.startsWith("guest_");
18
+ return <ChatApp initialConversationId={params.id} signedInUserId={signedIn ? auth.user_id : null} />;
19
+ }