@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
@@ -2,87 +2,101 @@
2
2
  @import "tw-animate-css";
3
3
 
4
4
  /* Tailwind v4 scans these globs for class names. `components/` is here so
5
- shadcn/ui component classes are seen — add more @source lines if you put
6
- markup elsewhere. */
5
+ shadcn/ui and chat component classes are seen. Add more @source lines if
6
+ you put markup elsewhere. */
7
7
  @source "../app/**/*.{tsx,ts,jsx,js}";
8
8
  @source "../components/**/*.{tsx,ts,jsx,js}";
9
9
 
10
10
  @custom-variant dark (&:where(.dark, .dark *));
11
11
 
12
- /* shadcn/ui design tokens (new-york / zinc). Edit these to re-theme the
13
- whole app; `npx shadcn@latest add <component>` drops new components that
14
- consume the same variables. Toggle dark mode by putting `class="dark"`
15
- on <html>. */
12
+ /* Design tokens. The shadcn/ui names (background, primary, sidebar, …) are
13
+ kept so `npx shadcn@latest add <component>` drops in components that match.
14
+ `signal` is the accent (send button, unread badges); `online` is the
15
+ presence dot. Toggle dark mode by putting `class="dark"` on <html>. */
16
16
  :root {
17
17
  --radius: 0.625rem;
18
- --background: oklch(1 0 0);
19
- --foreground: oklch(0.141 0.005 285.823);
18
+ --background: oklch(0.988 0.003 85);
19
+ --foreground: oklch(0.21 0.012 265);
20
20
  --card: oklch(1 0 0);
21
- --card-foreground: oklch(0.141 0.005 285.823);
21
+ --card-foreground: oklch(0.21 0.012 265);
22
22
  --popover: oklch(1 0 0);
23
- --popover-foreground: oklch(0.141 0.005 285.823);
24
- --primary: oklch(0.21 0.006 285.885);
23
+ --popover-foreground: oklch(0.21 0.012 265);
24
+ --primary: oklch(0.24 0.014 265);
25
25
  --primary-foreground: oklch(0.985 0 0);
26
- --secondary: oklch(0.967 0.001 286.375);
27
- --secondary-foreground: oklch(0.21 0.006 285.885);
28
- --muted: oklch(0.967 0.001 286.375);
29
- --muted-foreground: oklch(0.552 0.016 285.938);
30
- --accent: oklch(0.967 0.001 286.375);
31
- --accent-foreground: oklch(0.21 0.006 285.885);
32
- --destructive: oklch(0.577 0.245 27.325);
33
- --border: oklch(0.92 0.004 286.32);
34
- --input: oklch(0.92 0.004 286.32);
35
- --ring: oklch(0.705 0.015 286.067);
26
+ --secondary: oklch(0.955 0.004 85);
27
+ --secondary-foreground: oklch(0.24 0.014 265);
28
+ --muted: oklch(0.95 0.004 85);
29
+ --muted-foreground: oklch(0.53 0.012 265);
30
+ --accent: oklch(0.955 0.004 85);
31
+ --accent-foreground: oklch(0.24 0.014 265);
32
+ --destructive: oklch(0.577 0.215 27.325);
33
+ --border: oklch(0.905 0.005 85);
34
+ --input: oklch(0.905 0.005 85);
35
+ --ring: oklch(0.7 0.02 265);
36
+ --hover: oklch(0.955 0.004 85 / 0.7);
37
+ --signal: oklch(0.63 0.17 38);
38
+ --signal-foreground: oklch(0.99 0.005 60);
39
+ --online: oklch(0.72 0.17 150);
36
40
  --chart-1: oklch(0.646 0.222 41.116);
37
41
  --chart-2: oklch(0.6 0.118 184.704);
38
42
  --chart-3: oklch(0.398 0.07 227.392);
39
43
  --chart-4: oklch(0.828 0.189 84.429);
40
44
  --chart-5: oklch(0.769 0.188 70.08);
41
- --sidebar: oklch(0.985 0 0);
42
- --sidebar-foreground: oklch(0.141 0.005 285.823);
43
- --sidebar-primary: oklch(0.21 0.006 285.885);
44
- --sidebar-primary-foreground: oklch(0.985 0 0);
45
- --sidebar-accent: oklch(0.967 0.001 286.375);
46
- --sidebar-accent-foreground: oklch(0.21 0.006 285.885);
47
- --sidebar-border: oklch(0.92 0.004 286.32);
48
- --sidebar-ring: oklch(0.705 0.015 286.067);
45
+ --sidebar: oklch(0.2 0.013 265);
46
+ --sidebar-foreground: oklch(0.95 0.004 265);
47
+ --sidebar-muted: oklch(0.62 0.014 265);
48
+ --sidebar-muted-strong: oklch(0.8 0.01 265);
49
+ --sidebar-primary: oklch(0.63 0.17 38);
50
+ --sidebar-primary-foreground: oklch(0.99 0.005 60);
51
+ --sidebar-accent: oklch(0.275 0.016 265);
52
+ --sidebar-accent-foreground: oklch(0.98 0.003 265);
53
+ --sidebar-border: oklch(1 0 0 / 8%);
54
+ --sidebar-ring: oklch(0.63 0.17 38);
49
55
  }
