@pylonsync/create-pylon 0.19.1 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (696) hide show
  1. package/bin/create-pylon.js +30 -7
  2. package/package.json +1 -1
  3. package/templates/_root/AGENTS.md +1 -1
  4. package/templates/agency/.env.example +7 -0
  5. package/templates/agency/AGENTS.md +1 -1
  6. package/templates/agency/README.md +28 -3
  7. package/templates/agency/app/(marketing)/contact-form.tsx +20 -18
  8. package/templates/agency/app/(marketing)/page.tsx +3 -3
  9. package/templates/agency/app/(marketing)/work/page.tsx +3 -3
  10. package/templates/agency/app/auth-form.tsx +4 -4
  11. package/templates/agency/app/dashboard/dashboard-client.tsx +110 -64
  12. package/templates/agency/app/dashboard/page.tsx +14 -16
  13. package/templates/agency/app/icon.svg +3 -0
  14. package/templates/agency/app/login/page.tsx +12 -14
  15. package/templates/agency/components/marketing.tsx +12 -5
  16. package/templates/agency/functions/seedStudioBackoffice.ts +42 -11
  17. package/templates/agency/lib/agency.ts +22 -0
  18. package/templates/agency/lib/demo.ts +118 -0
  19. package/templates/agency/lib/site.config.ts +19 -13
  20. package/templates/agency/public/favicon.ico +0 -0
  21. package/templates/agency/tests/agency.test.ts +57 -0
  22. package/templates/ai-chat/.env.example +13 -27
  23. package/templates/ai-chat/AGENTS.md +1 -1
  24. package/templates/ai-chat/README.md +53 -60
  25. package/templates/ai-chat/app/(chat)/c/[id]/page.tsx +19 -0
  26. package/templates/ai-chat/app/(chat)/chat-client.tsx +657 -622
  27. package/templates/ai-chat/app/(chat)/layout.tsx +6 -43
  28. package/templates/ai-chat/app/(chat)/not-found.tsx +6 -10
  29. package/templates/ai-chat/app/(chat)/page.tsx +7 -11
  30. package/templates/ai-chat/app/auth-form.tsx +45 -38
  31. package/templates/ai-chat/app/error.tsx +9 -12
  32. package/templates/ai-chat/app/globals.css +464 -102
  33. package/templates/ai-chat/app/layout.tsx +11 -19
  34. package/templates/ai-chat/app/login/page.tsx +39 -24
  35. package/templates/ai-chat/app.ts +118 -43
  36. package/templates/ai-chat/components/chat/composer.tsx +193 -0
  37. package/templates/ai-chat/components/chat/empty-state.tsx +65 -0
  38. package/templates/ai-chat/components/chat/icons.tsx +198 -0
  39. package/templates/ai-chat/components/chat/markdown.tsx +164 -0
  40. package/templates/ai-chat/components/chat/message.tsx +183 -0
  41. package/templates/ai-chat/components/chat/setup.tsx +71 -0
  42. package/templates/ai-chat/components/chat/sidebar.tsx +288 -0
  43. package/templates/ai-chat/functions/_beginTurn.ts +145 -0
  44. package/templates/ai-chat/functions/_finishTurn.ts +55 -0
  45. package/templates/ai-chat/functions/assistantStatus.ts +13 -0
  46. package/templates/ai-chat/functions/claimGuestHistory.ts +39 -0
  47. package/templates/ai-chat/functions/createConversation.ts +28 -0
  48. package/templates/ai-chat/functions/deleteConversation.ts +15 -20
  49. package/templates/ai-chat/functions/prepareGuestClaim.ts +32 -0
  50. package/templates/ai-chat/functions/renameConversation.ts +19 -0
  51. package/templates/ai-chat/functions/respond.ts +88 -0
  52. package/templates/ai-chat/functions/seedExamples.ts +46 -0
  53. package/templates/ai-chat/functions/stopResponse.ts +30 -0
  54. package/templates/ai-chat/lib/brand-icon.ts +7 -0
  55. package/templates/ai-chat/lib/chat.ts +277 -0
  56. package/templates/ai-chat/lib/claim.ts +15 -0
  57. package/templates/ai-chat/lib/examples.ts +179 -0
  58. package/templates/ai-chat/lib/highlight.ts +107 -0
  59. package/templates/ai-chat/lib/markdown.ts +317 -0
  60. package/templates/ai-chat/lib/provider.ts +38 -0
  61. package/templates/ai-chat/lib/site.config.ts +55 -39
  62. package/templates/ai-chat/tests/chat.test.ts +174 -0
  63. package/templates/ai-chat/tests/components.test.tsx +57 -0
  64. package/templates/ai-chat/tests/markdown.test.ts +88 -0
  65. package/templates/ai-chat/tests/provider.test.ts +32 -0
  66. package/templates/ai-studio/AGENTS.md +1 -1
  67. package/templates/backend/consumer/apps/api/app/layout.tsx +33 -0
  68. package/templates/backend/consumer/apps/api/app/page.tsx +54 -0
  69. package/templates/backend/consumer/apps/api/app.ts +166 -0
  70. package/templates/backend/consumer/apps/api/functions/addComment.ts +24 -0
  71. package/templates/backend/consumer/apps/api/functions/createPost.ts +19 -36
  72. package/templates/backend/consumer/apps/api/functions/deletePost.ts +15 -13
  73. package/templates/backend/consumer/apps/api/functions/myProfile.ts +5 -8
  74. package/templates/backend/consumer/apps/api/functions/seedDemo.ts +95 -0
  75. package/templates/backend/consumer/apps/api/functions/setFollow.ts +36 -0
  76. package/templates/backend/consumer/apps/api/functions/setLike.ts +36 -0
  77. package/templates/backend/consumer/apps/api/functions/upsertProfile.ts +43 -37
  78. package/templates/backend/consumer/apps/api/lib/seed.ts +89 -0
  79. package/templates/backend/consumer/apps/api/lib/social.ts +50 -0
  80. package/templates/backend/consumer/apps/api/package.json +8 -2
  81. package/templates/backend/consumer/apps/api/public/images/posts/arch.jpg +0 -0
  82. package/templates/backend/consumer/apps/api/public/images/posts/coffee.jpg +0 -0
  83. package/templates/backend/consumer/apps/api/public/images/posts/dog.jpg +0 -0
  84. package/templates/backend/consumer/apps/api/public/images/posts/lisbon.jpg +0 -0
  85. package/templates/backend/consumer/apps/api/public/images/posts/ramen.jpg +0 -0
  86. package/templates/backend/consumer/apps/api/public/images/posts/run.jpg +0 -0
  87. package/templates/backend/consumer/apps/api/public/images/posts/sunset.jpg +0 -0
  88. package/templates/backend/consumer/apps/api/tsconfig.json +12 -2
  89. package/templates/barebones/AGENTS.md +1 -1
  90. package/templates/chat/AGENTS.md +1 -1
  91. package/templates/chat/README.md +43 -21
  92. package/templates/chat/app/chat-client.tsx +495 -85
  93. package/templates/chat/app/error.tsx +1 -1
  94. package/templates/chat/app/globals.css +170 -54
  95. package/templates/chat/app/layout.tsx +12 -9
  96. package/templates/chat/app/not-found.tsx +4 -2
  97. package/templates/chat/app/page.tsx +9 -19
  98. package/templates/chat/app.ts +96 -17
  99. package/templates/chat/components/chat/avatar.tsx +45 -0
  100. package/templates/chat/components/chat/composer.tsx +110 -0
  101. package/templates/chat/components/chat/message-list.tsx +285 -0
  102. package/templates/chat/components/chat/sidebar.tsx +190 -0
  103. package/templates/chat/functions/joinChat.ts +83 -0
  104. package/templates/chat/functions/renameMember.ts +25 -0
  105. package/templates/chat/lib/chat.ts +188 -0
  106. package/templates/chat/lib/seed.ts +78 -0
  107. package/templates/chat/tests/chat.test.ts +159 -0
  108. package/templates/chat/tests/composer.test.tsx +54 -0
  109. package/templates/consumer/AGENTS.md +1 -1
  110. package/templates/consumer/README.md +3 -3
  111. package/templates/consumer/app/globals.css +10 -3
  112. package/templates/consumer/app/icon.svg +1 -0
  113. package/templates/consumer/components/social/avatar.tsx +25 -11
  114. package/templates/consumer/functions/seedFeed.ts +3 -3
  115. package/templates/consumer/lib/seed.ts +43 -85
  116. package/templates/consumer/public/favicon.ico +0 -0
  117. package/templates/consumer/public/images/posts/arch.jpg +0 -0
  118. package/templates/consumer/public/images/posts/coffee.jpg +0 -0
  119. package/templates/consumer/public/images/posts/dog.jpg +0 -0
  120. package/templates/consumer/public/images/posts/lisbon.jpg +0 -0
  121. package/templates/consumer/public/images/posts/ramen.jpg +0 -0
  122. package/templates/consumer/public/images/posts/run.jpg +0 -0
  123. package/templates/consumer/public/images/posts/sunset.jpg +0 -0
  124. package/templates/consumer/tests/seed.test.ts +39 -0
  125. package/templates/creator/.env.example +7 -0
  126. package/templates/creator/AGENTS.md +1 -1
  127. package/templates/creator/README.md +24 -5
  128. package/templates/creator/app/(marketing)/newsletter-signup.tsx +33 -12
  129. package/templates/creator/app/auth-form.tsx +7 -7
  130. package/templates/creator/app/dashboard/dashboard-client.tsx +137 -100
  131. package/templates/creator/app/dashboard/page.tsx +18 -18
  132. package/templates/creator/app/icon.svg +2 -0
  133. package/templates/creator/app/login/page.tsx +14 -14
  134. package/templates/creator/app.ts +4 -4
  135. package/templates/creator/functions/seedDemo.ts +32 -0
  136. package/templates/creator/functions/subscribe.ts +4 -15
  137. package/templates/creator/lib/demo.ts +94 -0
  138. package/templates/creator/lib/site.config.ts +15 -10
  139. package/templates/creator/lib/stats.ts +35 -0
  140. package/templates/creator/public/favicon.ico +0 -0
  141. package/templates/creator/tests/counter.test.ts +92 -0
  142. package/templates/creator/tests/live-count.test.tsx +45 -0
  143. package/templates/crm/AGENTS.md +1 -1
  144. package/templates/crm/README.md +22 -1
  145. package/templates/crm/app/apple-icon.png +0 -0
  146. package/templates/crm/app/companies/companies-view.tsx +32 -14
  147. package/templates/crm/app/companies/page.tsx +2 -1
  148. package/templates/crm/app/contacts/contacts-view.tsx +34 -10
  149. package/templates/crm/app/contacts/page.tsx +2 -1
  150. package/templates/crm/app/deals/[id]/deal-view.tsx +137 -98
  151. package/templates/crm/app/deals/[id]/page.tsx +2 -1
  152. package/templates/crm/app/globals.css +29 -14
  153. package/templates/crm/app/icon.svg +1 -0
  154. package/templates/crm/app/layout.tsx +7 -5
  155. package/templates/crm/app/page.tsx +2 -1
  156. package/templates/crm/app/pipeline-view.tsx +5 -5
  157. package/templates/crm/app/workspace.tsx +29 -17
  158. package/templates/crm/app.ts +5 -5
  159. package/templates/crm/components/activity-timeline.tsx +14 -4
  160. package/templates/crm/components/app-shell.tsx +94 -0
  161. package/templates/crm/components/auth-form.tsx +35 -5
  162. package/templates/crm/components/avatar.tsx +5 -1
  163. package/templates/crm/components/board-skeleton.tsx +5 -5
  164. package/templates/crm/components/brand-mark.tsx +25 -0
  165. package/templates/crm/components/command-palette.tsx +17 -17
  166. package/templates/crm/components/data-table.tsx +97 -36
  167. package/templates/crm/components/deal-card.tsx +23 -27
  168. package/templates/crm/components/filter-tabs.tsx +101 -0
  169. package/templates/crm/components/metrics-bar.tsx +6 -6
  170. package/templates/crm/components/nav.tsx +15 -0
  171. package/templates/crm/components/page-header.tsx +43 -7
  172. package/templates/crm/components/pipeline-board.tsx +16 -10
  173. package/templates/crm/components/require-auth.tsx +24 -26
  174. package/templates/crm/components/sidebar.tsx +54 -49
  175. package/templates/crm/components/stage-badge.tsx +4 -2
  176. package/templates/crm/components/ui/button.tsx +5 -4
  177. package/templates/crm/components/ui/dialog.tsx +1 -1
  178. package/templates/crm/components/ui/input.tsx +1 -1
  179. package/templates/crm/components/ui/select.tsx +1 -1
  180. package/templates/crm/components/ui/textarea.tsx +1 -1
  181. package/templates/crm/functions/seedWorkspace.ts +32 -17
  182. package/templates/crm/lib/brand.ts +9 -0
  183. package/templates/crm/lib/demo-team.ts +51 -0
  184. package/templates/crm/lib/pipeline.ts +37 -0
  185. package/templates/crm/lib/search.ts +4 -3
  186. package/templates/crm/lib/seed.ts +53 -8
  187. package/templates/crm/public/favicon.ico +0 -0
  188. package/templates/crm/tests/components.test.tsx +5 -5
  189. package/templates/crm/tests/demo-team.test.ts +18 -0
  190. package/templates/crm/tests/pipeline.test.ts +59 -3
  191. package/templates/directory/.env.example +7 -0
  192. package/templates/directory/AGENTS.md +1 -1
  193. package/templates/directory/README.md +22 -3
  194. package/templates/directory/app/(marketing)/directory-browse.tsx +30 -12
  195. package/templates/directory/app/(marketing)/page.tsx +6 -4
  196. package/templates/directory/app/auth-form.tsx +4 -4
  197. package/templates/directory/app/dashboard/dashboard-client.tsx +177 -90
  198. package/templates/directory/app/dashboard/page.tsx +16 -14
  199. package/templates/directory/app/icon.svg +4 -0
  200. package/templates/directory/app/login/page.tsx +16 -15
  201. package/templates/directory/functions/seedDemo.ts +43 -0
  202. package/templates/directory/lib/demo.ts +145 -0
  203. package/templates/directory/lib/directory.ts +27 -0
  204. package/templates/directory/lib/site.config.ts +21 -16
  205. package/templates/directory/public/favicon.ico +0 -0
  206. package/templates/directory/tests/directory.test.ts +46 -0
  207. package/templates/expo/consumer/apps/expo/App.tsx +360 -288
  208. package/templates/expo/consumer/apps/expo/app.json +9 -1
  209. package/templates/expo/consumer/apps/expo/package.json +2 -1
  210. package/templates/helpdesk/AGENTS.md +1 -1
  211. package/templates/helpdesk/README.md +21 -0
  212. package/templates/helpdesk/app/apple-icon.png +0 -0
  213. package/templates/helpdesk/app/customers/customers-view.tsx +23 -7
  214. package/templates/helpdesk/app/customers/page.tsx +2 -1
  215. package/templates/helpdesk/app/globals.css +29 -14
  216. package/templates/helpdesk/app/icon.svg +1 -0
  217. package/templates/helpdesk/app/inbox-view.tsx +66 -47
  218. package/templates/helpdesk/app/layout.tsx +7 -5
  219. package/templates/helpdesk/app/page.tsx +4 -7
  220. package/templates/helpdesk/app/tickets/[id]/page.tsx +2 -1
  221. package/templates/helpdesk/app/tickets/[id]/ticket-view.tsx +134 -97
  222. package/templates/helpdesk/app/workspace.tsx +33 -22
  223. package/templates/helpdesk/app.ts +2 -2
  224. package/templates/helpdesk/components/app-shell.tsx +94 -0
  225. package/templates/helpdesk/components/auth-form.tsx +35 -5
  226. package/templates/helpdesk/components/avatar.tsx +5 -1
  227. package/templates/helpdesk/components/brand-mark.tsx +28 -0
  228. package/templates/helpdesk/components/command-palette.tsx +17 -17
  229. package/templates/helpdesk/components/data-table.tsx +97 -36
  230. package/templates/helpdesk/components/filter-tabs.tsx +101 -0
  231. package/templates/helpdesk/components/nav.tsx +14 -0
  232. package/templates/helpdesk/components/page-header.tsx +43 -7
  233. package/templates/helpdesk/components/priority-badge.tsx +49 -6
  234. package/templates/helpdesk/components/require-auth.tsx +24 -26
  235. package/templates/helpdesk/components/sidebar.tsx +54 -48
  236. package/templates/helpdesk/components/sla-indicator.tsx +5 -8
  237. package/templates/helpdesk/components/thread.tsx +7 -7
  238. package/templates/helpdesk/components/ticket-list.tsx +79 -19
  239. package/templates/helpdesk/components/ui/button.tsx +5 -4
  240. package/templates/helpdesk/components/ui/dialog.tsx +1 -1
  241. package/templates/helpdesk/components/ui/input.tsx +1 -1
  242. package/templates/helpdesk/components/ui/select.tsx +1 -1
  243. package/templates/helpdesk/components/ui/textarea.tsx +1 -1
  244. package/templates/helpdesk/functions/seedWorkspace.ts +25 -15
  245. package/templates/helpdesk/lib/brand.ts +9 -0
  246. package/templates/helpdesk/lib/demo-team.ts +51 -0
  247. package/templates/helpdesk/lib/search.ts +4 -3
  248. package/templates/helpdesk/lib/seed.ts +43 -2
  249. package/templates/helpdesk/lib/tickets.ts +68 -20
  250. package/templates/helpdesk/public/favicon.ico +0 -0
  251. package/templates/helpdesk/tests/components.test.tsx +10 -0
  252. package/templates/helpdesk/tests/demo-team.test.ts +18 -0
  253. package/templates/helpdesk/tests/tickets.test.ts +70 -0
  254. package/templates/imessage/.env.example +49 -0
  255. package/templates/imessage/AGENTS.md +153 -0
  256. package/templates/imessage/CLAUDE.md +7 -0
  257. package/templates/imessage/README.md +187 -0
  258. package/templates/imessage/app/console.tsx +382 -0
  259. package/templates/imessage/app/contacts/page.tsx +13 -0
  260. package/templates/imessage/app/error.tsx +16 -0
  261. package/templates/imessage/app/gate.tsx +31 -0
  262. package/templates/imessage/app/globals.css +183 -0
  263. package/templates/imessage/app/layout.tsx +17 -0
  264. package/templates/imessage/app/login/login-form.tsx +115 -0
  265. package/templates/imessage/app/login/page.tsx +30 -0
  266. package/templates/imessage/app/not-found.tsx +13 -0
  267. package/templates/imessage/app/page.tsx +14 -0
  268. package/templates/imessage/app/robots.ts +6 -0
  269. package/templates/imessage/app/setup/page.tsx +13 -0
  270. package/templates/imessage/app/sign-out.tsx +20 -0
  271. package/templates/imessage/app.ts +299 -0
  272. package/templates/imessage/bunfig +4 -0
  273. package/templates/imessage/components/avatar.tsx +46 -0
  274. package/templates/imessage/components/contact-panel.tsx +263 -0
  275. package/templates/imessage/components/contacts-view.tsx +172 -0
  276. package/templates/imessage/components/conversation-list.tsx +129 -0
  277. package/templates/imessage/components/nav-rail.tsx +67 -0
  278. package/templates/imessage/components/setup-view.tsx +408 -0
  279. package/templates/imessage/components/thread.tsx +229 -0
  280. package/templates/imessage/components/ui/badge.tsx +37 -0
  281. package/templates/imessage/components/ui/button.tsx +56 -0
  282. package/templates/imessage/components/ui/card.tsx +90 -0
  283. package/templates/imessage/components/ui/dialog.tsx +110 -0
  284. package/templates/imessage/components/ui/input.tsx +30 -0
  285. package/templates/imessage/components/ui/label.tsx +21 -0
  286. package/templates/imessage/components/ui/select.tsx +39 -0
  287. package/templates/imessage/components/ui/table.tsx +124 -0
  288. package/templates/imessage/components/ui/textarea.tsx +24 -0
  289. package/templates/imessage/components.json +20 -0
  290. package/templates/imessage/functions/answerNow.ts +34 -0
  291. package/templates/imessage/functions/assistant.ts +96 -0
  292. package/templates/imessage/functions/beginTurn.ts +101 -0
  293. package/templates/imessage/functions/cancelReminder.ts +13 -0
  294. package/templates/imessage/functions/claimOutbound.ts +44 -0
  295. package/templates/imessage/functions/clearDemo.ts +18 -0
  296. package/templates/imessage/functions/deleteNote.ts +11 -0
  297. package/templates/imessage/functions/deliverOutbound.ts +55 -0
  298. package/templates/imessage/functions/finishTurn.ts +84 -0
  299. package/templates/imessage/functions/fireReminder.ts +42 -0
  300. package/templates/imessage/functions/ingestInbound.ts +22 -0
  301. package/templates/imessage/functions/ownerAccount.ts +13 -0
  302. package/templates/imessage/functions/processTurn.ts +74 -0
  303. package/templates/imessage/functions/relayApply.ts +102 -0
  304. package/templates/imessage/functions/relaySync.ts +63 -0
  305. package/templates/imessage/functions/releaseOutbound.ts +18 -0
  306. package/templates/imessage/functions/resolveTurn.ts +25 -0
  307. package/templates/imessage/functions/saveContact.ts +27 -0
  308. package/templates/imessage/functions/seedDemo.ts +70 -0
  309. package/templates/imessage/functions/sendOwnerMessage.ts +28 -0
  310. package/templates/imessage/functions/sendblueWebhook.ts +49 -0
  311. package/templates/imessage/functions/setContactStatus.ts +17 -0
  312. package/templates/imessage/functions/settleOutbound.ts +35 -0
  313. package/templates/imessage/functions/setupStatus.ts +33 -0
  314. package/templates/imessage/functions/sweep.ts +58 -0
  315. package/templates/imessage/functions/toolCancelReminder.ts +17 -0
  316. package/templates/imessage/functions/toolForgetNote.ts +15 -0
  317. package/templates/imessage/functions/toolListNotes.ts +16 -0
  318. package/templates/imessage/functions/toolListReminders.ts +17 -0
  319. package/templates/imessage/functions/toolSaveNote.ts +22 -0
  320. package/templates/imessage/functions/toolSetReminder.ts +55 -0
  321. package/templates/imessage/functions/updateSettings.ts +48 -0
  322. package/templates/imessage/gitignore +11 -0
  323. package/templates/imessage/lib/agent-call.ts +251 -0
  324. package/templates/imessage/lib/assistant.ts +74 -0
  325. package/templates/imessage/lib/chunk.ts +135 -0
  326. package/templates/imessage/lib/demo.ts +68 -0
  327. package/templates/imessage/lib/format.ts +170 -0
  328. package/templates/imessage/lib/gate.ts +104 -0
  329. package/templates/imessage/lib/handles.ts +67 -0
  330. package/templates/imessage/lib/provider.ts +38 -0
  331. package/templates/imessage/lib/relay-protocol.ts +95 -0
  332. package/templates/imessage/lib/reminders.ts +41 -0
  333. package/templates/imessage/lib/secrets.ts +59 -0
  334. package/templates/imessage/lib/sendblue.ts +173 -0
  335. package/templates/imessage/lib/store.ts +407 -0
  336. package/templates/imessage/lib/transport.ts +135 -0
  337. package/templates/imessage/lib/utils.ts +10 -0
  338. package/templates/imessage/package.json +40 -0
  339. package/templates/imessage/relay/.env.example +14 -0
  340. package/templates/imessage/relay/applescript.ts +70 -0
  341. package/templates/imessage/relay/chatdb.ts +165 -0
  342. package/templates/imessage/relay/client.ts +41 -0
  343. package/templates/imessage/relay/main.ts +137 -0
  344. package/templates/imessage/relay/state.ts +35 -0
  345. package/templates/imessage/tests/agent-call.test.ts +112 -0
  346. package/templates/imessage/tests/applescript.test.ts +88 -0
  347. package/templates/imessage/tests/chatdb.test.ts +139 -0
  348. package/templates/imessage/tests/chunk.test.ts +61 -0
  349. package/templates/imessage/tests/fake-ctx.ts +88 -0
  350. package/templates/imessage/tests/ingest.test.ts +141 -0
  351. package/templates/imessage/tests/lib.test.ts +99 -0
  352. package/templates/imessage/tests/review-fixes.test.ts +138 -0
  353. package/templates/imessage/tests/secrets.test.ts +36 -0
  354. package/templates/imessage/tests/sendblue.test.ts +143 -0
  355. package/templates/imessage/tests/setup.ts +5 -0
  356. package/templates/imessage/tsconfig.json +18 -0
  357. package/templates/inventory/AGENTS.md +1 -1
  358. package/templates/inventory/README.md +21 -0
  359. package/templates/inventory/app/apple-icon.png +0 -0
  360. package/templates/inventory/app/globals.css +29 -14
  361. package/templates/inventory/app/icon.svg +1 -0
  362. package/templates/inventory/app/layout.tsx +7 -5
  363. package/templates/inventory/app/movements/movements-view.tsx +32 -10
  364. package/templates/inventory/app/movements/page.tsx +2 -1
  365. package/templates/inventory/app/page.tsx +4 -6
  366. package/templates/inventory/app/products/[id]/page.tsx +2 -1
  367. package/templates/inventory/app/products/[id]/product-view.tsx +55 -25
  368. package/templates/inventory/app/products-view.tsx +74 -33
  369. package/templates/inventory/app/workspace.tsx +34 -16
  370. package/templates/inventory/app.ts +10 -2
  371. package/templates/inventory/components/app-shell.tsx +94 -0
  372. package/templates/inventory/components/auth-form.tsx +35 -5
  373. package/templates/inventory/components/avatar.tsx +5 -1
  374. package/templates/inventory/components/brand-mark.tsx +25 -0
  375. package/templates/inventory/components/category-tile.tsx +49 -0
  376. package/templates/inventory/components/command-palette.tsx +17 -17
  377. package/templates/inventory/components/data-table.tsx +97 -36
  378. package/templates/inventory/components/filter-tabs.tsx +101 -0
  379. package/templates/inventory/components/nav.tsx +14 -0
  380. package/templates/inventory/components/page-header.tsx +43 -7
  381. package/templates/inventory/components/require-auth.tsx +24 -26
  382. package/templates/inventory/components/sidebar.tsx +54 -48
  383. package/templates/inventory/components/stock-level.tsx +30 -14
  384. package/templates/inventory/components/summary-bar.tsx +8 -15
  385. package/templates/inventory/components/ui/button.tsx +5 -4
  386. package/templates/inventory/components/ui/dialog.tsx +1 -1
  387. package/templates/inventory/components/ui/input.tsx +1 -1
  388. package/templates/inventory/components/ui/select.tsx +1 -1
  389. package/templates/inventory/components/ui/textarea.tsx +1 -1
  390. package/templates/inventory/functions/seedWorkspace.ts +18 -8
  391. package/templates/inventory/lib/brand.ts +9 -0
  392. package/templates/inventory/lib/demo-team.ts +51 -0
  393. package/templates/inventory/lib/search.ts +4 -3
  394. package/templates/inventory/lib/seed.ts +29 -2
  395. package/templates/inventory/lib/stock.ts +32 -0
  396. package/templates/inventory/public/favicon.ico +0 -0
  397. package/templates/inventory/tests/demo-team.test.ts +18 -0
  398. package/templates/inventory/tests/stock.test.ts +29 -0
  399. package/templates/invoices/AGENTS.md +1 -1
  400. package/templates/invoices/README.md +16 -0
  401. package/templates/invoices/app/apple-icon.png +0 -0
  402. package/templates/invoices/app/clients/clients-view.tsx +18 -6
  403. package/templates/invoices/app/clients/page.tsx +2 -1
  404. package/templates/invoices/app/globals.css +29 -14
  405. package/templates/invoices/app/icon.svg +1 -0
  406. package/templates/invoices/app/invoices/[id]/invoice-view.tsx +72 -48
  407. package/templates/invoices/app/invoices/[id]/page.tsx +2 -1
  408. package/templates/invoices/app/invoices-view.tsx +81 -32
  409. package/templates/invoices/app/layout.tsx +7 -5
  410. package/templates/invoices/app/page.tsx +4 -6
  411. package/templates/invoices/app/workspace.tsx +26 -16
  412. package/templates/invoices/app.ts +2 -2
  413. package/templates/invoices/components/app-shell.tsx +94 -0
  414. package/templates/invoices/components/auth-form.tsx +35 -5
  415. package/templates/invoices/components/avatar.tsx +5 -1
  416. package/templates/invoices/components/brand-mark.tsx +26 -0
  417. package/templates/invoices/components/command-palette.tsx +17 -17
  418. package/templates/invoices/components/data-table.tsx +97 -36
  419. package/templates/invoices/components/filter-tabs.tsx +101 -0
  420. package/templates/invoices/components/line-items.tsx +19 -14
  421. package/templates/invoices/components/nav.tsx +14 -0
  422. package/templates/invoices/components/page-header.tsx +43 -7
  423. package/templates/invoices/components/require-auth.tsx +24 -26
  424. package/templates/invoices/components/sidebar.tsx +54 -48
  425. package/templates/invoices/components/status-badge.tsx +7 -7
  426. package/templates/invoices/components/summary-bar.tsx +5 -5
  427. package/templates/invoices/components/ui/button.tsx +5 -4
  428. package/templates/invoices/components/ui/dialog.tsx +1 -1
  429. package/templates/invoices/components/ui/input.tsx +1 -1
  430. package/templates/invoices/components/ui/select.tsx +1 -1
  431. package/templates/invoices/components/ui/textarea.tsx +1 -1
  432. package/templates/invoices/functions/recordPayment.ts +3 -0
  433. package/templates/invoices/lib/billing.ts +40 -0
  434. package/templates/invoices/lib/brand.ts +9 -0
  435. package/templates/invoices/lib/search.ts +4 -3
  436. package/templates/invoices/public/favicon.ico +0 -0
  437. package/templates/invoices/tests/billing.test.ts +27 -0
  438. package/templates/ios/consumer/apps/ios/Package.swift +4 -3
  439. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ActivityView.swift +107 -0
  440. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/AppModel.swift +171 -0
  441. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/AuthView.swift +182 -0
  442. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/CommentsView.swift +136 -0
  443. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ComposeView.swift +152 -0
  444. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ExploreView.swift +117 -0
  445. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/FeedView.swift +175 -159
  446. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/MainTabView.swift +54 -0
  447. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Media.swift +271 -0
  448. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Models.swift +101 -4
  449. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/PostCard.swift +245 -0
  450. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileEditorView.swift +208 -0
  451. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileView.swift +183 -0
  452. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/RootView.swift +25 -31
  453. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/SocialStore.swift +285 -0
  454. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/StoryViewer.swift +155 -0
  455. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Theme.swift +82 -0
  456. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/__APP_NAME_PASCAL__App.swift +4 -47
  457. package/templates/ios/consumer/apps/ios/project.yml +13 -9
  458. package/templates/local-service/.env.example +7 -0
  459. package/templates/local-service/AGENTS.md +1 -1
  460. package/templates/local-service/README.md +26 -2
  461. package/templates/local-service/app/(marketing)/booking-widget.tsx +48 -9
  462. package/templates/local-service/app/(marketing)/page.tsx +66 -42
  463. package/templates/local-service/app/auth-form.tsx +8 -8
  464. package/templates/local-service/app/dashboard/dashboard-client.tsx +119 -92
  465. package/templates/local-service/app/dashboard/page.tsx +14 -14
  466. package/templates/local-service/app/icon.svg +2 -0
  467. package/templates/local-service/app/login/page.tsx +15 -15
  468. package/templates/local-service/functions/seedDemo.ts +49 -0
  469. package/templates/local-service/lib/demo.ts +135 -0
  470. package/templates/local-service/lib/hours.ts +44 -0
  471. package/templates/local-service/lib/site.config.ts +8 -3
  472. package/templates/local-service/lib/slots.ts +3 -0
  473. package/templates/local-service/public/favicon.ico +0 -0
  474. package/templates/local-service/tests/booking.test.ts +104 -0
  475. package/templates/marketplace/AGENTS.md +1 -1
  476. package/templates/marketplace/README.md +23 -15
  477. package/templates/marketplace/app/globals.css +172 -114
  478. package/templates/marketplace/app/layout.tsx +75 -30
  479. package/templates/marketplace/app/listing/[id]/page.tsx +210 -123
  480. package/templates/marketplace/app/me/page.tsx +4 -4
  481. package/templates/marketplace/app/page.tsx +221 -298
  482. package/templates/marketplace/app/sell/page.tsx +8 -11
  483. package/templates/marketplace/app.ts +32 -7
  484. package/templates/marketplace/client/ActivityFeed.tsx +311 -0
  485. package/templates/marketplace/client/AuthNav.tsx +24 -16
  486. package/templates/marketplace/client/ListingCard.tsx +118 -0
  487. package/templates/marketplace/client/LiveGrid.tsx +92 -0
  488. package/templates/marketplace/client/LiveListingStatus.tsx +32 -24
  489. package/templates/marketplace/client/LoginCard.tsx +114 -136
  490. package/templates/marketplace/client/MarketProvider.tsx +3 -5
  491. package/templates/marketplace/client/MyMarket.tsx +371 -185
  492. package/templates/marketplace/client/OfferPanel.tsx +421 -287
  493. package/templates/marketplace/client/SellForm.tsx +271 -192
  494. package/templates/marketplace/client/ThemeToggle.tsx +1 -0
  495. package/templates/marketplace/client/WatchButton.tsx +16 -16
  496. package/templates/marketplace/client/market.ts +61 -14
  497. package/templates/marketplace/functions/seedMarket.ts +143 -36
  498. package/templates/marketplace/functions/seedOffers.ts +101 -0
  499. package/templates/marketplace/lib/catalog.ts +72 -1
  500. package/templates/marketplace/public/images/listings/ceramic-lamp-640.webp +0 -0
  501. package/templates/marketplace/public/images/listings/ceramic-lamp.webp +0 -0
  502. package/templates/marketplace/public/images/listings/duffel-640.webp +0 -0
  503. package/templates/marketplace/public/images/listings/duffel.webp +0 -0
  504. package/templates/marketplace/public/images/listings/film-camera-640.webp +0 -0
  505. package/templates/marketplace/public/images/listings/film-camera.webp +0 -0
  506. package/templates/marketplace/public/images/listings/lounge-chair-640.webp +0 -0
  507. package/templates/marketplace/public/images/listings/lounge-chair.webp +0 -0
  508. package/templates/marketplace/public/images/listings/road-bike-640.webp +0 -0
  509. package/templates/marketplace/public/images/listings/road-bike.webp +0 -0
  510. package/templates/marketplace/public/images/listings/slipper-chair-640.webp +0 -0
  511. package/templates/marketplace/public/images/listings/slipper-chair.webp +0 -0
  512. package/templates/marketplace/public/images/listings/turntable-640.webp +0 -0
  513. package/templates/marketplace/public/images/listings/turntable.webp +0 -0
  514. package/templates/marketplace/public/images/listings/wardrobe-640.webp +0 -0
  515. package/templates/marketplace/public/images/listings/wardrobe.webp +0 -0
  516. package/templates/marketplace/public/images/listings/watch-640.webp +0 -0
  517. package/templates/marketplace/public/images/listings/watch.webp +0 -0
  518. package/templates/marketplace/public/images/listings/weekender-bag-640.webp +0 -0
  519. package/templates/marketplace/public/images/listings/weekender-bag.webp +0 -0
  520. package/templates/marketplace/tests/example.test.ts +64 -2
  521. package/templates/marketplace/tests/listing-card.test.tsx +61 -0
  522. package/templates/projects/AGENTS.md +1 -1
  523. package/templates/projects/README.md +24 -3
  524. package/templates/projects/app/apple-icon.png +0 -0
  525. package/templates/projects/app/clients/clients-view.tsx +20 -6
  526. package/templates/projects/app/clients/page.tsx +2 -1
  527. package/templates/projects/app/globals.css +29 -14
  528. package/templates/projects/app/icon.svg +1 -0
  529. package/templates/projects/app/layout.tsx +7 -5
  530. package/templates/projects/app/page.tsx +3 -5
  531. package/templates/projects/app/projects/[id]/page.tsx +2 -1
  532. package/templates/projects/app/projects/[id]/project-view.tsx +45 -30
  533. package/templates/projects/app/projects-view.tsx +105 -52
  534. package/templates/projects/app/workspace.tsx +41 -20
  535. package/templates/projects/app.ts +2 -2
  536. package/templates/projects/components/app-shell.tsx +94 -0
  537. package/templates/projects/components/auth-form.tsx +35 -5
  538. package/templates/projects/components/avatar.tsx +5 -1
  539. package/templates/projects/components/board-skeleton.tsx +3 -3
  540. package/templates/projects/components/brand-mark.tsx +25 -0
  541. package/templates/projects/components/command-palette.tsx +17 -17
  542. package/templates/projects/components/data-table.tsx +97 -36
  543. package/templates/projects/components/filter-tabs.tsx +101 -0
  544. package/templates/projects/components/meter.tsx +48 -0
  545. package/templates/projects/components/nav.tsx +14 -0
  546. package/templates/projects/components/page-header.tsx +43 -7
  547. package/templates/projects/components/require-auth.tsx +24 -26
  548. package/templates/projects/components/sidebar.tsx +54 -48
  549. package/templates/projects/components/summary-bar.tsx +49 -0
  550. package/templates/projects/components/task-board.tsx +10 -9
  551. package/templates/projects/components/time-dialog.tsx +29 -5
  552. package/templates/projects/components/ui/button.tsx +5 -4
  553. package/templates/projects/components/ui/dialog.tsx +1 -1
  554. package/templates/projects/components/ui/input.tsx +1 -1
  555. package/templates/projects/components/ui/select.tsx +1 -1
  556. package/templates/projects/components/ui/textarea.tsx +1 -1
  557. package/templates/projects/functions/seedWorkspace.ts +26 -12
  558. package/templates/projects/lib/brand.ts +9 -0
  559. package/templates/projects/lib/demo-team.ts +51 -0
  560. package/templates/projects/lib/search.ts +4 -3
  561. package/templates/projects/lib/seed.ts +35 -1
  562. package/templates/projects/lib/work.ts +63 -0
  563. package/templates/projects/public/favicon.ico +0 -0
  564. package/templates/projects/tests/demo-team.test.ts +18 -0
  565. package/templates/projects/tests/work.test.ts +65 -0
  566. package/templates/restaurant/.env.example +7 -0
  567. package/templates/restaurant/AGENTS.md +1 -1
  568. package/templates/restaurant/README.md +29 -3
  569. package/templates/restaurant/app/(marketing)/layout.tsx +4 -2
  570. package/templates/restaurant/app/(marketing)/page.tsx +47 -23
  571. package/templates/restaurant/app/(marketing)/reservation-widget.tsx +37 -7
  572. package/templates/restaurant/app/auth-form.tsx +8 -8
  573. package/templates/restaurant/app/dashboard/dashboard-client.tsx +241 -109
  574. package/templates/restaurant/app/dashboard/page.tsx +14 -14
  575. package/templates/restaurant/app/icon.svg +4 -0
  576. package/templates/restaurant/app/login/page.tsx +15 -15
  577. package/templates/restaurant/functions/seedDemo.ts +44 -0
  578. package/templates/restaurant/lib/demo.ts +144 -0
  579. package/templates/restaurant/lib/hours.ts +45 -0
  580. package/templates/restaurant/lib/site.config.ts +3 -3
  581. package/templates/restaurant/public/favicon.ico +0 -0
  582. package/templates/restaurant/tests/reservations.test.ts +67 -0
  583. package/templates/saas/AGENTS.md +1 -1
  584. package/templates/saas/README.md +54 -35
  585. package/templates/saas/app/(auth)/auth-form.tsx +25 -23
  586. package/templates/saas/app/(auth)/forgot-password/page.tsx +1 -1
  587. package/templates/saas/app/(auth)/layout.tsx +27 -33
  588. package/templates/saas/app/(auth)/login/page.tsx +1 -1
  589. package/templates/saas/app/(auth)/reset-password/page.tsx +1 -1
  590. package/templates/saas/app/(auth)/signup/page.tsx +1 -1
  591. package/templates/saas/app/(marketing)/layout.tsx +127 -253
  592. package/templates/saas/app/(marketing)/page.tsx +133 -372
  593. package/templates/saas/app/(marketing)/pricing/page.tsx +21 -19
  594. package/templates/saas/app/(marketing)/products/[slug]/page.tsx +40 -95
  595. package/templates/saas/app/dashboard/board-client.tsx +416 -0
  596. package/templates/saas/app/dashboard/dashboard-client.tsx +30 -615
  597. package/templates/saas/app/dashboard/layout.tsx +27 -13
  598. package/templates/saas/app/dashboard/overview-client.tsx +332 -0
  599. package/templates/saas/app/dashboard/page.tsx +29 -35
  600. package/templates/saas/app/dashboard/projects/[id]/page.tsx +29 -0
  601. package/templates/saas/app/dashboard/projects/page.tsx +11 -7
  602. package/templates/saas/app/dashboard/projects-client.tsx +398 -0
  603. package/templates/saas/app/globals.css +5 -4
  604. package/templates/saas/app/icon.svg +1 -0
  605. package/templates/saas/app/onboarding/onboarding-client.tsx +93 -26
  606. package/templates/saas/app/onboarding/page.tsx +6 -2
  607. package/templates/saas/app.ts +68 -13
  608. package/templates/saas/components/dashboard-shell.tsx +212 -132
  609. package/templates/saas/components/marketing.tsx +137 -274
  610. package/templates/saas/components/pricing-table.tsx +12 -11
  611. package/templates/saas/components/setup-checklist.tsx +31 -29
  612. package/templates/saas/components/task-ui.tsx +199 -0
  613. package/templates/saas/functions/createProject.ts +7 -13
  614. package/templates/saas/functions/createTask.ts +67 -0
  615. package/templates/saas/functions/deleteProject.ts +17 -0
  616. package/templates/saas/functions/deleteTask.ts +14 -0
  617. package/templates/saas/functions/seedWorkspace.ts +46 -0
  618. package/templates/saas/functions/setProjectStatus.ts +41 -0
  619. package/templates/saas/functions/updateProfile.ts +13 -0
  620. package/templates/saas/functions/updateProject.ts +4 -5
  621. package/templates/saas/functions/updateTask.ts +88 -0
  622. package/templates/saas/lib/members.ts +53 -0
  623. package/templates/saas/lib/names.ts +53 -0
  624. package/templates/saas/lib/plans.ts +11 -0
  625. package/templates/saas/lib/sample-workspace.ts +117 -0
  626. package/templates/saas/lib/site.config.ts +200 -463
  627. package/templates/saas/lib/tasks.ts +101 -0
  628. package/templates/saas/lib/use-live.ts +25 -0
  629. package/templates/saas/public/favicon.ico +0 -0
  630. package/templates/saas/public/screenshots/board.png +0 -0
  631. package/templates/saas/public/screenshots/members.png +0 -0
  632. package/templates/saas/public/screenshots/overview.png +0 -0
  633. package/templates/saas/public/screenshots/projects.png +0 -0
  634. package/templates/saas/scripts/capture-screenshots.mjs +156 -0
  635. package/templates/saas/tests/names.test.ts +35 -0
  636. package/templates/saas/tests/plans.test.ts +12 -0
  637. package/templates/saas/tests/sample-workspace.test.ts +46 -0
  638. package/templates/saas/tests/tasks.test.ts +81 -0
  639. package/templates/shop/AGENTS.md +1 -1
  640. package/templates/shop/README.md +25 -13
  641. package/templates/shop/app/(marketing)/layout.tsx +94 -67
  642. package/templates/shop/app/(marketing)/not-found.tsx +13 -12
  643. package/templates/shop/app/(marketing)/page.tsx +120 -59
  644. package/templates/shop/app/(marketing)/shop-client.tsx +364 -237
  645. package/templates/shop/app/(marketing)/success/page.tsx +75 -22
  646. package/templates/shop/app/globals.css +94 -1
  647. package/templates/shop/app/layout.tsx +4 -4
  648. package/templates/shop/app/login/page.tsx +4 -4
  649. package/templates/shop/app.ts +18 -6
  650. package/templates/shop/components/cart-button.tsx +33 -0
  651. package/templates/shop/components/marketing.tsx +17 -87
  652. package/templates/shop/components/shelf-count.tsx +36 -0
  653. package/templates/shop/functions/checkout.ts +17 -1
  654. package/templates/shop/lib/cart-store.ts +57 -0
  655. package/templates/shop/lib/shelf-store.ts +35 -0
  656. package/templates/shop/lib/shop.ts +27 -0
  657. package/templates/shop/lib/site.config.ts +108 -98
  658. package/templates/shop/public/images/hero.jpg +0 -0
  659. package/templates/shop/public/images/products/cedar-smoke.jpg +0 -0
  660. package/templates/shop/public/images/products/fig-leaf.jpg +0 -0
  661. package/templates/shop/public/images/products/linen.jpg +0 -0
  662. package/templates/shop/public/images/products/sea-salt.jpg +0 -0
  663. package/templates/shop/public/images/products/tobacco-oak.jpg +0 -0
  664. package/templates/shop/tests/shop.test.ts +64 -0
  665. package/templates/todo/AGENTS.md +1 -1
  666. package/templates/waitlist/.env.example +7 -0
  667. package/templates/waitlist/AGENTS.md +1 -1
  668. package/templates/waitlist/README.md +29 -13
  669. package/templates/waitlist/app/(marketing)/page.tsx +9 -7
  670. package/templates/waitlist/app/(marketing)/waitlist-hero.tsx +64 -31
  671. package/templates/waitlist/app/auth-form.tsx +8 -8
  672. package/templates/waitlist/app/dashboard/dashboard-client.tsx +132 -95
  673. package/templates/waitlist/app/dashboard/page.tsx +17 -16
  674. package/templates/waitlist/app/icon.svg +2 -0
  675. package/templates/waitlist/app/login/page.tsx +14 -14
  676. package/templates/waitlist/app.ts +6 -6
  677. package/templates/waitlist/functions/joinWaitlist.ts +6 -18
  678. package/templates/waitlist/functions/seedDemo.ts +32 -0
  679. package/templates/waitlist/lib/demo.ts +94 -0
  680. package/templates/waitlist/lib/launch.ts +19 -0
  681. package/templates/waitlist/lib/site.config.ts +14 -9
  682. package/templates/waitlist/lib/stats.ts +35 -0
  683. package/templates/waitlist/public/favicon.ico +0 -0
  684. package/templates/waitlist/tests/counter.test.ts +107 -0
  685. package/templates/waitlist/tests/live-count.test.tsx +48 -0
  686. package/templates/ai-chat/tests/example.test.ts +0 -12
  687. package/templates/backend/consumer/apps/api/functions/feed.ts +0 -57
  688. package/templates/backend/consumer/apps/api/functions/profilePosts.ts +0 -17
  689. package/templates/backend/consumer/apps/api/functions/toggleLike.ts +0 -48
  690. package/templates/backend/consumer/apps/api/schema.ts +0 -143
  691. package/templates/chat/functions/_keep.ts +0 -13
  692. package/templates/chat/tests/example.test.ts +0 -12
  693. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileSetupView.swift +0 -68
  694. package/templates/marketplace/client/LiveTicker.tsx +0 -116
  695. package/templates/marketplace/client/ScrollToListingsLink.tsx +0 -29
  696. package/templates/marketplace/public/images/marketplace-hero.webp +0 -0
