@pylonsync/create-pylon 0.19.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (675) hide show
  1. package/bin/create-pylon.js +30 -7
  2. package/package.json +1 -1
  3. package/templates/agency/.env.example +7 -0
  4. package/templates/agency/README.md +28 -3
  5. package/templates/agency/app/(marketing)/contact-form.tsx +20 -18
  6. package/templates/agency/app/(marketing)/page.tsx +3 -3
  7. package/templates/agency/app/(marketing)/work/page.tsx +3 -3
  8. package/templates/agency/app/auth-form.tsx +4 -4
  9. package/templates/agency/app/dashboard/dashboard-client.tsx +110 -64
  10. package/templates/agency/app/dashboard/page.tsx +14 -16
  11. package/templates/agency/app/icon.svg +3 -0
  12. package/templates/agency/app/login/page.tsx +12 -14
  13. package/templates/agency/components/marketing.tsx +12 -5
  14. package/templates/agency/functions/seedStudioBackoffice.ts +42 -11
  15. package/templates/agency/lib/agency.ts +22 -0
  16. package/templates/agency/lib/demo.ts +118 -0
  17. package/templates/agency/lib/site.config.ts +19 -13
  18. package/templates/agency/public/favicon.ico +0 -0
  19. package/templates/agency/tests/agency.test.ts +57 -0
  20. package/templates/ai-chat/.env.example +13 -27
  21. package/templates/ai-chat/README.md +53 -60
  22. package/templates/ai-chat/app/(chat)/c/[id]/page.tsx +19 -0
  23. package/templates/ai-chat/app/(chat)/chat-client.tsx +657 -622
  24. package/templates/ai-chat/app/(chat)/layout.tsx +6 -43
  25. package/templates/ai-chat/app/(chat)/not-found.tsx +6 -10
  26. package/templates/ai-chat/app/(chat)/page.tsx +7 -11
  27. package/templates/ai-chat/app/auth-form.tsx +45 -38
  28. package/templates/ai-chat/app/error.tsx +9 -12
  29. package/templates/ai-chat/app/globals.css +464 -102
  30. package/templates/ai-chat/app/layout.tsx +11 -19
  31. package/templates/ai-chat/app/login/page.tsx +39 -24
  32. package/templates/ai-chat/app.ts +118 -43
  33. package/templates/ai-chat/components/chat/composer.tsx +193 -0
  34. package/templates/ai-chat/components/chat/empty-state.tsx +65 -0
  35. package/templates/ai-chat/components/chat/icons.tsx +198 -0
  36. package/templates/ai-chat/components/chat/markdown.tsx +164 -0
  37. package/templates/ai-chat/components/chat/message.tsx +183 -0
  38. package/templates/ai-chat/components/chat/setup.tsx +71 -0
  39. package/templates/ai-chat/components/chat/sidebar.tsx +288 -0
  40. package/templates/ai-chat/functions/_beginTurn.ts +145 -0
  41. package/templates/ai-chat/functions/_finishTurn.ts +55 -0
  42. package/templates/ai-chat/functions/assistantStatus.ts +13 -0
  43. package/templates/ai-chat/functions/claimGuestHistory.ts +39 -0
  44. package/templates/ai-chat/functions/createConversation.ts +28 -0
  45. package/templates/ai-chat/functions/deleteConversation.ts +15 -20
  46. package/templates/ai-chat/functions/prepareGuestClaim.ts +32 -0
  47. package/templates/ai-chat/functions/renameConversation.ts +19 -0
  48. package/templates/ai-chat/functions/respond.ts +88 -0
  49. package/templates/ai-chat/functions/seedExamples.ts +46 -0
  50. package/templates/ai-chat/functions/stopResponse.ts +30 -0
  51. package/templates/ai-chat/lib/brand-icon.ts +7 -0
  52. package/templates/ai-chat/lib/chat.ts +277 -0
  53. package/templates/ai-chat/lib/claim.ts +15 -0
  54. package/templates/ai-chat/lib/examples.ts +179 -0
  55. package/templates/ai-chat/lib/highlight.ts +107 -0
  56. package/templates/ai-chat/lib/markdown.ts +317 -0
  57. package/templates/ai-chat/lib/provider.ts +38 -0
  58. package/templates/ai-chat/lib/site.config.ts +55 -39
  59. package/templates/ai-chat/tests/chat.test.ts +174 -0
  60. package/templates/ai-chat/tests/components.test.tsx +57 -0
  61. package/templates/ai-chat/tests/markdown.test.ts +88 -0
  62. package/templates/ai-chat/tests/provider.test.ts +32 -0
  63. package/templates/backend/consumer/apps/api/app/layout.tsx +33 -0
  64. package/templates/backend/consumer/apps/api/app/page.tsx +54 -0
  65. package/templates/backend/consumer/apps/api/app.ts +166 -0
  66. package/templates/backend/consumer/apps/api/functions/addComment.ts +24 -0
  67. package/templates/backend/consumer/apps/api/functions/createPost.ts +19 -36
  68. package/templates/backend/consumer/apps/api/functions/deletePost.ts +15 -13
  69. package/templates/backend/consumer/apps/api/functions/myProfile.ts +5 -8
  70. package/templates/backend/consumer/apps/api/functions/seedDemo.ts +95 -0
  71. package/templates/backend/consumer/apps/api/functions/setFollow.ts +36 -0
  72. package/templates/backend/consumer/apps/api/functions/setLike.ts +36 -0
  73. package/templates/backend/consumer/apps/api/functions/upsertProfile.ts +43 -37
  74. package/templates/backend/consumer/apps/api/lib/seed.ts +89 -0
  75. package/templates/backend/consumer/apps/api/lib/social.ts +50 -0
  76. package/templates/backend/consumer/apps/api/package.json +8 -2
  77. package/templates/backend/consumer/apps/api/public/images/posts/arch.jpg +0 -0
  78. package/templates/backend/consumer/apps/api/public/images/posts/coffee.jpg +0 -0
  79. package/templates/backend/consumer/apps/api/public/images/posts/dog.jpg +0 -0
  80. package/templates/backend/consumer/apps/api/public/images/posts/lisbon.jpg +0 -0
  81. package/templates/backend/consumer/apps/api/public/images/posts/ramen.jpg +0 -0
  82. package/templates/backend/consumer/apps/api/public/images/posts/run.jpg +0 -0
  83. package/templates/backend/consumer/apps/api/public/images/posts/sunset.jpg +0 -0
  84. package/templates/backend/consumer/apps/api/tsconfig.json +12 -2
  85. package/templates/chat/README.md +43 -21
  86. package/templates/chat/app/chat-client.tsx +495 -85
  87. package/templates/chat/app/error.tsx +1 -1
  88. package/templates/chat/app/globals.css +170 -54
  89. package/templates/chat/app/layout.tsx +12 -9
  90. package/templates/chat/app/not-found.tsx +4 -2
  91. package/templates/chat/app/page.tsx +9 -19
  92. package/templates/chat/app.ts +96 -17
  93. package/templates/chat/components/chat/avatar.tsx +45 -0
  94. package/templates/chat/components/chat/composer.tsx +110 -0
  95. package/templates/chat/components/chat/message-list.tsx +285 -0
  96. package/templates/chat/components/chat/sidebar.tsx +190 -0
  97. package/templates/chat/functions/joinChat.ts +83 -0
  98. package/templates/chat/functions/renameMember.ts +25 -0
  99. package/templates/chat/lib/chat.ts +188 -0
  100. package/templates/chat/lib/seed.ts +78 -0
  101. package/templates/chat/tests/chat.test.ts +159 -0
  102. package/templates/chat/tests/composer.test.tsx +54 -0
  103. package/templates/consumer/README.md +3 -3
  104. package/templates/consumer/app/globals.css +10 -3
  105. package/templates/consumer/app/icon.svg +1 -0
  106. package/templates/consumer/components/social/avatar.tsx +25 -11
  107. package/templates/consumer/functions/seedFeed.ts +3 -3
  108. package/templates/consumer/lib/seed.ts +43 -85
  109. package/templates/consumer/public/favicon.ico +0 -0
  110. package/templates/consumer/public/images/posts/arch.jpg +0 -0
  111. package/templates/consumer/public/images/posts/coffee.jpg +0 -0
  112. package/templates/consumer/public/images/posts/dog.jpg +0 -0
  113. package/templates/consumer/public/images/posts/lisbon.jpg +0 -0
  114. package/templates/consumer/public/images/posts/ramen.jpg +0 -0
  115. package/templates/consumer/public/images/posts/run.jpg +0 -0
  116. package/templates/consumer/public/images/posts/sunset.jpg +0 -0
  117. package/templates/consumer/tests/seed.test.ts +39 -0
  118. package/templates/creator/.env.example +7 -0
  119. package/templates/creator/README.md +24 -5
  120. package/templates/creator/app/(marketing)/newsletter-signup.tsx +33 -12
  121. package/templates/creator/app/auth-form.tsx +7 -7
  122. package/templates/creator/app/dashboard/dashboard-client.tsx +137 -100
  123. package/templates/creator/app/dashboard/page.tsx +18 -18
  124. package/templates/creator/app/icon.svg +2 -0
  125. package/templates/creator/app/login/page.tsx +14 -14
  126. package/templates/creator/app.ts +4 -4
  127. package/templates/creator/functions/seedDemo.ts +32 -0
  128. package/templates/creator/functions/subscribe.ts +4 -15
  129. package/templates/creator/lib/demo.ts +94 -0
  130. package/templates/creator/lib/site.config.ts +15 -10
  131. package/templates/creator/lib/stats.ts +35 -0
  132. package/templates/creator/public/favicon.ico +0 -0
  133. package/templates/creator/tests/counter.test.ts +92 -0
  134. package/templates/creator/tests/live-count.test.tsx +45 -0
  135. package/templates/crm/README.md +22 -1
  136. package/templates/crm/app/apple-icon.png +0 -0
  137. package/templates/crm/app/companies/companies-view.tsx +32 -14
  138. package/templates/crm/app/companies/page.tsx +2 -1
  139. package/templates/crm/app/contacts/contacts-view.tsx +34 -10
  140. package/templates/crm/app/contacts/page.tsx +2 -1
  141. package/templates/crm/app/deals/[id]/deal-view.tsx +137 -98
  142. package/templates/crm/app/deals/[id]/page.tsx +2 -1
  143. package/templates/crm/app/globals.css +29 -14
  144. package/templates/crm/app/icon.svg +1 -0
  145. package/templates/crm/app/layout.tsx +7 -5
  146. package/templates/crm/app/page.tsx +2 -1
  147. package/templates/crm/app/pipeline-view.tsx +5 -5
  148. package/templates/crm/app/workspace.tsx +29 -17
  149. package/templates/crm/app.ts +5 -5
  150. package/templates/crm/components/activity-timeline.tsx +14 -4
  151. package/templates/crm/components/app-shell.tsx +94 -0
  152. package/templates/crm/components/auth-form.tsx +35 -5
  153. package/templates/crm/components/avatar.tsx +5 -1
  154. package/templates/crm/components/board-skeleton.tsx +5 -5
  155. package/templates/crm/components/brand-mark.tsx +25 -0
  156. package/templates/crm/components/command-palette.tsx +17 -17
  157. package/templates/crm/components/data-table.tsx +97 -36
  158. package/templates/crm/components/deal-card.tsx +23 -27
  159. package/templates/crm/components/filter-tabs.tsx +101 -0
  160. package/templates/crm/components/metrics-bar.tsx +6 -6
  161. package/templates/crm/components/nav.tsx +15 -0
  162. package/templates/crm/components/page-header.tsx +43 -7
  163. package/templates/crm/components/pipeline-board.tsx +16 -10
  164. package/templates/crm/components/require-auth.tsx +24 -26
  165. package/templates/crm/components/sidebar.tsx +54 -49
  166. package/templates/crm/components/stage-badge.tsx +4 -2
  167. package/templates/crm/components/ui/button.tsx +5 -4
  168. package/templates/crm/components/ui/dialog.tsx +1 -1
  169. package/templates/crm/components/ui/input.tsx +1 -1
  170. package/templates/crm/components/ui/select.tsx +1 -1
  171. package/templates/crm/components/ui/textarea.tsx +1 -1
  172. package/templates/crm/functions/seedWorkspace.ts +32 -17
  173. package/templates/crm/lib/brand.ts +9 -0
  174. package/templates/crm/lib/demo-team.ts +51 -0
  175. package/templates/crm/lib/pipeline.ts +37 -0
  176. package/templates/crm/lib/search.ts +4 -3
  177. package/templates/crm/lib/seed.ts +53 -8
  178. package/templates/crm/public/favicon.ico +0 -0
  179. package/templates/crm/tests/components.test.tsx +5 -5
  180. package/templates/crm/tests/demo-team.test.ts +18 -0
  181. package/templates/crm/tests/pipeline.test.ts +59 -3
  182. package/templates/directory/.env.example +7 -0
  183. package/templates/directory/README.md +22 -3
  184. package/templates/directory/app/(marketing)/directory-browse.tsx +30 -12
  185. package/templates/directory/app/(marketing)/page.tsx +6 -4
  186. package/templates/directory/app/auth-form.tsx +4 -4
  187. package/templates/directory/app/dashboard/dashboard-client.tsx +177 -90
  188. package/templates/directory/app/dashboard/page.tsx +16 -14
  189. package/templates/directory/app/icon.svg +4 -0
  190. package/templates/directory/app/login/page.tsx +16 -15
  191. package/templates/directory/functions/seedDemo.ts +43 -0
  192. package/templates/directory/lib/demo.ts +145 -0
  193. package/templates/directory/lib/directory.ts +27 -0
  194. package/templates/directory/lib/site.config.ts +21 -16
  195. package/templates/directory/public/favicon.ico +0 -0
  196. package/templates/directory/tests/directory.test.ts +46 -0
  197. package/templates/expo/consumer/apps/expo/App.tsx +360 -288
  198. package/templates/expo/consumer/apps/expo/app.json +9 -1
  199. package/templates/expo/consumer/apps/expo/package.json +2 -1
  200. package/templates/helpdesk/README.md +21 -0
  201. package/templates/helpdesk/app/apple-icon.png +0 -0
  202. package/templates/helpdesk/app/customers/customers-view.tsx +23 -7
  203. package/templates/helpdesk/app/customers/page.tsx +2 -1
  204. package/templates/helpdesk/app/globals.css +29 -14
  205. package/templates/helpdesk/app/icon.svg +1 -0
  206. package/templates/helpdesk/app/inbox-view.tsx +66 -47
  207. package/templates/helpdesk/app/layout.tsx +7 -5
  208. package/templates/helpdesk/app/page.tsx +4 -7
  209. package/templates/helpdesk/app/tickets/[id]/page.tsx +2 -1
  210. package/templates/helpdesk/app/tickets/[id]/ticket-view.tsx +134 -97
  211. package/templates/helpdesk/app/workspace.tsx +33 -22
  212. package/templates/helpdesk/app.ts +2 -2
  213. package/templates/helpdesk/components/app-shell.tsx +94 -0
  214. package/templates/helpdesk/components/auth-form.tsx +35 -5
  215. package/templates/helpdesk/components/avatar.tsx +5 -1
  216. package/templates/helpdesk/components/brand-mark.tsx +28 -0
  217. package/templates/helpdesk/components/command-palette.tsx +17 -17
  218. package/templates/helpdesk/components/data-table.tsx +97 -36
  219. package/templates/helpdesk/components/filter-tabs.tsx +101 -0
  220. package/templates/helpdesk/components/nav.tsx +14 -0
  221. package/templates/helpdesk/components/page-header.tsx +43 -7
  222. package/templates/helpdesk/components/priority-badge.tsx +49 -6
  223. package/templates/helpdesk/components/require-auth.tsx +24 -26
  224. package/templates/helpdesk/components/sidebar.tsx +54 -48
  225. package/templates/helpdesk/components/sla-indicator.tsx +5 -8
  226. package/templates/helpdesk/components/thread.tsx +7 -7
  227. package/templates/helpdesk/components/ticket-list.tsx +79 -19
  228. package/templates/helpdesk/components/ui/button.tsx +5 -4
  229. package/templates/helpdesk/components/ui/dialog.tsx +1 -1
  230. package/templates/helpdesk/components/ui/input.tsx +1 -1
  231. package/templates/helpdesk/components/ui/select.tsx +1 -1
  232. package/templates/helpdesk/components/ui/textarea.tsx +1 -1
  233. package/templates/helpdesk/functions/seedWorkspace.ts +25 -15
  234. package/templates/helpdesk/lib/brand.ts +9 -0
  235. package/templates/helpdesk/lib/demo-team.ts +51 -0
  236. package/templates/helpdesk/lib/search.ts +4 -3
  237. package/templates/helpdesk/lib/seed.ts +43 -2
  238. package/templates/helpdesk/lib/tickets.ts +68 -20
  239. package/templates/helpdesk/public/favicon.ico +0 -0
  240. package/templates/helpdesk/tests/components.test.tsx +10 -0
  241. package/templates/helpdesk/tests/demo-team.test.ts +18 -0
  242. package/templates/helpdesk/tests/tickets.test.ts +70 -0
  243. package/templates/imessage/.env.example +49 -0
  244. package/templates/imessage/AGENTS.md +153 -0
  245. package/templates/imessage/CLAUDE.md +7 -0
  246. package/templates/imessage/README.md +187 -0
  247. package/templates/imessage/app/console.tsx +382 -0
  248. package/templates/imessage/app/contacts/page.tsx +13 -0
  249. package/templates/imessage/app/error.tsx +16 -0
  250. package/templates/imessage/app/gate.tsx +31 -0
  251. package/templates/imessage/app/globals.css +183 -0
  252. package/templates/imessage/app/layout.tsx +17 -0
  253. package/templates/imessage/app/login/login-form.tsx +115 -0
  254. package/templates/imessage/app/login/page.tsx +30 -0
  255. package/templates/imessage/app/not-found.tsx +13 -0
  256. package/templates/imessage/app/page.tsx +14 -0
  257. package/templates/imessage/app/robots.ts +6 -0
  258. package/templates/imessage/app/setup/page.tsx +13 -0
  259. package/templates/imessage/app/sign-out.tsx +20 -0
  260. package/templates/imessage/app.ts +299 -0
  261. package/templates/imessage/bunfig +4 -0
  262. package/templates/imessage/components/avatar.tsx +46 -0
  263. package/templates/imessage/components/contact-panel.tsx +263 -0
  264. package/templates/imessage/components/contacts-view.tsx +172 -0
  265. package/templates/imessage/components/conversation-list.tsx +129 -0
  266. package/templates/imessage/components/nav-rail.tsx +67 -0
  267. package/templates/imessage/components/setup-view.tsx +408 -0
  268. package/templates/imessage/components/thread.tsx +229 -0
  269. package/templates/imessage/components/ui/badge.tsx +37 -0
  270. package/templates/imessage/components/ui/button.tsx +56 -0
  271. package/templates/imessage/components/ui/card.tsx +90 -0
  272. package/templates/imessage/components/ui/dialog.tsx +110 -0
  273. package/templates/imessage/components/ui/input.tsx +30 -0
  274. package/templates/imessage/components/ui/label.tsx +21 -0
  275. package/templates/imessage/components/ui/select.tsx +39 -0
  276. package/templates/imessage/components/ui/table.tsx +124 -0
  277. package/templates/imessage/components/ui/textarea.tsx +24 -0
  278. package/templates/imessage/components.json +20 -0
  279. package/templates/imessage/functions/answerNow.ts +34 -0
  280. package/templates/imessage/functions/assistant.ts +96 -0
  281. package/templates/imessage/functions/beginTurn.ts +101 -0
  282. package/templates/imessage/functions/cancelReminder.ts +13 -0
  283. package/templates/imessage/functions/claimOutbound.ts +44 -0
  284. package/templates/imessage/functions/clearDemo.ts +18 -0
  285. package/templates/imessage/functions/deleteNote.ts +11 -0
  286. package/templates/imessage/functions/deliverOutbound.ts +55 -0
  287. package/templates/imessage/functions/finishTurn.ts +84 -0
  288. package/templates/imessage/functions/fireReminder.ts +42 -0
  289. package/templates/imessage/functions/ingestInbound.ts +22 -0
  290. package/templates/imessage/functions/ownerAccount.ts +13 -0
  291. package/templates/imessage/functions/processTurn.ts +74 -0
  292. package/templates/imessage/functions/relayApply.ts +102 -0
  293. package/templates/imessage/functions/relaySync.ts +63 -0
  294. package/templates/imessage/functions/releaseOutbound.ts +18 -0
  295. package/templates/imessage/functions/resolveTurn.ts +25 -0
  296. package/templates/imessage/functions/saveContact.ts +27 -0
  297. package/templates/imessage/functions/seedDemo.ts +70 -0
  298. package/templates/imessage/functions/sendOwnerMessage.ts +28 -0
  299. package/templates/imessage/functions/sendblueWebhook.ts +49 -0
  300. package/templates/imessage/functions/setContactStatus.ts +17 -0
  301. package/templates/imessage/functions/settleOutbound.ts +35 -0
  302. package/templates/imessage/functions/setupStatus.ts +33 -0
  303. package/templates/imessage/functions/sweep.ts +58 -0
  304. package/templates/imessage/functions/toolCancelReminder.ts +17 -0
  305. package/templates/imessage/functions/toolForgetNote.ts +15 -0
  306. package/templates/imessage/functions/toolListNotes.ts +16 -0
  307. package/templates/imessage/functions/toolListReminders.ts +17 -0
  308. package/templates/imessage/functions/toolSaveNote.ts +22 -0
  309. package/templates/imessage/functions/toolSetReminder.ts +55 -0
  310. package/templates/imessage/functions/updateSettings.ts +48 -0
  311. package/templates/imessage/gitignore +11 -0
  312. package/templates/imessage/lib/agent-call.ts +251 -0
  313. package/templates/imessage/lib/assistant.ts +74 -0
  314. package/templates/imessage/lib/chunk.ts +135 -0
  315. package/templates/imessage/lib/demo.ts +68 -0
  316. package/templates/imessage/lib/format.ts +170 -0
  317. package/templates/imessage/lib/gate.ts +104 -0
  318. package/templates/imessage/lib/handles.ts +67 -0
  319. package/templates/imessage/lib/provider.ts +38 -0
  320. package/templates/imessage/lib/relay-protocol.ts +95 -0
  321. package/templates/imessage/lib/reminders.ts +41 -0
  322. package/templates/imessage/lib/secrets.ts +59 -0
  323. package/templates/imessage/lib/sendblue.ts +173 -0
  324. package/templates/imessage/lib/store.ts +407 -0
  325. package/templates/imessage/lib/transport.ts +135 -0
  326. package/templates/imessage/lib/utils.ts +10 -0
  327. package/templates/imessage/package.json +40 -0
  328. package/templates/imessage/relay/.env.example +14 -0
  329. package/templates/imessage/relay/applescript.ts +70 -0
  330. package/templates/imessage/relay/chatdb.ts +165 -0
  331. package/templates/imessage/relay/client.ts +41 -0
  332. package/templates/imessage/relay/main.ts +137 -0
  333. package/templates/imessage/relay/state.ts +35 -0
  334. package/templates/imessage/tests/agent-call.test.ts +112 -0
  335. package/templates/imessage/tests/applescript.test.ts +88 -0
  336. package/templates/imessage/tests/chatdb.test.ts +139 -0
  337. package/templates/imessage/tests/chunk.test.ts +61 -0
  338. package/templates/imessage/tests/fake-ctx.ts +88 -0
  339. package/templates/imessage/tests/ingest.test.ts +141 -0
  340. package/templates/imessage/tests/lib.test.ts +99 -0
  341. package/templates/imessage/tests/review-fixes.test.ts +138 -0
  342. package/templates/imessage/tests/secrets.test.ts +36 -0
  343. package/templates/imessage/tests/sendblue.test.ts +143 -0
  344. package/templates/imessage/tests/setup.ts +5 -0
  345. package/templates/imessage/tsconfig.json +18 -0
  346. package/templates/inventory/README.md +21 -0
  347. package/templates/inventory/app/apple-icon.png +0 -0
  348. package/templates/inventory/app/globals.css +29 -14
  349. package/templates/inventory/app/icon.svg +1 -0
  350. package/templates/inventory/app/layout.tsx +7 -5
  351. package/templates/inventory/app/movements/movements-view.tsx +32 -10
  352. package/templates/inventory/app/movements/page.tsx +2 -1
  353. package/templates/inventory/app/page.tsx +4 -6
  354. package/templates/inventory/app/products/[id]/page.tsx +2 -1
  355. package/templates/inventory/app/products/[id]/product-view.tsx +55 -25
  356. package/templates/inventory/app/products-view.tsx +74 -33
  357. package/templates/inventory/app/workspace.tsx +34 -16
  358. package/templates/inventory/app.ts +10 -2
  359. package/templates/inventory/components/app-shell.tsx +94 -0
  360. package/templates/inventory/components/auth-form.tsx +35 -5
  361. package/templates/inventory/components/avatar.tsx +5 -1
  362. package/templates/inventory/components/brand-mark.tsx +25 -0
  363. package/templates/inventory/components/category-tile.tsx +49 -0
  364. package/templates/inventory/components/command-palette.tsx +17 -17
  365. package/templates/inventory/components/data-table.tsx +97 -36
  366. package/templates/inventory/components/filter-tabs.tsx +101 -0
  367. package/templates/inventory/components/nav.tsx +14 -0
  368. package/templates/inventory/components/page-header.tsx +43 -7
  369. package/templates/inventory/components/require-auth.tsx +24 -26
  370. package/templates/inventory/components/sidebar.tsx +54 -48
  371. package/templates/inventory/components/stock-level.tsx +30 -14
  372. package/templates/inventory/components/summary-bar.tsx +8 -15
  373. package/templates/inventory/components/ui/button.tsx +5 -4
  374. package/templates/inventory/components/ui/dialog.tsx +1 -1
  375. package/templates/inventory/components/ui/input.tsx +1 -1
  376. package/templates/inventory/components/ui/select.tsx +1 -1
  377. package/templates/inventory/components/ui/textarea.tsx +1 -1
  378. package/templates/inventory/functions/seedWorkspace.ts +18 -8
  379. package/templates/inventory/lib/brand.ts +9 -0
  380. package/templates/inventory/lib/demo-team.ts +51 -0
  381. package/templates/inventory/lib/search.ts +4 -3
  382. package/templates/inventory/lib/seed.ts +29 -2
  383. package/templates/inventory/lib/stock.ts +32 -0
  384. package/templates/inventory/public/favicon.ico +0 -0
  385. package/templates/inventory/tests/demo-team.test.ts +18 -0
  386. package/templates/inventory/tests/stock.test.ts +29 -0
  387. package/templates/invoices/README.md +16 -0
  388. package/templates/invoices/app/apple-icon.png +0 -0
  389. package/templates/invoices/app/clients/clients-view.tsx +18 -6
  390. package/templates/invoices/app/clients/page.tsx +2 -1
  391. package/templates/invoices/app/globals.css +29 -14
  392. package/templates/invoices/app/icon.svg +1 -0
  393. package/templates/invoices/app/invoices/[id]/invoice-view.tsx +72 -48
  394. package/templates/invoices/app/invoices/[id]/page.tsx +2 -1
  395. package/templates/invoices/app/invoices-view.tsx +81 -32
  396. package/templates/invoices/app/layout.tsx +7 -5
  397. package/templates/invoices/app/page.tsx +4 -6
  398. package/templates/invoices/app/workspace.tsx +26 -16
  399. package/templates/invoices/app.ts +2 -2
  400. package/templates/invoices/components/app-shell.tsx +94 -0
  401. package/templates/invoices/components/auth-form.tsx +35 -5
  402. package/templates/invoices/components/avatar.tsx +5 -1
  403. package/templates/invoices/components/brand-mark.tsx +26 -0
  404. package/templates/invoices/components/command-palette.tsx +17 -17
  405. package/templates/invoices/components/data-table.tsx +97 -36
  406. package/templates/invoices/components/filter-tabs.tsx +101 -0
  407. package/templates/invoices/components/line-items.tsx +19 -14
  408. package/templates/invoices/components/nav.tsx +14 -0
  409. package/templates/invoices/components/page-header.tsx +43 -7
  410. package/templates/invoices/components/require-auth.tsx +24 -26
  411. package/templates/invoices/components/sidebar.tsx +54 -48
  412. package/templates/invoices/components/status-badge.tsx +7 -7
  413. package/templates/invoices/components/summary-bar.tsx +5 -5
  414. package/templates/invoices/components/ui/button.tsx +5 -4
  415. package/templates/invoices/components/ui/dialog.tsx +1 -1
  416. package/templates/invoices/components/ui/input.tsx +1 -1
  417. package/templates/invoices/components/ui/select.tsx +1 -1
  418. package/templates/invoices/components/ui/textarea.tsx +1 -1
  419. package/templates/invoices/functions/recordPayment.ts +3 -0
  420. package/templates/invoices/lib/billing.ts +40 -0
  421. package/templates/invoices/lib/brand.ts +9 -0
  422. package/templates/invoices/lib/search.ts +4 -3
  423. package/templates/invoices/public/favicon.ico +0 -0
  424. package/templates/invoices/tests/billing.test.ts +27 -0
  425. package/templates/ios/consumer/apps/ios/Package.swift +4 -3
  426. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ActivityView.swift +107 -0
  427. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/AppModel.swift +171 -0
  428. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/AuthView.swift +182 -0
  429. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/CommentsView.swift +136 -0
  430. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ComposeView.swift +152 -0
  431. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ExploreView.swift +117 -0
  432. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/FeedView.swift +175 -159
  433. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/MainTabView.swift +54 -0
  434. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Media.swift +271 -0
  435. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Models.swift +101 -4
  436. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/PostCard.swift +245 -0
  437. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileEditorView.swift +208 -0
  438. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileView.swift +183 -0
  439. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/RootView.swift +25 -31
  440. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/SocialStore.swift +285 -0
  441. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/StoryViewer.swift +155 -0
  442. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Theme.swift +82 -0
  443. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/__APP_NAME_PASCAL__App.swift +4 -47
  444. package/templates/ios/consumer/apps/ios/project.yml +13 -9
  445. package/templates/local-service/.env.example +7 -0
  446. package/templates/local-service/README.md +26 -2
  447. package/templates/local-service/app/(marketing)/booking-widget.tsx +48 -9
  448. package/templates/local-service/app/(marketing)/page.tsx +66 -42
  449. package/templates/local-service/app/auth-form.tsx +8 -8
  450. package/templates/local-service/app/dashboard/dashboard-client.tsx +119 -92
  451. package/templates/local-service/app/dashboard/page.tsx +14 -14
  452. package/templates/local-service/app/icon.svg +2 -0
  453. package/templates/local-service/app/login/page.tsx +15 -15
  454. package/templates/local-service/functions/seedDemo.ts +49 -0
  455. package/templates/local-service/lib/demo.ts +135 -0
  456. package/templates/local-service/lib/hours.ts +44 -0
  457. package/templates/local-service/lib/site.config.ts +8 -3
  458. package/templates/local-service/lib/slots.ts +3 -0
  459. package/templates/local-service/public/favicon.ico +0 -0
  460. package/templates/local-service/tests/booking.test.ts +104 -0
  461. package/templates/marketplace/README.md +23 -15
  462. package/templates/marketplace/app/globals.css +172 -114
  463. package/templates/marketplace/app/layout.tsx +75 -30
  464. package/templates/marketplace/app/listing/[id]/page.tsx +210 -123
  465. package/templates/marketplace/app/me/page.tsx +4 -4
  466. package/templates/marketplace/app/page.tsx +221 -298
  467. package/templates/marketplace/app/sell/page.tsx +8 -11
  468. package/templates/marketplace/app.ts +32 -7
  469. package/templates/marketplace/client/ActivityFeed.tsx +311 -0
  470. package/templates/marketplace/client/AuthNav.tsx +24 -16
  471. package/templates/marketplace/client/ListingCard.tsx +118 -0
  472. package/templates/marketplace/client/LiveGrid.tsx +92 -0
  473. package/templates/marketplace/client/LiveListingStatus.tsx +32 -24
  474. package/templates/marketplace/client/LoginCard.tsx +114 -136
  475. package/templates/marketplace/client/MarketProvider.tsx +3 -5
  476. package/templates/marketplace/client/MyMarket.tsx +371 -185
  477. package/templates/marketplace/client/OfferPanel.tsx +421 -287
  478. package/templates/marketplace/client/SellForm.tsx +271 -192
  479. package/templates/marketplace/client/ThemeToggle.tsx +1 -0
  480. package/templates/marketplace/client/WatchButton.tsx +16 -16
  481. package/templates/marketplace/client/market.ts +61 -14
  482. package/templates/marketplace/functions/seedMarket.ts +143 -36
  483. package/templates/marketplace/functions/seedOffers.ts +101 -0
  484. package/templates/marketplace/lib/catalog.ts +72 -1
  485. package/templates/marketplace/public/images/listings/ceramic-lamp-640.webp +0 -0
  486. package/templates/marketplace/public/images/listings/ceramic-lamp.webp +0 -0
  487. package/templates/marketplace/public/images/listings/duffel-640.webp +0 -0
  488. package/templates/marketplace/public/images/listings/duffel.webp +0 -0
  489. package/templates/marketplace/public/images/listings/film-camera-640.webp +0 -0
  490. package/templates/marketplace/public/images/listings/film-camera.webp +0 -0
  491. package/templates/marketplace/public/images/listings/lounge-chair-640.webp +0 -0
  492. package/templates/marketplace/public/images/listings/lounge-chair.webp +0 -0
  493. package/templates/marketplace/public/images/listings/road-bike-640.webp +0 -0
  494. package/templates/marketplace/public/images/listings/road-bike.webp +0 -0
  495. package/templates/marketplace/public/images/listings/slipper-chair-640.webp +0 -0
  496. package/templates/marketplace/public/images/listings/slipper-chair.webp +0 -0
  497. package/templates/marketplace/public/images/listings/turntable-640.webp +0 -0
  498. package/templates/marketplace/public/images/listings/turntable.webp +0 -0
  499. package/templates/marketplace/public/images/listings/wardrobe-640.webp +0 -0
  500. package/templates/marketplace/public/images/listings/wardrobe.webp +0 -0
  501. package/templates/marketplace/public/images/listings/watch-640.webp +0 -0
  502. package/templates/marketplace/public/images/listings/watch.webp +0 -0
  503. package/templates/marketplace/public/images/listings/weekender-bag-640.webp +0 -0
  504. package/templates/marketplace/public/images/listings/weekender-bag.webp +0 -0
  505. package/templates/marketplace/tests/example.test.ts +64 -2
  506. package/templates/marketplace/tests/listing-card.test.tsx +61 -0
  507. package/templates/projects/README.md +24 -3
  508. package/templates/projects/app/apple-icon.png +0 -0
  509. package/templates/projects/app/clients/clients-view.tsx +20 -6
  510. package/templates/projects/app/clients/page.tsx +2 -1
  511. package/templates/projects/app/globals.css +29 -14
  512. package/templates/projects/app/icon.svg +1 -0
  513. package/templates/projects/app/layout.tsx +7 -5
  514. package/templates/projects/app/page.tsx +3 -5
  515. package/templates/projects/app/projects/[id]/page.tsx +2 -1
  516. package/templates/projects/app/projects/[id]/project-view.tsx +45 -30
  517. package/templates/projects/app/projects-view.tsx +105 -52
  518. package/templates/projects/app/workspace.tsx +41 -20
  519. package/templates/projects/app.ts +2 -2
  520. package/templates/projects/components/app-shell.tsx +94 -0
  521. package/templates/projects/components/auth-form.tsx +35 -5
  522. package/templates/projects/components/avatar.tsx +5 -1
  523. package/templates/projects/components/board-skeleton.tsx +3 -3
  524. package/templates/projects/components/brand-mark.tsx +25 -0
  525. package/templates/projects/components/command-palette.tsx +17 -17
  526. package/templates/projects/components/data-table.tsx +97 -36
  527. package/templates/projects/components/filter-tabs.tsx +101 -0
  528. package/templates/projects/components/meter.tsx +48 -0
  529. package/templates/projects/components/nav.tsx +14 -0
  530. package/templates/projects/components/page-header.tsx +43 -7
  531. package/templates/projects/components/require-auth.tsx +24 -26
  532. package/templates/projects/components/sidebar.tsx +54 -48
  533. package/templates/projects/components/summary-bar.tsx +49 -0
  534. package/templates/projects/components/task-board.tsx +10 -9
  535. package/templates/projects/components/time-dialog.tsx +29 -5
  536. package/templates/projects/components/ui/button.tsx +5 -4
  537. package/templates/projects/components/ui/dialog.tsx +1 -1
  538. package/templates/projects/components/ui/input.tsx +1 -1
  539. package/templates/projects/components/ui/select.tsx +1 -1
  540. package/templates/projects/components/ui/textarea.tsx +1 -1
  541. package/templates/projects/functions/seedWorkspace.ts +26 -12
  542. package/templates/projects/lib/brand.ts +9 -0
  543. package/templates/projects/lib/demo-team.ts +51 -0
  544. package/templates/projects/lib/search.ts +4 -3
  545. package/templates/projects/lib/seed.ts +35 -1
  546. package/templates/projects/lib/work.ts +63 -0
  547. package/templates/projects/public/favicon.ico +0 -0
  548. package/templates/projects/tests/demo-team.test.ts +18 -0
  549. package/templates/projects/tests/work.test.ts +65 -0
  550. package/templates/restaurant/.env.example +7 -0
  551. package/templates/restaurant/README.md +29 -3
  552. package/templates/restaurant/app/(marketing)/layout.tsx +4 -2
  553. package/templates/restaurant/app/(marketing)/page.tsx +47 -23
  554. package/templates/restaurant/app/(marketing)/reservation-widget.tsx +37 -7
  555. package/templates/restaurant/app/auth-form.tsx +8 -8
  556. package/templates/restaurant/app/dashboard/dashboard-client.tsx +241 -109
  557. package/templates/restaurant/app/dashboard/page.tsx +14 -14
  558. package/templates/restaurant/app/icon.svg +4 -0
  559. package/templates/restaurant/app/login/page.tsx +15 -15
  560. package/templates/restaurant/functions/seedDemo.ts +44 -0
  561. package/templates/restaurant/lib/demo.ts +144 -0
  562. package/templates/restaurant/lib/hours.ts +45 -0
  563. package/templates/restaurant/lib/site.config.ts +3 -3
  564. package/templates/restaurant/public/favicon.ico +0 -0
  565. package/templates/restaurant/tests/reservations.test.ts +67 -0
  566. package/templates/saas/README.md +54 -35
  567. package/templates/saas/app/(auth)/auth-form.tsx +25 -23
  568. package/templates/saas/app/(auth)/forgot-password/page.tsx +1 -1
  569. package/templates/saas/app/(auth)/layout.tsx +27 -33
  570. package/templates/saas/app/(auth)/login/page.tsx +1 -1
  571. package/templates/saas/app/(auth)/reset-password/page.tsx +1 -1
  572. package/templates/saas/app/(auth)/signup/page.tsx +1 -1
  573. package/templates/saas/app/(marketing)/layout.tsx +127 -253
  574. package/templates/saas/app/(marketing)/page.tsx +133 -372
  575. package/templates/saas/app/(marketing)/pricing/page.tsx +21 -19
  576. package/templates/saas/app/(marketing)/products/[slug]/page.tsx +40 -95
  577. package/templates/saas/app/dashboard/board-client.tsx +416 -0
  578. package/templates/saas/app/dashboard/dashboard-client.tsx +30 -615
  579. package/templates/saas/app/dashboard/layout.tsx +27 -13
  580. package/templates/saas/app/dashboard/overview-client.tsx +332 -0
  581. package/templates/saas/app/dashboard/page.tsx +29 -35
  582. package/templates/saas/app/dashboard/projects/[id]/page.tsx +29 -0
  583. package/templates/saas/app/dashboard/projects/page.tsx +11 -7
  584. package/templates/saas/app/dashboard/projects-client.tsx +398 -0
  585. package/templates/saas/app/globals.css +5 -4
  586. package/templates/saas/app/icon.svg +1 -0
  587. package/templates/saas/app/onboarding/onboarding-client.tsx +93 -26
  588. package/templates/saas/app/onboarding/page.tsx +6 -2
  589. package/templates/saas/app.ts +68 -13
  590. package/templates/saas/components/dashboard-shell.tsx +212 -132
  591. package/templates/saas/components/marketing.tsx +137 -274
  592. package/templates/saas/components/pricing-table.tsx +12 -11
  593. package/templates/saas/components/setup-checklist.tsx +31 -29
  594. package/templates/saas/components/task-ui.tsx +199 -0
  595. package/templates/saas/functions/createProject.ts +7 -13
  596. package/templates/saas/functions/createTask.ts +67 -0
  597. package/templates/saas/functions/deleteProject.ts +17 -0
  598. package/templates/saas/functions/deleteTask.ts +14 -0
  599. package/templates/saas/functions/seedWorkspace.ts +46 -0
  600. package/templates/saas/functions/setProjectStatus.ts +41 -0
  601. package/templates/saas/functions/updateProfile.ts +13 -0
  602. package/templates/saas/functions/updateProject.ts +4 -5
  603. package/templates/saas/functions/updateTask.ts +88 -0
  604. package/templates/saas/lib/members.ts +53 -0
  605. package/templates/saas/lib/names.ts +53 -0
  606. package/templates/saas/lib/plans.ts +11 -0
  607. package/templates/saas/lib/sample-workspace.ts +117 -0
  608. package/templates/saas/lib/site.config.ts +200 -463
  609. package/templates/saas/lib/tasks.ts +101 -0
  610. package/templates/saas/lib/use-live.ts +25 -0
  611. package/templates/saas/public/favicon.ico +0 -0
  612. package/templates/saas/public/screenshots/board.png +0 -0
  613. package/templates/saas/public/screenshots/members.png +0 -0
  614. package/templates/saas/public/screenshots/overview.png +0 -0
  615. package/templates/saas/public/screenshots/projects.png +0 -0
  616. package/templates/saas/scripts/capture-screenshots.mjs +156 -0
  617. package/templates/saas/tests/names.test.ts +35 -0
  618. package/templates/saas/tests/plans.test.ts +12 -0
  619. package/templates/saas/tests/sample-workspace.test.ts +46 -0
  620. package/templates/saas/tests/tasks.test.ts +81 -0
  621. package/templates/shop/README.md +25 -13
  622. package/templates/shop/app/(marketing)/layout.tsx +94 -67
  623. package/templates/shop/app/(marketing)/not-found.tsx +13 -12
  624. package/templates/shop/app/(marketing)/page.tsx +120 -59
  625. package/templates/shop/app/(marketing)/shop-client.tsx +364 -237
  626. package/templates/shop/app/(marketing)/success/page.tsx +75 -22
  627. package/templates/shop/app/globals.css +94 -1
  628. package/templates/shop/app/layout.tsx +4 -4
  629. package/templates/shop/app/login/page.tsx +4 -4
  630. package/templates/shop/app.ts +18 -6
  631. package/templates/shop/components/cart-button.tsx +33 -0
  632. package/templates/shop/components/marketing.tsx +17 -87
  633. package/templates/shop/components/shelf-count.tsx +36 -0
  634. package/templates/shop/functions/checkout.ts +17 -1
  635. package/templates/shop/lib/cart-store.ts +57 -0
  636. package/templates/shop/lib/shelf-store.ts +35 -0
  637. package/templates/shop/lib/shop.ts +27 -0
  638. package/templates/shop/lib/site.config.ts +108 -98
  639. package/templates/shop/public/images/hero.jpg +0 -0
  640. package/templates/shop/public/images/products/cedar-smoke.jpg +0 -0
  641. package/templates/shop/public/images/products/fig-leaf.jpg +0 -0
  642. package/templates/shop/public/images/products/linen.jpg +0 -0
  643. package/templates/shop/public/images/products/sea-salt.jpg +0 -0
  644. package/templates/shop/public/images/products/tobacco-oak.jpg +0 -0
  645. package/templates/shop/tests/shop.test.ts +64 -0
  646. package/templates/waitlist/.env.example +7 -0
  647. package/templates/waitlist/README.md +29 -13
  648. package/templates/waitlist/app/(marketing)/page.tsx +9 -7
  649. package/templates/waitlist/app/(marketing)/waitlist-hero.tsx +64 -31
  650. package/templates/waitlist/app/auth-form.tsx +8 -8
  651. package/templates/waitlist/app/dashboard/dashboard-client.tsx +132 -95
  652. package/templates/waitlist/app/dashboard/page.tsx +17 -16
  653. package/templates/waitlist/app/icon.svg +2 -0
  654. package/templates/waitlist/app/login/page.tsx +14 -14
  655. package/templates/waitlist/app.ts +6 -6
  656. package/templates/waitlist/functions/joinWaitlist.ts +6 -18
  657. package/templates/waitlist/functions/seedDemo.ts +32 -0
  658. package/templates/waitlist/lib/demo.ts +94 -0
  659. package/templates/waitlist/lib/launch.ts +19 -0
  660. package/templates/waitlist/lib/site.config.ts +14 -9
  661. package/templates/waitlist/lib/stats.ts +35 -0
  662. package/templates/waitlist/public/favicon.ico +0 -0
  663. package/templates/waitlist/tests/counter.test.ts +107 -0
  664. package/templates/waitlist/tests/live-count.test.tsx +48 -0
  665. package/templates/ai-chat/tests/example.test.ts +0 -12
  666. package/templates/backend/consumer/apps/api/functions/feed.ts +0 -57
  667. package/templates/backend/consumer/apps/api/functions/profilePosts.ts +0 -17
  668. package/templates/backend/consumer/apps/api/functions/toggleLike.ts +0 -48
  669. package/templates/backend/consumer/apps/api/schema.ts +0 -143
  670. package/templates/chat/functions/_keep.ts +0 -13
  671. package/templates/chat/tests/example.test.ts +0 -12
  672. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileSetupView.swift +0 -68
  673. package/templates/marketplace/client/LiveTicker.tsx +0 -116
  674. package/templates/marketplace/client/ScrollToListingsLink.tsx +0 -29
  675. package/templates/marketplace/public/images/marketplace-hero.webp +0 -0