50
56
 
51
57
  .dark {
52
- --background: oklch(0.141 0.005 285.823);
53
- --foreground: oklch(0.985 0 0);
54
- --card: oklch(0.21 0.006 285.885);
55
- --card-foreground: oklch(0.985 0 0);
56
- --popover: oklch(0.21 0.006 285.885);
57
- --popover-foreground: oklch(0.985 0 0);
58
- --primary: oklch(0.92 0.004 286.32);
59
- --primary-foreground: oklch(0.21 0.006 285.885);
60
- --secondary: oklch(0.274 0.006 286.033);
61
- --secondary-foreground: oklch(0.985 0 0);
62
- --muted: oklch(0.274 0.006 286.033);
63
- --muted-foreground: oklch(0.705 0.015 286.067);
64
- --accent: oklch(0.274 0.006 286.033);
65
- --accent-foreground: oklch(0.985 0 0);
58
+ --background: oklch(0.17 0.01 265);
59
+ --foreground: oklch(0.95 0.004 265);
60
+ --card: oklch(0.22 0.012 265);
61
+ --card-foreground: oklch(0.95 0.004 265);
62
+ --popover: oklch(0.22 0.012 265);
63
+ --popover-foreground: oklch(0.95 0.004 265);
64
+ --primary: oklch(0.92 0.004 265);
65
+ --primary-foreground: oklch(0.21 0.012 265);
66
+ --secondary: oklch(0.26 0.012 265);
67
+ --secondary-foreground: oklch(0.95 0.004 265);
68
+ --muted: oklch(0.26 0.012 265);
69
+ --muted-foreground: oklch(0.68 0.012 265);
70
+ --accent: oklch(0.26 0.012 265);
71
+ --accent-foreground: oklch(0.95 0.004 265);
66
72
  --destructive: oklch(0.704 0.191 22.216);
67
- --border: oklch(1 0 0 / 10%);
68
- --input: oklch(1 0 0 / 15%);
69
- --ring: oklch(0.552 0.016 285.938);
73
+ --border: oklch(1 0 0 / 9%);
74
+ --input: oklch(1 0 0 / 14%);
75
+ --ring: oklch(0.55 0.02 265);
76
+ --hover: oklch(1 0 0 / 4%);
77
+ --signal: oklch(0.68 0.16 40);
78
+ --signal-foreground: oklch(0.18 0.02 40);
79
+ --online: oklch(0.74 0.16 150);
70
80
  --chart-1: oklch(0.488 0.243 264.376);
71
81
  --chart-2: oklch(0.696 0.17 162.48);
72
82
  --chart-3: oklch(0.769 0.188 70.08);
73
83
  --chart-4: oklch(0.627 0.265 303.9);
74
84
  --chart-5: oklch(0.645 0.246 16.439);
75
- --sidebar: oklch(0.21 0.006 285.885);
76
- --sidebar-foreground: oklch(0.985 0 0);
77
- --sidebar-primary: oklch(0.488 0.243 264.376);
78
- --sidebar-primary-foreground: oklch(0.985 0 0);
79
- --sidebar-accent: oklch(0.274 0.006 286.033);
80
- --sidebar-accent-foreground: oklch(0.985 0 0);
81
- --sidebar-border: oklch(1 0 0 / 10%);
82
- --sidebar-ring: oklch(0.552 0.016 285.938);
85
+ --sidebar: oklch(0.14 0.01 265);
86
+ --sidebar-foreground: oklch(0.95 0.004 265);
87
+ --sidebar-muted: oklch(0.6 0.014 265);
88
+ --sidebar-muted-strong: oklch(0.8 0.01 265);
89
+ --sidebar-primary: oklch(0.68 0.16 40);
90
+ --sidebar-primary-foreground: oklch(0.18 0.02 40);
91
+ --sidebar-accent: oklch(0.23 0.014 265);
92
+ --sidebar-accent-foreground: oklch(0.98 0.003 265);
93
+ --sidebar-border: oklch(1 0 0 / 8%);
94
+ --sidebar-ring: oklch(0.68 0.16 40);
83
95
  }
