@pylonsync/create-pylon 0.19.1 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (675) hide show
  1. package/bin/create-pylon.js +30 -7
  2. package/package.json +1 -1
  3. package/templates/agency/.env.example +7 -0
  4. package/templates/agency/README.md +28 -3
  5. package/templates/agency/app/(marketing)/contact-form.tsx +20 -18
  6. package/templates/agency/app/(marketing)/page.tsx +3 -3
  7. package/templates/agency/app/(marketing)/work/page.tsx +3 -3
  8. package/templates/agency/app/auth-form.tsx +4 -4
  9. package/templates/agency/app/dashboard/dashboard-client.tsx +110 -64
  10. package/templates/agency/app/dashboard/page.tsx +14 -16
  11. package/templates/agency/app/icon.svg +3 -0
  12. package/templates/agency/app/login/page.tsx +12 -14
  13. package/templates/agency/components/marketing.tsx +12 -5
  14. package/templates/agency/functions/seedStudioBackoffice.ts +42 -11
  15. package/templates/agency/lib/agency.ts +22 -0
  16. package/templates/agency/lib/demo.ts +118 -0
  17. package/templates/agency/lib/site.config.ts +19 -13
  18. package/templates/agency/public/favicon.ico +0 -0
  19. package/templates/agency/tests/agency.test.ts +57 -0
  20. package/templates/ai-chat/.env.example +13 -27
  21. package/templates/ai-chat/README.md +53 -60
  22. package/templates/ai-chat/app/(chat)/c/[id]/page.tsx +19 -0
  23. package/templates/ai-chat/app/(chat)/chat-client.tsx +657 -622
  24. package/templates/ai-chat/app/(chat)/layout.tsx +6 -43
  25. package/templates/ai-chat/app/(chat)/not-found.tsx +6 -10
  26. package/templates/ai-chat/app/(chat)/page.tsx +7 -11
  27. package/templates/ai-chat/app/auth-form.tsx +45 -38
  28. package/templates/ai-chat/app/error.tsx +9 -12
  29. package/templates/ai-chat/app/globals.css +464 -102
  30. package/templates/ai-chat/app/layout.tsx +11 -19
  31. package/templates/ai-chat/app/login/page.tsx +39 -24
  32. package/templates/ai-chat/app.ts +118 -43
  33. package/templates/ai-chat/components/chat/composer.tsx +193 -0
  34. package/templates/ai-chat/components/chat/empty-state.tsx +65 -0
  35. package/templates/ai-chat/components/chat/icons.tsx +198 -0
  36. package/templates/ai-chat/components/chat/markdown.tsx +164 -0
  37. package/templates/ai-chat/components/chat/message.tsx +183 -0
  38. package/templates/ai-chat/components/chat/setup.tsx +71 -0
  39. package/templates/ai-chat/components/chat/sidebar.tsx +288 -0
  40. package/templates/ai-chat/functions/_beginTurn.ts +145 -0
  41. package/templates/ai-chat/functions/_finishTurn.ts +55 -0
  42. package/templates/ai-chat/functions/assistantStatus.ts +13 -0
  43. package/templates/ai-chat/functions/claimGuestHistory.ts +39 -0
  44. package/templates/ai-chat/functions/createConversation.ts +28 -0
  45. package/templates/ai-chat/functions/deleteConversation.ts +15 -20
  46. package/templates/ai-chat/functions/prepareGuestClaim.ts +32 -0
  47. package/templates/ai-chat/functions/renameConversation.ts +19 -0
  48. package/templates/ai-chat/functions/respond.ts +88 -0
  49. package/templates/ai-chat/functions/seedExamples.ts +46 -0
  50. package/templates/ai-chat/functions/stopResponse.ts +30 -0
  51. package/templates/ai-chat/lib/brand-icon.ts +7 -0
  52. package/templates/ai-chat/lib/chat.ts +277 -0
  53. package/templates/ai-chat/lib/claim.ts +15 -0
  54. package/templates/ai-chat/lib/examples.ts +179 -0
  55. package/templates/ai-chat/lib/highlight.ts +107 -0
  56. package/templates/ai-chat/lib/markdown.ts +317 -0
  57. package/templates/ai-chat/lib/provider.ts +38 -0
  58. package/templates/ai-chat/lib/site.config.ts +55 -39
  59. package/templates/ai-chat/tests/chat.test.ts +174 -0
  60. package/templates/ai-chat/tests/components.test.tsx +57 -0
  61. package/templates/ai-chat/tests/markdown.test.ts +88 -0
  62. package/templates/ai-chat/tests/provider.test.ts +32 -0
  63. package/templates/backend/consumer/apps/api/app/layout.tsx +33 -0
  64. package/templates/backend/consumer/apps/api/app/page.tsx +54 -0
  65. package/templates/backend/consumer/apps/api/app.ts +166 -0
  66. package/templates/backend/consumer/apps/api/functions/addComment.ts +24 -0
  67. package/templates/backend/consumer/apps/api/functions/createPost.ts +19 -36
  68. package/templates/backend/consumer/apps/api/functions/deletePost.ts +15 -13
  69. package/templates/backend/consumer/apps/api/functions/myProfile.ts +5 -8
  70. package/templates/backend/consumer/apps/api/functions/seedDemo.ts +95 -0
  71. package/templates/backend/consumer/apps/api/functions/setFollow.ts +36 -0
  72. package/templates/backend/consumer/apps/api/functions/setLike.ts +36 -0
  73. package/templates/backend/consumer/apps/api/functions/upsertProfile.ts +43 -37
  74. package/templates/backend/consumer/apps/api/lib/seed.ts +89 -0
  75. package/templates/backend/consumer/apps/api/lib/social.ts +50 -0
  76. package/templates/backend/consumer/apps/api/package.json +8 -2
  77. package/templates/backend/consumer/apps/api/public/images/posts/arch.jpg +0 -0
  78. package/templates/backend/consumer/apps/api/public/images/posts/coffee.jpg +0 -0
  79. package/templates/backend/consumer/apps/api/public/images/posts/dog.jpg +0 -0
  80. package/templates/backend/consumer/apps/api/public/images/posts/lisbon.jpg +0 -0
  81. package/templates/backend/consumer/apps/api/public/images/posts/ramen.jpg +0 -0
  82. package/templates/backend/consumer/apps/api/public/images/posts/run.jpg +0 -0
  83. package/templates/backend/consumer/apps/api/public/images/posts/sunset.jpg +0 -0
  84. package/templates/backend/consumer/apps/api/tsconfig.json +12 -2
  85. package/templates/chat/README.md +43 -21
  86. package/templates/chat/app/chat-client.tsx +495 -85
  87. package/templates/chat/app/error.tsx +1 -1
  88. package/templates/chat/app/globals.css +170 -54
  89. package/templates/chat/app/layout.tsx +12 -9
  90. package/templates/chat/app/not-found.tsx +4 -2
  91. package/templates/chat/app/page.tsx +9 -19
  92. package/templates/chat/app.ts +96 -17
  93. package/templates/chat/components/chat/avatar.tsx +45 -0
  94. package/templates/chat/components/chat/composer.tsx +110 -0
  95. package/templates/chat/components/chat/message-list.tsx +285 -0
  96. package/templates/chat/components/chat/sidebar.tsx +190 -0
  97. package/templates/chat/functions/joinChat.ts +83 -0
  98. package/templates/chat/functions/renameMember.ts +25 -0
  99. package/templates/chat/lib/chat.ts +188 -0
  100. package/templates/chat/lib/seed.ts +78 -0
  101. package/templates/chat/tests/chat.test.ts +159 -0
  102. package/templates/chat/tests/composer.test.tsx +54 -0
  103. package/templates/consumer/README.md +3 -3
  104. package/templates/consumer/app/globals.css +10 -3
  105. package/templates/consumer/app/icon.svg +1 -0
  106. package/templates/consumer/components/social/avatar.tsx +25 -11
  107. package/templates/consumer/functions/seedFeed.ts +3 -3
  108. package/templates/consumer/lib/seed.ts +43 -85
  109. package/templates/consumer/public/favicon.ico +0 -0
  110. package/templates/consumer/public/images/posts/arch.jpg +0 -0
  111. package/templates/consumer/public/images/posts/coffee.jpg +0 -0
  112. package/templates/consumer/public/images/posts/dog.jpg +0 -0
  113. package/templates/consumer/public/images/posts/lisbon.jpg +0 -0
  114. package/templates/consumer/public/images/posts/ramen.jpg +0 -0
  115. package/templates/consumer/public/images/posts/run.jpg +0 -0
  116. package/templates/consumer/public/images/posts/sunset.jpg +0 -0
  117. package/templates/consumer/tests/seed.test.ts +39 -0
  118. package/templates/creator/.env.example +7 -0
  119. package/templates/creator/README.md +24 -5
  120. package/templates/creator/app/(marketing)/newsletter-signup.tsx +33 -12
  121. package/templates/creator/app/auth-form.tsx +7 -7
  122. package/templates/creator/app/dashboard/dashboard-client.tsx +137 -100
  123. package/templates/creator/app/dashboard/page.tsx +18 -18
  124. package/templates/creator/app/icon.svg +2 -0
  125. package/templates/creator/app/login/page.tsx +14 -14
  126. package/templates/creator/app.ts +4 -4
  127. package/templates/creator/functions/seedDemo.ts +32 -0
  128. package/templates/creator/functions/subscribe.ts +4 -15
  129. package/templates/creator/lib/demo.ts +94 -0
  130. package/templates/creator/lib/site.config.ts +15 -10
  131. package/templates/creator/lib/stats.ts +35 -0
  132. package/templates/creator/public/favicon.ico +0 -0
  133. package/templates/creator/tests/counter.test.ts +92 -0
  134. package/templates/creator/tests/live-count.test.tsx +45 -0
  135. package/templates/crm/README.md +22 -1
  136. package/templates/crm/app/apple-icon.png +0 -0
  137. package/templates/crm/app/companies/companies-view.tsx +32 -14
  138. package/templates/crm/app/companies/page.tsx +2 -1
  139. package/templates/crm/app/contacts/contacts-view.tsx +34 -10
  140. package/templates/crm/app/contacts/page.tsx +2 -1
  141. package/templates/crm/app/deals/[id]/deal-view.tsx +137 -98
  142. package/templates/crm/app/deals/[id]/page.tsx +2 -1
  143. package/templates/crm/app/globals.css +29 -14
  144. package/templates/crm/app/icon.svg +1 -0
  145. package/templates/crm/app/layout.tsx +7 -5
  146. package/templates/crm/app/page.tsx +2 -1
  147. package/templates/crm/app/pipeline-view.tsx +5 -5
  148. package/templates/crm/app/workspace.tsx +29 -17
  149. package/templates/crm/app.ts +5 -5
  150. package/templates/crm/components/activity-timeline.tsx +14 -4
  151. package/templates/crm/components/app-shell.tsx +94 -0
  152. package/templates/crm/components/auth-form.tsx +35 -5
  153. package/templates/crm/components/avatar.tsx +5 -1
  154. package/templates/crm/components/board-skeleton.tsx +5 -5
  155. package/templates/crm/components/brand-mark.tsx +25 -0
  156. package/templates/crm/components/command-palette.tsx +17 -17
  157. package/templates/crm/components/data-table.tsx +97 -36
  158. package/templates/crm/components/deal-card.tsx +23 -27
  159. package/templates/crm/components/filter-tabs.tsx +101 -0
  160. package/templates/crm/components/metrics-bar.tsx +6 -6
  161. package/templates/crm/components/nav.tsx +15 -0
  162. package/templates/crm/components/page-header.tsx +43 -7
  163. package/templates/crm/components/pipeline-board.tsx +16 -10
  164. package/templates/crm/components/require-auth.tsx +24 -26
  165. package/templates/crm/components/sidebar.tsx +54 -49
  166. package/templates/crm/components/stage-badge.tsx +4 -2
  167. package/templates/crm/components/ui/button.tsx +5 -4
  168. package/templates/crm/components/ui/dialog.tsx +1 -1
  169. package/templates/crm/components/ui/input.tsx +1 -1
  170. package/templates/crm/components/ui/select.tsx +1 -1
  171. package/templates/crm/components/ui/textarea.tsx +1 -1
  172. package/templates/crm/functions/seedWorkspace.ts +32 -17
  173. package/templates/crm/lib/brand.ts +9 -0
  174. package/templates/crm/lib/demo-team.ts +51 -0
  175. package/templates/crm/lib/pipeline.ts +37 -0
  176. package/templates/crm/lib/search.ts +4 -3
  177. package/templates/crm/lib/seed.ts +53 -8
  178. package/templates/crm/public/favicon.ico +0 -0
  179. package/templates/crm/tests/components.test.tsx +5 -5
  180. package/templates/crm/tests/demo-team.test.ts +18 -0
  181. package/templates/crm/tests/pipeline.test.ts +59 -3
  182. package/templates/directory/.env.example +7 -0
  183. package/templates/directory/README.md +22 -3
  184. package/templates/directory/app/(marketing)/directory-browse.tsx +30 -12
  185. package/templates/directory/app/(marketing)/page.tsx +6 -4
  186. package/templates/directory/app/auth-form.tsx +4 -4
  187. package/templates/directory/app/dashboard/dashboard-client.tsx +177 -90
  188. package/templates/directory/app/dashboard/page.tsx +16 -14
  189. package/templates/directory/app/icon.svg +4 -0
  190. package/templates/directory/app/login/page.tsx +16 -15
  191. package/templates/directory/functions/seedDemo.ts +43 -0
  192. package/templates/directory/lib/demo.ts +145 -0
  193. package/templates/directory/lib/directory.ts +27 -0
  194. package/templates/directory/lib/site.config.ts +21 -16
  195. package/templates/directory/public/favicon.ico +0 -0
  196. package/templates/directory/tests/directory.test.ts +46 -0
  197. package/templates/expo/consumer/apps/expo/App.tsx +360 -288
  198. package/templates/expo/consumer/apps/expo/app.json +9 -1
  199. package/templates/expo/consumer/apps/expo/package.json +2 -1
  200. package/templates/helpdesk/README.md +21 -0
  201. package/templates/helpdesk/app/apple-icon.png +0 -0
  202. package/templates/helpdesk/app/customers/customers-view.tsx +23 -7
  203. package/templates/helpdesk/app/customers/page.tsx +2 -1
  204. package/templates/helpdesk/app/globals.css +29 -14
  205. package/templates/helpdesk/app/icon.svg +1 -0
  206. package/templates/helpdesk/app/inbox-view.tsx +66 -47
  207. package/templates/helpdesk/app/layout.tsx +7 -5
  208. package/templates/helpdesk/app/page.tsx +4 -7
  209. package/templates/helpdesk/app/tickets/[id]/page.tsx +2 -1
  210. package/templates/helpdesk/app/tickets/[id]/ticket-view.tsx +134 -97
  211. package/templates/helpdesk/app/workspace.tsx +33 -22
  212. package/templates/helpdesk/app.ts +2 -2
  213. package/templates/helpdesk/components/app-shell.tsx +94 -0
  214. package/templates/helpdesk/components/auth-form.tsx +35 -5
  215. package/templates/helpdesk/components/avatar.tsx +5 -1
  216. package/templates/helpdesk/components/brand-mark.tsx +28 -0
  217. package/templates/helpdesk/components/command-palette.tsx +17 -17
  218. package/templates/helpdesk/components/data-table.tsx +97 -36
  219. package/templates/helpdesk/components/filter-tabs.tsx +101 -0
  220. package/templates/helpdesk/components/nav.tsx +14 -0
  221. package/templates/helpdesk/components/page-header.tsx +43 -7
  222. package/templates/helpdesk/components/priority-badge.tsx +49 -6
  223. package/templates/helpdesk/components/require-auth.tsx +24 -26
  224. package/templates/helpdesk/components/sidebar.tsx +54 -48
  225. package/templates/helpdesk/components/sla-indicator.tsx +5 -8
  226. package/templates/helpdesk/components/thread.tsx +7 -7
  227. package/templates/helpdesk/components/ticket-list.tsx +79 -19
  228. package/templates/helpdesk/components/ui/button.tsx +5 -4
  229. package/templates/helpdesk/components/ui/dialog.tsx +1 -1
  230. package/templates/helpdesk/components/ui/input.tsx +1 -1
  231. package/templates/helpdesk/components/ui/select.tsx +1 -1
  232. package/templates/helpdesk/components/ui/textarea.tsx +1 -1
  233. package/templates/helpdesk/functions/seedWorkspace.ts +25 -15
  234. package/templates/helpdesk/lib/brand.ts +9 -0
  235. package/templates/helpdesk/lib/demo-team.ts +51 -0
  236. package/templates/helpdesk/lib/search.ts +4 -3
  237. package/templates/helpdesk/lib/seed.ts +43 -2
  238. package/templates/helpdesk/lib/tickets.ts +68 -20
  239. package/templates/helpdesk/public/favicon.ico +0 -0
  240. package/templates/helpdesk/tests/components.test.tsx +10 -0
  241. package/templates/helpdesk/tests/demo-team.test.ts +18 -0
  242. package/templates/helpdesk/tests/tickets.test.ts +70 -0
  243. package/templates/imessage/.env.example +49 -0
  244. package/templates/imessage/AGENTS.md +153 -0
  245. package/templates/imessage/CLAUDE.md +7 -0
  246. package/templates/imessage/README.md +187 -0
  247. package/templates/imessage/app/console.tsx +382 -0
  248. package/templates/imessage/app/contacts/page.tsx +13 -0
  249. package/templates/imessage/app/error.tsx +16 -0
  250. package/templates/imessage/app/gate.tsx +31 -0
  251. package/templates/imessage/app/globals.css +183 -0
  252. package/templates/imessage/app/layout.tsx +17 -0
  253. package/templates/imessage/app/login/login-form.tsx +115 -0
  254. package/templates/imessage/app/login/page.tsx +30 -0
  255. package/templates/imessage/app/not-found.tsx +13 -0
  256. package/templates/imessage/app/page.tsx +14 -0
  257. package/templates/imessage/app/robots.ts +6 -0
  258. package/templates/imessage/app/setup/page.tsx +13 -0
  259. package/templates/imessage/app/sign-out.tsx +20 -0
  260. package/templates/imessage/app.ts +299 -0
  261. package/templates/imessage/bunfig +4 -0
  262. package/templates/imessage/components/avatar.tsx +46 -0
  263. package/templates/imessage/components/contact-panel.tsx +263 -0
  264. package/templates/imessage/components/contacts-view.tsx +172 -0
  265. package/templates/imessage/components/conversation-list.tsx +129 -0
  266. package/templates/imessage/components/nav-rail.tsx +67 -0
  267. package/templates/imessage/components/setup-view.tsx +408 -0
  268. package/templates/imessage/components/thread.tsx +229 -0
  269. package/templates/imessage/components/ui/badge.tsx +37 -0
  270. package/templates/imessage/components/ui/button.tsx +56 -0
  271. package/templates/imessage/components/ui/card.tsx +90 -0
  272. package/templates/imessage/components/ui/dialog.tsx +110 -0
  273. package/templates/imessage/components/ui/input.tsx +30 -0
  274. package/templates/imessage/components/ui/label.tsx +21 -0
  275. package/templates/imessage/components/ui/select.tsx +39 -0
  276. package/templates/imessage/components/ui/table.tsx +124 -0
  277. package/templates/imessage/components/ui/textarea.tsx +24 -0
  278. package/templates/imessage/components.json +20 -0
  279. package/templates/imessage/functions/answerNow.ts +34 -0
  280. package/templates/imessage/functions/assistant.ts +96 -0
  281. package/templates/imessage/functions/beginTurn.ts +101 -0
  282. package/templates/imessage/functions/cancelReminder.ts +13 -0
  283. package/templates/imessage/functions/claimOutbound.ts +44 -0
  284. package/templates/imessage/functions/clearDemo.ts +18 -0
  285. package/templates/imessage/functions/deleteNote.ts +11 -0
  286. package/templates/imessage/functions/deliverOutbound.ts +55 -0
  287. package/templates/imessage/functions/finishTurn.ts +84 -0
  288. package/templates/imessage/functions/fireReminder.ts +42 -0
  289. package/templates/imessage/functions/ingestInbound.ts +22 -0
  290. package/templates/imessage/functions/ownerAccount.ts +13 -0
  291. package/templates/imessage/functions/processTurn.ts +74 -0
  292. package/templates/imessage/functions/relayApply.ts +102 -0
  293. package/templates/imessage/functions/relaySync.ts +63 -0
  294. package/templates/imessage/functions/releaseOutbound.ts +18 -0
  295. package/templates/imessage/functions/resolveTurn.ts +25 -0
  296. package/templates/imessage/functions/saveContact.ts +27 -0
  297. package/templates/imessage/functions/seedDemo.ts +70 -0
  298. package/templates/imessage/functions/sendOwnerMessage.ts +28 -0
  299. package/templates/imessage/functions/sendblueWebhook.ts +49 -0
  300. package/templates/imessage/functions/setContactStatus.ts +17 -0
  301. package/templates/imessage/functions/settleOutbound.ts +35 -0
  302. package/templates/imessage/functions/setupStatus.ts +33 -0
  303. package/templates/imessage/functions/sweep.ts +58 -0
  304. package/templates/imessage/functions/toolCancelReminder.ts +17 -0
  305. package/templates/imessage/functions/toolForgetNote.ts +15 -0
  306. package/templates/imessage/functions/toolListNotes.ts +16 -0
  307. package/templates/imessage/functions/toolListReminders.ts +17 -0
  308. package/templates/imessage/functions/toolSaveNote.ts +22 -0
  309. package/templates/imessage/functions/toolSetReminder.ts +55 -0
  310. package/templates/imessage/functions/updateSettings.ts +48 -0
  311. package/templates/imessage/gitignore +11 -0
  312. package/templates/imessage/lib/agent-call.ts +251 -0
  313. package/templates/imessage/lib/assistant.ts +74 -0
  314. package/templates/imessage/lib/chunk.ts +135 -0
  315. package/templates/imessage/lib/demo.ts +68 -0
  316. package/templates/imessage/lib/format.ts +170 -0
  317. package/templates/imessage/lib/gate.ts +104 -0
  318. package/templates/imessage/lib/handles.ts +67 -0
  319. package/templates/imessage/lib/provider.ts +38 -0
  320. package/templates/imessage/lib/relay-protocol.ts +95 -0
  321. package/templates/imessage/lib/reminders.ts +41 -0
  322. package/templates/imessage/lib/secrets.ts +59 -0
  323. package/templates/imessage/lib/sendblue.ts +173 -0
  324. package/templates/imessage/lib/store.ts +407 -0
  325. package/templates/imessage/lib/transport.ts +135 -0
  326. package/templates/imessage/lib/utils.ts +10 -0
  327. package/templates/imessage/package.json +40 -0
  328. package/templates/imessage/relay/.env.example +14 -0
  329. package/templates/imessage/relay/applescript.ts +70 -0
  330. package/templates/imessage/relay/chatdb.ts +165 -0
  331. package/templates/imessage/relay/client.ts +41 -0
  332. package/templates/imessage/relay/main.ts +137 -0
  333. package/templates/imessage/relay/state.ts +35 -0
  334. package/templates/imessage/tests/agent-call.test.ts +112 -0
  335. package/templates/imessage/tests/applescript.test.ts +88 -0
  336. package/templates/imessage/tests/chatdb.test.ts +139 -0
  337. package/templates/imessage/tests/chunk.test.ts +61 -0
  338. package/templates/imessage/tests/fake-ctx.ts +88 -0
  339. package/templates/imessage/tests/ingest.test.ts +141 -0
  340. package/templates/imessage/tests/lib.test.ts +99 -0
  341. package/templates/imessage/tests/review-fixes.test.ts +138 -0
  342. package/templates/imessage/tests/secrets.test.ts +36 -0
  343. package/templates/imessage/tests/sendblue.test.ts +143 -0
  344. package/templates/imessage/tests/setup.ts +5 -0
  345. package/templates/imessage/tsconfig.json +18 -0
  346. package/templates/inventory/README.md +21 -0
  347. package/templates/inventory/app/apple-icon.png +0 -0
  348. package/templates/inventory/app/globals.css +29 -14
  349. package/templates/inventory/app/icon.svg +1 -0
  350. package/templates/inventory/app/layout.tsx +7 -5
  351. package/templates/inventory/app/movements/movements-view.tsx +32 -10
  352. package/templates/inventory/app/movements/page.tsx +2 -1
  353. package/templates/inventory/app/page.tsx +4 -6
  354. package/templates/inventory/app/products/[id]/page.tsx +2 -1
  355. package/templates/inventory/app/products/[id]/product-view.tsx +55 -25
  356. package/templates/inventory/app/products-view.tsx +74 -33
  357. package/templates/inventory/app/workspace.tsx +34 -16
  358. package/templates/inventory/app.ts +10 -2
  359. package/templates/inventory/components/app-shell.tsx +94 -0
  360. package/templates/inventory/components/auth-form.tsx +35 -5
  361. package/templates/inventory/components/avatar.tsx +5 -1
  362. package/templates/inventory/components/brand-mark.tsx +25 -0
  363. package/templates/inventory/components/category-tile.tsx +49 -0
  364. package/templates/inventory/components/command-palette.tsx +17 -17
  365. package/templates/inventory/components/data-table.tsx +97 -36
  366. package/templates/inventory/components/filter-tabs.tsx +101 -0
  367. package/templates/inventory/components/nav.tsx +14 -0
  368. package/templates/inventory/components/page-header.tsx +43 -7
  369. package/templates/inventory/components/require-auth.tsx +24 -26
  370. package/templates/inventory/components/sidebar.tsx +54 -48
  371. package/templates/inventory/components/stock-level.tsx +30 -14
  372. package/templates/inventory/components/summary-bar.tsx +8 -15
  373. package/templates/inventory/components/ui/button.tsx +5 -4
  374. package/templates/inventory/components/ui/dialog.tsx +1 -1
  375. package/templates/inventory/components/ui/input.tsx +1 -1
  376. package/templates/inventory/components/ui/select.tsx +1 -1
  377. package/templates/inventory/components/ui/textarea.tsx +1 -1
  378. package/templates/inventory/functions/seedWorkspace.ts +18 -8
  379. package/templates/inventory/lib/brand.ts +9 -0
  380. package/templates/inventory/lib/demo-team.ts +51 -0
  381. package/templates/inventory/lib/search.ts +4 -3
  382. package/templates/inventory/lib/seed.ts +29 -2
  383. package/templates/inventory/lib/stock.ts +32 -0
  384. package/templates/inventory/public/favicon.ico +0 -0
  385. package/templates/inventory/tests/demo-team.test.ts +18 -0
  386. package/templates/inventory/tests/stock.test.ts +29 -0
  387. package/templates/invoices/README.md +16 -0
  388. package/templates/invoices/app/apple-icon.png +0 -0
  389. package/templates/invoices/app/clients/clients-view.tsx +18 -6
  390. package/templates/invoices/app/clients/page.tsx +2 -1
  391. package/templates/invoices/app/globals.css +29 -14
  392. package/templates/invoices/app/icon.svg +1 -0
  393. package/templates/invoices/app/invoices/[id]/invoice-view.tsx +72 -48
  394. package/templates/invoices/app/invoices/[id]/page.tsx +2 -1
  395. package/templates/invoices/app/invoices-view.tsx +81 -32
  396. package/templates/invoices/app/layout.tsx +7 -5
  397. package/templates/invoices/app/page.tsx +4 -6
  398. package/templates/invoices/app/workspace.tsx +26 -16
  399. package/templates/invoices/app.ts +2 -2
  400. package/templates/invoices/components/app-shell.tsx +94 -0
  401. package/templates/invoices/components/auth-form.tsx +35 -5
  402. package/templates/invoices/components/avatar.tsx +5 -1
  403. package/templates/invoices/components/brand-mark.tsx +26 -0
  404. package/templates/invoices/components/command-palette.tsx +17 -17
  405. package/templates/invoices/components/data-table.tsx +97 -36
  406. package/templates/invoices/components/filter-tabs.tsx +101 -0
  407. package/templates/invoices/components/line-items.tsx +19 -14
  408. package/templates/invoices/components/nav.tsx +14 -0
  409. package/templates/invoices/components/page-header.tsx +43 -7
  410. package/templates/invoices/components/require-auth.tsx +24 -26
  411. package/templates/invoices/components/sidebar.tsx +54 -48
  412. package/templates/invoices/components/status-badge.tsx +7 -7
  413. package/templates/invoices/components/summary-bar.tsx +5 -5
  414. package/templates/invoices/components/ui/button.tsx +5 -4
  415. package/templates/invoices/components/ui/dialog.tsx +1 -1
  416. package/templates/invoices/components/ui/input.tsx +1 -1
  417. package/templates/invoices/components/ui/select.tsx +1 -1
  418. package/templates/invoices/components/ui/textarea.tsx +1 -1
  419. package/templates/invoices/functions/recordPayment.ts +3 -0
  420. package/templates/invoices/lib/billing.ts +40 -0
  421. package/templates/invoices/lib/brand.ts +9 -0
  422. package/templates/invoices/lib/search.ts +4 -3
  423. package/templates/invoices/public/favicon.ico +0 -0
  424. package/templates/invoices/tests/billing.test.ts +27 -0
  425. package/templates/ios/consumer/apps/ios/Package.swift +4 -3
  426. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ActivityView.swift +107 -0
  427. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/AppModel.swift +171 -0
  428. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/AuthView.swift +182 -0
  429. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/CommentsView.swift +136 -0
  430. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ComposeView.swift +152 -0
  431. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ExploreView.swift +117 -0
  432. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/FeedView.swift +175 -159
  433. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/MainTabView.swift +54 -0
  434. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Media.swift +271 -0
  435. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Models.swift +101 -4
  436. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/PostCard.swift +245 -0
  437. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileEditorView.swift +208 -0
  438. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileView.swift +183 -0
  439. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/RootView.swift +25 -31
  440. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/SocialStore.swift +285 -0
  441. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/StoryViewer.swift +155 -0
  442. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Theme.swift +82 -0
  443. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/__APP_NAME_PASCAL__App.swift +4 -47
  444. package/templates/ios/consumer/apps/ios/project.yml +13 -9
  445. package/templates/local-service/.env.example +7 -0
  446. package/templates/local-service/README.md +26 -2
  447. package/templates/local-service/app/(marketing)/booking-widget.tsx +48 -9
  448. package/templates/local-service/app/(marketing)/page.tsx +66 -42
  449. package/templates/local-service/app/auth-form.tsx +8 -8
  450. package/templates/local-service/app/dashboard/dashboard-client.tsx +119 -92
  451. package/templates/local-service/app/dashboard/page.tsx +14 -14
  452. package/templates/local-service/app/icon.svg +2 -0
  453. package/templates/local-service/app/login/page.tsx +15 -15
  454. package/templates/local-service/functions/seedDemo.ts +49 -0
  455. package/templates/local-service/lib/demo.ts +135 -0
  456. package/templates/local-service/lib/hours.ts +44 -0
  457. package/templates/local-service/lib/site.config.ts +8 -3
  458. package/templates/local-service/lib/slots.ts +3 -0
  459. package/templates/local-service/public/favicon.ico +0 -0
  460. package/templates/local-service/tests/booking.test.ts +104 -0
  461. package/templates/marketplace/README.md +23 -15
  462. package/templates/marketplace/app/globals.css +172 -114
  463. package/templates/marketplace/app/layout.tsx +75 -30
  464. package/templates/marketplace/app/listing/[id]/page.tsx +210 -123
  465. package/templates/marketplace/app/me/page.tsx +4 -4
  466. package/templates/marketplace/app/page.tsx +221 -298
  467. package/templates/marketplace/app/sell/page.tsx +8 -11
  468. package/templates/marketplace/app.ts +32 -7
  469. package/templates/marketplace/client/ActivityFeed.tsx +311 -0
  470. package/templates/marketplace/client/AuthNav.tsx +24 -16
  471. package/templates/marketplace/client/ListingCard.tsx +118 -0
  472. package/templates/marketplace/client/LiveGrid.tsx +92 -0
  473. package/templates/marketplace/client/LiveListingStatus.tsx +32 -24
  474. package/templates/marketplace/client/LoginCard.tsx +114 -136
  475. package/templates/marketplace/client/MarketProvider.tsx +3 -5
  476. package/templates/marketplace/client/MyMarket.tsx +371 -185
  477. package/templates/marketplace/client/OfferPanel.tsx +421 -287
  478. package/templates/marketplace/client/SellForm.tsx +271 -192
  479. package/templates/marketplace/client/ThemeToggle.tsx +1 -0
  480. package/templates/marketplace/client/WatchButton.tsx +16 -16
  481. package/templates/marketplace/client/market.ts +61 -14
  482. package/templates/marketplace/functions/seedMarket.ts +143 -36
  483. package/templates/marketplace/functions/seedOffers.ts +101 -0
  484. package/templates/marketplace/lib/catalog.ts +72 -1
  485. package/templates/marketplace/public/images/listings/ceramic-lamp-640.webp +0 -0
  486. package/templates/marketplace/public/images/listings/ceramic-lamp.webp +0 -0
  487. package/templates/marketplace/public/images/listings/duffel-640.webp +0 -0
  488. package/templates/marketplace/public/images/listings/duffel.webp +0 -0
  489. package/templates/marketplace/public/images/listings/film-camera-640.webp +0 -0
  490. package/templates/marketplace/public/images/listings/film-camera.webp +0 -0
  491. package/templates/marketplace/public/images/listings/lounge-chair-640.webp +0 -0
  492. package/templates/marketplace/public/images/listings/lounge-chair.webp +0 -0
  493. package/templates/marketplace/public/images/listings/road-bike-640.webp +0 -0
  494. package/templates/marketplace/public/images/listings/road-bike.webp +0 -0
  495. package/templates/marketplace/public/images/listings/slipper-chair-640.webp +0 -0
  496. package/templates/marketplace/public/images/listings/slipper-chair.webp +0 -0
  497. package/templates/marketplace/public/images/listings/turntable-640.webp +0 -0
  498. package/templates/marketplace/public/images/listings/turntable.webp +0 -0
  499. package/templates/marketplace/public/images/listings/wardrobe-640.webp +0 -0
  500. package/templates/marketplace/public/images/listings/wardrobe.webp +0 -0
  501. package/templates/marketplace/public/images/listings/watch-640.webp +0 -0
  502. package/templates/marketplace/public/images/listings/watch.webp +0 -0
  503. package/templates/marketplace/public/images/listings/weekender-bag-640.webp +0 -0
  504. package/templates/marketplace/public/images/listings/weekender-bag.webp +0 -0
  505. package/templates/marketplace/tests/example.test.ts +64 -2
  506. package/templates/marketplace/tests/listing-card.test.tsx +61 -0
  507. package/templates/projects/README.md +24 -3
  508. package/templates/projects/app/apple-icon.png +0 -0
  509. package/templates/projects/app/clients/clients-view.tsx +20 -6
  510. package/templates/projects/app/clients/page.tsx +2 -1
  511. package/templates/projects/app/globals.css +29 -14
  512. package/templates/projects/app/icon.svg +1 -0
  513. package/templates/projects/app/layout.tsx +7 -5
  514. package/templates/projects/app/page.tsx +3 -5
  515. package/templates/projects/app/projects/[id]/page.tsx +2 -1
  516. package/templates/projects/app/projects/[id]/project-view.tsx +45 -30
  517. package/templates/projects/app/projects-view.tsx +105 -52
  518. package/templates/projects/app/workspace.tsx +41 -20
  519. package/templates/projects/app.ts +2 -2
  520. package/templates/projects/components/app-shell.tsx +94 -0
  521. package/templates/projects/components/auth-form.tsx +35 -5
  522. package/templates/projects/components/avatar.tsx +5 -1
  523. package/templates/projects/components/board-skeleton.tsx +3 -3
  524. package/templates/projects/components/brand-mark.tsx +25 -0
  525. package/templates/projects/components/command-palette.tsx +17 -17
  526. package/templates/projects/components/data-table.tsx +97 -36
  527. package/templates/projects/components/filter-tabs.tsx +101 -0
  528. package/templates/projects/components/meter.tsx +48 -0
  529. package/templates/projects/components/nav.tsx +14 -0
  530. package/templates/projects/components/page-header.tsx +43 -7
  531. package/templates/projects/components/require-auth.tsx +24 -26
  532. package/templates/projects/components/sidebar.tsx +54 -48
  533. package/templates/projects/components/summary-bar.tsx +49 -0
  534. package/templates/projects/components/task-board.tsx +10 -9
  535. package/templates/projects/components/time-dialog.tsx +29 -5
  536. package/templates/projects/components/ui/button.tsx +5 -4
  537. package/templates/projects/components/ui/dialog.tsx +1 -1
  538. package/templates/projects/components/ui/input.tsx +1 -1
  539. package/templates/projects/components/ui/select.tsx +1 -1
  540. package/templates/projects/components/ui/textarea.tsx +1 -1
  541. package/templates/projects/functions/seedWorkspace.ts +26 -12
  542. package/templates/projects/lib/brand.ts +9 -0
  543. package/templates/projects/lib/demo-team.ts +51 -0
  544. package/templates/projects/lib/search.ts +4 -3
  545. package/templates/projects/lib/seed.ts +35 -1
  546. package/templates/projects/lib/work.ts +63 -0
  547. package/templates/projects/public/favicon.ico +0 -0
  548. package/templates/projects/tests/demo-team.test.ts +18 -0
  549. package/templates/projects/tests/work.test.ts +65 -0
  550. package/templates/restaurant/.env.example +7 -0
  551. package/templates/restaurant/README.md +29 -3
  552. package/templates/restaurant/app/(marketing)/layout.tsx +4 -2
  553. package/templates/restaurant/app/(marketing)/page.tsx +47 -23
  554. package/templates/restaurant/app/(marketing)/reservation-widget.tsx +37 -7
  555. package/templates/restaurant/app/auth-form.tsx +8 -8
  556. package/templates/restaurant/app/dashboard/dashboard-client.tsx +241 -109
  557. package/templates/restaurant/app/dashboard/page.tsx +14 -14
  558. package/templates/restaurant/app/icon.svg +4 -0
  559. package/templates/restaurant/app/login/page.tsx +15 -15
  560. package/templates/restaurant/functions/seedDemo.ts +44 -0
  561. package/templates/restaurant/lib/demo.ts +144 -0
  562. package/templates/restaurant/lib/hours.ts +45 -0
  563. package/templates/restaurant/lib/site.config.ts +3 -3
  564. package/templates/restaurant/public/favicon.ico +0 -0
  565. package/templates/restaurant/tests/reservations.test.ts +67 -0
  566. package/templates/saas/README.md +54 -35
  567. package/templates/saas/app/(auth)/auth-form.tsx +25 -23
  568. package/templates/saas/app/(auth)/forgot-password/page.tsx +1 -1
  569. package/templates/saas/app/(auth)/layout.tsx +27 -33
  570. package/templates/saas/app/(auth)/login/page.tsx +1 -1
  571. package/templates/saas/app/(auth)/reset-password/page.tsx +1 -1
  572. package/templates/saas/app/(auth)/signup/page.tsx +1 -1
  573. package/templates/saas/app/(marketing)/layout.tsx +127 -253
  574. package/templates/saas/app/(marketing)/page.tsx +133 -372
  575. package/templates/saas/app/(marketing)/pricing/page.tsx +21 -19
  576. package/templates/saas/app/(marketing)/products/[slug]/page.tsx +40 -95
  577. package/templates/saas/app/dashboard/board-client.tsx +416 -0
  578. package/templates/saas/app/dashboard/dashboard-client.tsx +30 -615
  579. package/templates/saas/app/dashboard/layout.tsx +27 -13
  580. package/templates/saas/app/dashboard/overview-client.tsx +332 -0
  581. package/templates/saas/app/dashboard/page.tsx +29 -35
  582. package/templates/saas/app/dashboard/projects/[id]/page.tsx +29 -0
  583. package/templates/saas/app/dashboard/projects/page.tsx +11 -7
  584. package/templates/saas/app/dashboard/projects-client.tsx +398 -0
  585. package/templates/saas/app/globals.css +5 -4
  586. package/templates/saas/app/icon.svg +1 -0
  587. package/templates/saas/app/onboarding/onboarding-client.tsx +93 -26
  588. package/templates/saas/app/onboarding/page.tsx +6 -2
  589. package/templates/saas/app.ts +68 -13
  590. package/templates/saas/components/dashboard-shell.tsx +212 -132
  591. package/templates/saas/components/marketing.tsx +137 -274
  592. package/templates/saas/components/pricing-table.tsx +12 -11
  593. package/templates/saas/components/setup-checklist.tsx +31 -29
  594. package/templates/saas/components/task-ui.tsx +199 -0
  595. package/templates/saas/functions/createProject.ts +7 -13
  596. package/templates/saas/functions/createTask.ts +67 -0
  597. package/templates/saas/functions/deleteProject.ts +17 -0
  598. package/templates/saas/functions/deleteTask.ts +14 -0
  599. package/templates/saas/functions/seedWorkspace.ts +46 -0
  600. package/templates/saas/functions/setProjectStatus.ts +41 -0
  601. package/templates/saas/functions/updateProfile.ts +13 -0
  602. package/templates/saas/functions/updateProject.ts +4 -5
  603. package/templates/saas/functions/updateTask.ts +88 -0
  604. package/templates/saas/lib/members.ts +53 -0
  605. package/templates/saas/lib/names.ts +53 -0
  606. package/templates/saas/lib/plans.ts +11 -0
  607. package/templates/saas/lib/sample-workspace.ts +117 -0
  608. package/templates/saas/lib/site.config.ts +200 -463
  609. package/templates/saas/lib/tasks.ts +101 -0
  610. package/templates/saas/lib/use-live.ts +25 -0
  611. package/templates/saas/public/favicon.ico +0 -0
  612. package/templates/saas/public/screenshots/board.png +0 -0
  613. package/templates/saas/public/screenshots/members.png +0 -0
  614. package/templates/saas/public/screenshots/overview.png +0 -0
  615. package/templates/saas/public/screenshots/projects.png +0 -0
  616. package/templates/saas/scripts/capture-screenshots.mjs +156 -0
  617. package/templates/saas/tests/names.test.ts +35 -0
  618. package/templates/saas/tests/plans.test.ts +12 -0
  619. package/templates/saas/tests/sample-workspace.test.ts +46 -0
  620. package/templates/saas/tests/tasks.test.ts +81 -0
  621. package/templates/shop/README.md +25 -13
  622. package/templates/shop/app/(marketing)/layout.tsx +94 -67
  623. package/templates/shop/app/(marketing)/not-found.tsx +13 -12
  624. package/templates/shop/app/(marketing)/page.tsx +120 -59
  625. package/templates/shop/app/(marketing)/shop-client.tsx +364 -237
  626. package/templates/shop/app/(marketing)/success/page.tsx +75 -22
  627. package/templates/shop/app/globals.css +94 -1
  628. package/templates/shop/app/layout.tsx +4 -4
  629. package/templates/shop/app/login/page.tsx +4 -4
  630. package/templates/shop/app.ts +18 -6
  631. package/templates/shop/components/cart-button.tsx +33 -0
  632. package/templates/shop/components/marketing.tsx +17 -87
  633. package/templates/shop/components/shelf-count.tsx +36 -0
  634. package/templates/shop/functions/checkout.ts +17 -1
  635. package/templates/shop/lib/cart-store.ts +57 -0
  636. package/templates/shop/lib/shelf-store.ts +35 -0
  637. package/templates/shop/lib/shop.ts +27 -0
  638. package/templates/shop/lib/site.config.ts +108 -98
  639. package/templates/shop/public/images/hero.jpg +0 -0
  640. package/templates/shop/public/images/products/cedar-smoke.jpg +0 -0
  641. package/templates/shop/public/images/products/fig-leaf.jpg +0 -0
  642. package/templates/shop/public/images/products/linen.jpg +0 -0
  643. package/templates/shop/public/images/products/sea-salt.jpg +0 -0
  644. package/templates/shop/public/images/products/tobacco-oak.jpg +0 -0
  645. package/templates/shop/tests/shop.test.ts +64 -0
  646. package/templates/waitlist/.env.example +7 -0
  647. package/templates/waitlist/README.md +29 -13
  648. package/templates/waitlist/app/(marketing)/page.tsx +9 -7
  649. package/templates/waitlist/app/(marketing)/waitlist-hero.tsx +64 -31
  650. package/templates/waitlist/app/auth-form.tsx +8 -8
  651. package/templates/waitlist/app/dashboard/dashboard-client.tsx +132 -95
  652. package/templates/waitlist/app/dashboard/page.tsx +17 -16
  653. package/templates/waitlist/app/icon.svg +2 -0
  654. package/templates/waitlist/app/login/page.tsx +14 -14
  655. package/templates/waitlist/app.ts +6 -6
  656. package/templates/waitlist/functions/joinWaitlist.ts +6 -18
  657. package/templates/waitlist/functions/seedDemo.ts +32 -0
  658. package/templates/waitlist/lib/demo.ts +94 -0
  659. package/templates/waitlist/lib/launch.ts +19 -0
  660. package/templates/waitlist/lib/site.config.ts +14 -9
  661. package/templates/waitlist/lib/stats.ts +35 -0
  662. package/templates/waitlist/public/favicon.ico +0 -0
  663. package/templates/waitlist/tests/counter.test.ts +107 -0
  664. package/templates/waitlist/tests/live-count.test.tsx +48 -0
  665. package/templates/ai-chat/tests/example.test.ts +0 -12
  666. package/templates/backend/consumer/apps/api/functions/feed.ts +0 -57
  667. package/templates/backend/consumer/apps/api/functions/profilePosts.ts +0 -17
  668. package/templates/backend/consumer/apps/api/functions/toggleLike.ts +0 -48
  669. package/templates/backend/consumer/apps/api/schema.ts +0 -143
  670. package/templates/chat/functions/_keep.ts +0 -13
  671. package/templates/chat/tests/example.test.ts +0 -12
  672. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileSetupView.swift +0 -68
  673. package/templates/marketplace/client/LiveTicker.tsx +0 -116
  674. package/templates/marketplace/client/ScrollToListingsLink.tsx +0 -29
  675. package/templates/marketplace/public/images/marketplace-hero.webp +0 -0
