@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,94 @@
1
+ // Demo subscribers for local development.
2
+ //
3
+ // `pylon dev` fills an empty list with about 180 fictional subscribers spread
4
+ // over the last 30 days, so the counter, chart, and list have data. A deploy
5
+ // does not seed: `demoDataEnabled` is true only when PYLON_DEMO_DATA is on, or
6
+ // when it is unset and the process runs under `pylon dev`. Set
7
+ // PYLON_DEMO_DATA=0 to turn it off in development too.
8
+ //
9
+ // Pure data + pure shaping, so functions/seedDemo.ts stays a thin wrapper and
10
+ // this file is testable without a server.
11
+
12
+ const ON = new Set(["1", "true", "yes", "on"]);
13
+ const OFF = new Set(["0", "false", "no", "off"]);
14
+
15
+ /**
16
+ * True when demo rows may be written. An explicit PYLON_DEMO_DATA wins.
17
+ * Otherwise it needs both PYLON_DEV_MODE on and PYLON_DEV_WATCH_DIR set. Only
18
+ * `pylon dev` sets PYLON_DEV_WATCH_DIR; `pylon start` and the Docker image do
19
+ * not, and the image turns PYLON_DEV_MODE on by default, so dev mode alone is
20
+ * not proof of a local machine.
21
+ */
22
+ export function demoDataEnabled(env: Record<string, string | undefined>): boolean {
23
+ const explicit = env.PYLON_DEMO_DATA?.trim().toLowerCase();
24
+ if (explicit && ON.has(explicit)) return true;
25
+ if (explicit && OFF.has(explicit)) return false;
26
+ const dev = env.PYLON_DEV_MODE?.trim().toLowerCase();
27
+ const devMode = dev === "1" || dev === "true";
28
+ return devMode && Boolean(env.PYLON_DEV_WATCH_DIR?.trim());
29
+ }
30
+
31
+ const FIRST = [
32
+ "priya", "marcus", "lena", "tomas", "aisha", "ben", "hana", "diego", "sofia", "kwame",
33
+ "ingrid", "raj", "noor", "felix", "maya", "owen", "yuki", "carmen", "theo", "amara",
34
+ "jonas", "leila", "sam", "ines", "dev", "clara", "malik", "rosa", "ethan", "zara",
35
+ ];
36
+ const LAST = [
37
+ "nair", "bell", "fischer", "alvarez", "khan", "osei", "sato", "ruiz", "marin", "mensah",
38
+ "berg", "patel", "haddad", "wagner", "chen", "reilly", "tanaka", "vega", "park", "okafor",
39
+ ];
40
+ // Fictional company domains on the reserved .example TLD, so no demo address
41
+ // can reach a real inbox.
42
+ const DOMAINS = [
43
+ "fieldnote.example", "northwind.example", "quarry.example", "bluefin.example", "wren.example",
44
+ "copperline.example", "tidewater.example", "larkspur.example", "kestrel.example", "orchardlabs.example",
45
+ "sable.example", "ironwood.example", "halcyon.example", "pinecrest.example", "vantage.example",
46
+ ];
47
+
48
+ /** Small deterministic PRNG (mulberry32) so the demo set is stable per seed. */
49
+ function rng(seed: number): () => number {
50
+ let a = seed >>> 0;
51
+ return () => {
52
+ a = (a + 0x6d2b79f5) >>> 0;
53
+ let t = a;
54
+ t = Math.imul(t ^ (t >>> 15), t | 1);
55
+ t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
56
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
57
+ };
58
+ }
59
+
60
+ export interface DemoSubscriber {
61
+ email: string;
62
+ createdAt: string;
63
+ }
64
+
65
+ /**
66
+ * About 180 subscribers over the 30 days before `nowMs`, rising toward today.
67
+ * Emails are unique. Every timestamp is in the past.
68
+ */
69
+ export function demoSubscribers(nowMs: number, seed = 23): DemoSubscriber[] {
70
+ const rand = rng(seed);
71
+ const out: DemoSubscriber[] = [];
72
+ const seen = new Set<string>();
73
+ const DAY = 86_400_000;
74
+ for (let daysAgo = 29; daysAgo >= 0; daysAgo--) {
75
+ // Growth curve: ~2/day a month ago, ~10/day this week, plus noise.
76
+ const base = 2 + Math.round((29 - daysAgo) * 0.28);
77
+ const count = Math.max(0, base + Math.round((rand() - 0.5) * 4));
78
+ for (let i = 0; i < count; i++) {
79
+ const first = FIRST[Math.floor(rand() * FIRST.length)];
80
+ const last = LAST[Math.floor(rand() * LAST.length)];
81
+ const domain = DOMAINS[Math.floor(rand() * DOMAINS.length)];
82
+ const shape = rand();
83
+ const local = shape < 0.5 ? `${first}.${last}` : shape < 0.8 ? `${first}` : `${first[0]}${last}`;
84
+ const email = `${local}@${domain}`;
85
+ if (seen.has(email)) continue;
86
+ seen.add(email);
87
+ // A random minute inside that day, never later than `nowMs`.
88
+ const offset = Math.floor(rand() * DAY);
89
+ const ts = Math.min(nowMs - 60_000, nowMs - daysAgo * DAY - offset);
90
+ out.push({ email, createdAt: new Date(ts).toISOString() });
91
+ }
92
+ }
93
+ return out.sort((a, b) => (a.createdAt < b.createdAt ? -1 : 1));
94
+ }
@@ -48,7 +48,11 @@ export type CreatorConfig = BaseConfig & {
48
48
  ctaLabel: string;
49
49
  successMessage: string;
50
50
  counterLabel: string; // e.g. "readers subscribed"
51
- seedCount?: number; // vanity baseline added to the real live count
51
+ counterLabelOne: string; // the same label for a count of 1
52
+ // Subscribers you are moving over from another tool, added to the
53
+ // Subscriber table's count. Keep 0 unless those people are real; the page
54
+ // shows the sum as one number.
55
+ importedCount: number;
52
56
  };