84
96
 
85
97
  @theme inline {
98
+ --font-sans: var(--font-geist), ui-sans-serif, system-ui, -apple-system, sans-serif;
99
+ --font-mono: var(--font-geist-mono), ui-monospace, "SF Mono", Menlo, monospace;
86
100
  --radius-sm: calc(var(--radius) - 4px);
87
101
  --radius-md: calc(var(--radius) - 2px);
88
102
  --radius-lg: var(--radius);
@@ -105,6 +119,10 @@
105
119
  --color-border: var(--border);
106
120
  --color-input: var(--input);
107
121
  --color-ring: var(--ring);
122
+ --color-hover: var(--hover);
123
+ --color-signal: var(--signal);
124
+ --color-signal-foreground: var(--signal-foreground);
125
+ --color-online: var(--online);
108
126
  --color-chart-1: var(--chart-1);
109
127
  --color-chart-2: var(--chart-2);
110
128
  --color-chart-3: var(--chart-3);
@@ -112,12 +130,35 @@
112
130
  --color-chart-5: var(--chart-5);
113
131
  --color-sidebar: var(--sidebar);
114
132
  --color-sidebar-foreground: var(--sidebar-foreground);
133
+ --color-sidebar-muted: var(--sidebar-muted);
134
+ --color-sidebar-muted-strong: var(--sidebar-muted-strong);
115
135
  --color-sidebar-primary: var(--sidebar-primary);
116
136
  --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
117
137
  --color-sidebar-accent: var(--sidebar-accent);
118
138
  --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
119
139
  --color-sidebar-border: var(--sidebar-border);
120
140
  --color-sidebar-ring: var(--sidebar-ring);
141
+
142
+ --ease-out-soft: cubic-bezier(0.22, 1, 0.36, 1);
143
+ --shadow-composer:
144
+ 0 0 0 1px oklch(0.21 0.012 265 / 0.08), 0 1px 2px oklch(0.21 0.012 265 / 0.04),
145
+ 0 8px 24px -12px oklch(0.21 0.012 265 / 0.12);
146
+ --shadow-composer-focus:
147
+ 0 0 0 1px oklch(0.21 0.012 265 / 0.16), 0 0 0 4px oklch(0.63 0.17 38 / 0.12),
148
+ 0 10px 28px -12px oklch(0.21 0.012 265 / 0.16);
149
+ --shadow-float: 0 1px 2px oklch(0.21 0.012 265 / 0.1), 0 10px 30px -10px oklch(0.21 0.012 265 / 0.35);
150
+
151
+ --animate-message-in: message-in 260ms var(--ease-out-soft) both;
152
+ @keyframes message-in {
153
+ from {
154
+ opacity: 0;
155
+ transform: translateY(6px);
156
+ }
157
+ to {
158
+ opacity: 1;
159
+ transform: translateY(0);
160
+ }
161
+ }
121
162
  }