@@ -111,7 +111,8 @@ const OrgInvite = entity(
111
111
  const Project = entity(
112
112
  "Project",
113
113
  {
114
- orgId: field.id("Org"),
114
+ // `.readonly()`: set on insert, rejected on any client update.
115
+ orgId: field.id("Org").readonly(),
115
116
  name: field.string(),
116
117
  description: field.string().optional(),
117
118
  // "active" | "archived" — archived projects stay in the workspace but drop
@@ -122,6 +123,38 @@ const Project = entity(
122
123
  { indexes: [{ name: "by_org", fields: ["orgId"], unique: false }] },
123
124
  );
124
125
 
126
+ // A unit of work inside a project. `orgId` is copied from the project when
127
+ // the task is created, so the same one-line tenant policy scopes it.
128
+ // Statuses and priorities are listed in lib/tasks.ts. `assigneeName` is a
129
+ // copy of the assignee's display name, so the board renders names on the
130
+ // first server paint (the User policy only lets people read their own row);
131
+ // updateProfile refreshes it when a person renames themselves.
132
+ const Task = entity(
133
+ "Task",
134
+ {
135
+ orgId: field.id("Org").readonly(),
136
+ projectId: field.id("Project").readonly(),
137
+ title: field.string(),
138
+ status: field.string().default("todo"),
139
+ priority: field.string().default("none"),
140
+ assigneeId: field.id("User").optional(),
141
+ assigneeName: field.string().optional(),
142
+ // Calendar day, `YYYY-MM-DD`.
143
+ dueDate: field.string().optional(),
144
+ // Stamped by updateTask when the status moves to done; cleared when it
145
+ // moves back.
146
+ completedAt: field.datetime().optional(),
147
+ createdAt: field.datetime().defaultNow(),
148
+ },
149
+ {
150
+ indexes: [
151
+ { name: "by_org", fields: ["orgId"], unique: false },
152
+ { name: "by_project", fields: ["projectId"], unique: false },
153
+ { name: "by_assignee", fields: ["assigneeId"], unique: false },
154
+ ],
155
+ },
156
+ );
157
+
125
158
  // User rows: read your own; the auth subsystem owns writes.
126
159
  const userPolicy = policy({
127
160
  name: "user_self",
@@ -161,17 +194,31 @@ const orgInvitePolicy = policy({
161
194
  allowDelete: "false",
162
195
  });
163
196
 
164
- // Projects are scoped to your ACTIVE tenant. `auth.tenantId == data.orgId`
165
- // gates reads, edits, and deletes. Inserts go through the `createProject`
166
- // function instead, which enforces the free plan's project cap on the server
167
- // (lib/plans.ts) — a client cannot skip the paywall by writing the row itself.
197
+ // Projects and tasks are readable in your ACTIVE tenant only. Every write
198
+ // goes through a server function (functions/*Project.ts, *Task.ts):
199
+ // - createProject and setProjectStatus enforce the free plan's project cap
200
+ // (lib/plans.ts), so a client cannot skip the paywall.
201
+ // - An update policy is checked against the row as it is BEFORE the write,
202
+ // so `auth.tenantId == data.orgId` alone would let a client PATCH `orgId`
203
+ // and move a row into another workspace. `orgId` is `.readonly()`, and
204
+ // the functions never take `orgId` from the caller; they re-check
205
+ // membership against the row's own workspace.
206
+ // - deleteProject removes the project's tasks with it.
168
207
  const projectPolicy = policy({
169
208
  name: "project_tenant",
170
209
  entity: "Project",
171
210
  allowRead: "auth.tenantId == data.orgId",
172
211
  allowInsert: "false",
173
- allowUpdate: "auth.tenantId == data.orgId",
174
- allowDelete: "auth.tenantId == data.orgId",
212
+ allowUpdate: "false",
213
+ allowDelete: "false",
214
+ });
215
+ const taskPolicy = policy({
216
+ name: "task_tenant",
217
+ entity: "Task",
218
+ allowRead: "auth.tenantId == data.orgId",
219
+ allowInsert: "false",
220
+ allowUpdate: "false",
221
+ allowDelete: "false",
175
222
  });
176
223
 
177
224
  // Every non-internal function in functions/ becomes a manifest entry —
@@ -182,7 +229,7 @@ const fns = await discoverFunctions();
182
229
  const manifest = buildManifest({
183
230
  name: "__APP_NAME__",
184
231
  version: "0.1.0",
185
- entities: [User, Org, OrgMember, OrgInvite, Project, ...billing.manifest.entities],
232
+ entities: [User, Org, OrgMember, OrgInvite, Project, Task, ...billing.manifest.entities],
186
233
  queries: fns.queries,
187
234
  // The billing actions (createCheckoutSession / createBillingPortalSession /
188
235
  // cancelSubscription / restoreSubscription / stripeWebhook) are re-exported
@@ -199,25 +246,33 @@ const manifest = buildManifest({
199
246
  orgMemberPolicy,
200
247
  orgInvitePolicy,
201
248
  projectPolicy,
249
+ taskPolicy,
202
250
  ...billing.manifest.policies,
203
251
  ],
204
252
  // Email/password is on by default against the User entity. The org entities
205
253
  // above are named with the framework defaults (Org / OrgMember / OrgInvite),
206
254
  // so `/api/auth/orgs/*` + `/api/auth/select-org` work with no extra config.
207
255
  auth: auth(),
208
- // Self-hosted Inter (next/font parity): the build fetches the woff2, serves it
209
- // same-origin (no third-party request, no FOUT), preloads it, and synthesizes a
210
- // size-adjusted fallback face so there's no layout shift. globals.css reads it
211
- // via `var(--font-sans, …)`; layout.tsx carries no font <link>.
256
+ // Self-hosted Geist + Geist Mono: the build fetches the woff2, serves it
257
+ // same-origin, preloads it, and synthesizes a size-adjusted fallback face so
258
+ // there's no layout shift. globals.css reads them via `var(--font-sans, …)`
259
+ // and `var(--font-geist-mono, …)`; layout.tsx carries no font <link>.
212
260
  fonts: [
213
261
  font({
214
- family: "Inter",
262
+ family: "Geist",
215
263
  variable: "--font-sans",
216
264
  weights: ["400", "500", "600", "700"],
217
265
  subsets: ["latin"],
218
266
  display: "swap",
219
267
  preload: true,
220
268
  }),
269
+ font({
270
+ family: "Geist Mono",
271
+ variable: "--font-geist-mono",
272
+ weights: ["400", "500"],
273
+ subsets: ["latin"],
274
+ display: "swap",
275
+ }),
221
276
  ],
222
277
  routes: await discoverAppRoutes(),
223
278
  });
@@ -1,10 +1,11 @@
1
1
  "use client";
2
2
 
3
- import React from "react";
3
+ import React, { useState } from "react";
4
4
  import { Link, useRouter } from "@pylonsync/react";
5
5
  import { useAuth, OrganizationSwitcher } from "@pylonsync/client";
6
+ import * as DialogPrimitive from "@radix-ui/react-dialog";
6
7
  import {
7
- LayoutDashboard,
8
+ LayoutGrid,
8
9
  FolderKanban,
9
10
  Users,
10
11
  CreditCard,
@@ -12,207 +13,286 @@ import {
12
13
  LogOut,
13
14
  ExternalLink,
14
15
  ChevronsUpDown,
16
+ ChevronRight,
17
+ Menu,
18
+ X,
15
19
  type LucideIcon,
16
20
  } from "lucide-react";
21
+ import { Avatar, ProjectMark } from "@/components/task-ui";
22
+ import { useLiveRows } from "@/lib/use-live";
23
+ import { siteConfig } from "@/lib/site.config";
17
24
 
18
25
  export type NavKey = "overview" | "projects" | "members" | "billing" | "settings";
19
26
 
20
- // Exported so `app/dashboard/layout.tsx` can derive the active item + page
21
- // title from the request URL — one table drives both the sidebar and the
22
- // layout's routing awareness. `group` splits the sidebar into sections:
23
- // ungrouped items render first, then each named group under an eyebrow label.
27
+ // Exported so `app/dashboard/layout.tsx` can derive the active item and the
28
+ // page title from the request URL. One table drives both the sidebar and the
29
+ // layout. `group: "workspace"` items render in the lower block.
24
30
  export const NAV: {
25
31
  key: NavKey;
26
32
  label: string;
27
33
  href: string;
28
34
  Icon: LucideIcon;
29
- group?: string;
35
+ group?: "workspace";
30
36
  }[] = [
31
- { key: "overview", label: "Overview", href: "/dashboard", Icon: LayoutDashboard },
37
+ { key: "overview", label: "Overview", href: "/dashboard", Icon: LayoutGrid },
32
38
  { key: "projects", label: "Projects", href: "/dashboard/projects", Icon: FolderKanban },
33
- { key: "members", label: "Members", href: "/dashboard/members", Icon: Users, group: "Workspace" },
34
- { key: "billing", label: "Billing", href: "/dashboard/billing", Icon: CreditCard, group: "Workspace" },
35
- { key: "settings", label: "Settings", href: "/dashboard/settings", Icon: SettingsIcon, group: "Workspace" },
39
+ { key: "members", label: "Members", href: "/dashboard/members", Icon: Users, group: "workspace" },
40
+ { key: "billing", label: "Billing", href: "/dashboard/billing", Icon: CreditCard, group: "workspace" },
41
+ { key: "settings", label: "Settings", href: "/dashboard/settings", Icon: SettingsIcon, group: "workspace" },
36
42
  ];
37
43
 
38
- // Dashboard chrome: a fixed sidebar (logo, workspace switcher, grouped nav,
39
- // account card pinned to the bottom) plus a slim top bar. Rendered by
40
- // `app/dashboard/layout.tsx`, which wraps every /dashboard page — /dashboard
41
- // sits outside the `(marketing)` route group, so this shell is the only chrome
42
- // here. `userEmail` is resolved on the server (serverData.get("User", …)) and
43
- // passed in, so the menu shows a real email instead of a raw id.
44
+ export interface ShellProject {
45
+ id: string;
46
+ orgId: string;
47
+ name: string;
48
+ status?: string;
49
+ }
50
+
51
+ // Dashboard chrome. From md up: a sidebar on a grey frame with the page in a
52
+ // raised white panel. Below md: a top bar whose menu button opens the same
53
+ // sidebar as a drawer. Rendered by `app/dashboard/layout.tsx`, which resolves
54
+ // every prop on the server, so the first paint has the real names.
44
55
  export function DashboardShell({
45
56
  active,
46
57
  title,
58
+ parent,
59
+ activeProjectId,
60
+ tenantId,
61
+ userId,
62
+ userName,
47
63
  userEmail,
48
64
  orgName,
65
+ projects,
49
66
  children,
50
67
  }: {
51
68
  active: NavKey;
52
69
  title: string;
70
+ /** Breadcrumb parent, e.g. Projects on a project's board. */
71
+ parent?: { label: string; href: string };
72
+ activeProjectId?: string;
73
+ tenantId: string;
74
+ userId: string;
75
+ userName: string;
53
76
  userEmail: string;
54
- // Active org name, resolved on the server, so the workspace switcher renders
55
- // the real name on first paint instead of flashing in after hydration.
56
77
  orgName?: string;
78
+ projects: ShellProject[];
57
79
  children: React.ReactNode;
58
80
  }) {
59
- const router = useRouter();
60
- const ungrouped = NAV.filter((n) => !n.group);
61
- const groups = [...new Set(NAV.map((n) => n.group).filter(Boolean))] as string[];
81
+ const [drawer, setDrawer] = useState(false);
82
+ const sidebar = (
83
+ <Sidebar
84
+ active={active}
85
+ activeProjectId={activeProjectId}
86
+ tenantId={tenantId}
87
+ userId={userId}
88
+ userName={userName}
89
+ userEmail={userEmail}
90
+ orgName={orgName}
91
+ projects={projects}
92
+ onNavigate={() => setDrawer(false)}
93
+ />
94
+ );
62
95
  return (
63
- <div className="flex min-h-screen bg-white text-zinc-900">
64
- <aside className="hidden w-60 shrink-0 flex-col border-r border-zinc-200/70 bg-zinc-50/80 md:flex">
65
- <div className="flex h-14 shrink-0 items-center px-4">
66
- <Link href="/" className="flex items-center gap-2">
67
- <span className="flex size-6 items-center justify-center rounded-[7px] bg-zinc-900 text-[13px] font-bold text-white">
68
- A
69
- </span>
70
- <span className="text-[15px] font-semibold tracking-tight">Acme</span>
71
- </Link>
96
+ <div className="min-h-screen bg-[#f4f4f5] text-zinc-900 md:flex">
97
+ <aside className="sticky top-0 hidden h-screen w-[244px] shrink-0 md:block">{sidebar}</aside>
98
+
99
+ {/* Mobile top bar + drawer. */}
100
+ <DialogPrimitive.Root open={drawer} onOpenChange={setDrawer}>
101
+ <header className="sticky top-0 z-30 flex h-14 items-center gap-2 border-b border-zinc-200/80 bg-white/90 px-3 backdrop-blur md:hidden">
102
+ <DialogPrimitive.Trigger
103
+ aria-label="Open navigation"
104
+ className="flex size-9 items-center justify-center rounded-lg text-zinc-600 transition-colors hover:bg-zinc-100"
105
+ >
106
+ <Menu className="size-[18px]" strokeWidth={1.75} />
107
+ </DialogPrimitive.Trigger>
108
+ <div className="min-w-0 flex-1 truncate text-[14px] font-medium">{title}</div>
109
+ <Avatar id={userId} name={userName} />
110
+ </header>
111
+ <DialogPrimitive.Portal>
112
+ <DialogPrimitive.Overlay className="fixed inset-0 z-40 bg-zinc-950/30 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 md:hidden" />
113
+ <DialogPrimitive.Content className="fixed inset-y-0 left-0 z-50 w-[280px] max-w-[85vw] bg-[#f4f4f5] shadow-[0_0_0_1px_rgba(0,0,0,0.06),0_24px_64px_-12px_rgba(0,0,0,0.3)] data-[state=open]:animate-in data-[state=open]:slide-in-from-left data-[state=closed]:animate-out data-[state=closed]:slide-out-to-left md:hidden">
114
+ <DialogPrimitive.Title className="sr-only">Navigation</DialogPrimitive.Title>
115
+ <DialogPrimitive.Description className="sr-only">
116
+ Pages and projects in this workspace
117
+ </DialogPrimitive.Description>
118
+ <DialogPrimitive.Close
119
+ aria-label="Close navigation"
120
+ className="absolute right-2.5 top-3 z-10 flex size-8 items-center justify-center rounded-lg text-zinc-500 hover:bg-zinc-200/60"
121
+ >
122
+ <X className="size-4" />
123
+ </DialogPrimitive.Close>
124
+ {sidebar}
125
+ </DialogPrimitive.Content>
126
+ </DialogPrimitive.Portal>
127
+ </DialogPrimitive.Root>
128
+
129
+ <div className="min-w-0 flex-1 md:py-2 md:pr-2">
130
+ <div className="flex min-h-screen flex-col bg-white md:min-h-[calc(100vh-1rem)] md:rounded-xl md:shadow-[0_0_0_1px_rgba(0,0,0,0.06),0_1px_2px_rgba(0,0,0,0.04)]">
131
+ <div className="hidden h-12 shrink-0 items-center gap-1.5 border-b border-zinc-100 px-6 text-[13px] md:flex">
132
+ {parent ? (
133
+ <>
134
+ <Link href={parent.href} className="text-zinc-500 transition-colors hover:text-zinc-900">
135
+ {parent.label}
136
+ </Link>
137
+ <ChevronRight className="size-3.5 text-zinc-300" />
138
+ </>
139
+ ) : null}
140
+ <span className="truncate font-medium text-zinc-900">{title}</span>
141
+ </div>
142
+ <main className="min-w-0 flex-1 px-4 py-6 sm:px-6 md:px-8 md:py-8">{children}</main>
72
143
  </div>
144
+ </div>
145
+ </div>
146
+ );
147
+ }
148
+
149
+ function Sidebar({
150
+ active,
151
+ activeProjectId,
152
+ tenantId,
153
+ userId,
154
+ userName,
155
+ userEmail,
156
+ orgName,
157
+ projects,
158
+ onNavigate,
159
+ }: {
160
+ active: NavKey;
161
+ activeProjectId?: string;
162
+ tenantId: string;
163
+ userId: string;
164
+ userName: string;
165
+ userEmail: string;
166
+ orgName?: string;
167
+ projects: ShellProject[];
168
+ onNavigate: () => void;
169
+ }) {
170
+ const router = useRouter();
171
+ const live = useLiveRows<ShellProject>("Project", projects, tenantId)
172
+ .filter((p) => (p.status ?? "active") !== "archived")
173
+ .sort((a, b) => a.name.localeCompare(b.name, "en"));
174
+ const main = NAV.filter((n) => !n.group);
175
+ const workspace = NAV.filter((n) => n.group === "workspace");
176
+ return (
177
+ <div className="flex h-full flex-col px-3 pb-3">
178
+ <div className="flex h-14 shrink-0 items-center gap-2 px-2">
179
+ <Link href="/" className="flex items-center gap-2" onClick={onNavigate}>
180
+ <span className="flex size-[22px] items-center justify-center rounded-[6px] bg-zinc-900 text-[12px] font-bold text-white">
181
+ {siteConfig.brand.letter}
182
+ </span>
183
+ <span className="text-[14px] font-semibold tracking-tight">{siteConfig.brand.name}</span>
184
+ </Link>
185
+ </div>
73
186
 
74
- <div className="px-3 pb-1">
75
- {/* Every view's data is resolved server-side for the active tenant,
76
- so switching orgs re-renders the dashboard for the new workspace.
77
- A soft client navigation (router.push) re-fetches the SSR page —
78
- all data updates — without the full-reload white flash. */}
79
- <OrganizationSwitcher
80
- hidePersonal
81
- initialActiveName={orgName}
82
- onSwitched={() => router.push("/dashboard")}
83
- />
187
+ {/* Switching workspaces re-renders the dashboard for the new tenant.
188
+ router.push re-fetches the SSR page without a full reload. */}
189
+ <div className="[&>div]:w-full [&_button[aria-haspopup]]:w-full [&_button[aria-haspopup]]:justify-start [&_button[aria-haspopup]]:border-zinc-200/80 [&_button[aria-haspopup]]:bg-white [&_button[aria-haspopup]]:px-2 [&_button[aria-haspopup]]:py-1.5 [&_button[aria-haspopup]]:text-[13px] [&_button[aria-haspopup]]:shadow-[0_1px_1px_rgba(0,0,0,0.03)] [&_button[aria-haspopup]>span:nth-child(2)]:flex-1 [&_button[aria-haspopup]>span:nth-child(2)]:text-left">
190
+ <OrganizationSwitcher
191
+ hidePersonal
192
+ initialActiveName={orgName}
193
+ onSwitched={() => {
194
+ onNavigate();
195
+ router.push("/dashboard");
196
+ }}
197
+ />
198
+ </div>
199
+
200
+ <nav className="mt-4 flex-1 overflow-y-auto">
201
+ <div className="space-y-px">
202
+ {main.map((n) => (
203
+ <NavItem key={n.key} item={n} isActive={active === n.key && !activeProjectId} onNavigate={onNavigate} />
204
+ ))}
84
205
  </div>
85
206
 
86
- <nav className="flex-1 overflow-y-auto px-3 pt-3">
87
- <div className="space-y-0.5">
88
- {ungrouped.map((n) => (
89
- <NavItem key={n.key} item={n} isActive={active === n.key} />
90
- ))}
91
- </div>
92
- {groups.map((g) => (
93
- <div key={g} className="mt-6">
94
- <div className="mb-1.5 px-2.5 text-[11px] font-semibold uppercase tracking-wider text-zinc-400">
95
- {g}
96
- </div>
97
- <div className="space-y-0.5">
98
- {NAV.filter((n) => n.group === g).map((n) => (
99
- <NavItem key={n.key} item={n} isActive={active === n.key} />
100
- ))}
101
- </div>
207
+ {live.length > 0 ? (
208
+ <div className="mt-5">
209
+ <div className="px-2 pb-1 text-[12px] font-medium text-zinc-400">Active projects</div>
210
+ <div className="space-y-px">
211
+ {live.map((p) => (
212
+ <Link
213
+ key={p.id}
214
+ href={`/dashboard/projects/${p.id}`}
215
+ onClick={onNavigate}
216
+ className={
217
+ "flex items-center gap-2 rounded-md px-2 py-[5px] text-[13px] transition-colors " +
218
+ (activeProjectId === p.id
219
+ ? "bg-white font-medium text-zinc-900 shadow-[0_0_0_1px_rgba(0,0,0,0.05),0_1px_2px_rgba(0,0,0,0.05)]"
220
+ : "text-zinc-600 hover:bg-zinc-200/50 hover:text-zinc-900")
221
+ }
222
+ >
223
+ <ProjectMark id={p.id} name={p.name} className="size-[18px] text-[8.5px]" />
224
+ <span className="truncate">{p.name}</span>
225
+ </Link>
226
+ ))}
102
227
  </div>
103
- ))}
104
- </nav>
228
+ </div>
229
+ ) : null}
105
230
 
106
- {/* Account card pinned to the bottom of the column — the sidebar's
107
- base holds the session controls instead of trailing off into
108
- empty space. The dropdown opens UPWARD (bottom-full). */}
109
- <div className="border-t border-zinc-200/70 p-3">
110
- <UserMenu email={userEmail} direction="up" />
231
+ <div className="mt-5">
232
+ <div className="px-2 pb-1 text-[12px] font-medium text-zinc-400">Workspace</div>
233
+ <div className="space-y-px">
234
+ {workspace.map((n) => (
235
+ <NavItem key={n.key} item={n} isActive={active === n.key} onNavigate={onNavigate} />
236
+ ))}
237
+ </div>
111
238
  </div>
112
- </aside>
239
+ </nav>
113
240
 
114
- <div className="flex min-w-0 flex-1 flex-col">
115
- <header className="flex h-14 shrink-0 items-center justify-between border-b border-zinc-200/70 px-6">
116
- <h1 className="text-[15px] font-semibold">{title}</h1>
117
- {/* The sidebar (and its account card) is hidden below md, so the
118
- top bar carries the account menu on small screens only. */}
119
- <div className="md:hidden">
120
- <UserMenu email={userEmail} direction="down" />
121
- </div>
122
- </header>
123
- <main className="flex-1 p-6">
124
- <div className="mx-auto max-w-4xl">{children}</div>
125
- </main>
126
- </div>
241
+ <UserMenu userId={userId} name={userName} email={userEmail} />
127
242
  </div>
128
243
  );
129
244
  }
130
245
 
131
- // A single sidebar row. The active row reads as a raised white card — layered
132
- // translucent shadows instead of a hard border, so it sits naturally on the
133
- // zinc-50 column. Inactive rows tint on hover only.
134
246
  function NavItem({
135
247
  item,
136
248
  isActive,
249
+ onNavigate,
137
250
  }: {
138
251
  item: (typeof NAV)[number];
139
252
  isActive: boolean;
253
+ onNavigate: () => void;
140
254
  }) {
141
255
  return (
142
256
  <Link
143
257
  href={item.href}
258
+ onClick={onNavigate}
144
259
  className={
145
- "flex items-center gap-2.5 rounded-lg px-2.5 py-[7px] text-[13px] font-medium transition-colors " +
260
+ "flex items-center gap-2.5 rounded-md px-2 py-[6px] text-[13px] transition-colors " +
146
261
  (isActive
147
- ? "bg-white text-zinc-900 shadow-[0_1px_2px_rgba(0,0,0,0.06),0_0_0_1px_rgba(0,0,0,0.05)]"
262
+ ? "bg-white font-medium text-zinc-900 shadow-[0_0_0_1px_rgba(0,0,0,0.05),0_1px_2px_rgba(0,0,0,0.05)]"
148
263
  : "text-zinc-600 hover:bg-zinc-200/50 hover:text-zinc-900")
149
264
  }
150
265
  >
151
- <item.Icon
152
- className={"size-[17px] " + (isActive ? "text-zinc-700" : "text-zinc-400")}
153
- strokeWidth={2}
154
- />
266
+ <item.Icon className={"size-4 " + (isActive ? "text-zinc-800" : "text-zinc-400")} strokeWidth={1.75} />
155
267
  {item.label}
156
268
  </Link>
157
269
  );
158
270
  }
159
271
 
160
- // Avatar + dropdown (email, View site, Sign out). Native <details> so it opens
161
- // on click with no extra state. In the sidebar it renders as a full-width
162
- // account card whose menu opens upward; in the mobile top bar it's a compact
163
- // avatar with a downward menu.
164
- function UserMenu({
165
- email,
166
- direction,
167
- }: {
168
- email: string;
169
- direction: "up" | "down";
170
- }) {
272
+ // Account card pinned to the bottom of the sidebar. Native <details>, so it
273
+ // opens on click with no extra state; the menu opens upward.
274
+ function UserMenu({ userId, name, email }: { userId: string; name: string; email: string }) {
171
275
  const { signOut } = useAuth();
172
- const initial = (email.trim()[0] || "?").toUpperCase();
173
276
  async function onSignOut() {
174
277
  await signOut();
175
278
  window.location.assign("/");
176
279
  }
177
- const up = direction === "up";
178
280
  return (
179
- <details className="group relative">
180
- <summary
181
- className={
182
- "cursor-pointer select-none list-none marker:hidden [&::-webkit-details-marker]:hidden " +
183
- (up
184
- ? "flex w-full items-center gap-2.5 rounded-lg px-2 py-2 transition-colors hover:bg-zinc-200/50"
185
- : "flex size-9 items-center justify-center")
186
- }
187
- >
188
- <span className="flex size-7 shrink-0 items-center justify-center rounded-full bg-zinc-900 text-[11px] font-semibold text-white">
189
- {initial}
281
+ <details className="relative">
282
+ <summary className="flex cursor-pointer select-none list-none items-center gap-2.5 rounded-lg px-2 py-2 transition-colors marker:hidden hover:bg-zinc-200/50 [&::-webkit-details-marker]:hidden">
283
+ <Avatar id={userId} name={name} />
284
+ <span className="min-w-0 flex-1 leading-tight">
285
+ <span className="block truncate text-[13px] font-medium text-zinc-800">{name}</span>
286
+ <span className="block truncate text-[11.5px] text-zinc-500">{email}</span>
190
287
  </span>
191
- {up ? (
192
- <>
193
- <span className="min-w-0 flex-1 truncate text-[13px] font-medium text-zinc-700">
194
- {email || "Signed in"}
195
- </span>
196
- <ChevronsUpDown className="size-3.5 shrink-0 text-zinc-400" strokeWidth={2} />
197
- </>
198
- ) : null}
288
+ <ChevronsUpDown className="size-3.5 shrink-0 text-zinc-400" strokeWidth={1.75} />
199
289
  </summary>
200
- <div
201
- className={
202
- "absolute z-40 w-56 overflow-hidden rounded-xl bg-white p-1 shadow-[0_0_0_1px_rgba(0,0,0,0.06),0_16px_48px_-16px_rgba(0,0,0,0.25)] " +
203
- (up ? "bottom-full left-0 mb-2" : "right-0 top-full mt-2")
204
- }
205
- >
206
- <div className="border-b border-zinc-100 px-2.5 py-2">
207
- <div className="truncate text-[13px] font-medium text-zinc-900">
208
- {email || "Signed in"}
209
- </div>
210
- </div>
290
+ <div className="absolute bottom-full left-0 z-40 mb-2 w-full min-w-52 overflow-hidden rounded-xl bg-white p-1 shadow-[0_0_0_1px_rgba(0,0,0,0.06),0_16px_48px_-16px_rgba(0,0,0,0.25)]">
211
291
  <a
212
292
  href="/"
213
293
  className="flex items-center gap-2 rounded-lg px-2.5 py-2 text-[13px] text-zinc-700 transition-colors hover:bg-zinc-50"
214
294
  >
215
- <ExternalLink className="size-4 text-zinc-400" strokeWidth={2} />
295
+ <ExternalLink className="size-4 text-zinc-400" strokeWidth={1.75} />
216
296
  View site
217
297
  </a>
218
298
  <button
@@ -220,7 +300,7 @@ function UserMenu({
220
300
  onClick={onSignOut}
221
301
  className="flex w-full items-center gap-2 rounded-lg px-2.5 py-2 text-left text-[13px] text-zinc-700 transition-colors hover:bg-zinc-50"
222
302
  >
223
- <LogOut className="size-4 text-zinc-400" strokeWidth={2} />
303
+ <LogOut className="size-4 text-zinc-400" strokeWidth={1.75} />
224
304
  Sign out
225
305
  </button>
226
306
  </div>