@@ -0,0 +1,101 @@
1
+ // Task vocabulary and pure helpers. The server functions (createTask,
2
+ // updateTask) validate with these, and the dashboard renders from the same
3
+ // lists, so a status or priority can't exist in one place and not the other.
4
+ // tests/tasks.test.ts covers them without a running server.
5
+
6
+ export const TASK_STATUSES = ["todo", "in_progress", "in_review", "done"] as const;
7
+ export type TaskStatus = (typeof TASK_STATUSES)[number];
8
+
9
+ export const STATUS_LABEL: Record<TaskStatus, string> = {
10
+ todo: "To do",
11
+ in_progress: "In progress",
12
+ in_review: "In review",
13
+ done: "Done",
14
+ };
15
+
16
+ export const TASK_PRIORITIES = ["urgent", "high", "medium", "low", "none"] as const;
17
+ export type TaskPriority = (typeof TASK_PRIORITIES)[number];
18
+
19
+ export const PRIORITY_LABEL: Record<TaskPriority, string> = {
20
+ urgent: "Urgent",
21
+ high: "High",
22
+ medium: "Medium",
23
+ low: "Low",
24
+ none: "No priority",
25
+ };
26
+
27
+ export interface TaskRow {
28
+ id: string;
29
+ orgId: string;
30
+ projectId: string;
31
+ title: string;
32
+ status: string;
33
+ priority?: string | null;
34
+ assigneeId?: string | null;
35
+ assigneeName?: string | null;
36
+ dueDate?: string | null;
37
+ completedAt?: string | null;
38
+ createdAt: string;
39
+ }
40
+
41
+ export function isTaskStatus(value: unknown): value is TaskStatus {
42
+ return typeof value === "string" && (TASK_STATUSES as readonly string[]).includes(value);
43
+ }
44
+
45
+ export function isTaskPriority(value: unknown): value is TaskPriority {
46
+ return typeof value === "string" && (TASK_PRIORITIES as readonly string[]).includes(value);
47
+ }
48
+
49
+ /** Trimmed title, or null when it is empty or longer than 200 characters. */
50
+ export function normalizeTaskTitle(raw: string): string | null {
51
+ const title = raw.trim().replace(/\s+/g, " ");
52
+ if (title.length < 1 || title.length > 200) return null;
53
+ return title;
54
+ }
55
+
56
+ /** `YYYY-MM-DD` that is a real calendar date, or null. */
57
+ export function normalizeDueDate(raw: string | null | undefined): string | null {
58
+ if (!raw) return null;
59
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(raw)) return null;
60
+ const d = new Date(`${raw}T00:00:00Z`);
61
+ if (Number.isNaN(d.getTime()) || d.toISOString().slice(0, 10) !== raw) return null;
62
+ return raw;
63
+ }
64
+
65
+ /** Done / total counts and a 0–100 percentage for one project's tasks. */
66
+ export function progressOf(tasks: Pick<TaskRow, "status">[]): {
67
+ done: number;
68
+ total: number;
69
+ percent: number;
70
+ } {
71
+ const total = tasks.length;
72
+ const done = tasks.filter((t) => t.status === "done").length;
73
+ return { done, total, percent: total === 0 ? 0 : Math.round((done / total) * 100) };
74
+ }
75
+
76
+ const PRIORITY_RANK: Record<string, number> = { urgent: 0, high: 1, medium: 2, low: 3, none: 4 };
77
+
78
+ /**
79
+ * Board order inside a column: priority first, then the earliest due date,
80
+ * then the newest task. Undated tasks sort after dated ones.
81
+ */
82
+ export function compareTasks(a: TaskRow, b: TaskRow): number {
83
+ const pa = PRIORITY_RANK[a.priority ?? "none"] ?? 4;
84
+ const pb = PRIORITY_RANK[b.priority ?? "none"] ?? 4;
85
+ if (pa !== pb) return pa - pb;
86
+ const da = a.dueDate ?? "9999-12-31";
87
+ const db = b.dueDate ?? "9999-12-31";
88
+ if (da !== db) return da < db ? -1 : 1;
89
+ return a.createdAt < b.createdAt ? 1 : -1;
90
+ }
91
+
92
+ /** Whole days from `today` to `due` (both `YYYY-MM-DD`). Negative when overdue. */
93
+ export function daysUntil(due: string, today: string): number {
94
+ const ms = Date.parse(`${due}T00:00:00Z`) - Date.parse(`${today}T00:00:00Z`);
95
+ return Math.round(ms / 86_400_000);
96
+ }
97
+
98
+ /** `YYYY-MM-DD` for a Date, in UTC. */
99
+ export function isoDay(d: Date): string {
100
+ return d.toISOString().slice(0, 10);
101
+ }
@@ -0,0 +1,25 @@
1
+ import { useEffect, useState } from "react";
2
+ import { db } from "@pylonsync/react";
3
+
4
+ /**
5
+ * Rows of `entity` for one workspace, live. The server render and the first
6
+ * client paint use `initial` (resolved with `serverData` in the page), so the
7
+ * HTML and the hydrated tree match and nothing flashes empty. After mount the
8
+ * list switches to the synced replica, which updates on every write from any
9
+ * tab or teammate.
10
+ *
11
+ * The entity's read policy already limits the replica to the active
12
+ * workspace; the `orgId` filter drops rows from a previous workspace that
13
+ * can linger for a moment after switching.
14
+ */
15
+ export function useLiveRows<T extends { orgId: string }>(
16
+ entity: string,
17
+ initial: T[],
18
+ orgId: string,
19
+ ): T[] {
20
+ const [hydrated, setHydrated] = useState(false);
21
+ useEffect(() => setHydrated(true), []);
22
+ const { data, loading } = db.useQuery<T>(entity);
23
+ if (!hydrated || loading) return initial;
24
+ return data.filter((row) => row.orgId === orgId);
25
+ }
@@ -0,0 +1,156 @@
1
+ #!/usr/bin/env node
2
+ // Regenerates the product screenshots the landing page shows
3
+ // (public/screenshots/*.png) from this app's own dashboard.
4
+ //
5
+ // 1. Start the app: pylon dev
6
+ // 2. Install Playwright once: npm i -D playwright && npx playwright install chromium
7
+ // 3. Capture: node scripts/capture-screenshots.mjs
8
+ //
9
+ // The script signs up a workspace owner and three teammates through the auth
10
+ // API, invites the teammates, adds the sample projects (seedWorkspace), then
11
+ // opens the dashboard in Chromium and saves each screen. Run it against a
12
+ // fresh development database: it reuses the accounts on a second run, but the
13
+ // data then reflects whatever you changed in between.
14
+ //
15
+ // Set BASE_URL to capture from another port.
16
+
17
+ import { mkdir } from "node:fs/promises";
18
+ import { dirname, join } from "node:path";
19
+ import { fileURLToPath } from "node:url";
20
+
21
+ const BASE = (process.env.BASE_URL ?? "http://localhost:4321").replace(/\/$/, "");
22
+ const OUT = join(dirname(fileURLToPath(import.meta.url)), "..", "public", "screenshots");
23
+ const PASSWORD = "sample-password-2941";
24
+ const OWNER = { email: "dana@northwind.test", name: "Dana Reyes" };
25
+ const TEAM = [
26
+ { email: "sam@northwind.test", name: "Sam Okafor" },
27
+ { email: "priya@northwind.test", name: "Priya Raman" },
28
+ { email: "leo@northwind.test", name: "Leo Park" },
29
+ ];
30
+ const PENDING = ["maria@northwind.test", "jonas@northwind.test"];
31
+
32
+ let chromium;
33
+ try {
34
+ ({ chromium } = await import("playwright"));
35
+ } catch {
36
+ try {
37
+ ({ chromium } = await import("playwright-core"));
38
+ } catch {
39
+ console.error("Playwright is not installed. Run: npm i -D playwright && npx playwright install chromium");
40
+ process.exit(1);
41
+ }
42
+ }
43
+
44
+ async function api(path, { token, body, method = "POST" } = {}) {
45
+ const res = await fetch(BASE + path, {
46
+ method,
47
+ headers: {
48
+ "content-type": "application/json",
49
+ ...(token ? { authorization: `Bearer ${token}` } : {}),
50
+ },
51
+ body: body === undefined ? undefined : JSON.stringify(body),
52
+ });
53
+ const data = await res.json().catch(() => ({}));
54
+ if (!res.ok) {
55
+ const err = new Error(`${method} ${path} → ${res.status} ${data?.error?.code ?? ""} ${data?.error?.message ?? ""}`);
56
+ err.code = data?.error?.code;
57
+ throw err;
58
+ }
59
+ return data;
60
+ }
61
+
62
+ /** Register the account, or sign in when it already exists. Returns a token. */
63
+ async function account({ email, name }) {
64
+ try {
65
+ const s = await api("/api/auth/password/register", { body: { email, password: PASSWORD, displayName: name } });
66
+ return s.token;
67
+ } catch (err) {
68
+ if (err.code !== "EMAIL_TAKEN" && err.code !== "USER_EXISTS") throw err;
69
+ const s = await api("/api/auth/password/login", { body: { email, password: PASSWORD } });
70
+ return s.token;
71
+ }
72
+ }
73
+
74
+ async function setUp() {
75
+ const owner = await account(OWNER);
76
+ const orgs = await api("/api/auth/orgs", { token: owner, method: "GET" });
77
+ const list = Array.isArray(orgs) ? orgs : orgs.orgs ?? [];
78
+ let org = list.find((o) => o.name === "Northwind");
79
+ if (!org) org = await api("/api/auth/orgs", { token: owner, body: { name: "Northwind" } });
80
+ await api("/api/auth/select-org", { token: owner, body: { orgId: org.id } });
81
+
82
+ const members = await api(`/api/auth/orgs/${org.id}/members`, { token: owner, method: "GET" });
83
+ const roster = Array.isArray(members) ? members : members.members ?? [];
84
+ for (const person of TEAM) {
85
+ const token = await account(person);
86
+ if (roster.some((m) => m.email === person.email)) continue;
87
+ const invite = await api(`/api/auth/orgs/${org.id}/invites`, {
88
+ token: owner,
89
+ body: { email: person.email, role: "member" },
90
+ });
91
+ await api(`/api/auth/invites/${encodeURIComponent(invite.token)}/accept`, { token, body: {} });
92
+ }
93
+ for (const email of PENDING) {
94
+ await api(`/api/auth/orgs/${org.id}/invites`, { token: owner, body: { email, role: "member" } }).catch(() => {});
95
+ }
96
+
97
+ await api("/api/fn/seedWorkspace", { token: owner, body: { orgId: org.id } });
98
+ await api("/api/fn/completeOnboarding", { token: owner, body: { orgId: org.id } });
99
+ await api("/api/fn/dismissSetup", { token: owner, body: { orgId: org.id } });
100
+ return org;
101
+ }
102
+
103
+ // Hide the development-only overlay badge that `pylon dev` adds.
104
+ async function hideDevBadge(page) {
105
+ await page.evaluate(() => {
106
+ for (const el of document.querySelectorAll("body *")) {
107
+ if (el.children.length === 0 && el.textContent?.trim() === "pylon") {
108
+ let node = el;
109
+ while (node && getComputedStyle(node).position !== "fixed") node = node.parentElement;
110
+ if (node) node.style.display = "none";
111
+ }
112
+ }
113
+ });
114
+ }
115
+
116
+ async function shoot(page, path, file) {
117
+ await page.goto(BASE + path, { waitUntil: "load" });
118
+ await page.waitForTimeout(1500);
119
+ await hideDevBadge(page);
120
+ await page.screenshot({ path: join(OUT, file) });
121
+ console.log(`saved public/screenshots/${file}`);
122
+ }
123
+
124
+ const org = await setUp();
125
+ await mkdir(OUT, { recursive: true });
126
+
127
+ const browser = await chromium.launch();
128
+ const context = await browser.newContext({ viewport: { width: 1440, height: 900 }, deviceScaleFactor: 2 });
129
+ const page = await context.newPage();
130
+ // Signing in from the page stores the session cookie in this browser
131
+ // context. The active workspace belongs to the session, so select it here too.
132
+ await page.goto(BASE + "/login");
133
+ await page.evaluate(
134
+ async ({ email, password, orgId }) => {
135
+ const post = (path, body) =>
136
+ fetch(path, {
137
+ method: "POST",
138
+ headers: { "content-type": "application/json" },
139
+ credentials: "include",
140
+ body: JSON.stringify(body),
141
+ });
142
+ await post("/api/auth/password/login", { email, password });
143
+ await post("/api/auth/select-org", { orgId });
144
+ },
145
+ { email: OWNER.email, password: PASSWORD, orgId: org.id },
146
+ );
147
+
148
+ await shoot(page, "/dashboard", "overview.png");
149
+ await shoot(page, "/dashboard/projects", "projects.png");
150
+ await shoot(page, "/dashboard/members", "members.png");
151
+ await page.goto(BASE + "/dashboard/projects", { waitUntil: "load" });
152
+ await page.getByRole("link", { name: /Website relaunch/ }).first().click();
153
+ await page.waitForURL(/\/dashboard\/projects\/.+/);
154
+ await shoot(page, new URL(page.url()).pathname, "board.png");
155
+
156
+ await browser.close();
@@ -0,0 +1,35 @@
1
+ import { expect, test } from "bun:test";
2
+ import { firstName, initials, nameFromEmail, personName } from "../lib/names";
3
+
4
+ test("a name built from an email drops the +tag and digits", () => {
5
+ expect(nameFromEmail("dana+s13596@example.com")).toBe("Dana");
6
+ expect(nameFromEmail("dana.reyes@example.com")).toBe("Dana Reyes");
7
+ expect(nameFromEmail("sam_okafor-2@example.com")).toBe("Sam Okafor");
8
+ expect(nameFromEmail("1234@example.com")).toBe("");
9
+ expect(nameFromEmail(null)).toBe("");
10
+ });
11
+
12
+ test("the saved display name wins", () => {
13
+ expect(personName({ displayName: "Dana Reyes", email: "d@example.com" })).toBe("Dana Reyes");
14
+ expect(personName({ displayName: " ", email: "leo.park@example.com" })).toBe("Leo Park");
15
+ });
16
+
17
+ test("a name that is an email address counts as no name", () => {
18
+ expect(personName({ name: "priya@example.com", email: "priya@example.com" })).toBe("Priya");
19
+ });
20
+
21
+ test("an email with nothing readable falls back to the email", () => {
22
+ expect(personName({ email: "42@example.com" })).toBe("42@example.com");
23
+ });
24
+
25
+ test("greetings use the first word; avatars use two initials", () => {
26
+ expect(firstName({ displayName: "Dana Reyes" })).toBe("Dana");
27
+ expect(initials("Dana Reyes")).toBe("DR");
28
+ expect(initials("Priya Anand Raman")).toBe("PR");
29
+ expect(initials("Leo")).toBe("L");
30
+ expect(initials("")).toBe("?");
31
+ });
32
+
33
+ test("names longer than the UI limit are cut", () => {
34
+ expect(personName({ displayName: "x".repeat(500) })).toHaveLength(60);
35
+ });
@@ -6,6 +6,7 @@ import {
6
6
  canCreateProject,
7
7
  planById,
8
8
  planFromSubscription,
9
+ planFromSubscriptions,
9
10
  } from "../lib/plans";