122
163
 
123
164
  @layer base {
@@ -129,11 +170,86 @@
129
170
  border-color: var(--color-border, currentColor);
130
171
  outline-color: var(--color-ring);
131
172
  }
173
+ html {
174
+ -webkit-text-size-adjust: 100%;
175
+ }
132
176
  body {
133
177
  background-color: var(--color-background);
134
178
  color: var(--color-foreground);
179
+ font-family: var(--font-sans);
180
+ font-feature-settings: "ss01", "cv11";
181
+ -webkit-font-smoothing: antialiased;
182
+ -moz-osx-font-smoothing: grayscale;
183
+ text-rendering: optimizeLegibility;
135
184
  }
136
185
  button {
137
186
  cursor: pointer;
138
187
  }
188
+ button:disabled {
189
+ cursor: default;
190
+ }
191
+ }
192
+
193
+ /* Thin scrollbars that only show while the pointer is over the list. */
194
+ .scroll-area {
195
+ scrollbar-width: thin;
196
+ scrollbar-color: transparent transparent;
197
+ transition: scrollbar-color 200ms;
198
+ }
199
+ .scroll-area:hover {
200
+ scrollbar-color: oklch(0.5 0.01 265 / 0.3) transparent;
201
+ }
202
+
203
+ /* Three dots that rise in turn, beside "Maya is typing". */
204
+ .typing-dots > span {
205
+ width: 5px;
206
+ height: 5px;
207
+ border-radius: 9999px;
208
+ background: currentColor;
209
+ opacity: 0.35;
210
+ animation: typing-dot 1.2s var(--ease-out-soft) infinite;
211
+ }
212
+ .typing-dots > span:nth-child(2) {
213
+ animation-delay: 0.15s;
214
+ }
215
+ .typing-dots > span:nth-child(3) {
216
+ animation-delay: 0.3s;
217
+ }
218
+ @keyframes typing-dot {
219
+ 0%,
220
+ 60%,
221
+ 100% {
222
+ opacity: 0.35;
223
+ transform: translateY(0);
224
+ }
225
+ 30% {
226
+ opacity: 0.9;
227
+ transform: translateY(-3px);
228
+ }
229
+ }
230
+
231
+ @media (prefers-reduced-motion: reduce) {
232
+ *,
233
+ ::before,
234
+ ::after {
235
+ animation-duration: 0.01ms !important;
236
+ animation-iteration-count: 1 !important;
237
+ transition-duration: 0.01ms !important;
238
+ scroll-behavior: auto !important;
239
+ }
240
+ }
241
+
242
+ /* Delete button on your own messages: shown on hover or keyboard focus with a
243
+ mouse, always shown on touch screens (which have no hover). Hidden, it takes
244
+ no taps. */
245
+ @media (hover: hover) {
246
+ .delete-action {
247
+ opacity: 0;
248
+ pointer-events: none;
249
+ }
250
+ .group\/msg:hover .delete-action,
251
+ .delete-action:focus-visible {
252
+ opacity: 1;
253
+ pointer-events: auto;
254
+ }
139
255
  }
@@ -4,22 +4,25 @@ interface LayoutProps {
4
4
  children: React.ReactNode;
5
5
  }
6
6
 