53
57
  links?: { headline: string; body: string; items: LinkItem[] };
54
58
  };
@@ -62,7 +66,7 @@ export const siteConfig: CreatorConfig = {
62
66
  domain: "mayarivera.co",
63
67
  email: "hello@mayarivera.example",
64
68
  footerBlurb:
65
- "Product design coach and writer. I help designers do braver work — through 1:1 coaching, portfolio reviews, and a weekly newsletter.",
69
+ "Product design coach and writer. 1:1 coaching, portfolio reviews, and a weekly newsletter.",
66
70
  copyrightName: "Maya Rivera",
67
71
  socials: [
68
72
  {
@@ -83,14 +87,14 @@ export const siteConfig: CreatorConfig = {
83
87
  seo: {
84
88
  title: "Maya Rivera — product design coach & writer",
85
89
  description:
86
- "Product design coaching, portfolio reviews, and a weekly newsletter for designers who want to do braver work.",
90
+ "Product design coaching, portfolio reviews, and a weekly newsletter for product designers.",
87
91
  },
88
92
 
89
93
  hero: {
90
94
  name: "Maya Rivera",
91
95
  tagline: "Product design coach & writer.",
92
96
  intro:
93
- "I help product designers get unstuck, sharpen their portfolios, and do the bravest work of their careers. I spent fifteen years leading design teams, and now I spend that time with you.",
97
+ "I coach product designers on portfolios, critique, and the move to senior roles. I led design teams for fifteen years. Now I work with designers one at a time.",
94
98
  },
95
99
 
96
100
  about: {
@@ -106,7 +110,7 @@ export const siteConfig: CreatorConfig = {
106
110
  items: [
107
111
  {
108
112
  title: "1:1 coaching",
109
- body: "Monthly sessions on whatever's in your way — craft, career, confidence. We build a plan and I hold you to it.",
113
+ body: "Two sessions a month on your work and your next role. We agree on a plan and review it each session.",
110
114
  price: "from $400/mo",
111
115
  },
112
116
  {
@@ -151,7 +155,7 @@ export const siteConfig: CreatorConfig = {
151
155
  items: [
152
156
  {
153
157
  quote:
154
- "Maya helped me land a senior role in three months. The portfolio review alone was worth ten times the price.",
158
+ "Maya helped me land a senior role in three months. The portfolio review changed which projects I led with.",
155
159
  name: "Daniel Reyes",
156
160
  role: "Senior Product Designer",
157
161
  },
@@ -163,7 +167,7 @@ export const siteConfig: CreatorConfig = {
163
167
  },
164
168
  {
165
169
  quote:
166
- "I read the newsletter every week. It feels like a coaching session in my inbox.",
170
+ "I read the newsletter every week. Each issue has one thing I can use that week.",
167
171
  name: "Marcus Bell",
168
172
  role: "Product Designer",
169
173
  },
@@ -172,12 +176,13 @@ export const siteConfig: CreatorConfig = {
172
176
 
173
177
  newsletter: {
174
178
  name: "The Studio Notes",
175
- subcopy: "One short email every Sunday on design, taste, and doing brave work.",
179
+ subcopy: "One short email every Sunday on critique, portfolios, and design careers.",
176
180
  emailPlaceholder: "you@email.com",
177
181
  ctaLabel: "Subscribe",
178
182
  successMessage: "You are in. The next issue arrives on Sunday.",
179
- counterLabel: "designers reading",
180
- seedCount: 2400,
183
+ counterLabel: "designers subscribed",
184
+ counterLabelOne: "designer subscribed",
185
+ importedCount: 0,
181
186
  },
182
187
 
183
188
  links: {
@@ -28,3 +28,38 @@ export interface SubscriberStatsData {
28
28
  export type SubscriberStatsResult =
29
29
  | ({ authorized: true } & SubscriberStatsData)
30
30
  | { authorized: false };
31
+
32
+ /* ------------------------- public live counter ------------------------- */
33
+
34
+ // The public counter shows the Subscriber table's row count, read from the
35
+ // PII-free SubscriberCount row. `importedCount` is
36
+ // `siteConfig.newsletter.importedCount`: subscribers you are moving over from
37
+ // another tool. It is 0 by default, so the page shows only rows in the table.
38
+ export function publicCount(
39
+ countRows: ReadonlyArray<{ count: number }>,
40
+ importedCount = 0,
41
+ ): number {
42
+ const real = countRows.length > 0 ? Math.max(0, Math.floor(countRows[0].count)) : 0;
43
+ const imported = Number.isFinite(importedCount) ? Math.max(0, Math.floor(importedCount)) : 0;
44
+ return real + imported;
45
+ }
46
+
47
+ // The subset of `ctx.db.unsafe` the recount needs. Typed structurally so the
48
+ // helper runs against the real handle and a test double.
49
+ export interface CountDb {
50
+ list(entity: string): Promise<unknown[]>;
51
+ insert(entity: string, data: Record<string, unknown>): Promise<unknown>;
52
+ update(entity: string, id: string, data: Record<string, unknown>): Promise<unknown>;
53
+ }
54
+
55
+ // Write the Subscriber row count into the single SubscriberCount row, creating
56
+ // it if missing. Every writer of Subscriber calls this in the same transaction,
57
+ // so the public number is always the table's count.
58
+ export async function syncSubscriberCount(db: CountDb): Promise<number> {
59
+ const total = (await db.list("Subscriber")).length;
60
+ const row = (await db.list("SubscriberCount"))[0] as { id: string } | undefined;
61
+ const now = new Date().toISOString();
62
+ if (row) await db.update("SubscriberCount", row.id, { count: total, updatedAt: now });
63
+ else await db.insert("SubscriberCount", { count: total, updatedAt: now });
64
+ return total;
65
+ }
@@ -0,0 +1,92 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { publicCount, syncSubscriberCount, type CountDb } from "../lib/stats";
3
+ import { demoDataEnabled, demoSubscribers } from "../lib/demo";
4
+ import { siteConfig } from "../lib/site.config";
5
+
6
+ describe("publicCount", () => {
7
+ test("is the table's count when nothing is imported", () => {
8
+ expect(publicCount([{ count: 3 }])).toBe(3);
9
+ expect(publicCount([])).toBe(0);
10
+ });
11
+ test("adds only an explicit, non-negative imported count", () => {
12
+ expect(publicCount([{ count: 3 }], 40)).toBe(43);
13
+ expect(publicCount([{ count: 3 }], -5)).toBe(3);
14
+ expect(publicCount([{ count: 3 }], Number.NaN)).toBe(3);
15
+ });
16
+ test("the shipped config adds nothing to the real count", () => {
17
+ expect(siteConfig.newsletter.importedCount).toBe(0);
18
+ });
19
+ });
20
+
21
+ // An in-memory stand-in for ctx.db.unsafe.
22
+ function fakeDb(signups: number): CountDb & { rows: Record<string, Record<string, unknown>[]> } {
23
+ const rows: Record<string, Record<string, unknown>[]> = {
24
+ Subscriber: Array.from({ length: signups }, (_, i) => ({ id: `s${i}` })),
25
+ SubscriberCount: [],
26
+ };
27
+ return {
28
+ rows,
29
+ async list(entity) {
30
+ return rows[entity] ?? [];
31
+ },
32
+ async insert(entity, data) {
33
+ const id = `${entity}-${rows[entity].length}`;
34
+ rows[entity].push({ id, ...data });
35
+ return id;
36
+ },
37
+ async update(entity, id, data) {
38
+ const row = rows[entity].find((r) => r.id === id);
39
+ if (row) Object.assign(row, data);
40
+ return id;
41
+ },
42
+ };
43
+ }
44
+
45
+ describe("syncSubscriberCount", () => {
46
+ test("creates the stat row with the Subscriber count", async () => {
47
+ const db = fakeDb(5);
48
+ expect(await syncSubscriberCount(db)).toBe(5);
49
+ expect(db.rows.SubscriberCount).toHaveLength(1);
50
+ expect(db.rows.SubscriberCount[0].count).toBe(5);
51
+ });
52
+ test("recounts into the existing row instead of adding one", async () => {
53
+ const db = fakeDb(5);
54
+ await syncSubscriberCount(db);
55
+ db.rows.Subscriber.push({ id: "late" });
56
+ await syncSubscriberCount(db);
57
+ expect(db.rows.SubscriberCount).toHaveLength(1);
58
+ expect(db.rows.SubscriberCount[0].count).toBe(6);
59
+ });
60
+ });
61
+
62
+ describe("demoDataEnabled", () => {
63
+ test("needs a `pylon dev` process when PYLON_DEMO_DATA is unset", () => {
64
+ expect(demoDataEnabled({ PYLON_DEV_MODE: "1", PYLON_DEV_WATCH_DIR: "/src/app" })).toBe(true);
65
+ // The Docker base image turns dev mode on by default; without `pylon dev` it must not seed.
66
+ expect(demoDataEnabled({ PYLON_DEV_MODE: "true" })).toBe(false);
67
+ expect(demoDataEnabled({ PYLON_DEV_MODE: "true", PYLON_DEV_WATCH_DIR: "/src/app" })).toBe(true);
68
+ expect(demoDataEnabled({ PYLON_DEV_MODE: "false" })).toBe(false);
69
+ expect(demoDataEnabled({})).toBe(false);
70
+ });
71
+ test("an explicit PYLON_DEMO_DATA wins", () => {
72
+ expect(demoDataEnabled({ PYLON_DEV_MODE: "1", PYLON_DEV_WATCH_DIR: "/src/app", PYLON_DEMO_DATA: "0" })).toBe(false);
73
+ expect(demoDataEnabled({ PYLON_DEV_MODE: "false", PYLON_DEMO_DATA: "1" })).toBe(true);
74
+ });
75
+ });
76
+
77
+ describe("demoSubscribers", () => {
78
+ const now = Date.parse("2026-09-26T15:00:00Z");
79
+ const rows = demoSubscribers(now);
80
+ test("unique emails, all in the past 30 days", () => {
81
+ expect(new Set(rows.map((r) => r.email)).size).toBe(rows.length);
82
+ for (const r of rows) {
83
+ const t = Date.parse(r.createdAt);
84
+ expect(t).toBeLessThan(now);
85
+ expect(t).toBeGreaterThan(now - 31 * 86_400_000);
86
+ }
87
+ });
88
+ test("enough rows to fill the chart, stable for a seed", () => {
89
+ expect(rows.length).toBeGreaterThan(120);
90
+ expect(demoSubscribers(now)).toEqual(rows);
91
+ });
92
+ });
@@ -0,0 +1,45 @@
1
+ import { afterEach, expect, mock, test } from "bun:test";
2
+ import React from "react";
3
+ import { cleanup, render, screen } from "@testing-library/react";
4
+
5
+ // The live count reads the SubscriberCount row. Mock the data boundary so the
6
+ // test controls what the "server" says.
7
+ let rows: { id: string; count: number }[] = [];
8
+ let loading = false;
9
+ mock.module("@pylonsync/react", () => ({
10
+ db: { useQuery: () => ({ data: rows, loading }) },
11
+ callFn: async () => ({ seeded: false }),
12
+ }));
13
+ mock.module("@pylonsync/client", () => ({
14
+ EnsureGuest: ({ children }: { children: React.ReactNode }) => <>{children}</>,
15
+ }));
16
+ const { LiveCounter } = await import("../app/(marketing)/newsletter-signup");
17
+
18
+ afterEach(() => {
19
+ cleanup();
20
+ rows = [];
21
+ loading = false;
22
+ });
23
+
24
+ test("shows the table's count, with no vanity baseline", () => {
25
+ rows = [{ id: "c", count: 42 }];
26
+ render(<LiveCounter importedCount={0} label="designers subscribed" labelOne="designer subscribed" />);
27
+ expect(screen.getByTestId("live-count").textContent).toBe("42 designers subscribed");
28
+ });
29
+
30
+ test("shows nothing before the first sync or while the list is empty", () => {
31
+ loading = true;
32
+ const { unmount } = render(<LiveCounter importedCount={0} label="designers subscribed" labelOne="designer subscribed" />);
33
+ expect(screen.queryByTestId("live-count")).toBeNull();
34
+ unmount();
35
+ loading = false;
36
+ rows = [{ id: "c", count: 0 }];
37
+ render(<LiveCounter importedCount={0} label="designers subscribed" labelOne="designer subscribed" />);
38
+ expect(screen.queryByTestId("live-count")).toBeNull();
39
+ });
40
+
41
+ test("uses the singular label for one subscriber", () => {
42
+ rows = [{ id: "c", count: 1 }];
43
+ render(<LiveCounter importedCount={0} label="designers subscribed" labelOne="designer subscribed" />);
44
+ expect(screen.getByTestId("live-count").textContent).toBe("1 designer subscribed");
45
+ });
@@ -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`.
@@ -14,6 +14,11 @@ Open http://localhost:4321 and create an account. The first sign-in seeds a demo
14
14
  pipeline so the board isn't empty; delete `functions/seedWorkspace.ts` and
15
15
  `lib/seed.ts` once you have real customers.
16
16
 
17
+ The deals are spread across you and four demo teammates. Each is a User
18
+ row with a `@demo.invalid` address and no password, so outside dev mode nobody
19
+ can sign in as one (see `lib/demo-team.ts`). Delete those users along with the
20
+ seed.
21
+
17
22
  ## What's interesting here
18
23
 
19
24
  **The board is live.** Drag a deal to another column and it moves on every
@@ -31,6 +36,22 @@ win rate are computed from the deals themselves in `lib/pipeline.ts` — pure
31
36
  functions with no React and no `db`, so the arithmetic is unit-tested directly
32
37
  (`tests/pipeline.test.ts`).
33
38
 
39
+ ## Brand and layout
40
+
41
+ The product ships as **Dealbook**. The name lives in `lib/brand.ts`, and the
42
+ sidebar, sign-in screen, page titles, and phone drawer all read it. The mark is
43
+ `components/brand-mark.tsx`; `app/icon.svg`, `app/apple-icon.png`, and
44
+ `public/favicon.ico` draw the same shape for browsers and home screens. The
45
+ accent colour is `--brand` at the top of `app/globals.css`, and the typeface
46
+ (Instrument Sans) is set in `fonts` in `app.ts`.
47
+
48
+ The frame is `components/app-shell.tsx`. From 768px up the sidebar is a fixed
49
+ rail; below that it opens as a drawer from the menu button in each page header,
50
+ tables drop their lower-priority columns (`hideBelow` in
51
+ `components/data-table.tsx`), and boards scroll one column at a time. The nav
52
+ links are in `components/nav.tsx`. Every nav count comes from `navCounts` in
53
+ `app/workspace.tsx`, and page headers and tabs read the same numbers.
54
+
34
55
  ## Layout
35
56
 
36
57
  ```
@@ -84,7 +105,7 @@ already themed.
84
105
  - **Add a stage:** `PIPELINE` in `lib/pipeline.ts`. The board, the forecast, and
85
106
  the stage picker all read from it.
86
107
  - **Add a view:** drop `app/reports/page.tsx` and add it to `NAV` in
87
- `components/sidebar.tsx`.
108
+ `components/nav.tsx`.
88
109
 
89
110
  ## Deploy
90
111
 
Binary file
@@ -9,7 +9,7 @@ import { DataTable, type ColumnDef } from "@/components/data-table";
9
9
  import { EmptyState } from "@/components/empty-state";
10
10
  import { RecordDialog } from "@/components/record-dialog";
11
11
  import { Avatar } from "@/components/avatar";
12
- import { money, relativeTime } from "@/lib/pipeline";
12
+ import { isOpen, money, relativeTime } from "@/lib/pipeline";
13
13
  import { RequireAuth } from "@/components/require-auth";
14
14
  import { Workspace, type CompanyRow, type DealRow } from "../workspace";
15
15
 
@@ -17,30 +17,37 @@ export function CompaniesView() {
17
17
  const [open, setOpen] = useState(false);
18
18
 
19
19
  return (
20
- <RequireAuth title="CRM" description="Your team shares one pipeline. Anyone with an account sees it.">
20
+ <RequireAuth>
21
21
  <Workspace pathname="/companies">
22
22
  {(data) => {
23
- // Pipeline per company, computed here rather than stored — one less
24
- // thing to keep in sync when a deal moves.
23
+ // Open pipeline per company, computed from the deals rather than
24
+ // stored, so a moved deal can't leave it stale.
25
25
  const openValue = (companyId: string) =>
26
26
  data.deals
27
- .filter((deal: DealRow) => deal.companyId === companyId)
27
+ .filter((deal: DealRow) => deal.companyId === companyId && isOpen(deal))
28
28
  .reduce((sum, deal) => sum + (Number(deal.value) || 0), 0);
29
29
 
30
30
  const columns: ColumnDef<CompanyRow>[] = [
31
31
  {
32
32
  key: "name",
33
33
  header: "Company",
34
+ className: "w-[46%] md:w-[26%]",
34
35
  cell: (row) => (
35
- <span className="flex items-center gap-2">
36
- <Avatar name={row.name} size="sm" />
37
- <span className="truncate font-medium">{row.name}</span>
36
+ <span className="flex min-w-0 items-center gap-2.5">
37
+ <Avatar name={row.name} shape="square" />
38
+ <span className="min-w-0">
39
+ <span className="block truncate font-medium">{row.name}</span>
40
+ <span className="block truncate text-[12px] text-muted-foreground md:hidden">
41
+ {row.industry ?? row.domain ?? ""}
42
+ </span>
43
+ </span>
38
44
  </span>
39
45
  ),
40
46
  },
41
47
  {
42
48
  key: "domain",
43
49
  header: "Domain",
50
+ hideBelow: "md",
44
51
  cell: (row) => (
45
52
  <span className="text-muted-foreground">{row.domain ?? "—"}</span>
46
53
  ),
@@ -48,6 +55,7 @@ export function CompaniesView() {
48
55
  {
49
56
  key: "industry",
50
57
  header: "Industry",
58
+ hideBelow: "md",
51
59
  cell: (row) => (
52
60
  <span className="text-muted-foreground">{row.industry ?? "—"}</span>
53
61
  ),
@@ -55,19 +63,27 @@ export function CompaniesView() {
55
63
  {
56
64
  key: "size",
57
65
  header: "Size",
66
+ hideBelow: "lg",
67
+ className: "w-[110px]",
58
68
  cell: (row) => (
59
69
  <span className="text-muted-foreground">{row.size ?? "—"}</span>
60
70
  ),
61
71
  },
62
72
  {
63
73
  key: "pipeline",
64
- header: "Pipeline",
74
+ header: "Open pipeline",
65
75
  numeric: true,
66
- cell: (row) => money(openValue(row.id)),
76
+ className: "w-[130px]",
77
+ cell: (row) => {
78
+ const value = openValue(row.id);
79
+ return value > 0 ? money(value) : <span className="text-muted-foreground">—</span>;
80
+ },
67
81
  },
68
82
  {
69
83
  key: "created",
70
84
  header: "Added",
85
+ hideBelow: "sm",
86
+ className: "w-[110px]",
71
87
  cell: (row) => (
72
88
  <span className="text-muted-foreground">
73
89
  {relativeTime(row.createdAt)}
@@ -80,16 +96,18 @@ export function CompaniesView() {
80
96
 
81
97
  return (
82
98
  <>
83
- <PageHeader title="Companies" count={rows.length}>
99
+ <PageHeader title="Companies" count={data.navCounts["/companies"]}>
84
100
  <Button size="sm" onClick={() => setOpen(true)}>
85
101
  <Plus />
86
- New company
102
+ <span className="max-sm:sr-only">New company</span>
87
103
  </Button>
88
104
  </PageHeader>
89
105
 
90
106
  <DataTable
91
107
  rows={rows}
92
108
  columns={columns}
109
+ loading={data.loading}
110
+ label="Companies"
93
111
  empty={
94
112
  <EmptyState
95
113
  icon={<Building2 />}
@@ -111,8 +129,8 @@ export function CompaniesView() {
111
129
  submitLabel="Create company"
112
130
  onOpenChange={setOpen}
113
131
  fields={[
114
- { name: "name", label: "Name", required: true, placeholder: "Acme Inc" },
115
- { name: "domain", label: "Domain", placeholder: "acme.com" },
132
+ { name: "name", label: "Name", required: true, placeholder: "Northwind Logistics" },
133
+ { name: "domain", label: "Domain", placeholder: "northwind.co" },
116
134
  { name: "industry", label: "Industry", placeholder: "Logistics" },
117
135
  { name: "size", label: "Size", placeholder: "50-200" },
118
136
  ]}
@@ -1,9 +1,10 @@
1
1
  import React from "react";
2
2
  import type { Metadata, PageProps } from "@pylonsync/react";
3
+ import { BRAND } from "@/lib/brand";
3
4
  import { CompaniesView } from "./companies-view";
4
5
 
5
6
  export const metadata: Metadata = {
6
- title: "Companies",
7
+ title: `Companies · ${BRAND.name}`,
7
8
  robots: "noindex",
8
9
  };
9
10