10
11
 
11
12
  // Tier 1 (pure logic) — the cap and the plan derivation that
@@ -33,3 +34,14 @@ test("annual pricing derives its total and saving from the catalog", () => {
33
34
  expect(annualSavingsPercent(pro)).toBeGreaterThan(0);
34
35
  expect(annualSavingsPercent(planById("free")!)).toBe(0);
35
36
  });
37
+
38
+ test("a usable Pro row wins over an older canceled row", () => {
39
+ expect(
40
+ planFromSubscriptions([
41
+ { plan: "pro", status: "canceled" },
42
+ { plan: "pro", status: "trialing" },
43
+ ]),
44
+ ).toBe("pro");
45
+ expect(planFromSubscriptions([{ plan: "pro", status: "canceled" }])).toBe("free");
46
+ expect(planFromSubscriptions([])).toBe("free");
47
+ });
@@ -0,0 +1,46 @@
1
+ import { expect, test } from "bun:test";
2
+ import { FREE_PROJECT_LIMIT } from "../lib/plans";
3
+ import { SAMPLE_PROJECTS, planSampleWorkspace } from "../lib/sample-workspace";
4
+ import { isTaskPriority, isTaskStatus, normalizeTaskTitle } from "../lib/tasks";
5
+
6
+ const NOW = new Date("2026-09-26T12:00:00Z");
7
+
8
+ test("the samples leave a free workspace room for one project of its own", () => {
9
+ const active = SAMPLE_PROJECTS.filter((p) => p.status === "active").length;
10
+ expect(active).toBeLessThan(FREE_PROJECT_LIMIT);
11
+ });
12
+
13
+ test("every sample task passes the same validation as createTask", () => {
14
+ for (const p of SAMPLE_PROJECTS) {
15
+ for (const t of p.tasks) {
16
+ expect(normalizeTaskTitle(t.title)).toBe(t.title);
17
+ expect(isTaskStatus(t.status)).toBe(true);
18
+ expect(isTaskPriority(t.priority)).toBe(true);
19
+ }
20
+ }
21
+ });
22
+
23
+ test("due dates are relative to now, and done tasks get a completion time", () => {
24
+ const plan = planSampleWorkspace([], NOW);
25
+ const first = plan[0].tasks.find((t) => t.title === "Fix the mobile nav overlap on Safari")!;
26
+ expect(first.dueDate).toBe("2026-09-26");
27
+ const done = plan[0].tasks.find((t) => t.status === "done")!;
28
+ expect(done.completedAt).not.toBeNull();
29
+ expect(plan[0].tasks.filter((t) => t.status !== "done").every((t) => t.completedAt === null)).toBe(true);
30
+ });
31
+
32
+ test("tasks go only to the members passed in", () => {
33
+ const solo = planSampleWorkspace([{ userId: "u1", name: "Dana Reyes" }], NOW);
34
+ const assigned = solo.flatMap((p) => p.tasks).filter((t) => t.assigneeId);
35
+ expect(assigned.length).toBeGreaterThan(0);
36
+ expect(assigned.every((t) => t.assigneeId === "u1" && t.assigneeName === "Dana Reyes")).toBe(true);
37
+
38
+ const nobody = planSampleWorkspace([], NOW);
39
+ expect(nobody.flatMap((p) => p.tasks).every((t) => t.assigneeId === null)).toBe(true);
40
+ });
41
+
42
+ test("a team gets the work spread across people", () => {
43
+ const team = ["a", "b", "c", "d"].map((id) => ({ userId: id, name: id }));
44
+ const ids = new Set(planSampleWorkspace(team, NOW).flatMap((p) => p.tasks).map((t) => t.assigneeId));
45
+ expect(ids).toEqual(new Set(["a", "b", "c", "d", null]));
46
+ });
@@ -0,0 +1,81 @@
1
+ import { expect, test } from "bun:test";
2
+ import {
3
+ compareTasks,
4
+ daysUntil,
5
+ isTaskPriority,
6
+ isTaskStatus,
7
+ normalizeDueDate,
8
+ normalizeTaskTitle,
9
+ progressOf,
10
+ type TaskRow,
11
+ } from "../lib/tasks";
12
+
13
+ // Tier 1 (pure logic): the validation createTask/updateTask run on the
14
+ // server, and the ordering and progress math the dashboard renders.
15
+
16
+ test("statuses and priorities are closed lists", () => {
17
+ expect(isTaskStatus("in_review")).toBe(true);
18
+ expect(isTaskStatus("blocked")).toBe(false);
19
+ expect(isTaskStatus(undefined)).toBe(false);
20
+ expect(isTaskPriority("urgent")).toBe(true);
21
+ expect(isTaskPriority("critical")).toBe(false);
22
+ });
23
+
24
+ test("titles are trimmed, collapsed, and bounded", () => {
25
+ expect(normalizeTaskTitle(" Ship the build ")).toBe("Ship the build");
26
+ expect(normalizeTaskTitle(" ")).toBeNull();
27
+ expect(normalizeTaskTitle("x".repeat(200))).toHaveLength(200);
28
+ expect(normalizeTaskTitle("x".repeat(201))).toBeNull();
29
+ });
30
+
31
+ test("due dates must be real calendar days", () => {
32
+ expect(normalizeDueDate("2026-02-28")).toBe("2026-02-28");
33
+ expect(normalizeDueDate("2026-02-30")).toBeNull();
34
+ expect(normalizeDueDate("26-2-3")).toBeNull();
35
+ expect(normalizeDueDate("")).toBeNull();
36
+ expect(normalizeDueDate(null)).toBeNull();
37
+ });
38
+
39
+ test("progress counts done tasks", () => {
40
+ expect(progressOf([])).toEqual({ done: 0, total: 0, percent: 0 });
41
+ expect(progressOf([{ status: "done" }, { status: "todo" }, { status: "done" }])).toEqual({
42
+ done: 2,
43
+ total: 3,
44
+ percent: 67,
45
+ });
46
+ });
47
+
48
+ test("daysUntil is negative when overdue", () => {
49
+ expect(daysUntil("2026-09-26", "2026-09-26")).toBe(0);
50
+ expect(daysUntil("2026-10-01", "2026-09-26")).toBe(5);
51
+ expect(daysUntil("2026-09-20", "2026-09-26")).toBe(-6);
52
+ });
53
+
54
+ test("board order: priority, then due date, then newest", () => {
55
+ const row = (id: string, priority: string, dueDate: string | null, createdAt: string): TaskRow => ({
56
+ id,
57
+ orgId: "o",
58
+ projectId: "p",
59
+ title: id,
60
+ status: "todo",
61
+ priority,
62
+ dueDate,
63
+ createdAt,
64
+ });
65
+ const sorted = [
66
+ row("low-soon", "low", "2026-09-27", "2026-09-01"),
67
+ row("urgent", "urgent", null, "2026-09-01"),
68
+ row("high-late", "high", "2026-10-09", "2026-09-01"),
69
+ row("high-soon", "high", "2026-09-28", "2026-09-01"),
70
+ row("high-undated-new", "high", null, "2026-09-03"),
71
+ row("high-undated-old", "high", null, "2026-09-02"),
72
+ ].sort(compareTasks);
73
+ expect(sorted.map((t) => t.id)).toEqual([
74
+ "urgent",
75
+ "high-soon",
76
+ "high-late",
77
+ "high-undated-new",
78
+ "high-undated-old",
79
+ "low-soon",
80
+ ]);
81
+ });
@@ -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`.
@@ -22,8 +22,10 @@ out in one tab, then watch it change to "Sold out" in another.
22
22
 
23
23
  ## How the realtime works
24
24
 
25
- - `Product` is a public-read entity holding live stock; `app/shop-client.tsx`
26
- reads it with `db.useQuery`, so every card's stock count is live.
25
+ - `Product` is a public-read entity holding live stock;
26
+ `app/(marketing)/shop-client.tsx` reads it with `db.useQuery`, so every
27
+ card's stock count is live. The hero's shelf count reads the same totals
28
+ through `lib/shelf-store.ts`.
27
29
  - `functions/checkout.ts` is a public **action**. It calls
28
30
  `functions/reserveCart.ts` (an internal **mutation**) to re-check stock under a
29
31
  per-product advisory lock and HOLD it before the order is recorded — so two
@@ -57,21 +59,28 @@ Checkout is a single public `checkout` action that holds stock, then:
57
59
  (all cart lines priced inline from your catalog — no Stripe Products to set
58
60
  up) and redirects the shopper to it. The signed `stripeWebhook` action (at
59
61
  `/api/webhooks/stripeWebhook`) marks the order **paid** on success and
60
- **returns held stock** if a checkout is abandoned. The webhook signature is
61
- verified with `@pylonsync/stripe`'s constant-time verifier before any event is
62
- trusted.
62
+ **returns held stock** if a checkout is abandoned. Stripe collects a US
63
+ shipping address and charges the shipping terms from `lib/site.config.ts`
64
+ (flat rate, free over a threshold), the same numbers the cart shows. The
65
+ webhook signature is verified with `@pylonsync/stripe`'s constant-time
66
+ verifier before any event is trusted.
63
67
  - **Without Stripe keys** → the order is held as **reserved** for you to follow
64
- up on. The store still works end-to-end, so you can demo live inventory with
65
- zero setup.
68
+ up on, and the shopper lands on `/success?order=reserved`. The store still
69
+ works end-to-end, so you can demo live inventory with zero setup.
66
70
 
67
71
  See `.env.example` for the two env vars and the Stripe dashboard / `stripe