7
- // Full-height layout so the chat can fill the screen with a sticky composer
8
- // at the bottom. The page renders server-side first, then hydrates into the
9
- // live room.
7
+ // Full-height document shell. The chat fills the viewport; 404 and error
8
+ // pages center themselves inside it.
10
9
  export default function RootLayout({ children }: LayoutProps) {
11
10
  return (
12
11
  <html lang="en" className="h-full">
13
12
  <head>
14
13
  <meta charSet="utf-8" />
15
- <meta name="viewport" content="width=device-width, initial-scale=1" />
14
+ <meta
15
+ name="viewport"
16
+ content="width=device-width, initial-scale=1, viewport-fit=cover, interactive-widget=resizes-content"
17
+ />
18
+ <meta name="theme-color" content="#fbfaf8" />
19
+ <link
20
+ rel="icon"
21
+ href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='8' fill='%23d9623b'/%3E%3Cpath d='M9 11.5A2.5 2.5 0 0 1 11.5 9h9a2.5 2.5 0 0 1 2.5 2.5v6a2.5 2.5 0 0 1-2.5 2.5H15l-4 3.5V20h0a2 2 0 0 1-2-2z' fill='%23fff'/%3E%3C/svg%3E"
22
+ />
16
23
  <title>__APP_NAME__</title>
17
24
  </head>
18
- <body className="h-full bg-background text-foreground antialiased">
19
- <div className="mx-auto flex h-full max-w-lg flex-col px-4">
20
- {children}
21
- </div>
22
- </body>
25
+ <body className="h-full bg-background text-foreground antialiased">{children}</body>
23
26
  </html>
24
27
  );
25
28
  }
@@ -1,6 +1,7 @@
1
1
  import React from "react";
2
2
  import { Link, useRouter, type NotFoundProps } from "@pylonsync/react";
3
3
  import { Button } from "@/components/ui/button";
4
+ import { ArrowLeft } from "lucide-react";
4
5
 
5
6
  // `app/not-found.tsx` → rendered at HTTP 404 for any unmatched URL (and when
6
7
  // a page calls `response.notFound()`). It's HYDRATED, so it's interactive:
@@ -9,7 +10,7 @@ import { Button } from "@/components/ui/button";
9
10
  export default function NotFound(_props: NotFoundProps) {
10
11
  const router = useRouter();
11
12
  return (
12
- <div className="space-y-6">
13
+ <div className="mx-auto flex min-h-dvh max-w-md flex-col justify-center gap-6 px-6">
13
14
  <section>
14
15
  <h1 className="text-2xl font-semibold tracking-tight">404</h1>
15
16
  <p className="mt-2 text-muted-foreground">
@@ -18,7 +19,8 @@ export default function NotFound(_props: NotFoundProps) {
18
19
  </section>
19
20
  <div className="flex items-center gap-3">
20
21
  <Button onClick={() => router.back()} variant="outline">
21
- ← Go back
22
+ <ArrowLeft className="mr-1.5 size-4" aria-hidden />
23
+ Go back
22
24
  </Button>
23
25
  <Button asChild>
24
26
  <Link href="/">Home</Link>
@@ -1,26 +1,16 @@
1
1
  import React from "react";
2
- import { type Metadata } from "@pylonsync/react";
2
+ import { type Metadata, type PageProps } from "@pylonsync/react";
3
3
  import { ChatRoom } from "./chat-client";
4
4
 
5
5
  export const metadata: Metadata = {
6
- title: "__APP_NAME__ — realtime chat on Pylon",
7
- description:
8
- "A live chat room over one Pylon backend. Open two tabs and watch messages sync instantly.",
6
+ title: "__APP_NAME__",
7
+ description: "A shared chat room. Everyone in the room sees the same messages.",
9
8
  };
10
9
 
11
- // `app/page.tsx` → `/`. The header is server-rendered; `<ChatRoom>` is a client
12
- // island that mints a guest session and runs the live message subscription +
13
- // optimistic send in the browser.
14
- export default function IndexPage() {
15
- return (
16
- <>
17
- <header className="shrink-0 py-4">
18
- <h1 className="text-xl font-semibold tracking-tight">Room</h1>
19
- <p className="text-sm text-muted-foreground">
20
- One shared room. Open a second tab — messages sync instantly.
21
- </p>
22
- </header>
23
- <ChatRoom />
24
- </>
25
- );
10
+ // `app/page.tsx` → `/`. `?c=<slug>` opens a channel (`/?c=launch`).
11
+ // `<ChatRoom>` is a client island: the server renders its skeleton, and the
12
+ // browser mints a guest session, then runs the live channels, messages, and
13
+ // presence.
14
+ export default function IndexPage({ searchParams }: PageProps) {
15
+ return <ChatRoom initialChannel={searchParams.c} />;
26
16
  }
@@ -3,54 +3,133 @@ import {
3
3
  field,
4
4
  policy,
5
5
  auth,
6
+ font,
6
7
  buildManifest,
7
8
  discoverAppRoutes,
8
9
  discoverFunctions,
9
10
  } from "@pylonsync/sdk";
10
11
 
11
- // A chat message in the shared room. `authorId: field.owner()` stamps the
12
- // signed-in (guest) user's id server-side, so an optimistic
13
- // `db.insert("Message", { text })` can't forge the sender.
12
+ // Data model
13
+ //
14
+ // • Channel: a room in the sidebar (#general, #launch, …). Created by the
15
+ // seed; clients can read but not write.
16
+ // • Member: a display name + avatar hue per user, including guests. Written
17
+ // only by the joinChat / renameMember functions, which take the
18
+ // user id from the session.
19
+ // • Message: one chat line. `authorId: field.owner()` stamps the sender from
20
+ // the session on the server, so the optimistic
21
+ // `db.insert("Message", …)` in the client cannot forge it.
22
+ //
23
+ // Presence ("3 online") and typing indicators are not stored. They ride on a
24
+ // presence room (`useRoom` in app/chat-client.tsx).
25
+ const Channel = entity(
26
+ "Channel",
27
+ {
28
+ slug: field.string().unique(),
29
+ name: field.string(),
30
+ topic: field.string(),
31
+ position: field.int(),
32
+ },
33
+ { indexes: [{ name: "by_position", fields: ["position"], unique: false }] },
34
+ );
35
+
36
+ const Member = entity("Member", {
37
+ userId: field.string().unique(),
38
+ name: field.string(),
39
+ // Avatar color, as an OKLCH hue angle (0–359).
40
+ hue: field.int(),
41
+ createdAt: field.datetime().defaultNow(),
42
+ });
43
+
14
44
  const Message = entity(
15
45
  "Message",
16
46
  {
47
+ channelId: field.string(),
17
48
  authorId: field.string().owner(),
18
49
  text: field.string(),
19
50
  createdAt: field.datetime().defaultNow(),
20
51
  },
21
- { indexes: [{ name: "by_created", fields: ["createdAt"], unique: false }] },
52
+ {
53
+ indexes: [
54
+ { name: "by_channel_created", fields: ["channelId", "createdAt"], unique: false },
55
+ ],
56
+ },
22
57
  );
23
58
 
24
- // Public-read so the room renders for everyone; insert requires a session
25
- // (the owner is stamped by field.owner, see the note on allowInsert); delete
26
- // is owner-only. An entity with no policy is denied to clients by default.
59
+ // Everyone can read channels. Only the seed in functions/joinChat.ts writes them.
60
+ const channelPolicy = policy({
61
+ name: "channel_public_read",
62
+ entity: "Channel",
63
+ allowRead: "true",
64
+ allowInsert: "false",
65
+ allowUpdate: "false",
66
+ allowDelete: "false",
67
+ });
68
+
69
+ // Names and avatar hues are public so every message can show its author.
70
+ // Writes go through joinChat / renameMember, which validate the name and use
71
+ // the caller's session id.
72
+ const memberPolicy = policy({
73
+ name: "member_public_read",
74
+ entity: "Member",
75
+ allowRead: "true",
76
+ allowInsert: "false",
77
+ allowUpdate: "false",
78
+ allowDelete: "false",
79
+ });
80
+
81
+ // The room is public-read. Insert needs a session (guests count), a channel
82
+ // that exists, and no client-supplied `createdAt`: the server stamps the time,
83
+ // so a client cannot pin its message to the bottom with a future date.
84
+ // `auth.userId != null` rather than `== data.authorId`: field.owner() stamps
85
+ // the owner after the policy check, and rejects any other user's id.
86
+ // Messages can't be edited; the author can delete their own.
27
87
  const messagePolicy = policy({
28
88
  name: "message_room",
29
89
  entity: "Message",
30
90
  allowRead: "true",
31
- // `auth.userId != null`, not `== data.authorId`: field.owner() stamps the
32
- // owner AFTER the policy check, so it's null at insert-time. The stamp still
33
- // guarantees the message is attributed to the caller.
34
- allowInsert: "auth.userId != null",
91
+ allowInsert:
92
+ "auth.userId != null && data.createdAt == null && exists(Channel where id == data.channelId)",
35
93
  allowUpdate: "false",
36
94
  allowDelete: "auth.userId == data.authorId",
37
95
  });
38
96
 
39
97
  // buildManifest assembles the entities, policies, auth, and routes into the
40
- // single manifest `pylon dev` serves — SSR room + realtime API on one port.
41
- // Every non-internal function in functions/ becomes a manifest entry —
42
- // this is what makes them show up in /api/manifest, the OpenAPI spec,
43
- // and `pylon codegen`.
98
+ // single manifest `pylon dev` serves: SSR page + realtime API on one port.
99
+ // Every non-internal function in functions/ becomes a manifest entry, which
100
+ // is what makes them show up in /api/manifest, the OpenAPI spec, and
101
+ // `pylon codegen`.
44
102
  const fns = await discoverFunctions();
45
103
 
46
104
  const manifest = buildManifest({
47
105
  name: "__APP_NAME__",
48
106
  version: "0.1.0",
49
- entities: [Message],
107
+ entities: [Channel, Member, Message],
50
108
  queries: fns.queries,
51
109
  actions: fns.actions,
52
- policies: [messagePolicy],
110
+ policies: [channelPolicy, memberPolicy, messagePolicy],
53
111
  auth: auth(),
112
+ // Self-hosted Geist: the build fetches the woff2, serves it same-origin,
113
+ // preloads it, and adds a size-adjusted fallback face so text does not
114
+ // shift when it loads. globals.css reads the variables.
115
+ fonts: [
116
+ font({
117
+ family: "Geist",
118
+ variable: "--font-geist",
119
+ weights: ["400", "500", "600"],
120
+ subsets: ["latin"],
121
+ display: "swap",
122
+ preload: true,
123
+ }),
124
+ font({
125
+ family: "Geist Mono",
126
+ variable: "--font-geist-mono",
127
+ weights: ["400", "500"],
128
+ subsets: ["latin"],
129
+ display: "swap",
130
+ preload: false,
131
+ }),
132
+ ],
54
133
  routes: await discoverAppRoutes(),
55
134
  });
56
135
 
@@ -0,0 +1,45 @@
1
+ import React from "react";
2
+ import { cn } from "@/lib/utils";
3
+ import { initials } from "@/lib/chat";
4
+
5
+ interface AvatarProps {
6
+ name: string;
7
+ hue: number;
8
+ size?: "xs" | "sm" | "md";
9
+ online?: boolean;
10
+ className?: string;
11
+ }
12
+
13
+ const SIZES = {
14
+ xs: "size-5 text-[9px]",
15
+ sm: "size-7 text-[11px]",
16
+ md: "size-9 text-[13px]",
17
+ } as const;
18
+
19
+ /** Initials on a circle tinted by the member's hue. */
20
+ export function Avatar({ name, hue, size = "md", online, className }: AvatarProps) {
21
+ return (
22
+ <span className={cn("relative inline-flex shrink-0", className)}>
23
+ <span
24
+ aria-hidden
25
+ className={cn(
26
+ "inline-flex items-center justify-center rounded-full font-semibold tracking-tight select-none",
27
+ SIZES[size],
28
+ )}
29
+ style={{
30
+ backgroundColor: `oklch(0.9 0.05 ${hue})`,
31
+ color: `oklch(0.38 0.1 ${hue})`,
32
+ boxShadow: `inset 0 0 0 1px oklch(0.8 0.07 ${hue} / 0.6)`,
33
+ }}
34
+ >
35
+ {initials(name)}
36
+ </span>
37
+ {online ? (
38
+ <span
39
+ aria-hidden
40
+ className="absolute -right-0.5 -bottom-0.5 size-2.5 rounded-full bg-online ring-2 ring-[var(--avatar-ring,var(--background))]"
41
+ />
42
+ ) : null}
43
+ </span>
44
+ );
45
+ }