@@ -1,15 +1,19 @@
1
1
  import { mutation } from "@pylonsync/functions";
2
- import { shapeSeed } from "../lib/seed";
2
+ import { SEED_TEAM, shapeSeed } from "../lib/seed";
3
+ import { ensureDemoUser } from "../lib/demo-team";
3
4
 
4
5
  /**
5
6
  * Fill a brand-new workspace with a realistic pipeline, once.
6
7
  *
7
- * An empty CRM demonstrates nothing — no board, no forecast, no sense of what
8
- * the app is for. The client calls this after sign-in; it returns immediately
9
- * if any company already exists, so it is safe to call on every load and can
10
- * never duplicate the fixtures or touch real data.
8
+ * The client calls this after sign-in. It returns immediately if any company
9
+ * already exists, so it is safe to call on every load and never duplicates the
10
+ * fixtures or touches real data. An advisory lock stops two first loads from
11
+ * seeding twice.
11
12
  *
12
- * Delete this function and lib/seed.ts once the workspace has real customers.
13
+ * Deals are spread across the person who signed in and four demo teammates
14
+ * (see SEED_TEAM and lib/demo-team.ts). Delete this function,
15
+ * lib/seed.ts, and the `seedWorkspace` call in app/workspace.tsx once the
16
+ * workspace has real customers, then delete the demo users.
13
17
  */