68
72
  listen` setup.
69
73
 
70
74
  ## Rebrand it
71
75
 
72
- Brand, colors, products and starting stock, value props, reviews, and policies
73
- live in **`lib/site.config.ts`**. Editing that file updates the store, and a
74
- fresh database seeds products from it.
76
+ Brand, colors, hero and studio copy, products and starting stock, shipping
77
+ terms, reviews, and policies live in **`lib/site.config.ts`**. Editing that
78
+ file updates the store, and a fresh database seeds products from it.
79
+
80
+ Photos live in `public/images/`. Product photos are 4:5 portrait JPEGs
81
+ (1200x1500 works well); the hero is a wide landscape photo with open space on
82
+ the left for the headline. `tests/shop.test.ts` fails if the config points at
83
+ an image that is not in `public/`.
75
84
 
76
85
  ## Layout
77
86
 
@@ -85,9 +94,12 @@ functions/stripeWebhook.ts public webhook: verify signature, settle the order
85
94
  functions/{markGroupPaid,releaseGroup}.ts internal: settle/restore on webhook
86
95
  functions/ordersForOwner.ts owner-only query: orders + customer PII
87
96
  functions/{fulfill,cancel}Order.ts, restockProduct.ts owner-only mutations
88
- app/page.tsx the storefront (server-rendered)
89
- app/shop-client.tsx client island: live product grid + cart + checkout
90
- app/success/page.tsx Stripe post-payment landing
97
+ app/(marketing)/layout.tsx announcement bar, nav, footer
98
+ app/(marketing)/page.tsx the storefront (server-rendered)
99
+ app/(marketing)/shop-client.tsx client island: live product grid + cart + checkout
100
+ app/(marketing)/success/page.tsx order confirmation (Stripe or reserved)
101
+ lib/cart-store.ts, lib/shelf-store.ts state shared between client islands
102
+ public/images/ hero and product photos
91
103
  app/dashboard/ owner dashboard (auth-gated, live)
92
104
  ```
93
105