14
18
  export default mutation<Record<string, never>, { seeded: boolean }>({
15
19
  auth: "user",
@@ -17,44 +21,55 @@ export default mutation<Record<string, never>, { seeded: boolean }>({
17
21
  async handler(ctx) {
18
22
  // The guard is "has anything at all", not "has the seed", so a workspace
19
23
  // whose demo rows were deleted on purpose stays deleted.
20
- const existing = await ctx.db.query("Company", { $limit: 1 });
21
- if (existing.length > 0) return { seeded: false };
24
+ if ((await ctx.db.query("Company", { $limit: 1 })).length > 0) return { seeded: false };
25
+ await ctx.db.advisoryLock("crm_seed_workspace");
26
+ if ((await ctx.db.query("Company", { $limit: 1 })).length > 0) return { seeded: false };
22
27
 
23
28
  const seed = shapeSeed();
24
- const owner = ctx.auth.userId;
29
+ const me = ctx.auth.userId;
30
+
31
+ // Position 0 is the person who signed in; SEED_TEAM follows.
32
+ const owners: Array<string | null> = [me];
33
+ for (const person of SEED_TEAM) owners.push(await ensureDemoUser(ctx.db, person));
25
34
 
26
35
  const companyIds = new Map<string, string>();
27
- for (const company of seed.companies) {
28
- const id = await ctx.db.insert("Company", { ...company.row, ownerId: owner });
36
+ for (const [index, company] of seed.companies.entries()) {
37
+ const id = await ctx.db.insert("Company", {
38
+ ...company.row,
39
+ ownerId: owners[index % owners.length],
40
+ });
29
41
  companyIds.set(company.key, id as string);
30
42
  }
31
43
 
32
44
  const contactIds = new Map<string, string>();
33
- for (const contact of seed.contacts) {
45
+ for (const [index, contact] of seed.contacts.entries()) {
34
46
  const id = await ctx.db.insert("Contact", {
35
47
  ...contact.row,
36
48
  companyId: companyIds.get(contact.company) ?? null,
37
- ownerId: owner,
49
+ ownerId: owners[index % owners.length],
38
50
  });
39
51
  contactIds.set(String(contact.row.name), id as string);
40
52
  }
41
53
 
42
- const dealIds = new Map<string, string>();
54
+ const dealIds = new Map<string, { id: string; owner: string | null }>();
43
55
  for (const deal of seed.deals) {
56
+ const owner = owners[deal.owner] ?? me;
44
57
  const id = await ctx.db.insert("Deal", {
45
58
  ...deal.row,
46
59
  companyId: companyIds.get(deal.company) ?? null,
47
60
  contactId: contactIds.get(deal.contact) ?? null,
48
61
  ownerId: owner,
49
62
  });
50
- dealIds.set(deal.title, id as string);
63
+ dealIds.set(deal.title, { id: id as string, owner });
51
64
  }
52
65
 
66
+ // Activity on a deal is logged by the deal's owner.
53
67
  for (const activity of seed.activities) {
68
+ const deal = dealIds.get(activity.deal);
54
69
  await ctx.db.insert("Activity", {
55
70
  ...activity.row,
56
- dealId: dealIds.get(activity.deal) ?? null,
57
- ownerId: owner,
71
+ dealId: deal?.id ?? null,
72
+ ownerId: deal?.owner ?? me,
58
73
  });
59
74
  }
60
75
 
@@ -0,0 +1,9 @@
1
+ // The product name, in one place. The sidebar, the sign-in screen, the page
2
+ // title, and the drawer on phones all read it. Rename the product here; the
3
+ // mark is in components/brand-mark.tsx and the favicon is app/icon.svg.
4
+
5
+ export const BRAND = {
6
+ name: "Dealbook",
7
+ /** One line on the sign-in screen: what the app holds and who sees it. */
8
+ description: "Your team's pipeline, companies, and contacts. Everyone on the team sees the same board.",
9
+ } as const;
@@ -0,0 +1,51 @@
1
+ // Creates the demo teammates the seed spreads records across. Each is a User
2
+ // row with a `@demo.invalid` address and no password. Outside dev mode nobody
3
+ // can sign in as one: the address can't receive a sign-in or reset email.
4
+ // In dev mode (`PYLON_DEV_MODE=true`) the auth API returns sign-in codes
5
+ // directly, so anyone can sign in as a demo teammate there.
6
+
7
+ import type { DbWriter } from "@pylonsync/functions";
8
+
9
+ export interface DemoPerson {
10
+ name: string;
11
+ email: string;
12
+ }
13
+
14
+ /**
15
+ * The address to try on `attempt` (0, 1, 2, …): the person's own address
16
+ * first, then `local+2@demo.invalid`, `local+3@…`, and so on.
17
+ */
18
+ export function demoEmail(email: string, attempt: number): string {
19
+ if (attempt === 0) return email;
20
+ const at = email.indexOf("@");
21
+ return `${email.slice(0, at)}+${attempt + 1}${email.slice(at)}`;
22
+ }
23
+
24
+ /**
25
+ * A User row the seed may reuse: one it created itself on an earlier run.
26
+ * A row with a password or a verified email belongs to someone who
27
+ * registered that address, so the seed never assigns demo records to it.
28
+ */
29
+ export function isDemoRow(row: Record<string, unknown>): boolean {
30
+ return !row.passwordHash && !row.emailVerified;
31
+ }
32
+
33
+ /** Returns the id of a demo User row for `person`, creating it if needed. */
34
+ export async function ensureDemoUser(db: DbWriter, person: DemoPerson): Promise<string> {
35
+ for (let attempt = 0; attempt < 20; attempt += 1) {
36
+ const email = demoEmail(person.email, attempt);
37
+ // Users are written by auth, and their policy denies client inserts, so
38
+ // demo rows go through the server-trust surface. The read sees
39
+ // passwordHash, which is how a registered account is told apart.
40
+ const [existing] = await db.unsafe.query("User", { email, $limit: 1 });
41
+ if (!existing) {
42
+ return db.unsafe.insert("User", {
43
+ email,
44
+ displayName: person.name,
45
+ createdAt: new Date(Date.now() - 200 * 86_400_000).toISOString(),
46
+ });
47
+ }
48
+ if (isDemoRow(existing)) return existing.id as string;
49
+ }
50
+ throw new Error(`No free demo address for ${person.email}`);
51
+ }
@@ -17,6 +17,7 @@ export const PIPELINE: Stage[] = [
17
17
  { id: "lead", label: "Lead", probability: 0.1 },
18
18
  { id: "qualified", label: "Qualified", probability: 0.3 },
19
19
  { id: "proposal", label: "Proposal", probability: 0.6 },
20
+ { id: "negotiation", label: "Negotiation", probability: 0.8 },
20
21
  { id: "won", label: "Won", closed: "won", probability: 1 },
21
22
  { id: "lost", label: "Lost", closed: "lost", probability: 0 },
22
23
  ];
@@ -219,3 +220,39 @@ export function daysUntil(
219
220
  };
220
221
  return Math.round((startOfDay(then) - startOfDay(now)) / 86_400_000);
221
222
  }
223
+
224
+ export type CloseDue =
225
+ | { tone: "overdue"; days: number; label: string }
226
+ | { tone: "soon"; days: number; label: string };
227
+
228
+ /**
229
+ * The close-date warning for a deal card: "3d overdue", "Today", or "5d".
230
+ *
231
+ * Only open deals get one. A won or lost deal has closed, so its expected
232
+ * close date is history, not a deadline. Beyond a week out there is nothing
233
+ * to act on yet, so that returns null too.
234
+ */
235
+ export function closeDue(
236
+ deal: { stage: string; closeDate?: string | null },
237
+ now: number = Date.now(),
238
+ ): CloseDue | null {
239
+ if (!isOpen(deal)) return null;
240
+ const days = daysUntil(deal.closeDate, now);
241
+ if (days === null || days > 7) return null;
242
+ if (days < 0) return { tone: "overdue", days, label: `${Math.abs(days)}d overdue` };
243
+ return { tone: "soon", days, label: days === 0 ? "Today" : `${days}d` };
244
+ }
245
+
246
+ /** "Mar 3" for this year, "Mar 3, 2027" otherwise. */
247
+ export function shortDate(iso: string | null | undefined, now: number = Date.now()): string {
248
+ if (!iso) return "";
249
+ const then = Date.parse(iso);
250
+ if (!Number.isFinite(then)) return "";
251
+ const date = new Date(then);
252
+ const sameYear = date.getFullYear() === new Date(now).getFullYear();
253
+ return date.toLocaleDateString("en-US", {
254
+ month: "short",
255
+ day: "numeric",
256
+ ...(sameYear ? {} : { year: "numeric" }),
257
+ });
258
+ }
@@ -1,15 +1,16 @@
1
1
  // Client-side search powering the ⌘K palette.
2
2
  //
3
- // A small team's CRM fits in the synced replica, so search runs locally against
3
+ // A small team's workspace fits in the synced replica, so search runs locally against
4
4
  // data already in memory: results appear as you type, with no request per
5
5
  // keystroke. Swap in `db.useSearch` (Pylon's FTS) when a workspace outgrows
6
6
  // that — the component boundary doesn't change, only this file.
7
7
 
8
8
  export interface SearchItem {
9
9
  id: string;
10
- type: "deal" | "company" | "contact";
10
+ /** The record kind, such as "deal". The palette picks its icon from this. */
11
+ type: string;
11
12
  title: string;
12
- /** Second line: the company for a contact, the domain for a company. */
13
+ /** Shown on the right of the row: the company for a contact, say. */
13
14
  subtitle?: string;
14
15
  href: string;
15
16
  /** Extra text matched but not displayed — an email, say. */
@@ -7,6 +7,37 @@
7
7
  // Pure data + pure shaping, so the seeding function stays a thin wrapper and
8
8
  // this file can be edited (or emptied) without reading any server code.
9
9
 
10
+ /**
11
+ * The demo sales team. Each becomes a User row with no
12
+ * password (see lib/demo-team.ts); they exist so deals have realistic owners.
13
+ */
14
+ export interface SeedTeammate {
15
+ name: string;
16
+ email: string;
17
+ }
18
+
19
+ export const SEED_TEAM: SeedTeammate[] = [
20
+ { name: "Maya Castillo", email: "maya.castillo@demo.invalid" },
21
+ { name: "Jordan Ellis", email: "jordan.ellis@demo.invalid" },
22
+ { name: "Aisha Karim", email: "aisha.karim@demo.invalid" },
23
+ { name: "Noah Brandt", email: "noah.brandt@demo.invalid" },
24
+ ];
25
+
26
+ /**
27
+ * Who owns the seeded deal at `index`, as a position in `[you, ...SEED_TEAM]`.
28
+ * Stepping by 3 through a team of 5 spreads every stage across everyone,
29
+ * including the person who signed up.
30
+ */
31
+ export function seedOwner(index: number, teamSize: number = SEED_TEAM.length + 1): number {
32
+ return (index * 3 + 1) % teamSize;
33
+ }
34
+
35
+ /** A fictional US number in the 555-01xx range reserved for fiction. */
36
+ export function seedPhone(index: number): string {
37
+ const area = [212, 312, 415, 512, 617, 720][index % 6];
38
+ return `+1 (${area}) 555-01${String(index % 100).padStart(2, "0")}`;
39
+ }
40
+
10
41
  export interface SeedCompany {
11
42
  key: string;
12
43
  name: string;
@@ -131,14 +162,14 @@ export const SEED_DEALS: SeedDeal[] = [
131
162
  { company: "vantage", contact: "Anita Rao", title: "Installer scheduling", value: 26500, stage: "qualified", closeInDays: 38, age: 14 },
132
163
  { company: "tidewater", contact: "Sarah Lindqvist", title: "Shop floor work orders", value: 41000, stage: "qualified", closeInDays: 33, age: 19 },
133
164
 
134
- // Proposal
165
+ // Proposal and negotiation
135
166
  { company: "northwind", contact: "Dana Whitfield", title: "Fleet dispatch rollout", value: 48000, stage: "proposal", closeInDays: 12, age: 34 },
136
167
  { company: "riverbed", contact: "Tom Alvarez", title: "Multi-store inventory", value: 22000, stage: "proposal", closeInDays: 5, age: 29 },
137
- { company: "meridian", contact: "Rachel Stone", title: "Customer tracking portal", value: 88000, stage: "proposal", closeInDays: 9, age: 41 },
168
+ { company: "meridian", contact: "Rachel Stone", title: "Customer tracking portal", value: 88000, stage: "negotiation", closeInDays: 9, age: 41 },
138
169
  { company: "bluefin", contact: "Elena Vasquez", title: "Embedded reporting module", value: 36000, stage: "proposal", closeInDays: 14, age: 27 },
139
- { company: "harbor", contact: "Robert Chen", title: "Guest messaging platform", value: 67000, stage: "proposal", closeInDays: 7, age: 38 },
140
- { company: "halcyon", contact: "Margaret O'Neill", title: "Care plan tracking", value: 52000, stage: "proposal", closeInDays: 16, age: 31 },
141
- { company: "summit", contact: "Greg Holloway", title: "Security review tooling", value: 18000, stage: "proposal", closeInDays: 3, age: 24 },
170
+ { company: "harbor", contact: "Robert Chen", title: "Guest messaging platform", value: 67000, stage: "negotiation", closeInDays: 6, age: 38 },
171
+ { company: "halcyon", contact: "Margaret O'Neill", title: "Care plan tracking", value: 52000, stage: "negotiation", closeInDays: 13, age: 31 },
172
+ { company: "summit", contact: "Greg Holloway", title: "Security review tooling", value: 18000, stage: "negotiation", closeInDays: -2, age: 24 },
142
173
  { company: "sable", contact: "Isabel Moreau", title: "Loyalty programme", value: 11200, stage: "proposal", closeInDays: 10, age: 26 },
143
174
 
144
175
  // Won
@@ -208,7 +239,14 @@ export const SEED_ACTIVITIES: SeedActivity[] = [
208
239
  export interface ShapedSeed {
209
240
  companies: Array<{ key: string; row: Record<string, unknown> }>;
210
241
  contacts: Array<{ company: string; row: Record<string, unknown> }>;
211
- deals: Array<{ company: string; contact: string; title: string; row: Record<string, unknown> }>;
242
+ /** `owner` is a position in `[you, ...SEED_TEAM]`; see seedOwner. */
243
+ deals: Array<{
244
+ company: string;
245
+ contact: string;
246
+ title: string;
247
+ owner: number;
248
+ row: Record<string, unknown>;
249
+ }>;
212
250
  activities: Array<{ deal: string; row: Record<string, unknown> }>;
213
251
  }
214
252
 
@@ -239,12 +277,19 @@ export function shapeSeed(now: number = Date.now()): ShapedSeed {
239
277
  })),
240
278
  contacts: SEED_CONTACTS.map((c, index) => ({
241
279
  company: c.company,
242
- row: { name: c.name, email: c.email, title: c.title, createdAt: at(-110 + index * 2) },
280
+ row: {
281
+ name: c.name,
282
+ email: c.email,
283
+ title: c.title,
284
+ phone: seedPhone(index),
285
+ createdAt: at(-110 + index * 2),
286
+ },
243
287
  })),
244
- deals: SEED_DEALS.map((d) => ({
288
+ deals: SEED_DEALS.map((d, index) => ({
245
289
  company: d.company,
246
290
  contact: d.contact,
247
291
  title: d.title,
292
+ owner: seedOwner(index),
248
293
  row: {
249
294
  title: d.title,
250
295
  value: d.value,
Binary file
@@ -31,7 +31,7 @@ describe("PipelineBoard", () => {
31
31
  onOpen={() => {}}
32
32
  />,
33
33
  );
34
- for (const label of ["Lead", "Qualified", "Proposal", "Won"]) {
34
+ for (const label of ["Lead", "Qualified", "Proposal", "Negotiation", "Won"]) {
35
35
  expect(screen.getByLabelText(label)).toBeDefined();
36
36
  }
37
37
  // Lost is real in the model but deliberately not a column.
@@ -181,7 +181,7 @@ describe("CommandPalette", () => {
181
181
  render(
182
182
  <CommandPalette open items={items} onClose={() => {}} onSelect={() => {}} />,
183
183
  );
184
- fireEvent.change(screen.getByLabelText(/search deals/i), {
184
+ fireEvent.change(screen.getByLabelText(/^search/i), {
185
185
  target: { value: "acme" },
186
186
  });
187
187
  expect(screen.getByText("Acme Inc")).toBeDefined();
@@ -201,7 +201,7 @@ describe("CommandPalette", () => {
201
201
  onSelect={(item) => selected.push(item.id)}
202
202
  />,
203
203
  );
204
- const input = screen.getByLabelText(/search deals/i);
204
+ const input = screen.getByLabelText(/^search/i);
205
205
  fireEvent.change(input, { target: { value: "fleet" } });
206
206
  fireEvent.keyDown(input, { key: "Enter" });
207
207
  expect(selected).toEqual(["1"]);
@@ -221,7 +221,7 @@ describe("CommandPalette", () => {
221
221
  onSelect={(item) => selected.push(item.id)}
222
222
  />,
223
223
  );
224
- fireEvent.keyDown(screen.getByLabelText(/search deals/i), { key: "Escape" });
224
+ fireEvent.keyDown(screen.getByLabelText(/^search/i), { key: "Escape" });
225
225
  expect(closed).toBe(true);
226
226
  expect(selected).toEqual([]);
227
227
  });
@@ -237,7 +237,7 @@ describe("CommandPalette", () => {
237
237
  />,
238
238
  );
239
239
  expect(screen.getByText("New deal")).toBeDefined();
240
- fireEvent.change(screen.getByLabelText(/search deals/i), {
240
+ fireEvent.change(screen.getByLabelText(/^search/i), {
241
241
  target: { value: "acme" },
242
242
  });
243
243
  expect(screen.queryByText("New deal")).toBeNull();
@@ -0,0 +1,18 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { demoEmail, isDemoRow } from "../lib/demo-team";
3
+
4
+ describe("demo teammates", () => {
5
+ test("the first address is the person's own, then numbered aliases", () => {
6
+ expect(demoEmail("maya.castillo@demo.invalid", 0)).toBe("maya.castillo@demo.invalid");
7
+ expect(demoEmail("maya.castillo@demo.invalid", 1)).toBe("maya.castillo+2@demo.invalid");
8
+ expect(demoEmail("maya.castillo@demo.invalid", 2)).toBe("maya.castillo+3@demo.invalid");
9
+ });
10
+
11
+ test("a registered account is never reused as a demo teammate", () => {
12
+ // Someone who registered a demo address first must not be handed the
13
+ // seeded records.
14
+ expect(isDemoRow({ email: "x@demo.invalid", passwordHash: "argon2id$..." })).toBe(false);
15
+ expect(isDemoRow({ email: "x@demo.invalid", emailVerified: "2026-07-01T00:00:00Z" })).toBe(false);
16
+ expect(isDemoRow({ email: "x@demo.invalid", displayName: "Maya Castillo" })).toBe(true);
17
+ });
18
+ });
@@ -3,6 +3,7 @@ import {
3
3
  BOARD_STAGES,
4
4
  PIPELINE,
5
5
  accentIndex,
6
+ closeDue,
6
7
  daysUntil,
7
8
  groupByStage,
8
9
  initials,
@@ -17,6 +18,7 @@ import {
17
18
  sumValue,
18
19
  type Deal,
19
20
  } from "../lib/pipeline";
21
+ import { SEED_TEAM, shapeSeed } from "../lib/seed";
20
22
 
21
23
  const deal = (over: Partial<Deal> = {}): Deal => ({
22
24
  id: "d1",
@@ -34,6 +36,7 @@ describe("stages", () => {
34
36
  "lead",
35
37
  "qualified",
36
38
  "proposal",
39
+ "negotiation",
37
40
  "won",
38
41
  ]);
39
42
  expect(PIPELINE.some((s) => s.id === "lost")).toBe(true);
@@ -47,7 +50,8 @@ describe("stages", () => {
47
50
 
48
51
  test("advance and retreat stop at the ends", () => {
49
52
  expect(nextStage("lead")).toBe("qualified");
50
- expect(nextStage("proposal")).toBe("won");
53
+ expect(nextStage("proposal")).toBe("negotiation");
54
+ expect(nextStage("negotiation")).toBe("won");
51
55
  expect(nextStage("won")).toBeNull();
52
56
  expect(previousStage("qualified")).toBe("lead");
53
57
  expect(previousStage("lead")).toBeNull();
@@ -56,7 +60,8 @@ describe("stages", () => {
56
60
  test("an unknown stage is rejected, not coerced", () => {
57
61
  expect(isValidStage("proposal")).toBe(true);
58
62
  expect(isValidStage("Proposal")).toBe(false);
59
- expect(isValidStage("negotiation")).toBe(false);
63
+ expect(isValidStage("Negotiation")).toBe(false);
64
+ expect(isValidStage("closing")).toBe(false);
60
65
  expect(nextStage("nonsense")).toBeNull();
61
66
  });
62
67
  });
@@ -83,7 +88,7 @@ describe("groupByStage", () => {
83
88
 
84
89
  test("a deal with an unknown stage is dropped, not filed under Lead", () => {
85
90
  // Silently reclassifying it would hide a typo behind a plausible board.
86
- const columns = groupByStage([deal({ stage: "negotiation" })]);
91
+ const columns = groupByStage([deal({ stage: "closing" })]);
87
92
  expect(columns.every((c) => c.deals.length === 0)).toBe(true);
88
93
  });
89
94
 
@@ -217,3 +222,54 @@ describe("daysUntil", () => {
217
222
  expect(daysUntil("nope", now)).toBeNull();
218
223
  });
219
224
  });
225
+
226
+ describe("closeDue", () => {
227
+ const now = Date.parse("2026-07-27T18:00:00Z");
228
+ const past = "2026-07-20T12:00:00Z";
229
+
230
+ test("an open deal past its close date is overdue", () => {
231
+ expect(closeDue({ stage: "proposal", closeDate: past }, now)).toEqual({
232
+ tone: "overdue",
233
+ days: -7,
234
+ label: "7d overdue",
235
+ });
236
+ });
237
+
238
+ test("a won or lost deal is never overdue", () => {
239
+ // Won deals used to show "Nd overdue" in red on the board.
240
+ expect(closeDue({ stage: "won", closeDate: past }, now)).toBeNull();
241
+ expect(closeDue({ stage: "lost", closeDate: past }, now)).toBeNull();
242
+ });
243
+
244
+ test("inside a week reads as days left, today as Today", () => {
245
+ expect(closeDue({ stage: "lead", closeDate: "2026-07-30T12:00:00Z" }, now)?.label).toBe("3d");
246
+ expect(closeDue({ stage: "lead", closeDate: "2026-07-27T20:00:00Z" }, now)?.label).toBe("Today");
247
+ });
248
+
249
+ test("nothing to flag beyond a week or without a date", () => {
250
+ expect(closeDue({ stage: "lead", closeDate: "2026-08-20T12:00:00Z" }, now)).toBeNull();
251
+ expect(closeDue({ stage: "lead", closeDate: null }, now)).toBeNull();
252
+ });
253
+ });
254
+
255
+ describe("seed", () => {
256
+ test("every teammate, and the person who signed up, owns open deals", () => {
257
+ const seed = shapeSeed(Date.parse("2026-07-27T18:00:00Z"));
258
+ const owners = new Set(
259
+ seed.deals.filter((d) => isOpen({ stage: String(d.row.stage) })).map((d) => d.owner),
260
+ );
261
+ expect([...owners].sort()).toEqual(
262
+ Array.from({ length: SEED_TEAM.length + 1 }, (_, i) => i),
263
+ );
264
+ });
265
+
266
+ test("every seeded stage is a real stage", () => {
267
+ for (const deal of shapeSeed().deals) {
268
+ expect(isValidStage(String(deal.row.stage))).toBe(true);
269
+ }
270
+ });
271
+
272
+ test("demo teammates use a domain that can't receive mail", () => {
273
+ for (const person of SEED_TEAM) expect(person.email.endsWith("@demo.invalid")).toBe(true);
274
+ });
275
+ });
@@ -10,3 +10,10 @@ PYLON_OWNER_EMAIL=you@yourdirectory.com
10
10
  # ── Site URL (optional) ──────────────────────────────────────────────────────
11
11
  # Used by robots.txt + sitemap.xml. Point it at your real domain in production.
12
12
  # SITE_URL=https://yourdirectory.com
13
+
14
+ # ── Demo data ────────────────────────────────────────────────────────────────
15
+ # `pylon dev` fills an empty review queue with fictional submissions. Seeding
16
+ # runs only under `pylon dev` (it checks PYLON_DEV_MODE and
17
+ # PYLON_DEV_WATCH_DIR), so `pylon start`, Docker, and Pylon Cloud deploys never
18
+ # seed. Set 0 to start empty in development. Never set 1 on a deploy.
19
+ # PYLON_DEMO_DATA=0
@@ -45,25 +45,44 @@ details), Approve / Reject, and a live count of published listings.
45
45
  Set `PYLON_OWNER_EMAIL` in `.env` (see `.env.example`) to the email you'll sign
46
46
  in with, then create that account at `/login`.
47
47
 
48
+ ## Demo data
49
+
50
+ The curator's first dashboard load in `pylon dev` fills an empty review queue
51
+ with fictional submissions (`functions/seedDemo.ts`, `lib/demo.ts`). Seeding
52
+ runs only for the owner, only when `PYLON_DEMO_DATA` is on or when it is unset and the app runs under `pylon dev`, and only while the `Submission` table is empty. `pylon start`, Docker, and Pylon Cloud deploys do not seed, because only `pylon dev` sets `PYLON_DEV_WATCH_DIR`. Set `PYLON_DEMO_DATA=0` to start
53
+ empty in development.
54
+
55
+ The starter listings seed in every environment (`functions/seedListings.ts`),
56
+ because they are your directory's own content from `lib/site.config.ts`. Their
57
+ names are made up; replace them with real entries. Each listing shows a
58
+ monogram mark (`monogram` in `lib/directory.ts`) until you add logos.
59
+
48
60
  ## Rebrand it
49
61
 
50
62
  Brand, colors, hero copy, categories, starter listings, and submission copy
51
63
  live in **`lib/site.config.ts`**. Editing that file updates the directory, and
52
64
  a fresh database seeds from its starter listings.
53
65
 
66
+ The tab icon is `app/icon.svg` (with `public/favicon.ico` for browsers that
67
+ request `/favicon.ico`). Both carry this brand's letter and colors; replace
68
+ them when you rename the brand.
69
+
54
70
  ## Layout
55
71
 
56
72
  ```
57
73
  app.ts Listing (public, FTS) + Submission (PII) + User
58
74
  lib/site.config.ts ALL copy + brand + categories + seed listings
59
75
  functions/seedListings.ts idempotent seed from config
76
+ functions/seedDemo.ts dev-only demo review queue (see Demo data)
77
+ lib/directory.ts shared types, tags, monogram marks
78
+ lib/demo.ts demo submissions + the dev-only gate
60
79
  functions/submitListing.ts public mutation: write a pending Submission (PII)
61
80
  functions/upvote.ts public mutation: bump Listing.votes (live)
62
81
  functions/submissionsForOwner.ts owner-only query: queue + submitter PII
63
82
  functions/{approve,reject}Submission.ts owner-only moderation
64
- app/page.tsx hero + browse island
65
- app/directory-browse.tsx client island: live db.useSearch + facets + votes
66
- app/submit/page.tsx, submit-form.tsx the submit flow
83
+ app/(marketing)/page.tsx headline + browse island
84
+ app/(marketing)/directory-browse.tsx client island: live db.useSearch + facets + votes
85
+ app/(marketing)/submit/page.tsx, submit-form.tsx the submit flow
67
86
  app/dashboard/ curator moderation queue (auth-gated, live)
68
87
  ```
69
88